c0c2ea7769
Resolves: #2174752 Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
442 lines
13 KiB
Diff
442 lines
13 KiB
Diff
From 6974df39a708abf8bafbdfa2b7827e0f70f874cb Mon Sep 17 00:00:00 2001
|
|
From: Serge Hallyn <serge@hallyn.com>
|
|
Date: Mon, 6 Feb 2023 22:49:42 -0600
|
|
Subject: [PATCH] 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>
|
|
---
|
|
lib/get_pid.c | 51 +++++++++++++++++++++++++++++++++++++++++++++
|
|
lib/prototypes.h | 2 ++
|
|
man/newgidmap.1.xml | 11 ++++++++++
|
|
man/newuidmap.1.xml | 11 ++++++++++
|
|
src/newgidmap.c | 41 ++++++++++++++----------------------
|
|
src/newuidmap.c | 40 +++++++++++++----------------------
|
|
6 files changed, 106 insertions(+), 50 deletions(-)
|
|
|
|
diff --git a/lib/get_pid.c b/lib/get_pid.c
|
|
index 10184bf0..ab91d158 100644
|
|
--- a/lib/get_pid.c
|
|
+++ b/lib/get_pid.c
|
|
@@ -10,6 +10,9 @@
|
|
|
|
#include "prototypes.h"
|
|
#include "defines.h"
|
|
+#include <sys/types.h>
|
|
+#include <sys/stat.h>
|
|
+#include <fcntl.h>
|
|
|
|
int get_pid (const char *pidstr, pid_t *pid)
|
|
{
|
|
@@ -29,3 +32,51 @@ int get_pid (const char *pidstr, pid_t *pid)
|
|
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;
|
|
+}
|
|
diff --git a/lib/prototypes.h b/lib/prototypes.h
|
|
index 400d5b97..21df6f61 100644
|
|
--- a/lib/prototypes.h
|
|
+++ b/lib/prototypes.h
|
|
@@ -160,6 +160,8 @@ extern int getlong (const char *numstr, /*@out@*/long int *result);
|
|
|
|
/* get_pid.c */
|
|
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 */
|
|
extern int getrange (const char *range,
|
|
diff --git a/man/newgidmap.1.xml b/man/newgidmap.1.xml
|
|
index e4ebc69e..9b7683eb 100644
|
|
--- a/man/newgidmap.1.xml
|
|
+++ b/man/newgidmap.1.xml
|
|
@@ -116,6 +116,17 @@
|
|
<para>
|
|
Note that newgidmap may be used only once for a given process.
|
|
</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>
|
|
|
|
diff --git a/man/newuidmap.1.xml b/man/newuidmap.1.xml
|
|
index f5cb5b48..ca917a77 100644
|
|
--- a/man/newuidmap.1.xml
|
|
+++ b/man/newuidmap.1.xml
|
|
@@ -116,6 +116,17 @@
|
|
<para>
|
|
Note that newuidmap may be used only once for a given process.
|
|
</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 id='options'>
|
|
diff --git a/src/newgidmap.c b/src/newgidmap.c
|
|
index 01d0fe90..d6d29725 100644
|
|
--- a/src/newgidmap.c
|
|
+++ b/src/newgidmap.c
|
|
@@ -69,7 +69,7 @@ static void verify_ranges(struct passwd *pw, int ranges,
|
|
|
|
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);
|
|
}
|
|
|
|
@@ -143,15 +143,12 @@ out:
|
|
*/
|
|
int main(int argc, char **argv)
|
|
{
|
|
- char proc_dir_name[32];
|
|
char *target_str;
|
|
- pid_t target;
|
|
int proc_dir_fd;
|
|
int ranges;
|
|
struct map_range *mappings;
|
|
struct stat st;
|
|
struct passwd *pw;
|
|
- int written;
|
|
bool allow_setgroups = false;
|
|
|
|
Prog = Basename (argv[0]);
|
|
@@ -168,25 +165,19 @@ int main(int argc, char **argv)
|
|
/* Find the process that needs its user namespace
|
|
* gid mapping set.
|
|
*/
|
|
- target_str = argv[1];
|
|
- if (!get_pid(target_str, &target))
|
|
- usage();
|
|
|
|
- /* 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) || (written >= sizeof(proc_dir_name))) {
|
|
- fprintf(stderr, "%s: snprintf of proc path failed: %s\n",
|
|
- Prog, strerror(errno));
|
|
- }
|
|
-
|
|
- 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;
|
|
+ target_str = argv[1];
|
|
+ if (strlen(target_str) > 3 && strncmp(target_str, "fd:", 3) == 0) {
|
|
+ /* the user passed in a /proc/pid fd for the process */
|
|
+ target_str = &target_str[3];
|
|
+ proc_dir_fd = get_pidfd_from_fd(target_str);
|
|
+ if (proc_dir_fd < 0)
|
|
+ usage();
|
|
+ } else {
|
|
+ proc_dir_fd = open_pidfd(target_str);
|
|
+ if (proc_dir_fd < 0)
|
|
+ usage();
|
|
}
|
|
-
|
|
/* Who am i? */
|
|
pw = get_my_pwent ();
|
|
if (NULL == pw) {
|
|
@@ -200,8 +191,8 @@ int main(int argc, char **argv)
|
|
|
|
/* Get the effective uid and effective gid of the target process */
|
|
if (fstat(proc_dir_fd, &st) < 0) {
|
|
- fprintf(stderr, _("%s: Could not stat directory for target %u\n"),
|
|
- Prog, target);
|
|
+ fprintf(stderr, _("%s: Could not stat directory for process\n"),
|
|
+ Prog);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
@@ -213,8 +204,8 @@ int main(int argc, char **argv)
|
|
(!getdef_bool("GRANT_AUX_GROUP_SUBIDS") && (getgid() != pw->pw_gid)) ||
|
|
(pw->pw_uid != st.st_uid) ||
|
|
(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" ),
|
|
- Prog, target,
|
|
+ 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,
|
|
(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);
|
|
return EXIT_FAILURE;
|
|
diff --git a/src/newuidmap.c b/src/newuidmap.c
|
|
index e8798409..e99655c9 100644
|
|
--- a/src/newuidmap.c
|
|
+++ b/src/newuidmap.c
|
|
@@ -64,7 +64,7 @@ static void verify_ranges(struct passwd *pw, int ranges,
|
|
|
|
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);
|
|
}
|
|
|
|
@@ -73,15 +73,12 @@ static void usage(void)
|
|
*/
|
|
int main(int argc, char **argv)
|
|
{
|
|
- char proc_dir_name[32];
|
|
char *target_str;
|
|
- pid_t target;
|
|
int proc_dir_fd;
|
|
int ranges;
|
|
struct map_range *mappings;
|
|
struct stat st;
|
|
struct passwd *pw;
|
|
- int written;
|
|
|
|
Prog = Basename (argv[0]);
|
|
log_set_progname(Prog);
|
|
@@ -94,26 +91,20 @@ int main(int argc, char **argv)
|
|
if (argc < 2)
|
|
usage();
|
|
|
|
+ target_str = argv[1];
|
|
/* Find the process that needs its user namespace
|
|
* uid mapping set.
|
|
*/
|
|
- target_str = argv[1];
|
|
- if (!get_pid(target_str, &target))
|
|
- usage();
|
|
-
|
|
- /* 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) || (written >= sizeof(proc_dir_name))) {
|
|
- fprintf(stderr, "%s: snprintf of proc path failed: %s\n",
|
|
- Prog, strerror(errno));
|
|
- }
|
|
-
|
|
- 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;
|
|
+ if (strlen(target_str) > 3 && strncmp(target_str, "fd:", 3) == 0) {
|
|
+ /* the user passed in a /proc/pid fd for the process */
|
|
+ target_str = &target_str[3];
|
|
+ proc_dir_fd = get_pidfd_from_fd(target_str);
|
|
+ if (proc_dir_fd < 0)
|
|
+ usage();
|
|
+ } else {
|
|
+ proc_dir_fd = open_pidfd(target_str);
|
|
+ if (proc_dir_fd < 0)
|
|
+ usage();
|
|
}
|
|
|
|
/* Who am i? */
|
|
@@ -129,8 +120,7 @@ int main(int argc, char **argv)
|
|
|
|
/* Get the effective uid and effective gid of the target process */
|
|
if (fstat(proc_dir_fd, &st) < 0) {
|
|
- fprintf(stderr, _("%s: Could not stat directory for target %u\n"),
|
|
- Prog, target);
|
|
+ fprintf(stderr, _("%s: Could not stat directory for target process\n"), Prog);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
@@ -142,8 +132,8 @@ int main(int argc, char **argv)
|
|
(!getdef_bool("GRANT_AUX_GROUP_SUBIDS") && (getgid() != pw->pw_gid)) ||
|
|
(pw->pw_uid != st.st_uid) ||
|
|
(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" ),
|
|
- Prog, target,
|
|
+ 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,
|
|
(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);
|
|
return EXIT_FAILURE;
|
|
--
|
|
2.39.2
|
|
|
|
From 7ff33fae6f9cd79c0e012671c37a172e9a681d0b Mon Sep 17 00:00:00 2001
|
|
From: Serge Hallyn <serge@hallyn.com>
|
|
Date: Fri, 24 Feb 2023 13:52:32 -0600
|
|
Subject: [PATCH] get_pidfd_from_fd: return -1 on error, not 0
|
|
|
|
Fixes: 6974df39a: newuidmap and newgidmap: support passing pid as fd
|
|
Signed-off-by: Serge Hallyn <serge@hallyn.com>
|
|
---
|
|
lib/get_pid.c | 3 ++-
|
|
1 file changed, 2 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/lib/get_pid.c b/lib/get_pid.c
|
|
index ab91d158..5b6d9da4 100644
|
|
--- a/lib/get_pid.c
|
|
+++ b/lib/get_pid.c
|
|
@@ -35,6 +35,7 @@ int get_pid (const char *pidstr, pid_t *pid)
|
|
/*
|
|
* If use passed in fd:4 as an argument, then return the
|
|
* value '4', the fd to use.
|
|
+ * On error, return -1.
|
|
*/
|
|
int get_pidfd_from_fd(const char *pidfdstr)
|
|
{
|
|
@@ -47,7 +48,7 @@ int get_pidfd_from_fd(const char *pidfdstr)
|
|
|| ('\0' != *endptr)
|
|
|| (ERANGE == errno)
|
|
|| (/*@+longintegral@*/val != (pid_t)val)/*@=longintegral@*/) {
|
|
- return 0;
|
|
+ return -1;
|
|
}
|
|
|
|
return (int)val;
|
|
--
|
|
2.39.2
|
|
|
|
From 05e2adf509ba0e3779dae66a276b86927a8e1e0e Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Vin=C3=ADcius=20dos=20Santos=20Oliveira?=
|
|
<vini.ipsmaker@gmail.com>
|
|
Date: Fri, 24 Feb 2023 18:06:02 -0300
|
|
Subject: [PATCH] Validate fds created by the user
|
|
|
|
write_mapping() will do the following:
|
|
|
|
openat(proc_dir_fd, map_file, O_WRONLY);
|
|
|
|
An attacker could create a directory containing a symlink named
|
|
"uid_map" pointing to any file owned by root, and thus allow him to
|
|
overwrite any root-owned file.
|
|
---
|
|
lib/get_pid.c | 17 +++++++++++++++++
|
|
1 file changed, 17 insertions(+)
|
|
|
|
diff --git a/lib/get_pid.c b/lib/get_pid.c
|
|
index 5b6d9da4..8e5e6014 100644
|
|
--- a/lib/get_pid.c
|
|
+++ b/lib/get_pid.c
|
|
@@ -41,6 +41,8 @@ int get_pidfd_from_fd(const char *pidfdstr)
|
|
{
|
|
long long int val;
|
|
char *endptr;
|
|
+ struct stat st;
|
|
+ dev_t proc_st_dev, proc_st_rdev;
|
|
|
|
errno = 0;
|
|
val = strtoll (pidfdstr, &endptr, 10);
|
|
@@ -51,6 +53,21 @@ int get_pidfd_from_fd(const char *pidfdstr)
|
|
return -1;
|
|
}
|
|
|
|
+ if (stat("/proc/self/uid_map", &st) < 0) {
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ proc_st_dev = st.st_dev;
|
|
+ proc_st_rdev = st.st_rdev;
|
|
+
|
|
+ if (fstat(val, &st) < 0) {
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ if (st.st_dev != proc_st_dev || st.st_rdev != proc_st_rdev) {
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
return (int)val;
|
|
}
|
|
|
|
--
|
|
2.39.2
|
|
|