Files
shadow/lib/idmapping.c
Alejandro Colomar e7a292ed4f Use bzero(3) instead of its pattern
It was blessed by POSIX.1-2001, and GCC says that it won't go away,
possibly ever.

memset(3) is dangerous, as the 2nd and 3rd arguments can be accidentally
swapped --who remembers what's the order of the 2nd and 3rd parameters
to memset(3) without checking the manual page or some code that uses
it?--.  Some recent compilers may be able to catch that via some
warnings, but those are not infalible.  And even if compiler warnings
could always catch that, the time lost in fixing or checking the docs is
lost for no clear gain.  Having a sane API that is unambiguous is the
Right Thing (tm); and that API is bzero(3).

If someone doesn't believe memset(3) is error-prone, please read the
book "Unix Network Programming", Volume 1, 3rd Edition by Stevens, et
al., Section 1.2.  See a stackoverflow reference in the link below[1].

bzero(3) had a bad fame in the bad old days, because some ancient
systems (I'm talking of many decades ago) shipped a broken version of
bzero(3).  We can assume that all systems in which current shadow utils
can be built, have a working version of bzero(3) --if not, please fix
your broken system; don't blame the programmer--.

One reason that some use today to avoid bzero(3) in favor of memset(3)
is that memset(3) is more often used; but that's a circular reasoning.
Even if bzero(3) wasn't supported by the system, it would need to be
invented.  It's the right API.

Another reason that some argue is that POSIX.1-2008 removed the
specification of bzero(3).  That's not a problem, because GCC will
probably support it forever, and even if it didn't, we can redefine it
like we do with memzero().  bzero(3) is just a one-liner wrapper around
memset(3).

Link: [1] <https://stackoverflow.com/a/17097978>
Cc: Christian Göttsche <cgzones@googlemail.com>
Cc: Serge Hallyn <serge@hallyn.com>
Cc: Iker Pedrosa <ipedrosa@redhat.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
2023-09-01 09:39:23 +02:00

228 lines
7.0 KiB
C

/*
* SPDX-FileCopyrightText: 2013 Eric Biederman
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include "alloc.h"
#include "prototypes.h"
#include "stpeprintf.h"
#include "idmapping.h"
#if HAVE_SYS_CAPABILITY_H
#include <sys/prctl.h>
#include <sys/capability.h>
#endif
#include "shadowlog.h"
#include "sizeof.h"
struct map_range *get_map_ranges(int ranges, int argc, char **argv)
{
struct map_range *mappings, *mapping;
int idx, argidx;
if (ranges < 0 || argc < 0) {
fprintf(log_get_logfd(), "%s: error calculating number of arguments\n", log_get_progname());
return NULL;
}
if (ranges != ((argc + 2) / 3)) {
fprintf(log_get_logfd(), "%s: ranges: %u is wrong for argc: %d\n", log_get_progname(), ranges, argc);
return NULL;
}
if ((ranges * 3) > argc) {
fprintf(log_get_logfd(), "ranges: %u argc: %d\n",
ranges, argc);
fprintf(log_get_logfd(),
_( "%s: Not enough arguments to form %u mappings\n"),
log_get_progname(), ranges);
return NULL;
}
mappings = CALLOC(ranges, struct map_range);
if (!mappings) {
fprintf(log_get_logfd(), _( "%s: Memory allocation failure\n"),
log_get_progname());
exit(EXIT_FAILURE);
}
/* Gather up the ranges from the command line */
mapping = mappings;
for (idx = 0, argidx = 0; idx < ranges; idx++, argidx += 3, mapping++) {
if (!getulong(argv[argidx + 0], &mapping->upper)) {
free(mappings);
return NULL;
}
if (!getulong(argv[argidx + 1], &mapping->lower)) {
free(mappings);
return NULL;
}
if (!getulong(argv[argidx + 2], &mapping->count)) {
free(mappings);
return NULL;
}
if (ULONG_MAX - mapping->upper <= mapping->count || ULONG_MAX - mapping->lower <= mapping->count) {
fprintf(log_get_logfd(), _( "%s: subuid overflow detected.\n"), log_get_progname());
exit(EXIT_FAILURE);
}
if (mapping->upper > UINT_MAX ||
mapping->lower > UINT_MAX ||
mapping->count > UINT_MAX) {
fprintf(log_get_logfd(), _( "%s: subuid overflow detected.\n"), log_get_progname());
exit(EXIT_FAILURE);
}
if (mapping->lower + mapping->count > UINT_MAX ||
mapping->upper + mapping->count > UINT_MAX) {
fprintf(log_get_logfd(), _( "%s: subuid overflow detected.\n"), log_get_progname());
exit(EXIT_FAILURE);
}
if (mapping->lower + mapping->count < mapping->lower ||
mapping->upper + mapping->count < mapping->upper) {
/* this one really shouldn't be possible given previous checks */
fprintf(log_get_logfd(), _( "%s: subuid overflow detected.\n"), log_get_progname());
exit(EXIT_FAILURE);
}
}
return mappings;
}
/* Number of ascii digits needed to print any unsigned long in decimal.
* There are approximately 10 bits for every 3 decimal digits.
* So from bits to digits the formula is roundup((Number of bits)/10) * 3.
* For common sizes of integers this works out to:
* 2bytes --> 6 ascii estimate -> 65536 (5 real)
* 4bytes --> 12 ascii estimated -> 4294967296 (10 real)
* 8bytes --> 21 ascii estimated -> 18446744073709551616 (20 real)
* 16bytes --> 39 ascii estimated -> 340282366920938463463374607431768211456 (39 real)
*/
#define ULONG_DIGITS (((WIDTHOF(unsigned long) + 9)/10)*3)
#if HAVE_SYS_CAPABILITY_H
static inline bool maps_lower_root(int cap, int ranges, const struct map_range *mappings)
{
int idx;
const struct map_range *mapping;
if (cap != CAP_SETUID)
return false;
mapping = mappings;
for (idx = 0; idx < ranges; idx++, mapping++) {
if (mapping->lower == 0)
return true;
}
return false;
}
#endif
/*
* The ruid refers to the caller's uid and is used to reset the effective uid
* back to the callers real uid.
* This clutch mainly exists for setuid-based new{g,u}idmap binaries that are
* called in contexts where all capabilities other than the necessary
* CAP_SET{G,U}ID capabilities are dropped. Since the kernel will require
* assurance that the caller holds CAP_SYS_ADMIN over the target user namespace
* the only way it can confirm is in this case is if the effective uid is
* equivalent to the uid owning the target user namespace.
* Note, we only support this when a) new{g,u}idmap is not called by root and
* b) if the caller's uid and the uid retrieved via system appropriate means
* (shadow file or other) are identical. Specifically, this does not support
* when the root user calls the new{g,u}idmap binary for an unprivileged user.
* If this is wanted: use file capabilities!
*/
void write_mapping(int proc_dir_fd, int ranges, const struct map_range *mappings,
const char *map_file, uid_t ruid)
{
int idx;
const struct map_range *mapping;
size_t bufsize;
char *buf, *pos, *end;
int fd;
#if HAVE_SYS_CAPABILITY_H
int cap;
struct __user_cap_header_struct hdr = {_LINUX_CAPABILITY_VERSION_3, 0};
struct __user_cap_data_struct data[2] = {{0}};
if (strcmp(map_file, "uid_map") == 0) {
cap = CAP_SETUID;
} else if (strcmp(map_file, "gid_map") == 0) {
cap = CAP_SETGID;
} else {
fprintf(log_get_logfd(), _("%s: Invalid map file %s specified\n"), log_get_progname(), map_file);
exit(EXIT_FAILURE);
}
/* Align setuid- and fscaps-based new{g,u}idmap behavior. */
if (geteuid() == 0 && geteuid() != ruid) {
if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
fprintf(log_get_logfd(), _("%s: Could not prctl(PR_SET_KEEPCAPS)\n"), log_get_progname());
exit(EXIT_FAILURE);
}
if (seteuid(ruid) < 0) {
fprintf(log_get_logfd(), _("%s: Could not seteuid to %d\n"), log_get_progname(), ruid);
exit(EXIT_FAILURE);
}
}
/* Lockdown new{g,u}idmap by dropping all unneeded capabilities. */
bzero(data, sizeof(data));
data[0].effective = CAP_TO_MASK(cap);
/*
* When uid 0 from the ancestor userns is supposed to be mapped into
* the child userns we need to retain CAP_SETFCAP.
*/
if (maps_lower_root(cap, ranges, mappings))
data[0].effective |= CAP_TO_MASK(CAP_SETFCAP);
data[0].permitted = data[0].effective;
if (capset(&hdr, data) < 0) {
fprintf(log_get_logfd(), _("%s: Could not set caps\n"), log_get_progname());
exit(EXIT_FAILURE);
}
#endif
bufsize = ranges * ((ULONG_DIGITS + 1) * 3);
pos = buf = XMALLOC(bufsize, char);
end = buf + bufsize;
/* Build the mapping command */
mapping = mappings;
for (idx = 0; idx < ranges; idx++, mapping++) {
/* Append this range to the string that will be written */
pos = stpeprintf(pos, end, "%lu %lu %lu\n",
mapping->upper,
mapping->lower,
mapping->count);
}
if (pos == end || pos == NULL) {
fprintf(log_get_logfd(), _("%s: stpeprintf failed!\n"), log_get_progname());
exit(EXIT_FAILURE);
}
/* Write the mapping to the mapping file */
fd = openat(proc_dir_fd, map_file, O_WRONLY);
if (fd < 0) {
fprintf(log_get_logfd(), _("%s: open of %s failed: %s\n"),
log_get_progname(), map_file, strerror(errno));
exit(EXIT_FAILURE);
}
if (write_full(fd, buf, pos - buf) == -1) {
fprintf(log_get_logfd(), _("%s: write to %s failed: %s\n"),
log_get_progname(), map_file, strerror(errno));
exit(EXIT_FAILURE);
}
close(fd);
free(buf);
}