Files
nginx-unit/src/nxt_credential.c
Tiago Natel 411daeaa53 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.
2019-12-06 16:52:50 +00:00

351 lines
8.9 KiB
C

/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#include <nxt_main.h>
static nxt_int_t nxt_credential_groups_get(nxt_task_t *task, nxt_mp_t *mp,
nxt_credential_t *uc);
nxt_int_t
nxt_credential_get(nxt_task_t *task, nxt_mp_t *mp, nxt_credential_t *uc,
const char *group)
{
struct group *grp;
struct passwd *pwd;
nxt_errno = 0;
pwd = getpwnam(uc->user);
if (nxt_slow_path(pwd == NULL)) {
if (nxt_errno == 0) {
nxt_alert(task, "getpwnam(\"%s\") failed, user \"%s\" not found",
uc->user, uc->user);
} else {
nxt_alert(task, "getpwnam(\"%s\") failed %E", uc->user, nxt_errno);
}
return NXT_ERROR;
}
uc->uid = pwd->pw_uid;
uc->base_gid = pwd->pw_gid;
if (group != NULL && group[0] != '\0') {
nxt_errno = 0;
grp = getgrnam(group);
if (nxt_slow_path(grp == NULL)) {
if (nxt_errno == 0) {
nxt_alert(task,
"getgrnam(\"%s\") failed, group \"%s\" not found",
group, group);
} else {
nxt_alert(task, "getgrnam(\"%s\") failed %E", group, nxt_errno);
}
return NXT_ERROR;
}
uc->base_gid = grp->gr_gid;
}
nxt_debug(task, "about to get \"%s\" groups (uid:%d, base gid:%d)",
uc->user, uc->uid, uc->base_gid);
if (nxt_credential_groups_get(task, mp, uc) != NXT_OK) {
return NXT_ERROR;
}
#if (NXT_DEBUG)
{
u_char *p, *end;
nxt_uint_t i;
u_char msg[NXT_MAX_ERROR_STR];
p = msg;
end = msg + NXT_MAX_ERROR_STR;
for (i = 0; i < uc->ngroups; i++) {
p = nxt_sprintf(p, end, "%d%c", uc->gids[i],
i+1 < uc->ngroups ? ',' : '\0');
}
nxt_debug(task, "user \"%s\" has gids:%*s", uc->user, p - msg, msg);
}
#endif
return NXT_OK;
}
#if (NXT_HAVE_GETGROUPLIST && !NXT_MACOSX)
#define NXT_NGROUPS nxt_min(256, NGROUPS_MAX)
static nxt_int_t
nxt_credential_groups_get(nxt_task_t *task, nxt_mp_t *mp,
nxt_credential_t *uc)
{
int ngroups;
gid_t groups[NXT_NGROUPS];
ngroups = NXT_NGROUPS;
if (getgrouplist(uc->user, uc->base_gid, groups, &ngroups) < 0) {
if (nxt_slow_path(ngroups <= NXT_NGROUPS)) {
nxt_alert(task, "getgrouplist(\"%s\", %d, ...) failed %E", uc->user,
uc->base_gid, nxt_errno);
return NXT_ERROR;
}
}
if (ngroups > NXT_NGROUPS) {
if (ngroups > NGROUPS_MAX) {
ngroups = NGROUPS_MAX;
}
uc->ngroups = ngroups;
uc->gids = nxt_mp_alloc(mp, ngroups * sizeof(gid_t));
if (nxt_slow_path(uc->gids == NULL)) {
return NXT_ERROR;
}
if (nxt_slow_path(getgrouplist(uc->user, uc->base_gid, uc->gids,
&ngroups) < 0)) {
nxt_alert(task, "getgrouplist(\"%s\", %d) failed %E", uc->user,
uc->base_gid, nxt_errno);
return NXT_ERROR;
}
return NXT_OK;
}
uc->ngroups = ngroups;
uc->gids = nxt_mp_alloc(mp, ngroups * sizeof(gid_t));
if (nxt_slow_path(uc->gids == NULL)) {
return NXT_ERROR;
}
nxt_memcpy(uc->gids, groups, ngroups * sizeof(gid_t));
return NXT_OK;
}
#else
/*
* For operating systems that lack getgrouplist(3) or it's buggy (MacOS),
* nxt_credential_groups_get() stores an array of groups IDs which should be
* set by the setgroups() function for a given user. The initgroups()
* may block a just forked worker process for some time if LDAP or NDIS+
* is used, so nxt_credential_groups_get() allows to get worker user groups in
* main process. In a nutshell the initgroups() calls getgrouplist()
* followed by setgroups(). However older Solaris lacks the getgrouplist().
* Besides getgrouplist() does not allow to query the exact number of
* groups in some platforms, while NGROUPS_MAX can be quite large (e.g.
* 65536 on Linux).
* So nxt_credential_groups_get() emulates getgrouplist(): at first the
* function saves the super-user groups IDs, then calls initgroups() and saves
* the specified user groups IDs, and then restores the super-user groups IDs.
* This works at least on Linux, FreeBSD, and Solaris, but does not work
* on MacOSX, getgroups(2):
*
* To provide compatibility with applications that use getgroups() in
* environments where users may be in more than {NGROUPS_MAX} groups,
* a variant of getgroups(), obtained when compiling with either the
* macros _DARWIN_UNLIMITED_GETGROUPS or _DARWIN_C_SOURCE defined, can
* be used that is not limited to {NGROUPS_MAX} groups. However, this
* variant only returns the user's default group access list and not
* the group list modified by a call to setgroups(2).
*
* For such cases initgroups() is used in worker process as fallback.
*/
static nxt_int_t
nxt_credential_groups_get(nxt_task_t *task, nxt_mp_t *mp, nxt_credential_t *uc)
{
int nsaved, ngroups;
nxt_int_t ret;
nxt_gid_t *saved;
nsaved = getgroups(0, NULL);
if (nxt_slow_path(nsaved == -1)) {
nxt_alert(task, "getgroups(0, NULL) failed %E", nxt_errno);
return NXT_ERROR;
}
nxt_debug(task, "getgroups(0, NULL): %d", nsaved);
if (nsaved > NGROUPS_MAX) {
/* MacOSX case. */
uc->gids = NULL;
uc->ngroups = 0;
return NXT_OK;
}
saved = nxt_mp_alloc(mp, nsaved * sizeof(nxt_gid_t));
if (nxt_slow_path(saved == NULL)) {
return NXT_ERROR;
}
ret = NXT_ERROR;
nsaved = getgroups(nsaved, saved);
if (nxt_slow_path(nsaved == -1)) {
nxt_alert(task, "getgroups(%d) failed %E", nsaved, nxt_errno);
goto free;
}
nxt_debug(task, "getgroups(): %d", nsaved);
if (initgroups(uc->user, uc->base_gid) != 0) {
if (nxt_errno == NXT_EPERM) {
nxt_log(task, NXT_LOG_NOTICE,
"initgroups(%s, %d) failed %E, ignored",
uc->user, uc->base_gid, nxt_errno);
ret = NXT_OK;
goto free;
} else {
nxt_alert(task, "initgroups(%s, %d) failed %E",
uc->user, uc->base_gid, nxt_errno);
goto restore;
}
}
ngroups = getgroups(0, NULL);
if (nxt_slow_path(ngroups == -1)) {
nxt_alert(task, "getgroups(0, NULL) failed %E", nxt_errno);
goto restore;
}
nxt_debug(task, "getgroups(0, NULL): %d", ngroups);
uc->gids = nxt_mp_alloc(mp, ngroups * sizeof(nxt_gid_t));
if (nxt_slow_path(uc->gids == NULL)) {
goto restore;
}
ngroups = getgroups(ngroups, uc->gids);
if (nxt_slow_path(ngroups == -1)) {
nxt_alert(task, "getgroups(%d) failed %E", ngroups, nxt_errno);
goto restore;
}
uc->ngroups = ngroups;
ret = NXT_OK;
restore:
if (nxt_slow_path(setgroups(nsaved, saved) != 0)) {
nxt_alert(task, "setgroups(%d) failed %E", nsaved, nxt_errno);
ret = NXT_ERROR;
}
free:
nxt_mp_free(mp, saved);
return ret;
}
#endif
nxt_int_t
nxt_credential_setuid(nxt_task_t *task, nxt_credential_t *uc)
{
nxt_debug(task, "user cred set: \"%s\" uid:%d", uc->user, uc->uid);
if (setuid(uc->uid) != 0) {
#if (NXT_HAVE_CLONE)
if (nxt_errno == EINVAL) {
nxt_log(task, NXT_LOG_ERR, "The uid %d (user \"%s\") isn't "
"valid in the application namespace.", uc->uid, uc->user);
return NXT_ERROR;
}
#endif
nxt_alert(task, "setuid(%d) failed %E", uc->uid, nxt_errno);
return NXT_ERROR;
}
return NXT_OK;
}
nxt_int_t
nxt_credential_setgids(nxt_task_t *task, nxt_credential_t *uc)
{
nxt_runtime_t *rt;
nxt_debug(task, "user cred set gids: base gid:%d, ngroups: %d",
uc->base_gid, uc->ngroups);
rt = task->thread->runtime;
if (setgid(uc->base_gid) != 0) {
#if (NXT_HAVE_CLONE)
if (nxt_errno == EINVAL) {
nxt_log(task, NXT_LOG_ERR, "The gid %d isn't valid in the "
"application namespace.", uc->base_gid);
return NXT_ERROR;
}
#endif
nxt_alert(task, "setgid(%d) failed %E", uc->base_gid, nxt_errno);
return NXT_ERROR;
}
if (!rt->capabilities.setid) {
return NXT_OK;
}
if (nxt_slow_path(uc->ngroups > 0
&& setgroups(uc->ngroups, uc->gids) != 0)) {
#if (NXT_HAVE_CLONE)
if (nxt_errno == EINVAL) {
nxt_log(task, NXT_LOG_ERR, "The user \"%s\" (uid: %d) has "
"supplementary group ids not valid in the application "
"namespace.", uc->user, uc->uid);
return NXT_ERROR;
}
#endif
nxt_alert(task, "setgroups(%i) failed %E", uc->ngroups, nxt_errno);
return NXT_ERROR;
}
return NXT_OK;
}