]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lib/canonicalize: do restricted canonicalize in a subprocess
authorRian Hunter <rian@alum.mit.edu>
Sat, 13 Oct 2018 02:45:06 +0000 (19:45 -0700)
committerKarel Zak <kzak@redhat.com>
Fri, 30 Nov 2018 09:25:04 +0000 (10:25 +0100)
Accessing FUSE mounts require suid/sgid (saved uid) to be equal to the
owner of the mount. If mount is running as a setuid process, swapping
creds by only setting the euid/egid isn't enough to change the
suid/sgid as well. We must do a full setuid()/setgid(), but that
removes our ability to re-assume the identity of the original
euid. The solution is swap creds in a child process, preserving the
creds of the parent.

[kzak@redhat.com: - use switch() rather than if() for fork
  - use all-io.h
  - close unused pipe[] ends
  - be more strict about used types]

Addresses: https://github.com/karelzak/util-linux/pull/705
Co-Author: Karel Zak <kzak@redhat.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
lib/canonicalize.c

index f3a2a3af29d0880acfd8e5bd847eb7c147a1aae3..e85e0357cd517b35dfb2a16589ef7766517b5e47 100644 (file)
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 
 #include "canonicalize.h"
 #include "pathnames.h"
+#include "all-io.h"
 
 /*
  * Converts private "dm-N" names to "/dev/mapper/<name>"
@@ -140,39 +142,92 @@ char *canonicalize_path(const char *path)
 
 char *canonicalize_path_restricted(const char *path)
 {
-       char *canonical, *dmname;
-       int errsv;
-       uid_t euid;
-       gid_t egid;
+       char *canonical = NULL;
+       int errsv = 0;
+       int pipes[2];
+       ssize_t len;
+       pid_t pid;
 
        if (!path || !*path)
                return NULL;
 
-       euid = geteuid();
-       egid = getegid();
-
-       /* drop permissions */
-       if (setegid(getgid()) < 0 || seteuid(getuid()) < 0)
+       if (pipe(pipes) != 0)
                return NULL;
 
-       errsv = errno = 0;
-
-       canonical = realpath(path, NULL);
-       if (!canonical)
-               errsv = errno;
-       else if (is_dm_devname(canonical, &dmname)) {
-               char *dm = canonicalize_dm_name(dmname);
-               if (dm) {
-                       free(canonical);
-                       canonical = dm;
+       /*
+        * To accurately assume identity of getuid() we must use setuid()
+        * but if we do that, we lose ability to reassume euid of 0, so
+        * we fork to do the check to keep euid intact.
+        */
+       pid = fork();
+       switch (pid) {
+       case -1:
+               close(pipes[0]);
+               close(pipes[1]);
+               return NULL;                    /* fork error */
+       case 0:
+               close(pipes[0]);                /* close unused end */
+               pipes[0] = -1;
+               errno = 0;
+
+               /* drop permissions */
+               if (setgid(getgid()) < 0 || setuid(getuid()) < 0)
+                       canonical = NULL;       /* failed */
+               else {
+                       char *dmname = NULL;
+
+                       canonical = realpath(path, NULL);
+                       if (canonical && is_dm_devname(canonical, &dmname)) {
+                               char *dm = canonicalize_dm_name(dmname);
+                               if (dm) {
+                                       free(canonical);
+                                       canonical = dm;
+                               }
+                       }
                }
+
+               len = canonical ? strlen(canonical) : errno ? -errno : -EINVAL;
+
+               /* send lenght or errno */
+               write_all(pipes[1], (char *) &len, sizeof(len));
+               if (canonical)
+                       write_all(pipes[1], canonical, len);
+               exit(0);
+       default:
+               break;
        }
 
-       /* restore */
-       if (setegid(egid) < 0 || seteuid(euid) < 0) {
+       close(pipes[1]);                /* close unused end */
+       pipes[1] = -1;
+
+       /* read size or -errno */
+       if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len))
+               goto done;
+       if (len < 0) {
+               errsv = -len;
+               goto done;
+       }
+
+       canonical = malloc(len + 1);
+       if (!canonical) {
+               errsv = ENOMEM;
+               goto done;
+       }
+       /* read path */
+       if (read_all(pipes[0], canonical, len) != len) {
+               errsv = errno;
+               goto done;
+       }
+       canonical[len] = '\0';
+done:
+       if (errsv) {
                free(canonical);
-               return NULL;
+               canonical = NULL;
        }
+       close(pipes[0]);
+
+       /* We make a best effort to reap child */
+       waitpid(pid, NULL, 0);
 
        errno = errsv;
        return canonical;