From 1ecca8439d5ba51712a9c62c30fb2e656fd6df49 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Mon, 8 Oct 2018 18:18:18 +0200 Subject: [PATCH] new[ug]idmap: not require CAP_SYS_ADMIN in the parent userNS if the euid!=owner of the userns, the kernel returns EPERM when trying to write the uidmap and there is no CAP_SYS_ADMIN in the parent namespace. Signed-off-by: Giuseppe Scrivano --- configure.ac | 2 +- libmisc/idmapping.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- libmisc/idmapping.h | 2 +- src/Makefile.am | 4 ++-- src/newgidmap.c | 2 +- src/newuidmap.c | 2 +- 6 files changed, 50 insertions(+), 7 deletions(-) diff --git a/configure.ac b/configure.ac index 41068a5d..53a30579 100644 --- a/configure.ac +++ b/configure.ac @@ -34,7 +34,7 @@ AC_HEADER_STDBOOL AC_CHECK_HEADERS(errno.h fcntl.h limits.h unistd.h sys/time.h utmp.h \ utmpx.h termios.h termio.h sgtty.h sys/ioctl.h syslog.h paths.h \ - utime.h ulimit.h sys/resource.h gshadow.h lastlog.h \ + utime.h ulimit.h sys/capability.h sys/resource.h gshadow.h lastlog.h \ locale.h rpc/key_prot.h netdb.h acl/libacl.h attr/libattr.h \ attr/error_context.h) diff --git a/libmisc/idmapping.c b/libmisc/idmapping.c index 20f2d9c7..f54c9341 100644 --- a/libmisc/idmapping.c +++ b/libmisc/idmapping.c @@ -36,6 +36,10 @@ #include #include "prototypes.h" #include "idmapping.h" +#include +#if HAVE_SYS_CAPABILITY_H +# include +#endif struct map_range *get_map_ranges(int ranges, int argc, char **argv) { @@ -121,17 +125,56 @@ struct map_range *get_map_ranges(int ranges, int argc, char **argv) void write_mapping(int proc_dir_fd, int ranges, struct map_range *mappings, - const char *map_file) + const char *map_file, uid_t uid) { int idx; struct map_range *mapping; size_t bufsize; char *buf, *pos; int fd; +#if HAVE_SYS_CAPABILITY_H + struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 }; + struct __user_cap_data_struct data[2] = { { 0 } }; +#endif bufsize = ranges * ((ULONG_DIGITS + 1) * 3); pos = buf = xmalloc(bufsize); +#if HAVE_SYS_CAPABILITY_H + if (capget(&hdr, data) < 0) { + fprintf(stderr, _("%s: Could not get capabilities\n"), Prog); + exit(EXIT_FAILURE); + } + if (!(data[0].effective & CAP_TO_MASK(CAP_SYS_ADMIN)) && + uid != geteuid()) { + bool uid_map; + + if (strcmp(map_file, "uid_map") == 0) { + uid_map = true; + } else if (strcmp(map_file, "gid_map") == 0) { + uid_map = false; + } else { + fprintf(stderr, _("%s: Invalid map file %s specified\n"), Prog, map_file); + exit(EXIT_FAILURE); + } + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) { + fprintf(stderr, _("%s: Could not prctl(PR_SET_KEEPCAPS)\n"), Prog); + exit(EXIT_FAILURE); + } + if (seteuid(uid) < 0) { + fprintf(stderr, _("%s: Could not seteuid to %d\n"), Prog, uid); + exit(EXIT_FAILURE); + } + + memset(data, 0, sizeof(data)); + data[0].effective = data[0].permitted = CAP_TO_MASK(uid_map ? CAP_SETUID : CAP_SETGID); + if (capset(&hdr, data) < 0) { + fprintf(stderr, _("%s: Could not set caps\n"), Prog); + exit(EXIT_FAILURE); + } + } +#endif + /* Build the mapping command */ mapping = mappings; for (idx = 0; idx < ranges; idx++, mapping++) { diff --git a/libmisc/idmapping.h b/libmisc/idmapping.h index 58d000f2..4bfffa63 100644 --- a/libmisc/idmapping.h +++ b/libmisc/idmapping.h @@ -38,7 +38,7 @@ struct map_range { extern struct map_range *get_map_ranges(int ranges, int argc, char **argv); extern void write_mapping(int proc_dir_fd, int ranges, - struct map_range *mappings, const char *map_file); + struct map_range *mappings, const char *map_file, uid_t uid); #endif /* _ID_MAPPING_H_ */ diff --git a/src/Makefile.am b/src/Makefile.am index 3c98a8d3..277ef008 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -86,8 +86,8 @@ LIBCRYPT_NOPAM = $(LIBCRYPT) endif chage_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) -newuidmap_LDADD = $(LDADD) $(LIBSELINUX) -newgidmap_LDADD = $(LDADD) $(LIBSELINUX) +newuidmap_LDADD = $(LDADD) $(LIBSELINUX) $(LIBCAP) +newgidmap_LDADD = $(LDADD) $(LIBSELINUX) $(LIBCAP) chfn_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD) chgpasswd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBSELINUX) $(LIBCRYPT) chsh_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD) diff --git a/src/newgidmap.c b/src/newgidmap.c index 59a2e75c..70b87888 100644 --- a/src/newgidmap.c +++ b/src/newgidmap.c @@ -250,7 +250,7 @@ int main(int argc, char **argv) verify_ranges(pw, ranges, mappings, &allow_setgroups); write_setgroups(proc_dir_fd, allow_setgroups); - write_mapping(proc_dir_fd, ranges, mappings, "gid_map"); + write_mapping(proc_dir_fd, ranges, mappings, "gid_map", pw->pw_uid); sub_gid_close(); return EXIT_SUCCESS; diff --git a/src/newuidmap.c b/src/newuidmap.c index 1ba25e7a..45636a3c 100644 --- a/src/newuidmap.c +++ b/src/newuidmap.c @@ -179,7 +179,7 @@ int main(int argc, char **argv) verify_ranges(pw, ranges, mappings); - write_mapping(proc_dir_fd, ranges, mappings, "uid_map"); + write_mapping(proc_dir_fd, ranges, mappings, "uid_map", pw->pw_uid); sub_uid_close(); return EXIT_SUCCESS;