Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 56 additions & 6 deletions Documentation/implementation/user_identity.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
=====================
Expand Down Expand Up @@ -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()``
---------------------------------
Expand All @@ -77,13 +98,42 @@ 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
=============

``CONFIG_SCHED_USER_IDENTITY``
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``.
Expand Down
9 changes: 6 additions & 3 deletions binfmt/binfmt_checkexec.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 */

Expand All @@ -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;
}
Expand Down
8 changes: 4 additions & 4 deletions fs/inode/fs_inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
}
Expand Down
6 changes: 5 additions & 1 deletion include/limits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions include/nuttx/sched.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *******************************************************/
Expand Down Expand Up @@ -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
*
Expand Down
6 changes: 6 additions & 0 deletions include/sys/syscall_lookup.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion include/unistd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
11 changes: 0 additions & 11 deletions libs/libc/grp/lib_find_grpfile.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
#include <nuttx/lib/lib.h>

#include "grp/lib_grp.h"

/****************************************************************************
* Private Types
****************************************************************************/
Expand Down Expand Up @@ -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);
}

Expand Down
53 changes: 43 additions & 10 deletions libs/libc/grp/lib_initgroups.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
#include <nuttx/config.h>

#include <grp.h>
#include <limits.h>
#include <unistd.h>
#include <errno.h>

#include <nuttx/debug.h>

/****************************************************************************
* Public Functions
Expand All @@ -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
}
10 changes: 0 additions & 10 deletions libs/libc/pwd/lib_find_pwdfile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
9 changes: 7 additions & 2 deletions libs/libc/unistd/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
Loading
Loading