Skip to content

Commit e39264e

Browse files
committed
newgidmap: add deny_setgroups option to /etc/subgid
Add a new deny_setgroups (and corresponding allow_setgroups) option to /etc/subgid. The purpose of this option is to extend the security protections against CVE-2018-7169, so that even group mapping configured in /etc/subgid by an administrator can still disable setgroups. However, rather than the fairly lenient semantics for self-mapping, the semantics of /etc/subgid are stronger. If a mapping is encountered where "deny_setgroups" is set, then no other mapping can "undo" this restriction. The reason for this is that "deny_setgroups" indicates that (according to the administrator) the mapping is unsafe to allow setgroups in, and adding more mappings will not change this fact. "allow_setgroups" is the default, and setting it is a noop. The logic used when applying setgroups policies is unchanged (only denies are written, and we don't write anything if it's already denied). Signed-off-by: Aleksa Sarai <[email protected]>
1 parent f2bd038 commit e39264e

File tree

1 file changed

+71
-13
lines changed

1 file changed

+71
-13
lines changed

src/newgidmap.c

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,41 +46,98 @@
4646
*/
4747
const char *Prog;
4848

49-
static void parse_gid_options(char **options)
49+
typedef enum {
50+
DEFAULT = -1,
51+
FALSE = 0,
52+
TRUE = 1,
53+
} autobool_t;
54+
55+
/* Returns @arg, unless it's DEFAULT in that case @value is returned instead. */
56+
static inline autobool_t auto_or_else(autobool_t arg, autobool_t value)
57+
{
58+
return (arg == DEFAULT) ? value : arg;
59+
}
60+
61+
/* Converts @arg to a bool, using @dfl is @arg == DEFAULT. */
62+
static inline bool auto_bool(autobool_t arg, bool dfl)
63+
{
64+
return TRUE == auto_or_else(arg, dfl ? TRUE : FALSE);
65+
}
66+
67+
static void parse_gid_options(char **options, autobool_t *allow_setgroups)
5068
{
5169
int i;
70+
autobool_t new_allow_setgroups = DEFAULT;
5271

5372
if (NULL == options)
5473
return;
5574

75+
/*
76+
* Iterate over each option, and make sure it's in the valid option
77+
* set. We first just accumulate which options are specified for each
78+
* option-set, so that "deny_setgroups,allow_setgroups" doesn't end up
79+
* with weird semantics.
80+
*/
5681
for (i = 0; NULL != options[i]; i++) {
5782
char *option = options[i];
83+
bool is_valid = false;
5884

5985
if (strlen(option) < 1)
6086
continue;
6187

62-
/* No options are currently valid for /etc/subgid, so error out. */
88+
/* {allow,deny}_setgroups option-set -- default is allow_setgroups */
89+
else if (!strcmp(option, "allow_setgroups"))
90+
new_allow_setgroups = TRUE;
91+
else if (!strcmp(option, "deny_setgroups"))
92+
new_allow_setgroups = FALSE;
6393

64-
fprintf(stderr, _("%s: option '%s' is not understood\n"),
65-
Prog,
66-
option);
67-
exit(EXIT_FAILURE);
94+
/* Catch-all. */
95+
else {
96+
fprintf(stderr, _("%s: option '%s' is not understood\n"),
97+
Prog,
98+
option);
99+
exit(EXIT_FAILURE);
100+
}
101+
}
102+
103+
/*
104+
* We only change allow_setgroups to TRUE if it is still the default.
105+
* But we always override the value for deny_setgroups, for security
106+
* reasons (adding more mappings won't make setgroups(2) safer in the
107+
* old ones).
108+
*/
109+
switch (new_allow_setgroups) {
110+
case TRUE:
111+
*allow_setgroups = auto_or_else(*allow_setgroups, TRUE);
112+
break;
113+
case FALSE:
114+
*allow_setgroups = FALSE;
115+
break;
116+
case DEFAULT:
117+
/* noop */
118+
default:
119+
break;
68120
}
69121
}
70122

71-
static bool verify_range(struct passwd *pw, struct map_range *range, bool *allow_setgroups)
123+
static bool verify_range(struct passwd *pw, struct map_range *range,
124+
autobool_t *allow_setgroups)
72125
{
73126
/* An empty range is invalid */
74127
if (range->count == 0)
75128
return false;
76129

77-
/* Test /etc/subgid. If the mapping is valid then we allow setgroups. */
130+
/*
131+
* Test /etc/subgid. We only allow setgroups if none of the mapping
132+
* options have deny_setgroups set.
133+
*/
78134
if (have_sub_gids(pw->pw_name, range->lower, range->count)) {
79135
char **options = sub_gid_options(pw->pw_name, range->lower, range->count);
80136

81-
*allow_setgroups = true;
82-
parse_gid_options(options);
137+
parse_gid_options(options, allow_setgroups);
83138
free_list(options);
139+
140+
*allow_setgroups = auto_or_else(*allow_setgroups, TRUE);
84141
return true;
85142
}
86143

@@ -94,7 +151,8 @@ static bool verify_range(struct passwd *pw, struct map_range *range, bool *allow
94151
}
95152

96153
static void verify_ranges(struct passwd *pw, int ranges,
97-
struct map_range *mappings, bool *allow_setgroups)
154+
struct map_range *mappings,
155+
autobool_t *allow_setgroups)
98156
{
99157
struct map_range *mapping;
100158
int idx;
@@ -197,7 +255,7 @@ int main(int argc, char **argv)
197255
struct stat st;
198256
struct passwd *pw;
199257
int written;
200-
bool allow_setgroups = false;
258+
autobool_t allow_setgroups = DEFAULT;
201259

202260
Prog = Basename (argv[0]);
203261

@@ -274,7 +332,7 @@ int main(int argc, char **argv)
274332

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

277-
write_setgroups(proc_dir_fd, allow_setgroups);
335+
write_setgroups(proc_dir_fd, auto_bool(allow_setgroups, FALSE));
278336
write_mapping(proc_dir_fd, ranges, mappings, "gid_map");
279337
sub_gid_close();
280338

0 commit comments

Comments
 (0)