newuidmap and newgidmap: support passing pid as fd

Closes #635

newuidmap and newgidmap currently take an integner pid as
the first argument, determining the process id on which to
act.  Accept also "fd:N", where N must be an open file
descriptor to the /proc/pid directory for the process to
act upon.  This way, if you

exec 10</proc/99
newuidmap fd:10 100000 0 65536

and pid 99 dies and a new process happens to take pid 99 before
newuidmap happens to do its work, then since newuidmap will use
openat() using fd 10, it won't change the mapping for the new
process.

Example:

// terminal 1:
serge@jerom ~/src/nsexec$ ./nsexec -W -s 0 -S 0 -U
about to unshare with 10000000
Press any key to exec (I am 129176)

// terminal 2:
serge@jerom ~/src/shadow$ exec 10</proc/129176
serge@jerom ~/src/shadow$ sudo chown root src/newuidmap src/newgidmap
serge@jerom ~/src/shadow$ sudo chmod u+s src/newuidmap
serge@jerom ~/src/shadow$ sudo chmod u+s src/newgidmap
serge@jerom ~/src/shadow$ ./src/newuidmap fd:10 0 100000 10
serge@jerom ~/src/shadow$ ./src/newgidmap fd:10 0 100000 10

// Terminal 1:
uid=0(root) gid=0(root) groups=0(root)

Signed-off-by: Serge Hallyn <serge@hallyn.com>
This commit is contained in:
Serge Hallyn 2023-02-06 22:49:42 -06:00
parent 7668f77439
commit 6974df39a7
6 changed files with 106 additions and 50 deletions

View File

@ -10,6 +10,9 @@
#include "prototypes.h" #include "prototypes.h"
#include "defines.h" #include "defines.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int get_pid (const char *pidstr, pid_t *pid) int get_pid (const char *pidstr, pid_t *pid)
{ {
@ -29,3 +32,51 @@ int get_pid (const char *pidstr, pid_t *pid)
return 1; return 1;
} }
/*
* If use passed in fd:4 as an argument, then return the
* value '4', the fd to use.
*/
int get_pidfd_from_fd(const char *pidfdstr)
{
long long int val;
char *endptr;
errno = 0;
val = strtoll (pidfdstr, &endptr, 10);
if ( ('\0' == *pidfdstr)
|| ('\0' != *endptr)
|| (ERANGE == errno)
|| (/*@+longintegral@*/val != (pid_t)val)/*@=longintegral@*/) {
return 0;
}
return (int)val;
}
int open_pidfd(const char *pidstr)
{
int proc_dir_fd;
int written;
char proc_dir_name[32];
pid_t target;
if (get_pid(pidstr, &target) == 0)
return -ENOENT;
/* max string length is 6 + 10 + 1 + 1 = 18, allocate 32 bytes */
written = snprintf(proc_dir_name, sizeof(proc_dir_name), "/proc/%u/",
target);
if ((written <= 0) || ((size_t)written >= sizeof(proc_dir_name))) {
fprintf(stderr, "snprintf of proc path failed for %u: %s\n",
target, strerror(errno));
return -EINVAL;
}
proc_dir_fd = open(proc_dir_name, O_DIRECTORY);
if (proc_dir_fd < 0) {
fprintf(stderr, _("Could not open proc directory for target %u: %s\n"),
target, strerror(errno));
return -EINVAL;
}
return proc_dir_fd;
}

View File

