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:
parent
fb337e5a08
commit
821045f6ce
1 changed files with 156 additions and 20 deletions
152
file.c
152
file.c
|
@ -70,25 +70,32 @@ struct rpc_file_exec_context {
|
|||
|
||||
|
||||
static struct blob_buf buf;
|
||||
static char *canonpath;
|
||||
|
||||
enum {
|
||||
RPC_F_R_PATH,
|
||||
RPC_F_R_SESSION,
|
||||
__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_SESSION] = { .name = "ubus_rpc_session",
|
||||
.type = BLOBMSG_TYPE_STRING },
|
||||
};
|
||||
|
||||
enum {
|
||||
RPC_F_RB_PATH,
|
||||
RPC_F_RB_BASE64,
|
||||
RPC_F_RB_SESSION,
|
||||
__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_BASE64] = { .name = "base64", .type = BLOBMSG_TYPE_BOOL },
|
||||
[RPC_F_RB_SESSION] = { .name = "ubus_rpc_session",
|
||||
.type = BLOBMSG_TYPE_STRING },
|
||||
};
|
||||
|
||||
enum {
|
||||
|
@ -97,6 +104,7 @@ enum {
|
|||
RPC_F_RW_APPEND,
|
||||
RPC_F_RW_MODE,
|
||||
RPC_F_RW_BASE64,
|
||||
RPC_F_RW_SESSION,
|
||||
__RPC_F_RW_MAX,
|
||||
};
|
||||
|
||||
|
@ -106,12 +114,15 @@ static const struct blobmsg_policy rpc_file_rw_policy[__RPC_F_RW_MAX] = {
|
|||
[RPC_F_RW_APPEND] = { .name = "append", .type = BLOBMSG_TYPE_BOOL },
|
||||
[RPC_F_RW_MODE] = { .name = "mode", .type = BLOBMSG_TYPE_INT32 },
|
||||
[RPC_F_RW_BASE64] = { .name = "base64", .type = BLOBMSG_TYPE_BOOL },
|
||||
[RPC_F_RW_SESSION] = { .name = "ubus_rpc_session",
|
||||
.type = BLOBMSG_TYPE_STRING },
|
||||
};
|
||||
|
||||
enum {
|
||||
RPC_E_CMD,
|
||||
RPC_E_PARM,
|
||||
RPC_E_ENV,
|
||||
RPC_E_SESSION,
|
||||
__RPC_E_MAX,
|
||||
};
|
||||
|
||||
|
@ -119,6 +130,8 @@ static const struct blobmsg_policy rpc_exec_policy[__RPC_E_MAX] = {
|
|||
[RPC_E_CMD] = { .name = "command", .type = BLOBMSG_TYPE_STRING },
|
||||
[RPC_E_PARM] = { .name = "params", .type = BLOBMSG_TYPE_ARRAY },
|
||||
[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[] = {
|
||||
|
@ -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 **
|
||||
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;
|
||||
}
|
||||
|
||||
*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))
|
||||
return NULL;
|
||||
|
@ -194,7 +307,13 @@ rpc_file_read(struct ubus_context *ctx, struct ubus_object *obj,
|
|||
if (!tb[RPC_F_RB_PATH])
|
||||
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))
|
||||
return rpc_errno_status();
|
||||
|
@ -271,6 +390,7 @@ rpc_file_write(struct ubus_context *ctx, struct ubus_object *obj,
|
|||
int append = O_TRUNC;
|
||||
mode_t prev_mode, mode = 0666;
|
||||
int fd, rv = 0;
|
||||
char *path = NULL;
|
||||
void *data = NULL;
|
||||
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])
|
||||
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_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]);
|
||||
|
||||
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);
|
||||
if (fd < 0)
|
||||
return rpc_errno_status();
|
||||
|
@ -615,7 +743,7 @@ rpc_fdclose(int fd)
|
|||
}
|
||||
|
||||
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,
|
||||
struct ubus_context *ctx, struct ubus_request_data *req)
|
||||
{
|
||||
|
@ -629,7 +757,7 @@ rpc_file_exec_run(const char *cmd,
|
|||
struct blob_attr *cur;
|
||||
|
||||
uint8_t arglen;
|
||||
char **args, **tmp;
|
||||
char *executable, **args, **tmp;
|
||||
|
||||
struct rpc_file_exec_context *c;
|
||||
|
||||
|
@ -638,6 +766,14 @@ rpc_file_exec_run(const char *cmd,
|
|||
if (!cmd)
|
||||
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));
|
||||
|
||||
if (!c)
|
||||
|
@ -675,7 +811,7 @@ rpc_file_exec_run(const char *cmd,
|
|||
if (!args)
|
||||
return UBUS_STATUS_UNKNOWN_ERROR;
|
||||
|
||||
args[0] = (char *)cmd;
|
||||
args[0] = (char *)executable;
|
||||
args[1] = NULL;
|
||||
|
||||
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();
|
||||
|
||||
default:
|
||||
|
@ -756,7 +892,7 @@ rpc_file_exec(struct ubus_context *ctx, struct ubus_object *obj,
|
|||
if (!tb[RPC_E_CMD])
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue