Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

newgidmap: enforce setgroups=deny if self-mapping a group #97

Merged
merged 2 commits into from
Feb 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ a lot of mail...

Adam Rudnicki <adam@v-lo.krakow.pl>
Alan Curry <pacman@tardis.mars.net>
Aleksa Sarai <cyphar@cyphar.com>
Alexander O. Yuriev <alex@bach.cis.temple.edu>
Algis Rudys <arudys@rice.edu>
Andreas Jaeger <aj@arthur.rhein-neckar.de>
Expand Down
89 changes: 80 additions & 9 deletions src/newgidmap.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,32 +46,37 @@
*/
const char *Prog;

static bool verify_range(struct passwd *pw, struct map_range *range)

static bool verify_range(struct passwd *pw, struct map_range *range, bool *allow_setgroups)
{
/* An empty range is invalid */
if (range->count == 0)
return false;

/* Test /etc/subgid */
if (have_sub_gids(pw->pw_name, range->lower, range->count))
/* Test /etc/subgid. If the mapping is valid then we allow setgroups. */
if (have_sub_gids(pw->pw_name, range->lower, range->count)) {
*allow_setgroups = true;
return true;
}

/* Allow a process to map its own gid */
if ((range->count == 1) && (pw->pw_gid == range->lower))
/* Allow a process to map its own gid. */
if ((range->count == 1) && (pw->pw_gid == range->lower)) {
/* noop -- if setgroups is enabled already we won't disable it. */
return true;
}

return false;
}

static void verify_ranges(struct passwd *pw, int ranges,
struct map_range *mappings)
struct map_range *mappings, bool *allow_setgroups)
{
struct map_range *mapping;
int idx;

mapping = mappings;
for (idx = 0; idx < ranges; idx++, mapping++) {
if (!verify_range(pw, mapping)) {
if (!verify_range(pw, mapping, allow_setgroups)) {
fprintf(stderr, _( "%s: gid range [%lu-%lu) -> [%lu-%lu) not allowed\n"),
Prog,
mapping->upper,
Expand All @@ -89,6 +94,70 @@ static void usage(void)
exit(EXIT_FAILURE);
}

void write_setgroups(int proc_dir_fd, bool allow_setgroups)
{
int setgroups_fd;
char *policy, policy_buffer[4096];

/*
* Default is "deny", and any "allow" will out-rank a "deny". We don't
* forcefully write an "allow" here because the process we are writing
* mappings for may have already set themselves to "deny" (and "allow"
* is the default anyway). So allow_setgroups == true is a noop.
*/
policy = "deny\n";
if (allow_setgroups)
return;

setgroups_fd = openat(proc_dir_fd, "setgroups", O_RDWR|O_CLOEXEC);
if (setgroups_fd < 0) {
/*
* If it's an ENOENT then we are on too old a kernel for the setgroups
* code to exist. Emit a warning and bail on this.
*/
if (ENOENT == errno) {
fprintf(stderr, _("%s: kernel doesn't support setgroups restrictions\n"), Prog);
goto out;
}
fprintf(stderr, _("%s: couldn't open process setgroups: %s\n"),
Prog,
strerror(errno));
exit(EXIT_FAILURE);
}

/*
* Check whether the policy is already what we want. /proc/self/setgroups
* is write-once, so attempting to write after it's already written to will
* fail.
*/
if (read(setgroups_fd, policy_buffer, sizeof(policy_buffer)) < 0) {
fprintf(stderr, _("%s: failed to read setgroups: %s\n"),
Prog,
strerror(errno));
exit(EXIT_FAILURE);
}
if (!strncmp(policy_buffer, policy, strlen(policy)))
goto out;

/* Write the policy. */
if (lseek(setgroups_fd, 0, SEEK_SET) < 0) {
fprintf(stderr, _("%s: failed to seek setgroups: %s\n"),
Prog,
strerror(errno));
exit(EXIT_FAILURE);
}
if (dprintf(setgroups_fd, "%s", policy) < 0) {
fprintf(stderr, _("%s: failed to setgroups %s policy: %s\n"),
Prog,
policy,
strerror(errno));
exit(EXIT_FAILURE);
}

out:
close(setgroups_fd);
}

/*
* newgidmap - Set the gid_map for the specified process
*/
Expand All @@ -103,6 +172,7 @@ int main(int argc, char **argv)
struct stat st;
struct passwd *pw;
int written;
bool allow_setgroups = false;

Prog = Basename (argv[0]);

Expand Down Expand Up @@ -145,7 +215,7 @@ int main(int argc, char **argv)
(unsigned long) getuid ()));
return EXIT_FAILURE;
}

/* 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"),
Expand Down Expand Up @@ -177,8 +247,9 @@ int main(int argc, char **argv)
if (!mappings)
usage();

verify_ranges(pw, ranges, mappings);
verify_ranges(pw, ranges, mappings, &allow_setgroups);

write_setgroups(proc_dir_fd, allow_setgroups);
write_mapping(proc_dir_fd, ranges, mappings, "gid_map");
sub_gid_close();

Expand Down