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);

Reply via email to