This is an automated email from the ASF dual-hosted git repository.
acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git
The following commit(s) were added to refs/heads/master by this push:
new 7358692c5 system/sudo: add setuid privilege helper
7358692c5 is described below
commit 7358692c598bc99d5b38da84d2060fe006ea07d1
Author: Abhishek Mishra <[email protected]>
AuthorDate: Fri Aug 14 17:21:23 2026 +0000
system/sudo: add setuid privilege helper
Install a UNIX-style setuid-root sudo app: the kernel raises euid on
exec, userspace verifies the caller with passwd_verify(), then
setresuid/setgroups and execvp() run the command. Ostest covers
setuid exec after a hard credential drop.
Signed-off-by: Abhishek Mishra <[email protected]>
---
builtin/exec_builtin.c | 5 +
examples/hello/CMakeLists.txt | 36 ++-
examples/hello/Kconfig | 10 +
examples/hello/Makefile | 6 +
nshlib/nsh_fileapps.c | 8 +
nshlib/nsh_identity.c | 35 ++-
{examples/hello => system/sudo}/CMakeLists.txt | 24 +-
system/sudo/Kconfig | 47 ++++
examples/hello/Makefile => system/sudo/Make.defs | 19 +-
{examples/hello => system/sudo}/Makefile | 20 +-
system/sudo/sudo_main.c | 336 +++++++++++++++++++++++
testing/ostest/multiuser.c | 75 +++++
12 files changed, 570 insertions(+), 51 deletions(-)
diff --git a/builtin/exec_builtin.c b/builtin/exec_builtin.c
index b87133243..f6edf1a5f 100644
--- a/builtin/exec_builtin.c
+++ b/builtin/exec_builtin.c
@@ -209,6 +209,11 @@ int exec_builtin(FAR const char *appname, FAR char * const
*argv,
/* Load and execute the application. */
ret = posix_spawn(&pid, builtin->name, &file_actions, &attr, argv, NULL);
+ if (ret == EACCES)
+ {
+ goto errout_with_actions;
+ }
+
if (ret != 0 && builtin->main != NULL)
#endif
{
diff --git a/examples/hello/CMakeLists.txt b/examples/hello/CMakeLists.txt
index 94fefd612..1fb3f2f4b 100644
--- a/examples/hello/CMakeLists.txt
+++ b/examples/hello/CMakeLists.txt
@@ -21,13 +21,31 @@
#
##############################################################################
if(CONFIG_EXAMPLES_HELLO)
- nuttx_add_application(
- NAME
- ${CONFIG_EXAMPLES_HELLO_PROGNAME}
- SRCS
- hello_main.c
- STACKSIZE
- ${CONFIG_EXAMPLES_HELLO_STACKSIZE}
- PRIORITY
- ${CONFIG_EXAMPLES_HELLO_PRIORITY})
+ if(CONFIG_EXAMPLES_HELLO_RESTRICTED)
+ nuttx_add_application(
+ NAME
+ ${CONFIG_EXAMPLES_HELLO_PROGNAME}
+ SRCS
+ hello_main.c
+ STACKSIZE
+ ${CONFIG_EXAMPLES_HELLO_STACKSIZE}
+ PRIORITY
+ ${CONFIG_EXAMPLES_HELLO_PRIORITY}
+ UID
+ 0
+ GID
+ 0
+ MODE
+ 484)
+ else()
+ nuttx_add_application(
+ NAME
+ ${CONFIG_EXAMPLES_HELLO_PROGNAME}
+ SRCS
+ hello_main.c
+ STACKSIZE
+ ${CONFIG_EXAMPLES_HELLO_STACKSIZE}
+ PRIORITY
+ ${CONFIG_EXAMPLES_HELLO_PRIORITY})
+ endif()
endif()
diff --git a/examples/hello/Kconfig b/examples/hello/Kconfig
index a35bddc23..685b143ca 100644
--- a/examples/hello/Kconfig
+++ b/examples/hello/Kconfig
@@ -26,4 +26,14 @@ config EXAMPLES_HELLO_STACKSIZE
int "Hello stack size"
default DEFAULT_TASK_STACKSIZE
+config EXAMPLES_HELLO_RESTRICTED
+ bool "Install hello as owner-execute only (0744)"
+ default n
+ depends on SCHED_USER_IDENTITY
+ ---help---
+ Set the installed mode to ``-rwxr--r--`` owned by root so a
+ non-root user cannot exec ``/bin/hello``. Used with
+ ``CONFIG_SYSTEM_SUDO`` to show ``sudo /bin/hello`` restoring
+ access.
+
endif
diff --git a/examples/hello/Makefile b/examples/hello/Makefile
index e8bcc4985..8c1311094 100644
--- a/examples/hello/Makefile
+++ b/examples/hello/Makefile
@@ -29,6 +29,12 @@ PRIORITY = $(CONFIG_EXAMPLES_HELLO_PRIORITY)
STACKSIZE = $(CONFIG_EXAMPLES_HELLO_STACKSIZE)
MODULE = $(CONFIG_EXAMPLES_HELLO)
+ifeq ($(CONFIG_EXAMPLES_HELLO_RESTRICTED),y)
+UID = 0
+GID = 0
+MODE = 0744
+endif
+
# Hello, World! Example
MAINSRC = hello_main.c
diff --git a/nshlib/nsh_fileapps.c b/nshlib/nsh_fileapps.c
index 66f41e36f..1d6cfe69d 100644
--- a/nshlib/nsh_fileapps.c
+++ b/nshlib/nsh_fileapps.c
@@ -253,6 +253,14 @@ int nsh_fileapp(FAR struct nsh_vtbl_s *vtbl, FAR const
char *cmd,
*/
ret = posix_spawnp(&pid, cmd, &file_actions, &attr, argv, environ);
+ if (ret == EACCES)
+ {
+ nsh_error(vtbl, "nsh: %s: Permission denied\n", cmd);
+ rc = 1;
+ ret = OK;
+ goto errout_with_actions;
+ }
+
if (ret == OK)
{
/* The application was successfully started with pre-emption disabled.
diff --git a/nshlib/nsh_identity.c b/nshlib/nsh_identity.c
index 5027975c5..98204d408 100644
--- a/nshlib/nsh_identity.c
+++ b/nshlib/nsh_identity.c
@@ -178,10 +178,14 @@ static bool nsh_verify_credentials(FAR const char
*username,
* Name: nsh_switch_credentials
*
* Description:
- * Switch the session to the given UID/GID. NSH starts with real UID/GID
- * zero; file permission checks use the effective identity. When the real
- * UID is still zero, only the effective UID/GID are changed so that a
- * later 'su' can regain root via seteuid(0) after password verification.
+ * Switch the session to the given UID/GID.
+ *
+ * When the real UID is still zero and the target is not root, set the
+ * real and effective IDs to the target and keep saved-root (suid/sgid
+ * 0). File DAC then uses the unprivileged effective ID, while setuid
+ * helpers such as sudo still see the real UID of the invoking user
+ * after S_ISUID raises the effective UID to 0. A later ``su root``
+ * can restore root from the saved IDs after password verification.
*
****************************************************************************/
@@ -189,15 +193,25 @@ static int nsh_switch_credentials(uid_t uid, gid_t gid)
{
if (getuid() == 0)
{
- if (geteuid() != 0 || getegid() != 0)
+ if (geteuid() != 0)
+ {
+ if (seteuid(0) != 0)
+ {
+ return -errno;
+ }
+ }
+
+ if (uid == 0)
{
- if (seteuid(0) != 0 || setegid(0) != 0)
+ if (setresgid(0, 0, 0) != 0 || setresuid(0, 0, 0) != 0)
{
return -errno;
}
+
+ return OK;
}
- if (seteuid(uid) != 0 || setegid(gid) != 0)
+ if (setresgid(gid, gid, 0) != 0 || setresuid(uid, uid, 0) != 0)
{
return -errno;
}
@@ -205,7 +219,7 @@ static int nsh_switch_credentials(uid_t uid, gid_t gid)
return OK;
}
- if (setuid(uid) != 0 || setgid(gid) != 0)
+ if (setresgid(gid, gid, gid) != 0 || setresuid(uid, uid, uid) != 0)
{
return -errno;
}
@@ -222,8 +236,9 @@ static int nsh_switch_credentials(uid_t uid, gid_t gid)
*
* Description:
* Look up 'username' in the passwd database and set the calling task's
- * session identity. When NSH still has real UID zero, only the effective
- * UID/GID are updated so that 'su' can switch users later.
+ * session identity. When switching from real UID zero to a non-root
+ * user, real and effective IDs become that user and saved-root is kept
+ * so ``su root`` can restore privileges after authentication.
*
* Input Parameters:
* username - Login name to assume
diff --git a/examples/hello/CMakeLists.txt b/system/sudo/CMakeLists.txt
similarity index 83%
copy from examples/hello/CMakeLists.txt
copy to system/sudo/CMakeLists.txt
index 94fefd612..0d11931dc 100644
--- a/examples/hello/CMakeLists.txt
+++ b/system/sudo/CMakeLists.txt
@@ -1,5 +1,5 @@
#
##############################################################################
-# apps/examples/hello/CMakeLists.txt
+# apps/system/sudo/CMakeLists.txt
#
# SPDX-License-Identifier: Apache-2.0
#
@@ -20,14 +20,22 @@
#
#
##############################################################################
-if(CONFIG_EXAMPLES_HELLO)
+if(CONFIG_SYSTEM_SUDO)
nuttx_add_application(
NAME
- ${CONFIG_EXAMPLES_HELLO_PROGNAME}
- SRCS
- hello_main.c
- STACKSIZE
- ${CONFIG_EXAMPLES_HELLO_STACKSIZE}
+ sudo
PRIORITY
- ${CONFIG_EXAMPLES_HELLO_PRIORITY})
+ 100
+ STACKSIZE
+ ${CONFIG_DEFAULT_TASK_STACKSIZE}
+ MODULE
+ ${CONFIG_SYSTEM_SUDO}
+ UID
+ 0
+ GID
+ 0
+ MODE
+ 35309
+ SRCS
+ sudo_main.c)
endif()
diff --git a/system/sudo/Kconfig b/system/sudo/Kconfig
new file mode 100644
index 000000000..a64cdea5f
--- /dev/null
+++ b/system/sudo/Kconfig
@@ -0,0 +1,47 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+config SYSTEM_SUDO
+ tristate "sudo (setuid privilege helper)"
+ default n
+ depends on SCHED_USER_IDENTITY
+ depends on LIBC_PASSWD_FILE
+ depends on FSUTILS_PASSWD
+ depends on LIBC_EXECFUNCS
+ ---help---
+ Install a Linux-style setuid-root ``sudo`` program. The kernel
+ raises the effective UID to zero when the ELF is loaded (see
+ ``nx_uid`` / ``nx_mode`` in Application.mk). ``sudo`` verifies
+ the invoking user's password with ``passwd_verify()``, becomes
+ fully root via ``setresuid()`` / ``setresgid()``, then
+ ``execvp()``s the requested command.
+
+ Requires ``CONFIG_LIBC_EXECFUNCS`` so NSH (or ``posix_spawn``)
+ loads the application ELF instead of calling its entry point
+ directly from the builtin registry.
+
+ Non-root callers must appear in the sudoers file
+ (``SYSTEM_SUDO_SUDOERS_PATH``) or in
+ ``SYSTEM_SUDO_ALLOWED_USERS``. Real UID 0 is always allowed.
+
+if SYSTEM_SUDO
+
+config SYSTEM_SUDO_SUDOERS_PATH
+ string "Path to sudoers allowlist"
+ default "/etc/sudoers"
+ ---help---
+ Simple allowlist, one username per line. Lines starting with
+ ``#`` are comments. A Linux-style first field is also accepted
+ (``user ALL=(ALL) ALL``). Generate this file in ROMFS with
+ ``CONFIG_BOARD_ETC_ROMFS_PASSWD_EXTRA_ENABLE``.
+
+config SYSTEM_SUDO_ALLOWED_USERS
+ string "Compile-time sudoers usernames"
+ default ""
+ ---help---
+ Optional comma-separated extra allowlist compiled into sudo.
+ Use this when /etc/sudoers is not present. Example: ``user``.
+
+endif # SYSTEM_SUDO
diff --git a/examples/hello/Makefile b/system/sudo/Make.defs
similarity index 73%
copy from examples/hello/Makefile
copy to system/sudo/Make.defs
index e8bcc4985..09a90f66f 100644
--- a/examples/hello/Makefile
+++ b/system/sudo/Make.defs
@@ -1,5 +1,5 @@
############################################################################
-# apps/examples/hello/Makefile
+# apps/system/sudo/Make.defs
#
# SPDX-License-Identifier: Apache-2.0
#
@@ -20,17 +20,6 @@
#
############################################################################
-include $(APPDIR)/Make.defs
-
-# Hello, World! built-in application info
-
-PROGNAME = $(CONFIG_EXAMPLES_HELLO_PROGNAME)
-PRIORITY = $(CONFIG_EXAMPLES_HELLO_PRIORITY)
-STACKSIZE = $(CONFIG_EXAMPLES_HELLO_STACKSIZE)
-MODULE = $(CONFIG_EXAMPLES_HELLO)
-
-# Hello, World! Example
-
-MAINSRC = hello_main.c
-
-include $(APPDIR)/Application.mk
+ifneq ($(CONFIG_SYSTEM_SUDO),)
+CONFIGURED_APPS += $(APPDIR)/system/sudo
+endif
diff --git a/examples/hello/Makefile b/system/sudo/Makefile
similarity index 76%
copy from examples/hello/Makefile
copy to system/sudo/Makefile
index e8bcc4985..6e22818fe 100644
--- a/examples/hello/Makefile
+++ b/system/sudo/Makefile
@@ -1,5 +1,5 @@
############################################################################
-# apps/examples/hello/Makefile
+# apps/system/sudo/Makefile
#
# SPDX-License-Identifier: Apache-2.0
#
@@ -22,15 +22,17 @@
include $(APPDIR)/Make.defs
-# Hello, World! built-in application info
+PROGNAME = sudo
+PRIORITY = 100
+STACKSIZE = $(CONFIG_DEFAULT_TASK_STACKSIZE)
+MODULE = $(CONFIG_SYSTEM_SUDO)
-PROGNAME = $(CONFIG_EXAMPLES_HELLO_PROGNAME)
-PRIORITY = $(CONFIG_EXAMPLES_HELLO_PRIORITY)
-STACKSIZE = $(CONFIG_EXAMPLES_HELLO_STACKSIZE)
-MODULE = $(CONFIG_EXAMPLES_HELLO)
+MAINSRC = sudo_main.c
-# Hello, World! Example
-
-MAINSRC = hello_main.c
+# Set-user-ID root executable (Linux-style sudo): S_IFREG | S_ISUID |
+# S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
+UID = 0
+GID = 0
+MODE = 35309
include $(APPDIR)/Application.mk
diff --git a/system/sudo/sudo_main.c b/system/sudo/sudo_main.c
new file mode 100644
index 000000000..53d96856f
--- /dev/null
+++ b/system/sudo/sudo_main.c
@@ -0,0 +1,336 @@
+/****************************************************************************
+ * apps/system/sudo/sudo_main.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 <nuttx/config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <fsutils/passwd.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define SUDO_PROBE_ARG "--probe"
+#define SUDO_MAX_PASSWORD 256
+#define SUDO_SUDOERS_LINE 128
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: sudo_read_password
+ ****************************************************************************/
+
+static int sudo_read_password(FAR char *password, size_t buflen)
+{
+ struct termios saved;
+ struct termios cfg;
+ ssize_t nread;
+ int errcode = 0;
+ bool restore = false;
+
+ if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved) == 0)
+ {
+ cfg = saved;
+ cfg.c_lflag &= (tcflag_t)~ECHO;
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &cfg) == 0)
+ {
+ restore = true;
+ }
+ }
+
+ password[0] = '\0';
+ nread = read(STDIN_FILENO, password, buflen - 1);
+ if (nread < 0)
+ {
+ errcode = errno;
+ }
+
+ if (restore)
+ {
+ tcsetattr(STDIN_FILENO, TCSANOW, &saved);
+ }
+
+ if (nread < 0)
+ {
+ return -errcode;
+ }
+
+ if (nread == 0)
+ {
+ return -ENODATA;
+ }
+
+ password[nread] = '\0';
+
+ if (password[nread - 1] == '\n')
+ {
+ password[nread - 1] = '\0';
+ }
+
+ fputc('\n', stderr);
+ return 0;
+}
+
+/****************************************************************************
+ * Name: sudo_token_eq
+ ****************************************************************************/
+
+static bool sudo_token_eq(FAR const char *name, FAR const char *tok,
+ size_t toklen)
+{
+ return strncmp(name, tok, toklen) == 0 && name[toklen] == '\0';
+}
+
+/****************************************************************************
+ * Name: sudo_name_in_csv
+ ****************************************************************************/
+
+static bool sudo_name_in_csv(FAR const char *name, FAR const char *list)
+{
+ FAR const char *p = list;
+ FAR const char *start;
+
+ if (name == NULL || list == NULL)
+ {
+ return false;
+ }
+
+ while (*p != '\0')
+ {
+ while (*p == ',' || isspace((unsigned char)*p))
+ {
+ p++;
+ }
+
+ if (*p == '\0')
+ {
+ break;
+ }
+
+ start = p;
+ while (*p != '\0' && *p != ',' && !isspace((unsigned char)*p))
+ {
+ p++;
+ }
+
+ if (sudo_token_eq(name, start, p - start))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/****************************************************************************
+ * Name: sudo_name_in_file
+ ****************************************************************************/
+
+static bool sudo_name_in_file(FAR const char *name, FAR const char *path)
+{
+ FAR FILE *fp;
+ char line[SUDO_SUDOERS_LINE];
+ FAR char *tok;
+ FAR char *end;
+
+ fp = fopen(path, "r");
+ if (fp == NULL)
+ {
+ return false;
+ }
+
+ while (fgets(line, sizeof(line), fp) != NULL)
+ {
+ tok = line;
+ while (*tok != '\0' && isspace((unsigned char)*tok))
+ {
+ tok++;
+ }
+
+ if (*tok == '\0' || *tok == '#')
+ {
+ continue;
+ }
+
+ end = tok;
+ while (*end != '\0' && !isspace((unsigned char)*end))
+ {
+ end++;
+ }
+
+ if (sudo_token_eq(name, tok, end - tok))
+ {
+ fclose(fp);
+ return true;
+ }
+ }
+
+ fclose(fp);
+ return false;
+}
+
+/****************************************************************************
+ * Name: sudo_user_allowed
+ *
+ * Description:
+ * Real UID 0 may always run sudo. Other users must appear in the sudoers
+ * file and/or CONFIG_SYSTEM_SUDO_ALLOWED_USERS.
+ *
+ ****************************************************************************/
+
+static bool sudo_user_allowed(FAR const char *name, uid_t ruid)
+{
+ if (ruid == 0)
+ {
+ return true;
+ }
+
+ if (sudo_name_in_csv(name, CONFIG_SYSTEM_SUDO_ALLOWED_USERS))
+ {
+ return true;
+ }
+
+ return sudo_name_in_file(name, CONFIG_SYSTEM_SUDO_SUDOERS_PATH);
+}
+
+/****************************************************************************
+ * Name: sudo_lookup_invoker
+ ****************************************************************************/
+
+static int sudo_lookup_invoker(FAR struct passwd *result,
+ FAR char *buf, size_t buflen)
+{
+ FAR struct passwd *found;
+ int ret;
+
+ ret = getpwuid_r(getuid(), result, buf, buflen, &found);
+ if (ret != 0)
+ {
+ return -ret;
+ }
+
+ if (found == NULL)
+ {
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * sudo_main
+ *
+ * Description:
+ * Linux-style setuid-root helper: the kernel raises effective UID to the
+ * file owner on exec (see nx_uid/nx_mode in the application build).
+ * This program checks the sudoers allowlist, verifies the invoking user's
+ * password, becomes fully root with setresuid/setresgid, then execvp()s
+ * the requested command.
+ *
+ ****************************************************************************/
+
+int main(int argc, FAR char *argv[])
+{
+ struct passwd invoker;
+ char pwbuf[CONFIG_LIBC_PASSWD_LINESIZE];
+ char password[SUDO_MAX_PASSWORD + 1];
+ int ret;
+
+ if (geteuid() != 0)
+ {
+ fprintf(stderr, "sudo: effective uid is not 0\n");
+ return 1;
+ }
+
+ if (argc >= 2 && strcmp(argv[1], SUDO_PROBE_ARG) == 0)
+ {
+ printf("ruid=%d euid=%d\n", getuid(), geteuid());
+ return geteuid() == 0 && getuid() != 0 ? 0 : 1;
+ }
+
+ if (argc < 2)
+ {
+ fprintf(stderr, "usage: sudo <command> [args...]\n");
+ return 1;
+ }
+
+ ret = sudo_lookup_invoker(&invoker, pwbuf, sizeof(pwbuf));
+ if (ret < 0)
+ {
+ fprintf(stderr, "sudo: cannot resolve invoking user: %d\n", -ret);
+ return 1;
+ }
+
+ if (!sudo_user_allowed(invoker.pw_name, getuid()))
+ {
+ fprintf(stderr, "sudo: %s is not in the sudoers file\n",
+ invoker.pw_name);
+ return 1;
+ }
+
+ fprintf(stderr, "[sudo] password for %s: ", invoker.pw_name);
+
+ ret = sudo_read_password(password, sizeof(password));
+ if (ret < 0)
+ {
+ fprintf(stderr, "sudo: password read failed: %d\n", -ret);
+ return 1;
+ }
+
+ ret = passwd_verify(invoker.pw_name, password);
+ explicit_bzero(password, sizeof(password));
+ if (!PASSWORD_VERIFY_MATCH(ret))
+ {
+ fprintf(stderr, "sudo: authentication failure\n");
+ return 1;
+ }
+
+ if (setresuid(0, 0, 0) != 0 || setresgid(0, 0, 0) != 0)
+ {
+ fprintf(stderr, "sudo: cannot set root identity: %d\n", errno);
+ return 1;
+ }
+
+ initgroups("root", 0);
+ execvp(argv[1], &argv[1]);
+ fprintf(stderr, "sudo: exec failed: %d\n", errno);
+ return 1;
+}
diff --git a/testing/ostest/multiuser.c b/testing/ostest/multiuser.c
index e9a55721c..cb9d5fba4 100644
--- a/testing/ostest/multiuser.c
+++ b/testing/ostest/multiuser.c
@@ -40,6 +40,10 @@
#include <sys/wait.h>
#include <unistd.h>
+#if defined(CONFIG_LIBC_EXECFUNCS) && defined(CONFIG_SYSTEM_SUDO)
+# include <spawn.h>
+#endif
+
#if !defined(CONFIG_DISABLE_MQUEUE)
# include <mqueue.h>
#endif
@@ -1325,6 +1329,69 @@ static int multiuser_passwd_test(FAR struct mu_ctx_s
*ctx)
#endif /* CONFIG_LIBC_PASSWD_FILE && CONFIG_TESTING_OSTEST_MULTIUSER */
+#if defined(CONFIG_LIBC_EXECFUNCS) && defined(CONFIG_SYSTEM_SUDO) && \
+ defined(CONFIG_SCHED_WAITPID) && !defined(CONFIG_BUILD_KERNEL)
+
+static int multiuser_sudo_setuid_test(FAR struct mu_ctx_s *ctx)
+{
+ posix_spawnattr_t attr;
+ FAR char * const spawn_argv[] =
+ {
+ (FAR char *)"sudo", (FAR char *)"--probe", NULL
+ };
+
+ pid_t pid;
+ int status;
+ int ret;
+
+ printf("multiuser: setuid sudo exec after hard credential drop\n");
+
+ mu_restore_root(ctx);
+ ret = setuid(MU_UID1);
+ if (mu_expect_ok(ctx, "setuid(1000) before sudo exec", ret) != 0)
+ {
+ return ctx->failures;
+ }
+
+ mu_check_eq(ctx, "parent euid before sudo", geteuid(), MU_UID1);
+
+ ret = posix_spawnattr_init(&attr);
+ if (mu_expect_ok(ctx, "posix_spawnattr_init", ret) != 0)
+ {
+ return ctx->failures;
+ }
+
+ ret = posix_spawn(&pid, "sudo", NULL, &attr, spawn_argv, NULL);
+ posix_spawnattr_destroy(&attr);
+ if (mu_expect_ok(ctx, "posix_spawn(sudo --probe)", ret) != 0)
+ {
+ mu_restore_root(ctx);
+ return ctx->failures;
+ }
+
+ if (waitpid(pid, &status, 0) != pid)
+ {
+ mu_fail(ctx, "waitpid(sudo) errno=%d", errno);
+ mu_restore_root(ctx);
+ return ctx->failures;
+ }
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS)
+ {
+ mu_fail(ctx, "sudo --probe status=%d", status);
+ }
+ else
+ {
+ mu_pass("sudo --probe after hard drop");
+ }
+
+ mu_check_eq(ctx, "parent euid after sudo", geteuid(), MU_UID1);
+ mu_restore_root(ctx);
+ return ctx->failures;
+}
+
+#endif /* CONFIG_LIBC_EXECFUNCS && CONFIG_SYSTEM_SUDO && CONFIG_SCHED_WAITPID
*/
+
#endif /* CONFIG_SCHED_USER_IDENTITY */
/****************************************************************************
@@ -1389,6 +1456,14 @@ int multiuser_test(void)
"(need LIBC_PASSWD_FILE and TESTING_OSTEST_MULTIUSER)\n");
#endif
+#if defined(CONFIG_LIBC_EXECFUNCS) && defined(CONFIG_SYSTEM_SUDO) && \
+ defined(CONFIG_SCHED_WAITPID) && !defined(CONFIG_BUILD_KERNEL)
+ multiuser_sudo_setuid_test(&ctx);
+#else
+ printf("multiuser: skipping setuid sudo exec test "
+ "(need LIBC_EXECFUNCS, SYSTEM_SUDO, SCHED_WAITPID)\n");
+#endif
+
mu_restore_root(&ctx);
printf("multiuser_test: %d failure(s)\n", ctx.failures);