@ -160,6 +160,8 @@ extern int getlong (const char *numstr, /*@out@*/long int *result);
/* get_pid.c */ /* get_pid.c */
extern int get_pid (const char *pidstr, pid_t *pid); extern int get_pid (const char *pidstr, pid_t *pid);
extern int get_pidfd_from_fd(const char *pidfdstr);
extern int open_pidfd(const char *pidstr);
/* getrange */ /* getrange */
extern int getrange (const char *range, extern int getrange (const char *range,

View File

@ -116,6 +116,17 @@
<para> <para>
Note that newgidmap may be used only once for a given process. Note that newgidmap may be used only once for a given process.
</para> </para>
<para>
Instead of an integer process id, the first argument may be
specified as <replaceable>fd:N</replaceable>, where the integer N
is the file descriptor number for the calling process's opened
file for <filename>/proc/[pid[</filename>. In this case,
<command>newgidmap</command> will use
<refentrytitle>openat</refentrytitle><manvolnum>2</manvolnum>
to open the <filename>gid_map</filename> file under that
directory, avoiding a TOCTTOU in case the process exits and
the pid is immediately reused.
</para>
</refsect1> </refsect1>

View File

@ -116,6 +116,17 @@
<para> <para>
Note that newuidmap may be used only once for a given process. Note that newuidmap may be used only once for a given process.
</para> </para>
<para>
Instead of an integer process id, the first argument may be
specified as <replaceable>fd:N</replaceable>, where the integer N
is the file descriptor number for the calling process's opened
file for <filename>/proc/[pid[</filename>. In this case,
<command>newuidmap</command> will use
<refentrytitle>openat</refentrytitle><manvolnum>2</manvolnum>
to open the <filename>uid_map</filename> file under that
directory, avoiding a TOCTTOU in case the process exits and
the pid is immediately reused.
</para>
</refsect1> </refsect1>
<refsect1 id='options'> <refsect1 id='options'>

View File

@ -69,7 +69,7 @@ static void verify_ranges(struct passwd *pw, int ranges,
static void usage(void) static void usage(void)
{ {
fprintf(stderr, _("usage: %s <pid> <gid> <lowergid> <count> [ <gid> <lowergid> <count> ] ... \n"), Prog); fprintf(stderr, _("usage: %s [<pid|fd:<pidfd>] <gid> <lowergid> <count> [ <gid> <lowergid> <count> ] ... \n"), Prog);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -143,15 +143,12 @@ out:
*/ */
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
char proc_dir_name[32];
char *target_str; char *target_str;
pid_t target;
int proc_dir_fd; int proc_dir_fd;
int ranges; int ranges;
struct map_range *mappings; struct map_range *mappings;
struct stat st; struct stat st;
struct passwd *pw; struct passwd *pw;
int written;
bool allow_setgroups = false; bool allow_setgroups = false;
Prog = Basename (argv[0]); Prog = Basename (argv[0]);
@ -168,25 +165,19 @@ int main(int argc, char **argv)
/* Find the process that needs its user namespace /* Find the process that needs its user namespace
* gid mapping set. * gid mapping set.
*/ */
target_str = argv[1]; target_str = argv[1];
if (!get_pid(target_str, &target)) if (strlen(target_str) > 3 && strncmp(target_str, "fd:", 3) == 0) {
usage(); /* the user passed in a /proc/pid fd for the process */
target_str = &target_str[3];
/* max string length is 6 + 10 + 1 + 1 = 18, allocate 32 bytes */ proc_dir_fd = get_pidfd_from_fd(target_str);
written = snprintf(proc_dir_name, sizeof(proc_dir_name), "/proc/%u/", if (proc_dir_fd < 0)
target); usage();
if ((written <= 0) || ((size_t)written >= sizeof(proc_dir_name))) { } else {
fprintf(stderr, "%s: snprintf of proc path failed: %s\n", proc_dir_fd = open_pidfd(target_str);
Prog, strerror(errno)); if (proc_dir_fd < 0)
usage();
} }
proc_dir_fd = open(proc_dir_name, O_DIRECTORY);
if (proc_dir_fd < 0) {
fprintf(stderr, _("%s: Could not open proc directory for target %u\n"),
Prog, target);
return EXIT_FAILURE;
}
/* Who am i? */ /* Who am i? */
pw = get_my_pwent (); pw = get_my_pwent ();
if (NULL == pw) { if (NULL == pw) {
@ -200,8 +191,8 @@ int main(int argc, char **argv)
/* Get the effective uid and effective gid of the target process */ /* Get the effective uid and effective gid of the target process */
if (fstat(proc_dir_fd, &st) < 0) { if (fstat(proc_dir_fd, &st) < 0) {
fprintf(stderr, _("%s: Could not stat directory for target %u\n"), fprintf(stderr, _("%s: Could not stat directory for process\n"),
Prog, target); Prog);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -213,8 +204,8 @@ int main(int argc, char **argv)
(!getdef_bool("GRANT_AUX_GROUP_SUBIDS") && (getgid() != pw->pw_gid)) || (!getdef_bool("GRANT_AUX_GROUP_SUBIDS") && (getgid() != pw->pw_gid)) ||
(pw->pw_uid != st.st_uid) || (pw->pw_uid != st.st_uid) ||
(getgid() != st.st_gid)) { (getgid() != st.st_gid)) {
fprintf(stderr, _( "%s: Target %u is owned by a different user: uid:%lu pw_uid:%lu st_uid:%lu, gid:%lu pw_gid:%lu st_gid:%lu\n" ), fprintf(stderr, _( "%s: Target process is owned by a different user: uid:%lu pw_uid:%lu st_uid:%lu, gid:%lu pw_gid:%lu st_gid:%lu\n" ),
Prog, target, Prog,
(unsigned long int)getuid(), (unsigned long int)pw->pw_uid, (unsigned long int)st.st_uid, (unsigned long int)getuid(), (unsigned long int)pw->pw_uid, (unsigned long int)st.st_uid,
(unsigned long int)getgid(), (unsigned long int)pw->pw_gid, (unsigned long int)st.st_gid); (unsigned long int)getgid(), (unsigned long int)pw->pw_gid, (unsigned long int)st.st_gid);
return EXIT_FAILURE; return EXIT_FAILURE;

View File

@ -64,7 +64,7 @@ static void verify_ranges(struct passwd *pw, int ranges,
static void usage(void) static void usage(void)
{ {
fprintf(stderr, _("usage: %s <pid> <uid> <loweruid> <count> [ <uid> <loweruid> <count> ] ... \n"), Prog); fprintf(stderr, _("usage: %s [<pid>|fd:<pidfd>] <uid> <loweruid> <count> [ <uid> <loweruid> <count> ] ... \n"), Prog);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -73,15 +73,12 @@ static void usage(void)
*/ */
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
char proc_dir_name[32];
char *target_str; char *target_str;
pid_t target;
int proc_dir_fd; int proc_dir_fd;
int ranges; int ranges;
struct map_range *mappings; struct map_range *mappings;
struct stat st; struct stat st;
struct passwd *pw; struct passwd *pw;
int written;
Prog = Basename (argv[0]); Prog = Basename (argv[0]);
log_set_progname(Prog); log_set_progname(Prog);
@ -94,26 +91,20 @@ int main(int argc, char **argv)
if (argc < 2) if (argc < 2)
usage(); usage();
target_str = argv[1];
/* Find the process that needs its user namespace /* Find the process that needs its user namespace
* uid mapping set. * uid mapping set.
*/ */
target_str = argv[1]; if (strlen(target_str) > 3 && strncmp(target_str, "fd:", 3) == 0) {
if (!get_pid(target_str, &target)) /* the user passed in a /proc/pid fd for the process */
usage(); target_str = &target_str[3];
proc_dir_fd = get_pidfd_from_fd(target_str);
/* max string length is 6 + 10 + 1 + 1 = 18, allocate 32 bytes */ if (proc_dir_fd < 0)
written = snprintf(proc_dir_name, sizeof(proc_dir_name), "/proc/%u/", usage();
target); } else {
if ((written <= 0) || ((size_t)written >= sizeof(proc_dir_name))) { proc_dir_fd = open_pidfd(target_str);
fprintf(stderr, "%s: snprintf of proc path failed: %s\n", if (proc_dir_fd < 0)
Prog, strerror(errno)); usage();
}
proc_dir_fd = open(proc_dir_name, O_DIRECTORY);
if (proc_dir_fd < 0) {
fprintf(stderr, _("%s: Could not open proc directory for target %u\n"),
Prog, target);
return EXIT_FAILURE;
} }
/* Who am i? */ /* Who am i? */
@ -129,8 +120,7 @@ int main(int argc, char **argv)
/* Get the effective uid and effective gid of the target process */ /* Get the effective uid and effective gid of the target process */
if (fstat(proc_dir_fd, &st) < 0) { if (fstat(proc_dir_fd, &st) < 0) {
fprintf(stderr, _("%s: Could not stat directory for target %u\n"), fprintf(stderr, _("%s: Could not stat directory for target process\n"), Prog);
Prog, target);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -142,8 +132,8 @@ int main(int argc, char **argv)
(!getdef_bool("GRANT_AUX_GROUP_SUBIDS") && (getgid() != pw->pw_gid)) || (!getdef_bool("GRANT_AUX_GROUP_SUBIDS") && (getgid() != pw->pw_gid)) ||
(pw->pw_uid != st.st_uid) || (pw->pw_uid != st.st_uid) ||
(getgid() != st.st_gid)) { (getgid() != st.st_gid)) {
fprintf(stderr, _( "%s: Target process %u is owned by a different user: uid:%lu pw_uid:%lu st_uid:%lu, gid:%lu pw_gid:%lu st_gid:%lu\n" ), fprintf(stderr, _( "%s: Target process is owned by a different user: uid:%lu pw_uid:%lu st_uid:%lu, gid:%lu pw_gid:%lu st_gid:%lu\n" ),
Prog, target, Prog,
(unsigned long int)getuid(), (unsigned long int)pw->pw_uid, (unsigned long int)st.st_uid, (unsigned long int)getuid(), (unsigned long int)pw->pw_uid, (unsigned long int)st.st_uid,
(unsigned long int)getgid(), (unsigned long int)pw->pw_gid, (unsigned long int)st.st_gid); (unsigned long int)getgid(), (unsigned long int)pw->pw_gid, (unsigned long int)st.st_gid);
return EXIT_FAILURE; return EXIT_FAILURE;