summaryrefslogtreecommitdiffhomepage
path: root/src/nxt_clone.c
diff options
context:
space:
mode:
authorTiago Natel <t.nateldemoura@f5.com>2019-12-06 16:52:50 +0000
committerTiago Natel <t.nateldemoura@f5.com>2019-12-06 16:52:50 +0000
commit411daeaa532c47328ab901a7ba9ea5dcd876be06 (patch)
tree90774780cf42278a5cb148992604e8fdb864f54b /src/nxt_clone.c
parented2492a66afdf578d1e4f99dc098ab685607b3ba (diff)
downloadunit-411daeaa532c47328ab901a7ba9ea5dcd876be06.tar.gz
unit-411daeaa532c47328ab901a7ba9ea5dcd876be06.tar.bz2
Isolation: allowed the use of credentials with unpriv userns.
The setuid/setgid syscalls requires root capabilities but if the kernel supports unprivileged user namespace then the child process has the full set of capabilities in the new namespace, then we can allow setting "user" and "group" in such cases (this is a common security use case). Tests were added to ensure user gets meaningful error messages for uid/gid mapping misconfigurations.
Diffstat (limited to 'src/nxt_clone.c')
-rw-r--r--src/nxt_clone.c311
1 files changed, 246 insertions, 65 deletions
diff --git a/src/nxt_clone.c b/src/nxt_clone.c
index a2c376a3..9ee3c012 100644
--- a/src/nxt_clone.c
+++ b/src/nxt_clone.c
@@ -25,26 +25,18 @@ nxt_clone(nxt_int_t flags)
#if (NXT_HAVE_CLONE_NEWUSER)
-/* map uid 65534 to unit pid */
-#define NXT_DEFAULT_UNPRIV_MAP "65534 %d 1"
-
-nxt_int_t nxt_clone_proc_setgroups(nxt_task_t *task, pid_t child_pid,
+nxt_int_t nxt_clone_credential_setgroups(nxt_task_t *task, pid_t child_pid,
const char *str);
-nxt_int_t nxt_clone_proc_map_set(nxt_task_t *task, const char* mapfile,
- pid_t pid, nxt_int_t defval, nxt_conf_value_t *mapobj);
-nxt_int_t nxt_clone_proc_map_write(nxt_task_t *task, const char *mapfile,
+nxt_int_t nxt_clone_credential_map_set(nxt_task_t *task, const char* mapfile,
+ pid_t pid, nxt_int_t default_container, nxt_int_t default_host,
+ nxt_clone_credential_map_t *map);
+nxt_int_t nxt_clone_credential_map_write(nxt_task_t *task, const char *mapfile,
pid_t pid, u_char *mapinfo);
-typedef struct {
- nxt_int_t container;
- nxt_int_t host;
- nxt_int_t size;
-} nxt_clone_procmap_t;
-
-
nxt_int_t
-nxt_clone_proc_setgroups(nxt_task_t *task, pid_t child_pid, const char *str)
+nxt_clone_credential_setgroups(nxt_task_t *task, pid_t child_pid,
+ const char *str)
{
int fd, n;
u_char *p, *end;
@@ -89,8 +81,8 @@ nxt_clone_proc_setgroups(nxt_task_t *task, pid_t child_pid, const char *str)
nxt_int_t
-nxt_clone_proc_map_write(nxt_task_t *task, const char *mapfile, pid_t pid,
- u_char *mapinfo)
+nxt_clone_credential_map_write(nxt_task_t *task, const char *mapfile,
+ pid_t pid, u_char *mapinfo)
{
int len, mapfd;
u_char *p, *end;
@@ -139,17 +131,13 @@ nxt_clone_proc_map_write(nxt_task_t *task, const char *mapfile, pid_t pid,
nxt_int_t
-nxt_clone_proc_map_set(nxt_task_t *task, const char* mapfile, pid_t pid,
- nxt_int_t defval, nxt_conf_value_t *mapobj)
+nxt_clone_credential_map_set(nxt_task_t *task, const char* mapfile, pid_t pid,
+ nxt_int_t default_container, nxt_int_t default_host,
+ nxt_clone_credential_map_t *map)
{
- u_char *p, *end, *mapinfo;
- nxt_int_t container, host, size;
- nxt_int_t ret, len, count, i;
- nxt_conf_value_t *obj, *value;
-
- static nxt_str_t str_cont = nxt_string("container");
- static nxt_str_t str_host = nxt_string("host");
- static nxt_str_t str_size = nxt_string("size");
+ u_char *p, *end, *mapinfo;
+ nxt_int_t ret, len;
+ nxt_uint_t i;
/*
* uid_map one-entry size:
@@ -157,44 +145,28 @@ nxt_clone_proc_map_set(nxt_task_t *task, const char* mapfile, pid_t pid,
*/
len = sizeof(u_char) * (10 + 10 + 10 + 2 + 1);
- if (mapobj != NULL) {
- count = nxt_conf_array_elements_count(mapobj);
-
- if (count == 0) {
- goto default_map;
- }
-
- len = len * count + 1;
+ if (map->size > 0) {
+ len = len * map->size + 1;
mapinfo = nxt_malloc(len);
if (nxt_slow_path(mapinfo == NULL)) {
- nxt_alert(task, "failed to allocate uid_map buffer");
return NXT_ERROR;
}
p = mapinfo;
end = mapinfo + len;
- for (i = 0; i < count; i++) {
- obj = nxt_conf_get_array_element(mapobj, i);
+ for (i = 0; i < map->size; i++) {
+ p = nxt_sprintf(p, end, "%d %d %d", map->map[i].container,
+ map->map[i].host, map->map[i].size);
- value = nxt_conf_get_object_member(obj, &str_cont, NULL);
- container = nxt_conf_get_integer(value);
-
- value = nxt_conf_get_object_member(obj, &str_host, NULL);
- host = nxt_conf_get_integer(value);
-
- value = nxt_conf_get_object_member(obj, &str_size, NULL);
- size = nxt_conf_get_integer(value);
-
- p = nxt_sprintf(p, end, "%d %d %d", container, host, size);
if (nxt_slow_path(p == end)) {
- nxt_alert(task, "write past the uid_map buffer");
+ nxt_alert(task, "write past the mapinfo buffer");
nxt_free(mapinfo);
return NXT_ERROR;
}
- if (i+1 < count) {
+ if (i+1 < map->size) {
*p++ = '\n';
} else {
@@ -203,27 +175,24 @@ nxt_clone_proc_map_set(nxt_task_t *task, const char* mapfile, pid_t pid,
}
} else {
-
-default_map:
-
mapinfo = nxt_malloc(len);
if (nxt_slow_path(mapinfo == NULL)) {
- nxt_alert(task, "failed to allocate uid_map buffer");
return NXT_ERROR;
}
end = mapinfo + len;
- p = nxt_sprintf(mapinfo, end, NXT_DEFAULT_UNPRIV_MAP, defval);
+ p = nxt_sprintf(mapinfo, end, "%d %d 1",
+ default_container, default_host);
*p = '\0';
if (nxt_slow_path(p == end)) {
- nxt_alert(task, "write past the %s buffer", mapfile);
+ nxt_alert(task, "write past mapinfo buffer");
nxt_free(mapinfo);
return NXT_ERROR;
}
}
- ret = nxt_clone_proc_map_write(task, mapfile, pid, mapinfo);
+ ret = nxt_clone_credential_map_write(task, mapfile, pid, mapinfo);
nxt_free(mapinfo);
@@ -232,31 +201,50 @@ default_map:
nxt_int_t
-nxt_clone_proc_map(nxt_task_t *task, pid_t pid, nxt_process_clone_t *clone)
+nxt_clone_credential_map(nxt_task_t *task, pid_t pid,
+ nxt_credential_t *app_creds, nxt_clone_t *clone)
{
nxt_int_t ret;
- nxt_int_t uid, gid;
+ nxt_int_t default_host_uid;
+ nxt_int_t default_host_gid;
const char *rule;
nxt_runtime_t *rt;
rt = task->thread->runtime;
- uid = geteuid();
- gid = getegid();
- rule = rt->capabilities.setid ? "allow" : "deny";
+ if (rt->capabilities.setid) {
+ rule = "allow";
+
+ /*
+ * By default we don't map a privileged user
+ */
+ default_host_uid = app_creds->uid;
+ default_host_gid = app_creds->base_gid;
+ } else {
+ rule = "deny";
+
+ default_host_uid = nxt_euid;
+ default_host_gid = nxt_egid;
+ }
+
+ ret = nxt_clone_credential_map_set(task, "uid_map", pid, app_creds->uid,
+ default_host_uid,
+ &clone->uidmap);
- ret = nxt_clone_proc_map_set(task, "uid_map", pid, uid, clone->uidmap);
if (nxt_slow_path(ret != NXT_OK)) {
return NXT_ERROR;
}
- ret = nxt_clone_proc_setgroups(task, pid, rule);
+ ret = nxt_clone_credential_setgroups(task, pid, rule);
if (nxt_slow_path(ret != NXT_OK)) {
nxt_alert(task, "failed to write /proc/%d/setgroups", pid);
return NXT_ERROR;
}
- ret = nxt_clone_proc_map_set(task, "gid_map", pid, gid, clone->gidmap);
+ ret = nxt_clone_credential_map_set(task, "gid_map", pid, app_creds->base_gid,
+ default_host_gid,
+ &clone->gidmap);
+
if (nxt_slow_path(ret != NXT_OK)) {
return NXT_ERROR;
}
@@ -264,4 +252,197 @@ nxt_clone_proc_map(nxt_task_t *task, pid_t pid, nxt_process_clone_t *clone)
return NXT_OK;
}
+
+nxt_int_t
+nxt_clone_vldt_credential_uidmap(nxt_task_t *task,
+ nxt_clone_credential_map_t *map, nxt_credential_t *creds)
+{
+ nxt_int_t id;
+ nxt_uint_t i;
+ nxt_runtime_t *rt;
+ nxt_clone_map_entry_t m;
+
+ if (map->size == 0) {
+ return NXT_OK;
+ }
+
+ rt = task->thread->runtime;
+
+ if (!rt->capabilities.setid) {
+ if (nxt_slow_path(map->size > 1)) {
+ nxt_log(task, NXT_LOG_NOTICE, "\"uidmap\" field has %d entries "
+ "but unprivileged unit has a maximum of 1 map.",
+ map->size);
+
+ return NXT_ERROR;
+ }
+
+ id = map->map[0].host;
+
+ if (nxt_slow_path((nxt_uid_t) id != nxt_euid)) {
+ nxt_log(task, NXT_LOG_NOTICE, "\"uidmap\" field has an entry for "
+ "host uid %d but unprivileged unit can only map itself "
+ "(uid %d) into child namespaces.", id, nxt_euid);
+
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+ }
+
+ for (i = 0; i < map->size; i++) {
+ m = map->map[i];
+
+ if (creds->uid >= (nxt_uid_t) m.container
+ && creds->uid < (nxt_uid_t) (m.container + m.size))
+ {
+ return NXT_OK;
+ }
+ }
+
+ nxt_log(task, NXT_LOG_NOTICE, "\"uidmap\" field has no \"container\" "
+ "entry for user \"%s\" (uid %d)", creds->user, creds->uid);
+
+ return NXT_ERROR;
+}
+
+
+nxt_int_t
+nxt_clone_vldt_credential_gidmap(nxt_task_t *task,
+ nxt_clone_credential_map_t *map, nxt_credential_t *creds)
+{
+ nxt_uint_t base_ok, gid_ok, gids_ok;
+ nxt_uint_t i, j;
+ nxt_runtime_t *rt;
+ nxt_clone_map_entry_t m;
+
+ rt = task->thread->runtime;
+
+ if (!rt->capabilities.setid) {
+ if (creds->ngroups > 0
+ && !(creds->ngroups == 1 && creds->gids[0] == creds->base_gid)) {
+ nxt_log(task, NXT_LOG_NOTICE,
+ "unprivileged unit disallow supplementary groups for "
+ "new namespace (user \"%s\" has %d group%s).",
+ creds->user, creds->ngroups,
+ creds->ngroups > 1 ? "s" : "");
+
+ return NXT_ERROR;
+ }
+
+ if (map->size == 0) {
+ return NXT_OK;
+ }
+
+ if (nxt_slow_path(map->size > 1)) {
+ nxt_log(task, NXT_LOG_NOTICE, "\"gidmap\" field has %d entries "
+ "but unprivileged unit has a maximum of 1 map.",
+ map->size);
+
+ return NXT_ERROR;
+ }
+
+ m = map->map[0];
+
+ if (nxt_slow_path((nxt_gid_t) m.host != nxt_egid)) {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has an entry for "
+ "host gid %d but unprivileged unit can only map itself "
+ "(gid %d) into child namespaces.", m.host, nxt_egid);
+
+ return NXT_ERROR;
+ }
+
+ if (nxt_slow_path(m.size > 1)) {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has an entry with "
+ "\"size\": %d, but for unprivileged unit it must be 1.",
+ m.size);
+
+ return NXT_ERROR;
+ }
+
+ if (nxt_slow_path((nxt_gid_t) m.container != creds->base_gid)) {
+ nxt_log(task, NXT_LOG_ERR,
+ "\"gidmap\" field has no \"container\" entry for gid %d.",
+ creds->base_gid);
+
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+ }
+
+ if (map->size == 0) {
+ if (creds->ngroups > 0
+ && !(creds->ngroups == 1 && creds->gids[0] == creds->base_gid))
+ {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has no entries "
+ "but user \"%s\" has %d suplementary group%s.",
+ creds->user, creds->ngroups,
+ creds->ngroups > 1 ? "s" : "");
+
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+ }
+
+ base_ok = 0;
+ gids_ok = 0;
+
+ for (i = 0; i < creds->ngroups; i++) {
+ gid_ok = 0;
+
+ for (j = 0; j < map->size; j++) {
+ m = map->map[j];
+
+ if (!base_ok && creds->base_gid >= (nxt_gid_t) m.container
+ && creds->base_gid < (nxt_gid_t) (m.container+m.size))
+ {
+ base_ok = 1;
+ }
+
+ if (creds->gids[i] >= (nxt_gid_t) m.container
+ && creds->gids[i] < (nxt_gid_t) (m.container+m.size))
+ {
+ gid_ok = 1;
+ break;
+ }
+ }
+
+ if (nxt_fast_path(gid_ok)) {
+ gids_ok++;
+ }
+ }
+
+ if (!base_ok) {
+ for (i = 0; i < map->size; i++) {
+ m = map->map[i];
+
+ if (creds->base_gid >= (nxt_gid_t) m.container
+ && creds->base_gid < (nxt_gid_t) (m.container+m.size))
+ {
+ base_ok = 1;
+ break;
+ }
+ }
+ }
+
+ if (nxt_slow_path(!base_ok)) {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has no \"container\" "
+ "entry for gid %d.", creds->base_gid);
+
+ return NXT_ERROR;
+ }
+
+ if (nxt_slow_path(gids_ok < creds->ngroups)) {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has missing "
+ "suplementary gid mappings (found %d out of %d).", gids_ok,
+ creds->ngroups);
+
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+}
+
#endif