file: add path based read/write/exec ACL checks

Introduce ACL checks to verify that the requested path may be read, written
or executed. This allows to restrict ubus file commands to specific paths.

To setup the required ACLs, the following ubus command may be used
on the command line:

ubus call session grant '{
  "ubus_rpc_session": "d41d8cd98f00b204e9800998ecf8427e",
  "scope": "file",
  "objects": [
    [ "/etc", "read" ],
    [ "/etc/*", "write" ],
    [ "/sbin/sysupgrade", "exec" ]
  ]
}'

The "read", "list", "stat" and "md5" procedures require "read" permissions,
the "write" procedure requires "write" permission and the "exec" procedure
requires "exec" permissions.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich 2019-09-01 18:05:19 +02:00
parent fb337e5a08
commit 821045f6ce

176
file.c
View file

@ -70,25 +70,32 @@ struct rpc_file_exec_context {
static struct blob_buf buf; static struct blob_buf buf;
static char *canonpath;
enum { enum {
RPC_F_R_PATH, RPC_F_R_PATH,
RPC_F_R_SESSION,
__RPC_F_R_MAX, __RPC_F_R_MAX,
}; };
static const struct blobmsg_policy rpc_file_r_policy[__RPC_F_R_MAX] = { static const struct blobmsg_policy rpc_file_r_policy[__RPC_F_R_MAX] = {
[RPC_F_R_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, [RPC_F_R_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
[RPC_F_R_SESSION] = { .name = "ubus_rpc_session",
.type = BLOBMSG_TYPE_STRING },
}; };
enum { enum {
RPC_F_RB_PATH, RPC_F_RB_PATH,
RPC_F_RB_BASE64, RPC_F_RB_BASE64,
RPC_F_RB_SESSION,
__RPC_F_RB_MAX, __RPC_F_RB_MAX,
}; };
static const struct blobmsg_policy rpc_file_rb_policy[__RPC_F_RB_MAX] = { static const struct blobmsg_policy rpc_file_rb_policy[__RPC_F_RB_MAX] = {
[RPC_F_RB_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, [RPC_F_RB_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
[RPC_F_RB_BASE64] = { .name = "base64", .type = BLOBMSG_TYPE_BOOL }, [RPC_F_RB_BASE64] = { .name = "base64", .type = BLOBMSG_TYPE_BOOL },
[RPC_F_RB_SESSION] = { .name = "ubus_rpc_session",
.type = BLOBMSG_TYPE_STRING },
}; };
enum { enum {
@ -97,28 +104,34 @@ enum {
RPC_F_RW_APPEND, RPC_F_RW_APPEND,
RPC_F_RW_MODE, RPC_F_RW_MODE,
RPC_F_RW_BASE64, RPC_F_RW_BASE64,
RPC_F_RW_SESSION,
__RPC_F_RW_MAX, __RPC_F_RW_MAX,
}; };
static const struct blobmsg_policy rpc_file_rw_policy[__RPC_F_RW_MAX] = { static const struct blobmsg_policy rpc_file_rw_policy[__RPC_F_RW_MAX] = {
[RPC_F_RW_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, [RPC_F_RW_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
[RPC_F_RW_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING }, [RPC_F_RW_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING },
[RPC_F_RW_APPEND] = { .name = "append", .type = BLOBMSG_TYPE_BOOL }, [RPC_F_RW_APPEND] = { .name = "append", .type = BLOBMSG_TYPE_BOOL },
[RPC_F_RW_MODE] = { .name = "mode", .type = BLOBMSG_TYPE_INT32 }, [RPC_F_RW_MODE] = { .name = "mode", .type = BLOBMSG_TYPE_INT32 },
[RPC_F_RW_BASE64] = { .name = "base64", .type = BLOBMSG_TYPE_BOOL }, [RPC_F_RW_BASE64] = { .name = "base64", .type = BLOBMSG_TYPE_BOOL },
[RPC_F_RW_SESSION] = { .name = "ubus_rpc_session",
.type = BLOBMSG_TYPE_STRING },
}; };
enum { enum {
RPC_E_CMD, RPC_E_CMD,
RPC_E_PARM, RPC_E_PARM,
RPC_E_ENV, RPC_E_ENV,
RPC_E_SESSION,
__RPC_E_MAX, __RPC_E_MAX,
}; };
static const struct blobmsg_policy rpc_exec_policy[__RPC_E_MAX] = { static const struct blobmsg_policy rpc_exec_policy[__RPC_E_MAX] = {
[RPC_E_CMD] = { .name = "command", .type = BLOBMSG_TYPE_STRING }, [RPC_E_CMD] = { .name = "command", .type = BLOBMSG_TYPE_STRING },
[RPC_E_PARM] = { .name = "params", .type = BLOBMSG_TYPE_ARRAY }, [RPC_E_PARM] = { .name = "params", .type = BLOBMSG_TYPE_ARRAY },
[RPC_E_ENV] = { .name = "env", .type = BLOBMSG_TYPE_TABLE }, [RPC_E_ENV] = { .name = "env", .type = BLOBMSG_TYPE_TABLE },
[RPC_E_SESSION] = { .name = "ubus_rpc_session",
.type = BLOBMSG_TYPE_STRING },
}; };
static const char *d_types[] = { static const char *d_types[] = {
@ -155,6 +168,94 @@ rpc_errno_status(void)
} }
} }
static bool
rpc_file_read_access(const struct blob_attr *sid, const char *path)
{
if (!sid)
return true;
return ops->session_access(blobmsg_data(sid), "file", path, "read");
}
static bool
rpc_file_write_access(const struct blob_attr *sid, const char *path)
{
if (!sid)
return true;
return ops->session_access(blobmsg_data(sid), "file", path, "write");
}
static bool
rpc_file_exec_access(const struct blob_attr *sid, const char *path)
{
if (!sid)
return true;
return ops->session_access(blobmsg_data(sid), "file", path, "exec");
}
static char *
rpc_canonicalize_path(const char *path)
{
char *cp;
const char *p;
if (path == NULL || *path == '\0')
return NULL;
if (canonpath != NULL)
free(canonpath);
canonpath = strdup(path);
if (canonpath == NULL)
return NULL;
/* normalize */
for (cp = canonpath, p = path; *p != '\0'; ) {
if (*p != '/')
goto next;
/* skip repeating / */
if (p[1] == '/') {
p++;
continue;
}
/* /./ or /../ */
if (p[1] == '.') {
/* skip /./ */
if ((p[2] == '\0') || (p[2] == '/')) {
p += 2;
continue;
}
/* collapse /x/../ */
if ((p[2] == '.') && ((p[3] == '\0') || (p[3] == '/'))) {
while ((cp > canonpath) && (*--cp != '/'))
;
p += 3;
continue;
}
}
next:
*cp++ = *p++;
}
/* remove trailing slash if not root / */
if ((cp > canonpath + 1) && (cp[-1] == '/'))
cp--;
else if (cp == canonpath)
*cp++ = '/';
*cp = '\0';
return canonpath;
}
static struct blob_attr ** static struct blob_attr **
rpc_check_path(struct blob_attr *msg, char **path, struct stat *s) rpc_check_path(struct blob_attr *msg, char **path, struct stat *s)
{ {
@ -168,7 +269,19 @@ rpc_check_path(struct blob_attr *msg, char **path, struct stat *s)
return NULL; return NULL;
} }
*path = blobmsg_data(tb[RPC_F_R_PATH]); *path = rpc_canonicalize_path(blobmsg_get_string(tb[RPC_F_R_PATH]));
if (*path == NULL)
{
errno = ENOMEM;
return NULL;
}
if (!rpc_file_read_access(tb[RPC_F_R_SESSION], *path))
{
errno = EACCES;
return NULL;
}
if (stat(*path, s)) if (stat(*path, s))
return NULL; return NULL;
@ -194,7 +307,13 @@ rpc_file_read(struct ubus_context *ctx, struct ubus_object *obj,
if (!tb[RPC_F_RB_PATH]) if (!tb[RPC_F_RB_PATH])
return rpc_errno_status(); return rpc_errno_status();
path = blobmsg_data(tb[RPC_F_RB_PATH]); path = rpc_canonicalize_path(blobmsg_get_string(tb[RPC_F_RB_PATH]));
if (path == NULL)
return UBUS_STATUS_UNKNOWN_ERROR;
if (!rpc_file_read_access(tb[RPC_F_RB_SESSION], path))
return UBUS_STATUS_PERMISSION_DENIED;
if (stat(path, &s)) if (stat(path, &s))
return rpc_errno_status(); return rpc_errno_status();
@ -271,6 +390,7 @@ rpc_file_write(struct ubus_context *ctx, struct ubus_object *obj,
int append = O_TRUNC; int append = O_TRUNC;
mode_t prev_mode, mode = 0666; mode_t prev_mode, mode = 0666;
int fd, rv = 0; int fd, rv = 0;
char *path = NULL;
void *data = NULL; void *data = NULL;
ssize_t data_len = 0; ssize_t data_len = 0;
@ -280,6 +400,14 @@ rpc_file_write(struct ubus_context *ctx, struct ubus_object *obj,
if (!tb[RPC_F_RW_PATH] || !tb[RPC_F_RW_DATA]) if (!tb[RPC_F_RW_PATH] || !tb[RPC_F_RW_DATA])
return UBUS_STATUS_INVALID_ARGUMENT; return UBUS_STATUS_INVALID_ARGUMENT;
path = rpc_canonicalize_path(blobmsg_get_string(tb[RPC_F_RW_PATH]));
if (path == NULL)
return UBUS_STATUS_UNKNOWN_ERROR;
if (!rpc_file_write_access(tb[RPC_F_RW_SESSION], path))
return UBUS_STATUS_PERMISSION_DENIED;
data = blobmsg_data(tb[RPC_F_RW_DATA]); data = blobmsg_data(tb[RPC_F_RW_DATA]);
data_len = blobmsg_data_len(tb[RPC_F_RW_DATA]) - 1; data_len = blobmsg_data_len(tb[RPC_F_RW_DATA]) - 1;
@ -290,7 +418,7 @@ rpc_file_write(struct ubus_context *ctx, struct ubus_object *obj,
mode = blobmsg_get_u32(tb[RPC_F_RW_MODE]); mode = blobmsg_get_u32(tb[RPC_F_RW_MODE]);
prev_mode = umask(0); prev_mode = umask(0);
fd = open(blobmsg_data(tb[RPC_F_RW_PATH]), O_CREAT | O_WRONLY | append, mode); fd = open(path, O_CREAT | O_WRONLY | append, mode);
umask(prev_mode); umask(prev_mode);
if (fd < 0) if (fd < 0)
return rpc_errno_status(); return rpc_errno_status();
@ -615,7 +743,7 @@ rpc_fdclose(int fd)
} }
static int static int
rpc_file_exec_run(const char *cmd, rpc_file_exec_run(const char *cmd, const struct blob_attr *sid,
const struct blob_attr *arg, const struct blob_attr *env, const struct blob_attr *arg, const struct blob_attr *env,
struct ubus_context *ctx, struct ubus_request_data *req) struct ubus_context *ctx, struct ubus_request_data *req)
{ {
@ -629,7 +757,7 @@ rpc_file_exec_run(const char *cmd,
struct blob_attr *cur; struct blob_attr *cur;
uint8_t arglen; uint8_t arglen;
char **args, **tmp; char *executable, **args, **tmp;
struct rpc_file_exec_context *c; struct rpc_file_exec_context *c;
@ -638,6 +766,14 @@ rpc_file_exec_run(const char *cmd,
if (!cmd) if (!cmd)
return UBUS_STATUS_NOT_FOUND; return UBUS_STATUS_NOT_FOUND;
executable = rpc_canonicalize_path(cmd);
if (executable == NULL)
return UBUS_STATUS_UNKNOWN_ERROR;
if (!rpc_file_exec_access(sid, executable))
return UBUS_STATUS_PERMISSION_DENIED;
c = malloc(sizeof(*c)); c = malloc(sizeof(*c));
if (!c) if (!c)
@ -675,7 +811,7 @@ rpc_file_exec_run(const char *cmd,
if (!args) if (!args)
return UBUS_STATUS_UNKNOWN_ERROR; return UBUS_STATUS_UNKNOWN_ERROR;
args[0] = (char *)cmd; args[0] = (char *)executable;
args[1] = NULL; args[1] = NULL;
if (arg) if (arg)
@ -717,7 +853,7 @@ rpc_file_exec_run(const char *cmd,
} }
} }
if (execv(cmd, args)) if (execv(executable, args))
return rpc_errno_status(); return rpc_errno_status();
default: default:
@ -756,8 +892,8 @@ rpc_file_exec(struct ubus_context *ctx, struct ubus_object *obj,
if (!tb[RPC_E_CMD]) if (!tb[RPC_E_CMD])
return UBUS_STATUS_INVALID_ARGUMENT; return UBUS_STATUS_INVALID_ARGUMENT;
return rpc_file_exec_run(blobmsg_data(tb[RPC_E_CMD]), return rpc_file_exec_run(blobmsg_data(tb[RPC_E_CMD]), tb[RPC_E_SESSION],
tb[RPC_E_PARM], tb[RPC_E_ENV], ctx, req); tb[RPC_E_PARM], tb[RPC_E_ENV], ctx, req);
} }