|
19 | 19 | #include "shadowlog.h" |
20 | 20 | #include "subordinateio.h" |
21 | 21 |
|
| 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 | + |
22 | 191 | /* |
23 | 192 | * find_new_sub_gids_linear - Find an unused subordinate GID range via |
24 | 193 | * linear search. |
@@ -74,6 +243,9 @@ int find_new_sub_gids (uid_t uid, id_t *range_start, unsigned long *range_count) |
74 | 243 | return -1; |
75 | 244 | } |
76 | 245 |
|
| 246 | + if (getdef_bool ("SUB_GID_DETERMINISTIC")) |
| 247 | + return find_new_sub_gids_deterministic (uid, range_start, range_count); |
| 248 | + |
77 | 249 | return find_new_sub_gids_linear (range_start, range_count); |
78 | 250 | } |
79 | 251 |
|
|
0 commit comments