blob: b6b259159d56b3c161f212c52c7b2421e48ae714 [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#define LOG_MODULE_NAME net_lwm2m_ac_control
#define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#include "lwm2m_obj_access_control.h"
#include <stdint.h>
#include <zephyr/init.h>
#define READ BIT(0)
#define WRITE BIT(1)
#define ACEXEC BIT(2)
#define DELETE BIT(3)
#define CREATE BIT(4)
/* For compatibility with lwm2m_op_perms */
#define WRITE_ATTR BIT(8)
#define DISCOVER BIT(9)
static int operation_to_acperm(int operation)
{
switch (operation) {
case LWM2M_OP_READ:
return READ;
case LWM2M_OP_WRITE:
return WRITE;
case LWM2M_OP_EXECUTE:
return ACEXEC;
case LWM2M_OP_DELETE:
return DELETE;
case LWM2M_OP_CREATE:
return CREATE;
case LWM2M_OP_WRITE_ATTR:
return WRITE_ATTR;
case LWM2M_OP_DISCOVER:
return DISCOVER;
default:
return 0;
}
}
#define ACCESS_CONTROL_VERSION_MAJOR 1
#define ACCESS_CONTROL_VERSION_MINOR 0
#define AC_OBJ_ID LWM2M_OBJECT_ACCESS_CONTROL_ID
#define MAX_SERVER_COUNT CONFIG_LWM2M_SERVER_INSTANCE_COUNT
#define MAX_INSTANCE_COUNT CONFIG_LWM2M_ACCESS_CONTROL_INSTANCE_COUNT
#define OBJ_LVL_MAX_ID 65535
#define ACCESS_CONTROL_OBJECT_ID 0
#define ACCESS_CONTROL_OBJECT_INSTANCE_ID 1
#define ACCESS_CONTROL_ACL_ID 2
#define ACCESS_CONTROL_ACCESS_CONTROL_OWNER 3
#define ACCESS_CONTROL_MAX_ID 4
struct ac_data {
uint16_t obj_id;
uint16_t obj_inst_id;
int16_t acl[MAX_SERVER_COUNT + 1];
uint16_t ac_owner;
};
static struct ac_data ac_data[MAX_INSTANCE_COUNT];
static struct lwm2m_engine_obj ac_obj;
static struct lwm2m_engine_obj_field fields[] = {
OBJ_FIELD_DATA(ACCESS_CONTROL_OBJECT_ID, RW, U16),
OBJ_FIELD_DATA(ACCESS_CONTROL_OBJECT_INSTANCE_ID, RW, U16),
/* Mark obj id and obj_inst id is RO, but needs to be written to by bootstrap server */
OBJ_FIELD_DATA(ACCESS_CONTROL_ACL_ID, RW_OPT, U16),
OBJ_FIELD_DATA(ACCESS_CONTROL_ACCESS_CONTROL_OWNER, RW, U16),
};
static struct lwm2m_engine_obj_inst inst[MAX_INSTANCE_COUNT];
static struct lwm2m_engine_res res[MAX_INSTANCE_COUNT][ACCESS_CONTROL_MAX_ID];
/* Calculated as follows:
* + ACCESS_CONTROL_MAX_ID - 1 (not counting the acl instance)
* + MAX_SERVER_COUNT + 1 (one acl for every server plus default)
*/
static struct lwm2m_engine_res_inst res_inst[MAX_INSTANCE_COUNT]
[MAX_SERVER_COUNT + ACCESS_CONTROL_MAX_ID];
static int obj_inst_to_index(uint16_t obj_id, uint16_t obj_inst_id)
{
int i, ret = -ENOENT;
for (i = 0; i < ARRAY_SIZE(inst); i++) {
if (inst[i].obj && ac_data[i].obj_id == obj_id &&
ac_data[i].obj_inst_id == obj_inst_id) {
ret = i;
break;
}
}
return ret;
}
static bool available_obj_inst_id(int obj_inst_id)
{
for (int index = 0; index < ARRAY_SIZE(inst); index++) {
if (inst[index].obj && inst[index].obj_inst_id == obj_inst_id) {
return false;
}
}
return true;
}
void access_control_add(uint16_t obj_id, uint16_t obj_inst_id, int server_obj_inst_id)
{
/* If ac_obj not created */
if (!ac_obj.fields) {
return;
}
if (obj_id == AC_OBJ_ID) {
return;
}
if (obj_inst_to_index(obj_id, obj_inst_id) >= 0) {
LOG_DBG("Access control for obj_inst /%d/%d already exist", obj_id, obj_inst_id);
return;
}
int index, avail = -1;
for (index = 0; index < ARRAY_SIZE(inst); index++) {
/* Save first available slot index */
if (avail < 0 && !inst[index].obj) {
avail = index;
}
}
if (avail < 0) {
LOG_ERR("Can not create access control instance - no more room: %u", obj_inst_id);
return;
}
int ssid;
if (server_obj_inst_id < 0) {
ssid = CONFIG_LWM2M_SERVER_DEFAULT_SSID;
} else {
ssid = lwm2m_server_get_ssid(server_obj_inst_id);
}
if (ssid < 0) {
LOG_DBG("No server object instance %d - using default", server_obj_inst_id);
ssid = CONFIG_LWM2M_SERVER_DEFAULT_SSID;
}
int ac_obj_inst_id = avail;
while (!available_obj_inst_id(ac_obj_inst_id)) {
ac_obj_inst_id++;
}
struct lwm2m_engine_obj_inst *obj_inst = NULL;
lwm2m_create_obj_inst(AC_OBJ_ID, ac_obj_inst_id, &obj_inst);
ac_data[avail].obj_id = obj_id;
ac_data[avail].obj_inst_id = obj_inst_id;
ac_data[avail].ac_owner = ssid;
}
void access_control_add_obj(uint16_t obj_id, int server_obj_inst_id)
{
access_control_add(obj_id, OBJ_LVL_MAX_ID, server_obj_inst_id);
}
void access_control_remove(uint16_t obj_id, uint16_t obj_inst_id)
{
/* If ac_obj not created */
if (!ac_obj.fields) {
return;
}
if (obj_id == AC_OBJ_ID) {
return;
}
int idx = obj_inst_to_index(obj_id, obj_inst_id);
if (idx < 0) {
LOG_DBG("Cannot remove access control for /%d/%d - not found", obj_id, obj_inst_id);
return;
}
ac_data[idx].obj_id = 0;
ac_data[idx].obj_inst_id = 0;
ac_data[idx].ac_owner = 0;
for (int i = 0; i < MAX_SERVER_COUNT + 1; i++) {
ac_data[idx].acl[i] = 0;
}
lwm2m_delete_obj_inst(AC_OBJ_ID, idx);
}
void access_control_remove_obj(uint16_t obj_id)
{
access_control_remove(obj_id, OBJ_LVL_MAX_ID);
}
static bool check_acl_table(uint16_t obj_id, uint16_t obj_inst_id, uint16_t short_server_id,
uint16_t access)
{
/* Get the index of the ac instance regarding obj_id and obj_inst_id */
int idx = obj_inst_to_index(obj_id, obj_inst_id);
if (idx < 0) {
LOG_DBG("Access control for obj_inst /%d/%d not found", obj_id, obj_inst_id);
return false;
}
uint16_t access_rights = 0;
uint16_t default_rights = 0;
bool server_has_acl = false;
for (int i = 0; i < MAX_SERVER_COUNT + 1; i++) {
int res_inst_id = res_inst[idx][ACCESS_CONTROL_ACL_ID + i].res_inst_id;
/* If server has access or if default exist */
if (res_inst_id == short_server_id) {
access_rights |= ac_data[idx].acl[i];
server_has_acl = true;
} else if (res_inst_id == 0) {
default_rights |= ac_data[idx].acl[i];
}
}
if (server_has_acl) {
return (access_rights & access) == access;
}
/* Full access if server is the ac_owner and no acl is specified for that server */
if (ac_data[idx].ac_owner == short_server_id) {
return true;
}
/* Return default rights */
return (default_rights & access) == access;
}
int access_control_check_access(uint16_t obj_id, uint16_t obj_inst_id, uint16_t server_obj_inst,
uint16_t operation, bool bootstrap_mode)
{
#if defined(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
if (bootstrap_mode) {
return 0; /* Full access for bootstrap servers */
}
#else
ARG_UNUSED(bootstrap_mode);
#endif
/* If ac_obj not created */
if (!ac_obj.fields) {
return 0;
}
uint16_t access = operation_to_acperm(operation);
int short_server_id = lwm2m_server_get_ssid(server_obj_inst);
if (short_server_id < 0) {
LOG_ERR("No server obj instance %u exist", server_obj_inst);
return -EACCES;
}
if (obj_id == AC_OBJ_ID) {
switch (access) {
case READ:
return 0;
case ACEXEC:
case DELETE:
case CREATE:
return -EPERM; /* Method not allowed */
case WRITE: /* Only ac_owner can write to ac_obj */
for (int index = 0; index < ARRAY_SIZE(inst); index++) {
if (inst[index].obj && inst[index].obj_inst_id == obj_inst_id) {
if (ac_data[index].ac_owner == short_server_id) {
return 0;
}
}
}
return -EACCES;
default:
return -EACCES;
}
}
/* only DISCOVER, WRITE_ATTR and CREATE allowed on object */
if (obj_inst_id == OBJ_LVL_MAX_ID) {
if (access == DISCOVER || access == WRITE_ATTR) {
return 0;
}
if (access != CREATE) {
return -EACCES;
}
}
if (access == CREATE) {
obj_inst_id = OBJ_LVL_MAX_ID;
}
if (check_acl_table(obj_id, obj_inst_id, short_server_id, access)) {
return 0;
}
return -EACCES;
}
static void add_existing_objects(void)
{
/* register all objects in the sys-list */
struct lwm2m_engine_obj *obj;
SYS_SLIST_FOR_EACH_CONTAINER(lwm2m_engine_obj_list(), obj, node) {
access_control_add_obj(obj->obj_id, -1);
}
/* register all object instances in the sys-list */
struct lwm2m_engine_obj_inst *obj_inst;
SYS_SLIST_FOR_EACH_CONTAINER(lwm2m_engine_obj_inst_list(), obj_inst, node) {
access_control_add(obj_inst->obj->obj_id, obj_inst->obj_inst_id, -1);
}
}
static int write_validate_cb(uint16_t obj_inst_id, uint16_t res_id, uint16_t res_inst_id,
uint8_t *data, uint16_t data_len, bool last_block, size_t total_size)
{
/* validates and removes acl instances for non-existing servers */
if (res_inst_id == 0) {
return 0;
}
/* If there is a server instance with ssid == res_inst_id, return */
if (lwm2m_server_short_id_to_inst(res_inst_id) >= 0) {
return 0;
}
/* if that res inst id does not match any ssid's, remove it */
int idx = -1;
for (int i = 0; i < ARRAY_SIZE(inst); i++) {
if (inst[i].obj && inst[i].obj_inst_id == obj_inst_id) {
idx = i;
break;
}
}
if (idx < 0) {
LOG_ERR("Object instance not found - %u", obj_inst_id);
return -ENOENT;
}
for (int i = 0; i < ARRAY_SIZE(inst); i++) {
if (res_inst[idx][ACCESS_CONTROL_ACL_ID + i].res_inst_id == res_inst_id) {
res_inst[idx][ACCESS_CONTROL_ACL_ID + i].res_inst_id =
RES_INSTANCE_NOT_CREATED;
break;
}
}
return 0;
}
static struct lwm2m_engine_obj_inst *ac_create(uint16_t obj_inst_id)
{
int index, avail = -1, i = 0, j = 0;
/* Check that there is no other instance with this ID */
for (index = 0; index < ARRAY_SIZE(inst); index++) {
if (inst[index].obj && inst[index].obj_inst_id == obj_inst_id) {
LOG_ERR("Can not create access control instance - "
"already existing: %u",
obj_inst_id);
return NULL;
}
/* Save first available slot index */
if (avail < 0 && !inst[index].obj) {
avail = index;
}
}
if (avail < 0) {
LOG_ERR("Can not create access control instance - no more room: %u", obj_inst_id);
return NULL;
}
/* Set default values */
(void)memset(res[avail], 0, sizeof(res[avail][0]) * ARRAY_SIZE(res[avail]));
init_res_instance(res_inst[avail], ARRAY_SIZE(res_inst[avail]));
/* initialize instance resource data */
INIT_OBJ_RES_DATA(ACCESS_CONTROL_OBJECT_ID, res[avail], i, res_inst[avail], j,
&ac_data[avail].obj_id, sizeof(ac_data[avail].obj_id));
INIT_OBJ_RES_DATA(ACCESS_CONTROL_OBJECT_INSTANCE_ID, res[avail], i, res_inst[avail], j,
&ac_data[avail].obj_inst_id, sizeof(ac_data[avail].obj_inst_id));
INIT_OBJ_RES(ACCESS_CONTROL_ACL_ID, res[avail], i, res_inst[avail], j, MAX_SERVER_COUNT + 1,
true, false, ac_data[avail].acl, sizeof(ac_data[avail].acl[0]), NULL, NULL,
write_validate_cb, NULL, NULL);
INIT_OBJ_RES_DATA(ACCESS_CONTROL_ACCESS_CONTROL_OWNER, res[avail], i, res_inst[avail], j,
&ac_data[avail].ac_owner, sizeof(ac_data[avail].ac_owner));
inst[avail].resources = res[avail];
inst[avail].resource_count = i;
LOG_DBG("Create access control instance: %d", obj_inst_id);
return &inst[avail];
}
static int ac_control_init(const struct device *dev)
{
ac_obj.obj_id = LWM2M_OBJECT_ACCESS_CONTROL_ID;
ac_obj.version_major = ACCESS_CONTROL_VERSION_MAJOR;
ac_obj.version_minor = ACCESS_CONTROL_VERSION_MINOR;
ac_obj.is_core = true;
ac_obj.fields = fields;
ac_obj.field_count = ARRAY_SIZE(fields);
ac_obj.max_instance_count = ARRAY_SIZE(inst);
ac_obj.create_cb = ac_create;
lwm2m_register_obj(&ac_obj);
#if !IS_ENABLED(CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP)
/* add the objects/object instances that were created before the ac control object */
add_existing_objects();
#endif /* CONFIG_LWM2M_RD_CLIENT_SUPPORT_BOOTSTRAP */
return 0;
}
SYS_INIT(ac_control_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);