]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
test_main: Run tests as unprivileged user
authorDag-Erling Smørgrav <des@des.dev>
Mon, 2 Mar 2026 10:47:12 +0000 (11:47 +0100)
committerDag-Erling Smørgrav <des@des.dev>
Mon, 2 Mar 2026 11:42:05 +0000 (12:42 +0100)
If run as root (as is the case in CI), switch to an unprivileged user
(default: nobody) before running each test, and switch back after.
This makes it possible to write tests that rely on file permissions.

Note that tests that use the UID or GID must now check the EUID / EGID
instead as they will be different while the test is running.  The only
way to avoid that is to run each test case in a child process, which
would hugely increase the complexity of test_run().

cpio/test/test_format_newc.c
libarchive/test/test_write_disk_perms.c
test_utils/test_main.c

index 33aa16d07a815b11444af2f048f2e1deca6570bf..9d4e4e9fb6743f46add0c0007a671cdcb2a631f2 100644 (file)
@@ -6,6 +6,13 @@
  */
 #include "test.h"
 
+#ifdef HAVE_GETEUID
+#define getuid() geteuid()
+#endif
+#ifdef HAVE_GETEGID
+#define getgid() getegid()
+#endif
+
 /* Number of bytes needed to pad 'n' to multiple of 'block', assuming
  * that 'block' is a power of two. This trick can be more easily
  * remembered as -n & (block - 1), but many compilers quite reasonably
index ec9bae252825d915729626ef38522e3ffe4c53e1..4f246e9bc2f0234e5127e80f624328ec2c516315 100644 (file)
 
 #if !defined(_WIN32) || defined(__CYGWIN__)
 
+#ifdef HAVE_GETEUID
+#define getuid() geteuid()
+#endif
+#ifdef HAVE_GETEGID
+#define getgid() getegid()
+#endif
+
 #define UMASK 022
 
 static long _default_gid = -1;
index 0716c83d5e2b43d9d5a4c80054d21a99697e6dc8..1f641bbe8db799be4f45e61eeafbcb46ca7f61e9 100644 (file)
@@ -96,6 +96,12 @@ extern char **environ;
 #  define USE_POSIX_SPAWN 1
 # endif
 #endif
+#if !defined(_WIN32)
+# if HAVE_PWD_H && HAVE_GETEUID && HAVE_GETEGID
+#  include <pwd.h>
+#  define RUN_TEST_UNPRIV 1
+# endif
+#endif
 
 #ifndef nitems
 #define nitems(arr) (sizeof(arr) / sizeof((arr)[0]))
@@ -179,6 +185,14 @@ const char *testprogfile;
 const char *testprog;
 #endif
 
+#ifdef RUN_TEST_UNPRIV
+/* Unprivileged user to run as */
+const char *tuser = "nobody";
+/* Original and test credentials */
+uid_t ouid, tuid;
+uid_t ogid, tgid;
+#endif
+
 #if defined(_WIN32) && !defined(__CYGWIN__)
 static void    *GetFunctionKernel32(const char *);
 static int      my_CreateSymbolicLinkA(const char *, const char *, int);
@@ -3626,8 +3640,11 @@ test_run(int i, const char *tmpdir)
                exit(1);
        }
        testworkdir = workdir;
-       if (!assertMakeDir(testworkdir, 0755)
-           || !assertChdir(testworkdir)) {
+       if (!assertMakeDir(testworkdir, 0755) ||
+#ifdef RUN_TEST_UNPRIV
+           (tuser != NULL && !assertChown(testworkdir, tuid, tgid)) ||
+#endif
+           !assertChdir(testworkdir)) {
                fprintf(stderr,
                    "ERROR: Can't chdir to work dir %s\n", testworkdir);
                exit(1);
@@ -3636,10 +3653,28 @@ test_run(int i, const char *tmpdir)
        set_c_locale();
        /* Record the umask before we run the test. */
        umask(oldumask = umask(0));
+#ifdef RUN_TEST_UNPRIV
+       /*
+        * Temporarily drop privileges.
+        */
+       if (tuser != NULL) {
+               (void)setegid(tuid);
+               (void)seteuid(tuid);
+       }
+#endif
        /*
         * Run the actual test.
         */
        (*tests[i].func)();
+#ifdef RUN_TEST_UNPRIV
+       /*
+        * Restore original credentials.
+        */
+       if (tuser != NULL) {
+               (void)seteuid(ouid);
+               (void)setegid(ogid);
+       }
+#endif
        /*
         * Clean up and report afterwards.
         */
@@ -3965,6 +4000,9 @@ main(int argc, char **argv)
 #endif
        char *pwd, *testprogdir, *tmp2 = NULL, *vlevel = NULL;
        char tmpdir_timestamp[32];
+#ifdef RUN_TEST_UNPRIV
+       struct passwd *pw;
+#endif
 
        (void)argc; /* UNUSED */
 
@@ -4126,6 +4164,11 @@ main(int argc, char **argv)
                        case 's':
                                fail_if_tests_skipped = 1;
                                break;
+#ifdef RUN_TEST_UNPRIV
+                       case 'U':
+                               tuser = optarg;
+                               break;
+#endif
                        case 'u':
                                until_failure++;
                                break;
@@ -4210,6 +4253,28 @@ main(int argc, char **argv)
        }
 #endif
 
+#ifdef RUN_TEST_UNPRIV
+       /*
+        * Check if we are root, and get user to run as.
+        */
+       ouid = getuid();
+       ogid = getgid();
+       if (ouid == 0) {
+               if ((pw = getpwnam(tuser)) == NULL) {
+                       fprintf(stderr, "ERROR: Unknown user %s\n", tuser);
+                       exit(1);
+               }
+               tuid = pw->pw_uid;
+               tgid = pw->pw_gid;
+               printf("Will switch to user %s (uid %d gid %d)\n", tuser,
+                   tuid, tgid);
+       } else {
+               tuser = NULL;
+               tuid = ouid;
+               tgid = ogid;
+       }
+#endif
+
        /*
         * Create a temp directory for the following tests.
         * Include the time the tests started as part of the name,