Skip to content

Commit 3353237

Browse files
committed
subid: Add deterministic subid ranges
This adds four new options to /etc/login.defs: * SUB_UID_DETERMINISTIC * UNSAFE_SUB_UID_DETERMINISTIC_WRAP * SUB_GID_DETERMINISTIC * UNSAFE_SUB_GID_DETERMINISTIC_WRAP In a lab where users are created ad hoc subids might drift from one host to the other. If there is a shared home area, this drift can create some frustration. The manpages provide documentation on how these can be used. Signed-off-by: Pat Riehecky <riehecky@fnal.gov>
1 parent 324ce1d commit 3353237

2 files changed

Lines changed: 344 additions & 0 deletions

File tree

lib/find_new_sub_gids.c

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,175 @@
1919
#include "shadowlog.h"
2020
#include "subordinateio.h"
2121

22+
/*
23+
* find_new_sub_gids_deterministic - Assign a subordinate GID range by UID.
24+
*
25+
* Calculates a deterministic subordinate GID range for a given UID based
26+
* on its offset from UID_MIN. Loads SUB_GID_COUNT from login.defs and
27+
* writes it back to *range_count on success.
28+
*
29+
* BASE FORMULA:
30+
* uid_offset = uid - UID_MIN
31+
* logical_offset = uid_offset * SUB_GID_COUNT
32+
* start_id = SUB_GID_MIN + logical_offset
33+
* end_id = start_id + SUB_GID_COUNT - 1
34+
*
35+
* DETERMINISTIC-SAFE MODE (default):
36+
* All arithmetic overflow is a hard error. The assigned range must fit
37+
* entirely within [SUB_GID_MIN, SUB_GID_MAX]. Allocation is monotonic
38+
* and guaranteed non-overlapping.
39+
*
40+
* UNSAFE_SUB_GID_DETERMINISTIC_WRAP MODE:
41+
* Activated with UNSAFE_SUB_GID_DETERMINISTIC_WRAP yes
42+
*
43+
* WARNING: SECURITY RISK!
44+
* WARNING: MAY CAUSE RANGE OVERLAPS!
45+
* WARNING: MAY CAUSE CONTAINER ESCAPES!
46+
*
47+
* The subordinate GID space is treated as a ring. Arithmetic overflow
48+
* is normalised via modulo over [SUB_GID_MIN, SUB_GID_MAX].
49+
* This means ranges MAY overlap for large UID populations!
50+
* Intended only for development, testing, or constrained lab environments.
51+
*
52+
* Return 0 on success, -1 if no GIDs are available.
53+
*/
54+
static int find_new_sub_gids_deterministic (uid_t uid,
55+
id_t *range_start,
56+
unsigned long *range_count)
57+
{
58+
unsigned long uid_min;
59+
unsigned long sub_gid_min;
60+
unsigned long sub_gid_max;
61+
unsigned long uid_offset;
62+
unsigned long space;
63+
unsigned long count;
64+
bool allow_wrap;
65+
66+
assert (range_start != NULL);
67+
assert (range_count != NULL);
68+
69+
uid_min = getdef_ulong ("UID_MIN", 1000UL);
70+
sub_gid_min = getdef_ulong ("SUB_GID_MIN", 65536UL);
71+
sub_gid_max = getdef_ulong ("SUB_GID_MAX", 4294967295UL);
72+
count = getdef_ulong ("SUB_GID_COUNT", 65536UL);
73+
allow_wrap = getdef_bool ("UNSAFE_SUB_GID_DETERMINISTIC_WRAP");
74+
75+
if ((unsigned long)uid < uid_min) {
76+
fprintf (log_get_logfd (),
77+
_("%s: UID %lu is less than UID_MIN %lu,"
78+
" cannot calculate deterministic subordinate GIDs\n"),
79+
log_get_progname (),
80+
(unsigned long)uid, uid_min);
81+
return -1;
82+
}
83+
84+
/*
85+
* Validate configuration before using min/max in any arithmetic.
86+
* If sub_gid_min > sub_gid_max, the space calculation below would
87+
* unsigned-wrap and silently produce a nonsense value.
88+
*/
89+
if (sub_gid_min > sub_gid_max || count == 0) {
90+
fprintf (log_get_logfd (),
91+
_("%s: Invalid configuration: SUB_GID_MIN (%lu),"
92+
" SUB_GID_MAX (%lu), SUB_GID_COUNT (%lu)\n"),
93+
log_get_progname (),
94+
sub_gid_min, sub_gid_max, count);
95+
return -1;
96+
}
97+
98+
uid_offset = (unsigned long)uid - uid_min;
99+
space = sub_gid_max - sub_gid_min + 1;
100+
101+
/*
102+
* A range larger than the entire configured space can never be placed
103+
* without overlap, regardless of mode.
104+
*/
105+
if (count > space) {
106+
fprintf (log_get_logfd (),
107+
_("%s: Not enough space for any subordinate GIDs"
108+
" (SUB_GID_MIN=%lu, SUB_GID_MAX=%lu,"
109+
" SUB_GID_COUNT=%lu)\n"),
110+
log_get_progname (),
111+
sub_gid_min, sub_gid_max, count);
112+
return -1;
113+
}
114+
115+
if (!allow_wrap) {
116+
/*
117+
* DETERMINISTIC-SAFE MODE
118+
*
119+
* Three overflow guards are required:
120+
*
121+
* 1. uid_offset * count -> product
122+
* Overflows for large UIDs or large counts.
123+
*
124+
* 2. sub_gid_min + product -> start_id
125+
* Overflows when the offset pushes start past UINTMAX_MAX.
126+
*
127+
* 3. start_id + (count-1) -> end_id
128+
* Overflows when start_id is near UINTMAX_MAX even after
129+
* guards 1 and 2 pass. Required on platforms where
130+
* uintmax_t is wider than the sub-GID value space.
131+
*
132+
* Omitting any one leaves a range-escape vector on some
133+
* possible arch/config combinations.
134+
*/
135+
uintmax_t product = 0;
136+
uintmax_t start_id = 0;
137+
uintmax_t end_id = 0;
138+
139+
if (__builtin_mul_overflow (uid_offset, count, &product)) {
140+
fprintf (log_get_logfd (),
141+
_("%s: Overflow calculating deterministic"
142+
" subordinate GID range for UID %lu\n"),
143+
log_get_progname (), (unsigned long)uid);
144+
return -1;
145+
}
146+
147+
if (__builtin_add_overflow (sub_gid_min, product, &start_id)) {
148+
fprintf (log_get_logfd (),
149+
_("%s: Overflow calculating deterministic"
150+
" subordinate GID range for UID %lu\n"),
151+
log_get_progname (), (unsigned long)uid);
152+
return -1;
153+
}
154+
155+
if (__builtin_add_overflow (start_id, count - 1, &end_id)) {
156+
fprintf (log_get_logfd (),
157+
_("%s: Overflow calculating deterministic"
158+
" subordinate GID range for UID %lu\n"),
159+
log_get_progname (), (unsigned long)uid);
160+
return -1;
161+
}
162+
163+
if (end_id > sub_gid_max) {
164+
fprintf (log_get_logfd (),
165+
_("%s: Deterministic subordinate GID range"
166+
" for UID %lu exceeds SUB_GID_MAX (%lu)\n"),
167+
log_get_progname (),
168+
(unsigned long)uid, sub_gid_max);
169+
return -1;
170+
}
171+
172+
*range_start = (id_t)start_id;
173+
*range_count = count;
174+
return 0;
175+
}
176+
177+
/*
178+
* UNSAFE_SUB_GID_DETERMINISTIC_WRAP MODE
179+
*
180+
* Promote to uintmax_t before multiplying to avoid truncation on
181+
* 32-bit platforms where unsigned long is 32 bits. The modulo
182+
* folds the result back into [0, space) before adding min.
183+
*/
184+
uintmax_t logical_offset = (uintmax_t)uid_offset * (uintmax_t)count;
185+
186+
*range_start = (id_t)(sub_gid_min + (unsigned long)(logical_offset % space));
187+
*range_count = count;
188+
return 0;
189+
}
190+
22191
/*
23192
* find_new_sub_gids_linear - Find an unused subordinate GID range via
24193
* linear search.
@@ -74,6 +243,9 @@ int find_new_sub_gids (uid_t uid, id_t *range_start, unsigned long *range_count)
74243
return -1;
75244
}
76245

246+
if (getdef_bool ("SUB_GID_DETERMINISTIC"))
247+
return find_new_sub_gids_deterministic (uid, range_start, range_count);
248+
77249
return find_new_sub_gids_linear (range_start, range_count);
78250
}
79251

lib/find_new_sub_uids.c

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,175 @@
1919
#include "shadowlog.h"
2020
#include "subordinateio.h"
2121

22+
/*
23+
* find_new_sub_uids_deterministic - Assign a subordinate UID range by UID.
24+
*
25+
* Calculates a deterministic subordinate UID range for a given UID based
26+
* on its offset from UID_MIN. Loads SUB_UID_COUNT from login.defs and
27+
* writes it back to *range_count on success.
28+
*
29+
* BASE FORMULA:
30+
* uid_offset = uid - UID_MIN
31+
* logical_offset = uid_offset * SUB_UID_COUNT
32+
* start_id = SUB_UID_MIN + logical_offset
33+
* end_id = start_id + SUB_UID_COUNT - 1
34+
*
35+
* DETERMINISTIC-SAFE MODE (default):
36+
* All arithmetic overflow is a hard error. The assigned range must fit
37+
* entirely within [SUB_UID_MIN, SUB_UID_MAX]. Allocation is monotonic
38+
* and guaranteed non-overlapping.
39+
*
40+
* UNSAFE_SUB_UID_DETERMINISTIC_WRAP MODE:
41+
* Activated with UNSAFE_SUB_UID_DETERMINISTIC_WRAP yes
42+
*
43+
* WARNING: SECURITY RISK!
44+
* WARNING: MAY CAUSE RANGE OVERLAPS!
45+
* WARNING: MAY CAUSE CONTAINER ESCAPES!
46+
*
47+
* The subordinate UID space is treated as a ring. Arithmetic overflow
48+
* is normalised via modulo over [SUB_UID_MIN, SUB_UID_MAX].
49+
* This means ranges MAY overlap for large UID populations!
50+
* Intended only for development, testing, or constrained lab environments.
51+
*
52+
* Return 0 on success, -1 if no UIDs are available.
53+
*/
54+
static int find_new_sub_uids_deterministic (uid_t uid,
55+
id_t *range_start,
56+
unsigned long *range_count)
57+
{
58+
unsigned long uid_min;
59+
unsigned long sub_uid_min;
60+
unsigned long sub_uid_max;
61+
unsigned long uid_offset;
62+
unsigned long space;
63+
unsigned long count;
64+
bool allow_wrap;
65+
66+
assert (range_start != NULL);
67+
assert (range_count != NULL);
68+
69+
uid_min = getdef_ulong ("UID_MIN", 1000UL);
70+
sub_uid_min = getdef_ulong ("SUB_UID_MIN", 65536UL);
71+
sub_uid_max = getdef_ulong ("SUB_UID_MAX", 4294967295UL);
72+
count = getdef_ulong ("SUB_UID_COUNT", 65536UL);
73+
allow_wrap = getdef_bool ("UNSAFE_SUB_UID_DETERMINISTIC_WRAP");
74+
75+
if ((unsigned long)uid < uid_min) {
76+
fprintf (log_get_logfd (),
77+
_("%s: UID %lu is less than UID_MIN %lu,"
78+
" cannot calculate deterministic subordinate UIDs\n"),
79+
log_get_progname (),
80+
(unsigned long)uid, uid_min);
81+
return -1;
82+
}
83+
84+
/*
85+
* Validate configuration before using min/max in any arithmetic.
86+
* If sub_uid_min > sub_uid_max, the space calculation below would
87+
* unsigned-wrap and silently produce a nonsense value.
88+
*/
89+
if (sub_uid_min > sub_uid_max || count == 0) {
90+
fprintf (log_get_logfd (),
91+
_("%s: Invalid configuration: SUB_UID_MIN (%lu),"
92+
" SUB_UID_MAX (%lu), SUB_UID_COUNT (%lu)\n"),
93+
log_get_progname (),
94+
sub_uid_min, sub_uid_max, count);
95+
return -1;
96+
}
97+
98+
uid_offset = (unsigned long)uid - uid_min;
99+
space = sub_uid_max - sub_uid_min + 1;
100+
101+
/*
102+
* A range larger than the entire configured space can never be placed
103+
* without overlap, regardless of mode.
104+
*/
105+
if (count > space) {
106+
fprintf (log_get_logfd (),
107+
_("%s: Not enough space for any subordinate UIDs"
108+
" (SUB_UID_MIN=%lu, SUB_UID_MAX=%lu,"
109+
" SUB_UID_COUNT=%lu)\n"),
110+
log_get_progname (),
111+
sub_uid_min, sub_uid_max, count);
112+
return -1;
113+
}
114+
115+
if (!allow_wrap) {
116+
/*
117+
* DETERMINISTIC-SAFE MODE
118+
*
119+
* Three overflow guards are required:
120+
*
121+
* 1. uid_offset * count -> product
122+
* Overflows for large UIDs or large counts.
123+
*
124+
* 2. sub_uid_min + product -> start_id
125+
* Overflows when the offset pushes start past UINTMAX_MAX.
126+
*
127+
* 3. start_id + (count-1) -> end_id
128+
* Overflows when start_id is near UINTMAX_MAX even after
129+
* guards 1 and 2 pass. Required on platforms where
130+
* uintmax_t is wider than the sub-UID value space.
131+
*
132+
* Omitting any one leaves a range-escape vector on some
133+
* possible arch/config combinations.
134+
*/
135+
uintmax_t product = 0;
136+
uintmax_t start_id = 0;
137+
uintmax_t end_id = 0;
138+
139+
if (__builtin_mul_overflow (uid_offset, count, &product)) {
140+
fprintf (log_get_logfd (),
141+
_("%s: Overflow calculating deterministic"
142+
" subordinate UID range for UID %lu\n"),
143+
log_get_progname (), (unsigned long)uid);
144+
return -1;
145+
}
146+
147+
if (__builtin_add_overflow (sub_uid_min, product, &start_id)) {
148+
fprintf (log_get_logfd (),
149+
_("%s: Overflow calculating deterministic"
150+
" subordinate UID range for UID %lu\n"),
151+
log_get_progname (), (unsigned long)uid);
152+
return -1;
153+
}
154+
155+
if (__builtin_add_overflow (start_id, count - 1, &end_id)) {
156+
fprintf (log_get_logfd (),
157+
_("%s: Overflow calculating deterministic"
158+
" subordinate UID range for UID %lu\n"),
159+
log_get_progname (), (unsigned long)uid);
160+
return -1;
161+
}
162+
163+
if (end_id > sub_uid_max) {
164+
fprintf (log_get_logfd (),
165+
_("%s: Deterministic subordinate UID range"
166+
" for UID %lu exceeds SUB_UID_MAX (%lu)\n"),
167+
log_get_progname (),
168+
(unsigned long)uid, sub_uid_max);
169+
return -1;
170+
}
171+
172+
*range_start = (id_t)start_id;
173+
*range_count = count;
174+
return 0;
175+
}
176+
177+
/*
178+
* UNSAFE_SUB_UID_DETERMINISTIC_WRAP MODE
179+
*
180+
* Promote to uintmax_t before multiplying to avoid truncation on
181+
* 32-bit platforms where unsigned long is 32 bits. The modulo
182+
* folds the result back into [0, space) before adding min.
183+
*/
184+
uintmax_t logical_offset = (uintmax_t)uid_offset * (uintmax_t)count;
185+
186+
*range_start = (id_t)(sub_uid_min + (unsigned long)(logical_offset % space));
187+
*range_count = count;
188+
return 0;
189+
}
190+
22191
/*
23192
* find_new_sub_uids_linear - Find an unused subordinate UID range via
24193
* linear search.
@@ -74,6 +243,9 @@ int find_new_sub_uids (uid_t uid, id_t *range_start, unsigned long *range_count)
74243
return -1;
75244
}
76245

246+
if (getdef_bool ("SUB_UID_DETERMINISTIC"))
247+
return find_new_sub_uids_deterministic (uid, range_start, range_count);
248+
77249
return find_new_sub_uids_linear (range_start, range_count);
78250
}
79251

0 commit comments

Comments
 (0)