diff --git a/Documentation/implementation/user_identity.rst b/Documentation/implementation/user_identity.rst index a831a1f2353a2..e7fe651069a97 100644 --- a/Documentation/implementation/user_identity.rst +++ b/Documentation/implementation/user_identity.rst @@ -18,16 +18,37 @@ The full POSIX three-field credential model is stored in ``struct task_group_s`` * ``tg_euid`` / ``tg_egid`` — effective IDs used for permission checks. * ``tg_suid`` / ``tg_sgid`` — saved set-IDs that allow a non-root process to restore a previously held effective ID. +* ``tg_groups`` / ``tg_ngroups`` — supplementary group IDs (when + ``CONFIG_SCHED_NGROUPS`` is greater than zero). -All six fields are zero-initialized at task creation, so the initial task runs -as root (UID/GID 0) unless explicitly changed. +All six primary credential fields are zero-initialized at task creation, so +the initial task runs as root (UID/GID 0) unless explicitly changed. The +supplementary list starts empty. + +Supplementary Groups +==================== + +When ``CONFIG_SCHED_NGROUPS`` is greater than zero: + +* ``setgroups()`` replaces the calling task group's supplementary list + (requires effective UID 0). +* ``getgroups()`` returns that list as stored (may be empty after + ``setgroups(0, NULL)``). The effective GID is not invented into an + empty list; use ``getegid()`` for the effective GID. +* ``initgroups()`` builds a membership list with ``getgrouplist()`` (from + ``/etc/group`` when ``CONFIG_LIBC_GROUP_FILE`` is enabled) and installs it + with ``setgroups()``. +* ``NGROUPS_MAX`` equals ``CONFIG_SCHED_NGROUPS``. + +Filesystem DAC (``fs_checkmode()``) grants the group-class mode bits when the +file's group matches ``tg_egid`` **or** any entry in ``tg_groups``. Inheritance =========== When a new task is created, ``group_inherit_identity()`` in -``sched/group/group_create.c`` copies all six credential fields from the parent -task group to the child task group. +``sched/group/group_create.c`` copies all credential fields — including the +supplementary group list — from the parent task group to the child. Privilege Transitions ===================== @@ -56,8 +77,8 @@ When the effective ID is non-zero, the requested value must equal the real or the saved ID. Otherwise the function returns ``-1`` with ``errno`` set to ``EPERM``. -This implements the standard POSIX pattern of temporarily dropping privileges -with ``seteuid()`` or ``setegid()`` and later restoring them to the saved value. +This implements temporary privilege drop with ``seteuid()`` / +``setegid()`` and later restore from the saved ID. ``setreuid()`` and ``setregid()`` --------------------------------- @@ -77,6 +98,28 @@ set-ID is set to the new effective ID. These functions return the real, effective, and saved set-IDs for the calling task group. Any output pointer may be ``NULL`` if that ID is not needed. +``setresuid()`` and ``setresgid()`` +----------------------------------- + +These functions set the real, effective, and saved set-IDs in one call. +Pass ``(uid_t)-1`` / ``(gid_t)-1`` to leave an ID unchanged. When the +effective UID is zero, any values may be assigned. When the effective +UID is non-zero, each new ID must equal the current real, effective, or +saved ID. + +Soft drop (keep saved-root):: + + setresgid(gid, gid, 0); + setresuid(uid, uid, 0); + +Hard drop (clear saved-root):: + + setresgid(gid, gid, gid); + setresuid(uid, uid, uid); + +``setresgid()`` requires effective UID zero to assign arbitrary GIDs. +Change group IDs before dropping the effective UID. + Configuration ============= @@ -84,6 +127,13 @@ Configuration Enables per-task-group credential tracking. Without this option, stub root-only versions of all credential interfaces are provided. +``CONFIG_SCHED_NGROUPS`` + Maximum supplementary group IDs per task group (default 8). Visible only + when ``CONFIG_SCHED_USER_IDENTITY`` is enabled. Becomes ``NGROUPS_MAX``. + ``getgrouplist()`` / ``initgroups()`` return failure (they do **not** + silently truncate) when membership exceeds this limit; ``initgroups()`` + also logs a warning. Increase ``CONFIG_SCHED_NGROUPS`` if needed. + ``CONFIG_FS_PERMISSION`` Enables filesystem ownership and permission enforcement. Requires ``CONFIG_SCHED_USER_IDENTITY`` and ``CONFIG_PSEUDOFS_ATTRIBUTES``. diff --git a/binfmt/binfmt_checkexec.c b/binfmt/binfmt_checkexec.c index 1436298dc24a4..9d94d36300c6e 100644 --- a/binfmt/binfmt_checkexec.c +++ b/binfmt/binfmt_checkexec.c @@ -57,6 +57,7 @@ int binfmt_checkexecperm(FAR struct binary_s *bin) { FAR struct tcb_s *rtcb; + FAR struct task_group_s *rgroup; mode_t xbits; rtcb = nxsched_self(); @@ -66,7 +67,9 @@ int binfmt_checkexecperm(FAR struct binary_s *bin) return OK; } - if (rtcb->group->tg_euid == 0) + rgroup = rtcb->group; + + if (rgroup->tg_euid == 0) { /* Root can execute any file that has at least one execute bit set */ @@ -78,11 +81,11 @@ int binfmt_checkexecperm(FAR struct binary_s *bin) return OK; } - if (rtcb->group->tg_euid == bin->uid) + if (rgroup->tg_euid == bin->uid) { xbits = S_IXUSR; } - else if (rtcb->group->tg_egid == bin->gid) + else if (nxsched_has_gid(rtcb, bin->gid)) { xbits = S_IXGRP; } diff --git a/fs/inode/fs_inode.c b/fs/inode/fs_inode.c index cc9b411535627..4b1838ed7aa1c 100644 --- a/fs/inode/fs_inode.c +++ b/fs/inode/fs_inode.c @@ -66,9 +66,9 @@ static rw_semaphore_t g_inode_lock = RWSEM_INITIALIZER; int fs_checkmode(uid_t owner, gid_t group, mode_t mode, int amode) { FAR struct tcb_s *rtcb; + FAR struct task_group_s *rgroup; mode_t perm; uid_t uid; - gid_t gid; rtcb = nxsched_self(); if ((rtcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_KERNEL) @@ -77,14 +77,14 @@ int fs_checkmode(uid_t owner, gid_t group, mode_t mode, int amode) } DEBUGASSERT(rtcb->group != NULL); - uid = rtcb->group->tg_euid; - gid = rtcb->group->tg_egid; + rgroup = rtcb->group; + uid = rgroup->tg_euid; if (uid == owner) { perm = (mode >> 6) & 7; } - else if (gid == group) + else if (nxsched_has_gid(rtcb, group)) { perm = (mode >> 3) & 7; } diff --git a/include/limits.h b/include/limits.h index 047dc3d6b85d7..4e8bd5599e43f 100644 --- a/include/limits.h +++ b/include/limits.h @@ -124,7 +124,11 @@ #define _POSIX_MAX_CANON 255 #define _POSIX_MAX_INPUT 255 #define _POSIX_NAME_MAX CONFIG_NAME_MAX -#define _POSIX_NGROUPS_MAX 0 +#if defined(CONFIG_SCHED_NGROUPS) && CONFIG_SCHED_NGROUPS > 0 +# define _POSIX_NGROUPS_MAX CONFIG_SCHED_NGROUPS +#else +# define _POSIX_NGROUPS_MAX 0 +#endif #define _POSIX_OPEN_MAX 16 #define _POSIX_PATH_MAX CONFIG_PATH_MAX #define _POSIX_PIPE_BUF 512 diff --git a/include/nuttx/sched.h b/include/nuttx/sched.h index c2f6df139823e..a053490662162 100644 --- a/include/nuttx/sched.h +++ b/include/nuttx/sched.h @@ -466,6 +466,10 @@ struct task_group_s gid_t tg_egid; /* Effective group identity */ uid_t tg_suid; /* Saved set-user identity */ gid_t tg_sgid; /* Saved set-group identity */ +# if CONFIG_SCHED_NGROUPS > 0 + int tg_ngroups; /* Number of supplementary group IDs */ + gid_t tg_groups[CONFIG_SCHED_NGROUPS]; +# endif #endif /* Group membership *******************************************************/ @@ -861,6 +865,43 @@ EXTERN const struct tcbinfo_s g_tcbinfo; * Public Function Prototypes ****************************************************************************/ +/**************************************************************************** + * Name: nxsched_has_gid + * + * Description: + * Return true if the task's group matches 'gid' via the effective GID or + * any supplementary group ID. + * + ****************************************************************************/ + +#ifdef CONFIG_SCHED_USER_IDENTITY +static inline_function bool nxsched_has_gid(FAR struct tcb_s *tcb, + gid_t gid) +{ + FAR struct task_group_s *group = tcb->group; +#if CONFIG_SCHED_NGROUPS > 0 + int i; +#endif + + if (group->tg_egid == gid) + { + return true; + } + +#if CONFIG_SCHED_NGROUPS > 0 + for (i = 0; i < group->tg_ngroups; i++) + { + if (group->tg_groups[i] == gid) + { + return true; + } + } +#endif + + return false; +} +#endif + /**************************************************************************** * Name: nxsched_self * diff --git a/include/sys/syscall_lookup.h b/include/sys/syscall_lookup.h index 242366c8fd605..55a20f7f13586 100644 --- a/include/sys/syscall_lookup.h +++ b/include/sys/syscall_lookup.h @@ -71,8 +71,14 @@ SYSCALL_LOOKUP(sethostname, 2) SYSCALL_LOOKUP(geteuid, 0) SYSCALL_LOOKUP(setegid, 1) SYSCALL_LOOKUP(getegid, 0) +# if CONFIG_SCHED_NGROUPS > 0 + SYSCALL_LOOKUP(setgroups, 2) + SYSCALL_LOOKUP(getgroups, 2) +# endif SYSCALL_LOOKUP(setreuid, 2) SYSCALL_LOOKUP(setregid, 2) + SYSCALL_LOOKUP(setresuid, 3) + SYSCALL_LOOKUP(setresgid, 3) SYSCALL_LOOKUP(getresuid, 3) SYSCALL_LOOKUP(getresgid, 3) #endif diff --git a/include/unistd.h b/include/unistd.h index 885bbed12a532..e46dda9d3cb80 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -506,7 +506,11 @@ int setregid(gid_t rgid, gid_t egid); int getresuid(FAR uid_t *ruid, FAR uid_t *euid, FAR uid_t *suid); int getresgid(FAR gid_t *rgid, FAR gid_t *egid, FAR gid_t *sgid); -int getgroups(int, gid_t[]); +int setresuid(uid_t ruid, uid_t euid, uid_t suid); +int setresgid(gid_t rgid, gid_t egid, gid_t sgid); + +int getgroups(int, FAR gid_t[]); +int setgroups(int, FAR const gid_t *); int getentropy(FAR void *buffer, size_t length); diff --git a/libs/libc/grp/lib_find_grpfile.c b/libs/libc/grp/lib_find_grpfile.c index 98c4cc4815027..999f082ad19f9 100644 --- a/libs/libc/grp/lib_find_grpfile.c +++ b/libs/libc/grp/lib_find_grpfile.c @@ -36,7 +36,6 @@ #include #include "grp/lib_grp.h" - /**************************************************************************** * Private Types ****************************************************************************/ @@ -396,16 +395,6 @@ int grp_findby_name(FAR const char *gname, FAR struct group *entry, int grp_findby_gid(gid_t gid, FAR struct group *entry, FAR char *buffer, size_t buflen) { - /* Verify that the GID is in the valid range of 0 through INT16_MAX. - * OpenGroup.org does not specify a GID_MAX or GID_MIN. Instead we use a - * priori knowledge that gid_t is type int16_t. - */ - - if ((uint16_t)gid > INT16_MAX) - { - return -EINVAL; - } - return grp_foreach(grp_match_gid, (uintptr_t)gid, entry, buffer, buflen); } diff --git a/libs/libc/grp/lib_initgroups.c b/libs/libc/grp/lib_initgroups.c index 5329ba96095d8..5ae27b62fdb1a 100644 --- a/libs/libc/grp/lib_initgroups.c +++ b/libs/libc/grp/lib_initgroups.c @@ -27,6 +27,11 @@ #include #include +#include +#include +#include + +#include /**************************************************************************** * Public Functions @@ -36,28 +41,56 @@ * Name: initgroups * * Description: - * The group database /etc/group is read to determine all groups of which - * user is a member. The additional group group is also added to this set, - * which is then used to set the supplementary group IDs of the calling - * process. + * The group database is read to determine all groups of which user is a + * member. The additional group 'group' is also included. The resulting + * set is installed as the calling process's supplementary group IDs via + * setgroups(). * * Input Parameters: - * user - Name of the user to query the /etc/group database for. + * user - Name of the user to query the group database for. * group - Additional gid to add to the list of group IDs. * * Returned Value: - * The initgroups() function returns zero if successful, and -1 in case of - * failure, in which case errno is set appropriately. + * Zero if successful, and -1 on failure with errno set. * ****************************************************************************/ int initgroups(FAR const char *user, gid_t group) { - /* There currently is no support for supplementary group IDs in NuttX. - * Thus, just ignore this request silently and report success. - */ +#if defined(CONFIG_SCHED_NGROUPS) && CONFIG_SCHED_NGROUPS > 0 + gid_t groups[NGROUPS_MAX]; + int ngroups = NGROUPS_MAX; + int ret; + + if (user == NULL) + { + set_errno(EINVAL); + return ERROR; + } + + ret = getgrouplist(user, group, groups, &ngroups); + if (ret < 0) + { + /* Buffer too small or lookup failure — errno already set by + * getgrouplist when applicable. + */ + + if (ngroups > NGROUPS_MAX) + { + swarn("initgroups: user '%s' has %d groups, NGROUPS_MAX=%d\n", + user, ngroups, NGROUPS_MAX); + set_errno(EINVAL); + } + + return ERROR; + } + + return setgroups(ret, groups); +#else + /* Without supplementary group storage, succeed silently. */ UNUSED(user); UNUSED(group); return 0; +#endif } diff --git a/libs/libc/pwd/lib_find_pwdfile.c b/libs/libc/pwd/lib_find_pwdfile.c index 88da93bbdac7f..bd520c998b708 100644 --- a/libs/libc/pwd/lib_find_pwdfile.c +++ b/libs/libc/pwd/lib_find_pwdfile.c @@ -382,16 +382,6 @@ int pwd_findby_name(FAR const char *uname, FAR struct passwd *entry, int pwd_findby_uid(uid_t uid, FAR struct passwd *entry, FAR char *buffer, size_t buflen) { - /* Verify that the UID is in the valid range of 0 through INT16_MAX. - * OpenGroup.org does not specify a UID_MAX or UID_MIN. Instead we use a - * priori knowledge that uid_t is type int16_t. - */ - - if ((uint16_t)uid > INT16_MAX) - { - return -EINVAL; - } - return pwd_foreach(pwd_match_uid, (uintptr_t)uid, entry, buffer, buflen); } diff --git a/libs/libc/unistd/CMakeLists.txt b/libs/libc/unistd/CMakeLists.txt index 834d61355ffd0..b85e65e54d6cb 100644 --- a/libs/libc/unistd/CMakeLists.txt +++ b/libs/libc/unistd/CMakeLists.txt @@ -63,7 +63,6 @@ set(SRCS lib_getpgrp.c lib_getpgid.c lib_getsid.c - lib_getgroups.c lib_setpgid.c lib_setsid.c lib_lockf.c @@ -90,7 +89,13 @@ if(NOT CONFIG_SCHED_USER_IDENTITY) lib_setregid.c lib_getresuid.c lib_getresgid.c - lib_issetugid.c) + lib_setresuid.c + lib_setresgid.c + lib_issetugid.c + lib_getgroups.c + lib_setgroups.c) +elseif(NOT CONFIG_SCHED_NGROUPS) + list(APPEND SRCS lib_getgroups.c lib_setgroups.c) endif() if(NOT CONFIG_DISABLE_ENVIRON) diff --git a/libs/libc/unistd/Make.defs b/libs/libc/unistd/Make.defs index d4fbc70879be6..3e9c6dfeb2175 100644 --- a/libs/libc/unistd/Make.defs +++ b/libs/libc/unistd/Make.defs @@ -32,7 +32,7 @@ CSRCS += lib_setrlimit.c lib_getrlimit.c lib_setpriority.c lib_getpriority.c CSRCS += lib_futimes.c lib_lutimes.c lib_gethostname.c lib_sethostname.c CSRCS += lib_fchownat.c lib_linkat.c lib_readlinkat.c lib_symlinkat.c CSRCS += lib_unlinkat.c lib_usleep.c lib_getpgrp.c lib_getpgid.c -CSRCS += lib_getsid.c lib_getgroups.c lib_setpgid.c lib_setsid.c +CSRCS += lib_getsid.c lib_setpgid.c lib_setsid.c CSRCS += lib_lockf.c lib_flock.c lib_getpass.c CSRCS += lib_chdir.c lib_fchdir.c lib_confstr.c lib_ulimit.c @@ -40,7 +40,12 @@ ifneq ($(CONFIG_SCHED_USER_IDENTITY),y) CSRCS += lib_setuid.c lib_setgid.c lib_getuid.c lib_getgid.c CSRCS += lib_seteuid.c lib_setegid.c lib_geteuid.c lib_getegid.c CSRCS += lib_setreuid.c lib_setregid.c lib_getresuid.c lib_getresgid.c -CSRCS += lib_issetugid.c +CSRCS += lib_setresuid.c lib_setresgid.c +CSRCS += lib_issetugid.c lib_getgroups.c lib_setgroups.c +else +ifeq ($(CONFIG_SCHED_NGROUPS),0) +CSRCS += lib_getgroups.c lib_setgroups.c +endif endif ifneq ($(CONFIG_DISABLE_ENVIRON),y) diff --git a/libs/libc/unistd/lib_getgroups.c b/libs/libc/unistd/lib_getgroups.c index 53cbc11ba82c2..f28704b23765d 100644 --- a/libs/libc/unistd/lib_getgroups.c +++ b/libs/libc/unistd/lib_getgroups.c @@ -38,9 +38,10 @@ * * Description: * The getgroups() function returns the supplementary group IDs of the - * calling process in the array grouplist. NuttX does not support - * supplementary group IDs, so the calling process is treated as belonging - * to a single group: its effective group ID. + * calling process in the array grouplist. Stub when + * CONFIG_SCHED_USER_IDENTITY is disabled or CONFIG_SCHED_NGROUPS is 0: + * there is no supplementary group list. The effective group ID is not + * synthesized; callers that need it should use getegid(). * * Input Parameters: * gidsetsize - The number of elements available in grouplist. @@ -55,31 +56,15 @@ int getgroups(int gidsetsize, gid_t grouplist[]) { + UNUSED(grouplist); + if (gidsetsize < 0) { set_errno(EINVAL); - return -1; - } - - /* If gidsetsize is zero, return the number of group IDs without touching - * grouplist. - */ - - if (gidsetsize == 0) - { - return 1; - } - - if (grouplist == NULL) - { - set_errno(EFAULT); - return -1; + return ERROR; } - /* NuttX has no notion of supplementary group IDs. Report the single - * effective group ID of the calling process. - */ + /* Empty supplementary list (do not synthesize egid). */ - grouplist[0] = getegid(); - return 1; + return 0; } diff --git a/libs/libc/unistd/lib_setgroups.c b/libs/libc/unistd/lib_setgroups.c new file mode 100644 index 0000000000000..2ff3bb2ee2d8c --- /dev/null +++ b/libs/libc/unistd/lib_setgroups.c @@ -0,0 +1,51 @@ +/**************************************************************************** + * libs/libc/unistd/lib_setgroups.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: setgroups + * + * Description: + * Stub when CONFIG_SCHED_USER_IDENTITY is disabled or + * CONFIG_SCHED_NGROUPS is 0. Supplementary groups are not supported. + * + ****************************************************************************/ + +int setgroups(int size, FAR const gid_t *list) +{ + UNUSED(size); + UNUSED(list); + set_errno(ENOSYS); + return ERROR; +} diff --git a/libs/libc/unistd/lib_setresgid.c b/libs/libc/unistd/lib_setresgid.c new file mode 100644 index 0000000000000..ae0ad16262c54 --- /dev/null +++ b/libs/libc/unistd/lib_setresgid.c @@ -0,0 +1,69 @@ +/**************************************************************************** + * libs/libc/unistd/lib_setresgid.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: setresgid + * + * Description: + * The setresgid() function sets the real, effective, and saved + * set-group-ID of the calling process. Stub when + * CONFIG_SCHED_USER_IDENTITY is disabled: only root (0) or unchanged + * ((gid_t)-1) values are accepted. + * + * Input Parameters: + * rgid - Real group ID, or (gid_t)-1 to leave unchanged. + * egid - Effective group ID, or (gid_t)-1 to leave unchanged. + * sgid - Saved set-group-ID, or (gid_t)-1 to leave unchanged. + * + * Returned Value: + * Zero if successful and -1 in case of failure, in which case errno is set + * appropriately. + * + ****************************************************************************/ + +int setresgid(gid_t rgid, gid_t egid, gid_t sgid) +{ + /* NuttX only supports the group identity 'root' with a gid value of 0. */ + + if ((rgid == (gid_t)-1 || rgid == 0) && + (egid == (gid_t)-1 || egid == 0) && + (sgid == (gid_t)-1 || sgid == 0)) + { + return 0; + } + + set_errno(EINVAL); + return ERROR; +} diff --git a/libs/libc/unistd/lib_setresuid.c b/libs/libc/unistd/lib_setresuid.c new file mode 100644 index 0000000000000..847ded42a7e0c --- /dev/null +++ b/libs/libc/unistd/lib_setresuid.c @@ -0,0 +1,68 @@ +/**************************************************************************** + * libs/libc/unistd/lib_setresuid.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: setresuid + * + * Description: + * The setresuid() function sets the real, effective, and saved set-user-ID + * of the calling process. Stub when CONFIG_SCHED_USER_IDENTITY is + * disabled: only root (0) or unchanged ((uid_t)-1) values are accepted. + * + * Input Parameters: + * ruid - Real user ID, or (uid_t)-1 to leave unchanged. + * euid - Effective user ID, or (uid_t)-1 to leave unchanged. + * suid - Saved set-user-ID, or (uid_t)-1 to leave unchanged. + * + * Returned Value: + * Zero if successful and -1 in case of failure, in which case errno is set + * appropriately. + * + ****************************************************************************/ + +int setresuid(uid_t ruid, uid_t euid, uid_t suid) +{ + /* NuttX only supports the user identity 'root' with a uid value of 0. */ + + if ((ruid == (uid_t)-1 || ruid == 0) && + (euid == (uid_t)-1 || euid == 0) && + (suid == (uid_t)-1 || suid == 0)) + { + return 0; + } + + set_errno(EINVAL); + return ERROR; +} diff --git a/libs/libc/unistd/lib_sysconf.c b/libs/libc/unistd/lib_sysconf.c index dc6bfb8cba186..428482aea52d5 100644 --- a/libs/libc/unistd/lib_sysconf.c +++ b/libs/libc/unistd/lib_sysconf.c @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -241,6 +242,9 @@ long sysconf(int name) case _SC_OPEN_MAX: return OPEN_MAX; + case _SC_NGROUPS_MAX: + return NGROUPS_MAX; + case _SC_ATEXIT_MAX: return ATEXIT_MAX; diff --git a/sched/Kconfig b/sched/Kconfig index 5532fc4114b31..bac8e961aa2d3 100644 --- a/sched/Kconfig +++ b/sched/Kconfig @@ -803,6 +803,21 @@ config SCHED_USER_IDENTITY If this option is not selected, stub root-only versions of these interfaces are available instead. +if SCHED_USER_IDENTITY + +config SCHED_NGROUPS + int "Maximum supplementary group IDs" + default 8 + range 0 64 + ---help--- + Maximum number of supplementary group IDs per task group. This + value becomes NGROUPS_MAX and sizes the getgroups()/setgroups() + list stored in each task group. Set to 0 to disable supplementary + group storage (getgroups then always returns an empty list). + The effective GID is separate and available via getegid(). + +endif # SCHED_USER_IDENTITY + config SCHED_THREAD_LOCAL bool "Support __thread/thread_local keyword" default n diff --git a/sched/group/CMakeLists.txt b/sched/group/CMakeLists.txt index 42f21d504bce6..5353552c8492f 100644 --- a/sched/group/CMakeLists.txt +++ b/sched/group/CMakeLists.txt @@ -57,7 +57,12 @@ if(CONFIG_SCHED_USER_IDENTITY) group_setregid.c group_getresuid.c group_getresgid.c + group_setresuid.c + group_setresgid.c group_issetugid.c) + if(CONFIG_SCHED_NGROUPS) + list(APPEND SRCS group_setgroups.c group_getgroups.c) + endif() endif() if(CONFIG_SIG_SIGSTOP_ACTION) diff --git a/sched/group/Make.defs b/sched/group/Make.defs index f505a0d80f539..b2605e53fe5a0 100644 --- a/sched/group/Make.defs +++ b/sched/group/Make.defs @@ -42,7 +42,11 @@ ifeq ($(CONFIG_SCHED_USER_IDENTITY),y) CSRCS += group_setuid.c group_setgid.c group_getuid.c group_getgid.c CSRCS += group_seteuid.c group_setegid.c group_geteuid.c group_getegid.c CSRCS += group_setreuid.c group_setregid.c group_getresuid.c group_getresgid.c +CSRCS += group_setresuid.c group_setresgid.c CSRCS += group_issetugid.c +ifneq ($(CONFIG_SCHED_NGROUPS),0) +CSRCS += group_setgroups.c group_getgroups.c +endif endif ifeq ($(CONFIG_SIG_SIGSTOP_ACTION),y) diff --git a/sched/group/group_create.c b/sched/group/group_create.c index c7b1af1fd465d..1fb32bf7ff218 100644 --- a/sched/group/group_create.c +++ b/sched/group/group_create.c @@ -26,6 +26,7 @@ #include +#include #include #include #include @@ -84,6 +85,14 @@ static inline void group_inherit_identity(FAR struct task_group_s *group) group->tg_egid = rgroup->tg_egid; group->tg_suid = rgroup->tg_suid; group->tg_sgid = rgroup->tg_sgid; +#if CONFIG_SCHED_NGROUPS > 0 + group->tg_ngroups = rgroup->tg_ngroups; + if (rgroup->tg_ngroups > 0) + { + memcpy(group->tg_groups, rgroup->tg_groups, + rgroup->tg_ngroups * sizeof(gid_t)); + } +#endif } #else # define group_inherit_identity(group) diff --git a/sched/group/group_getgroups.c b/sched/group/group_getgroups.c new file mode 100644 index 0000000000000..90d9a8dd53884 --- /dev/null +++ b/sched/group/group_getgroups.c @@ -0,0 +1,100 @@ +/**************************************************************************** + * sched/group/group_getgroups.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: getgroups + * + * Description: + * getgroups() returns the supplementary group IDs of the calling + * process. The returned list is exactly the set installed by + * setgroups()/initgroups() (may be empty). The effective group ID is + * not synthesized into an empty list; callers that need it should use + * getegid(). + * + * Input Parameters: + * gidsetsize - Number of slots in grouplist, or 0 to query the count. + * grouplist - Buffer for group IDs (unused when gidsetsize is 0). + * + * Returned Value: + * Number of group IDs on success; -1 on failure with errno set. + * + ****************************************************************************/ + +int getgroups(int gidsetsize, FAR gid_t grouplist[]) +{ + FAR struct tcb_s *rtcb; + FAR struct task_group_s *rgroup; + int count; + + if (gidsetsize < 0) + { + set_errno(EINVAL); + return ERROR; + } + + rtcb = this_task(); + rgroup = rtcb->group; + DEBUGASSERT(rgroup != NULL); + + count = rgroup->tg_ngroups; + + if (gidsetsize == 0) + { + return count; + } + + if (grouplist == NULL) + { + set_errno(EFAULT); + return ERROR; + } + + if (gidsetsize < count) + { + set_errno(EINVAL); + return ERROR; + } + + if (count > 0) + { + memcpy(grouplist, rgroup->tg_groups, count * sizeof(gid_t)); + } + + return count; +} diff --git a/sched/group/group_setegid.c b/sched/group/group_setegid.c index 733f11f84dc4d..e80d503e395d8 100644 --- a/sched/group/group_setegid.c +++ b/sched/group/group_setegid.c @@ -62,17 +62,6 @@ int setegid(gid_t gid) FAR struct tcb_s *rtcb; FAR struct task_group_s *rgroup; - /* Verify that the GID is in the valid range of 0 through INT16_MAX. - * OpenGroup.org does not specify a GID_MAX or GID_MIN. Instead we use a - * priori knowledge that gid_t is type int16_t. - */ - - if ((uint16_t)gid > INT16_MAX) - { - set_errno(EINVAL); - return ERROR; - } - /* Get the currently executing thread's task group. */ rtcb = this_task(); diff --git a/sched/group/group_seteuid.c b/sched/group/group_seteuid.c index 758a63553653c..3fd4469b98e53 100644 --- a/sched/group/group_seteuid.c +++ b/sched/group/group_seteuid.c @@ -64,17 +64,6 @@ int seteuid(uid_t uid) FAR struct tcb_s *rtcb; FAR struct task_group_s *rgroup; - /* Verify that the UID is in the valid range of 0 through INT16_MAX. - * OpenGroup.org does not specify a UID_MAX or UID_MIN. Instead we use a - * priori knowledge that uid_t is type int16_t. - */ - - if ((uint16_t)uid > INT16_MAX) - { - set_errno(EINVAL); - return ERROR; - } - /* Get the currently executing thread's task group. */ rtcb = this_task(); diff --git a/sched/group/group_setgid.c b/sched/group/group_setgid.c index 72131e5ee5f25..812958b2f9bb7 100644 --- a/sched/group/group_setgid.c +++ b/sched/group/group_setgid.c @@ -63,17 +63,6 @@ int setgid(gid_t gid) FAR struct tcb_s *rtcb; FAR struct task_group_s *rgroup; - /* Verify that the GID is in the valid range of 0 through INT16_MAX. - * OpenGroup.org does not specify a GID_MAX or GID_MIN. Instead we use a - * priori knowledge that gid_t is type int16_t. - */ - - if ((uint16_t)gid > INT16_MAX) - { - set_errno(EINVAL); - return ERROR; - } - /* Get the currently executing thread's task group. */ rtcb = this_task(); diff --git a/sched/group/group_setgroups.c b/sched/group/group_setgroups.c new file mode 100644 index 0000000000000..7f6243dbb1cf5 --- /dev/null +++ b/sched/group/group_setgroups.c @@ -0,0 +1,92 @@ +/**************************************************************************** + * sched/group/group_setgroups.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: setgroups + * + * Description: + * setgroups() sets the supplementary group IDs for the calling process. + * Only a process with an effective user ID of 0 may change the list. + * + * Input Parameters: + * size - Number of group IDs in list (0 to clear). + * list - Array of supplementary group IDs, or NULL when size is 0. + * + * Returned Value: + * Zero on success; -1 on failure with errno set. + * + ****************************************************************************/ + +int setgroups(int size, FAR const gid_t *list) +{ + FAR struct tcb_s *rtcb; + FAR struct task_group_s *rgroup; + + if (size < 0 || size > CONFIG_SCHED_NGROUPS) + { + set_errno(EINVAL); + return ERROR; + } + + if (size > 0 && list == NULL) + { + set_errno(EFAULT); + return ERROR; + } + + rtcb = this_task(); + rgroup = rtcb->group; + DEBUGASSERT(rgroup != NULL); + + /* Only root (effective UID 0) may install a new supplementary set. */ + + if (rgroup->tg_euid != 0) + { + set_errno(EPERM); + return ERROR; + } + + if (size > 0) + { + memcpy(rgroup->tg_groups, list, size * sizeof(gid_t)); + } + + rgroup->tg_ngroups = size; + return OK; +} diff --git a/sched/group/group_setregid.c b/sched/group/group_setregid.c index 61360f939ddd0..5ec7676d8b0f7 100644 --- a/sched/group/group_setregid.c +++ b/sched/group/group_setregid.c @@ -63,18 +63,6 @@ int setregid(gid_t rgid, gid_t egid) gid_t old_egid; gid_t old_sgid; - if (rgid != (gid_t)-1 && (uint16_t)rgid > INT16_MAX) - { - set_errno(EINVAL); - return ERROR; - } - - if (egid != (gid_t)-1 && (uint16_t)egid > INT16_MAX) - { - set_errno(EINVAL); - return ERROR; - } - if (rgid == (gid_t)-1 && egid == (gid_t)-1) { return OK; diff --git a/sched/group/group_setresgid.c b/sched/group/group_setresgid.c new file mode 100644 index 0000000000000..825877c1516b2 --- /dev/null +++ b/sched/group/group_setresgid.c @@ -0,0 +1,92 @@ +/**************************************************************************** + * sched/group/group_setresgid.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: setresgid + * + * Description: + * setresgid() sets the real, effective, and saved set-group-IDs of the + * calling process. The value (gid_t)-1 + * for any argument leaves that ID unchanged. + * + ****************************************************************************/ + +int setresgid(gid_t rgid, gid_t egid, gid_t sgid) +{ + FAR struct tcb_s *rtcb; + FAR struct task_group_s *rgroup; + gid_t old_rgid; + gid_t old_egid; + gid_t old_sgid; + gid_t new_rgid; + gid_t new_egid; + gid_t new_sgid; + + rtcb = this_task(); + rgroup = rtcb->group; + DEBUGASSERT(rgroup != NULL); + + old_rgid = rgroup->tg_gid; + old_egid = rgroup->tg_egid; + old_sgid = rgroup->tg_sgid; + + new_rgid = (rgid == (gid_t)-1) ? old_rgid : rgid; + new_egid = (egid == (gid_t)-1) ? old_egid : egid; + new_sgid = (sgid == (gid_t)-1) ? old_sgid : sgid; + + /* Non-root euid may only select among current real/effective/saved. */ + + if (rgroup->tg_euid != 0) + { + if ((new_rgid != old_rgid && new_rgid != old_egid && + new_rgid != old_sgid) || + (new_egid != old_rgid && new_egid != old_egid && + new_egid != old_sgid) || + (new_sgid != old_rgid && new_sgid != old_egid && + new_sgid != old_sgid)) + { + set_errno(EPERM); + return ERROR; + } + } + + rgroup->tg_gid = new_rgid; + rgroup->tg_egid = new_egid; + rgroup->tg_sgid = new_sgid; + return OK; +} diff --git a/sched/group/group_setresuid.c b/sched/group/group_setresuid.c new file mode 100644 index 0000000000000..ca2d6e3267718 --- /dev/null +++ b/sched/group/group_setresuid.c @@ -0,0 +1,93 @@ +/**************************************************************************** + * sched/group/group_setresuid.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: setresuid + * + * Description: + * setresuid() sets the real, effective, and saved set-user-IDs of the + * calling process. The value (uid_t)-1 for any argument leaves that + * ID unchanged. + * + ****************************************************************************/ + +int setresuid(uid_t ruid, uid_t euid, uid_t suid) +{ + FAR struct tcb_s *rtcb; + FAR struct task_group_s *rgroup; + uid_t old_ruid; + uid_t old_euid; + uid_t old_suid; + uid_t new_ruid; + uid_t new_euid; + uid_t new_suid; + + rtcb = this_task(); + rgroup = rtcb->group; + DEBUGASSERT(rgroup != NULL); + + old_ruid = rgroup->tg_uid; + old_euid = rgroup->tg_euid; + old_suid = rgroup->tg_suid; + + new_ruid = (ruid == (uid_t)-1) ? old_ruid : ruid; + new_euid = (euid == (uid_t)-1) ? old_euid : euid; + new_suid = (suid == (uid_t)-1) ? old_suid : suid; + + if (old_euid != 0) + { + /* Unprivileged: each new ID must be one of the current r/e/s UIDs. */ + + if ((new_ruid != old_ruid && new_ruid != old_euid && + new_ruid != old_suid) || + (new_euid != old_ruid && new_euid != old_euid && + new_euid != old_suid) || + (new_suid != old_ruid && new_suid != old_euid && + new_suid != old_suid)) + { + set_errno(EPERM); + return ERROR; + } + } + + rgroup->tg_uid = new_ruid; + rgroup->tg_euid = new_euid; + rgroup->tg_suid = new_suid; + return OK; +} diff --git a/sched/group/group_setreuid.c b/sched/group/group_setreuid.c index 0610f9fdda689..1d37e070e2cb2 100644 --- a/sched/group/group_setreuid.c +++ b/sched/group/group_setreuid.c @@ -64,18 +64,6 @@ int setreuid(uid_t ruid, uid_t euid) uid_t old_euid; uid_t old_suid; - if (ruid != (uid_t)-1 && (uint16_t)ruid > INT16_MAX) - { - set_errno(EINVAL); - return ERROR; - } - - if (euid != (uid_t)-1 && (uint16_t)euid > INT16_MAX) - { - set_errno(EINVAL); - return ERROR; - } - if (ruid == (uid_t)-1 && euid == (uid_t)-1) { return OK; diff --git a/sched/group/group_setuid.c b/sched/group/group_setuid.c index 16073ed531317..3842410859449 100644 --- a/sched/group/group_setuid.c +++ b/sched/group/group_setuid.c @@ -64,17 +64,6 @@ int setuid(uid_t uid) FAR struct tcb_s *rtcb; FAR struct task_group_s *rgroup; - /* Verify that the UID is in the valid range of 0 through INT16_MAX. - * OpenGroup.org does not specify a UID_MAX or UID_MIN. Instead we use a - * priori knowledge that uid_t is type int16_t. - */ - - if ((uint16_t)uid > INT16_MAX) - { - set_errno(EINVAL); - return ERROR; - } - /* Get the currently executing thread's task group. */ rtcb = this_task(); diff --git a/syscall/syscall.csv b/syscall/syscall.csv index f5675cea0a0ed..c488776b3c49b 100644 --- a/syscall/syscall.csv +++ b/syscall/syscall.csv @@ -45,6 +45,7 @@ "getenv","stdlib.h","!defined(CONFIG_DISABLE_ENVIRON)","FAR char *","FAR const char *" "geteuid","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY)","uid_t" "getgid","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY)","gid_t" +"getgroups","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY) && (CONFIG_SCHED_NGROUPS > 0)","int","int","FAR gid_t *" "gethostname","unistd.h","","int","FAR char *","size_t" "getitimer","sys/time.h","!defined(CONFIG_DISABLE_POSIX_TIMERS)","int","int","FAR struct itimerval *" "getpeername","sys/socket.h","defined(CONFIG_NET)","int","int","FAR struct sockaddr *","FAR socklen_t *" @@ -158,8 +159,11 @@ "sendmsg","sys/socket.h","defined(CONFIG_NET)","ssize_t","int","FAR const struct msghdr *","int" "sendto","sys/socket.h","defined(CONFIG_NET)","ssize_t","int","FAR const void *","size_t","int","FAR const struct sockaddr *","socklen_t" "setegid","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY)","int","gid_t" +"setgroups","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY) && (CONFIG_SCHED_NGROUPS > 0)","int","int","FAR const gid_t *" "setregid","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY)","int","gid_t","gid_t" "setreuid","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY)","int","uid_t","uid_t" +"setresgid","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY)","int","gid_t","gid_t","gid_t" +"setresuid","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY)","int","uid_t","uid_t","uid_t" "setenv","stdlib.h","!defined(CONFIG_DISABLE_ENVIRON)","int","FAR const char *","FAR const char *","int" "seteuid","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY)","int","uid_t" "setgid","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY)","int","gid_t"