From 821045f6ce341fdc39fc8dafc765411b0f4954d1 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sun, 1 Sep 2019 18:05:19 +0200 Subject: [PATCH] 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 --- file.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 156 insertions(+), 20 deletions(-) diff --git a/file.c b/file.c index 067d365..40ed9b9 100644 --- a/file.c +++ b/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_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_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,28 +104,34 @@ enum { RPC_F_RW_APPEND, RPC_F_RW_MODE, RPC_F_RW_BASE64, + RPC_F_RW_SESSION, __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_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING }, - [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_PATH] = { .name = "path", .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_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, }; 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_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,8 +892,8 @@ 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]), - tb[RPC_E_PARM], tb[RPC_E_ENV], ctx, req); + 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); }