Skip to content

Commit 65b691f

Browse files
gnoackl0kod
authored andcommitted
landlock: Transpose the layer masks data structure
The layer masks data structure tracks the requested but unfulfilled access rights during an operation's security check. It stores one bit for each combination of access right and layer index. If the bit is set, that access right is not granted (yet) in the given layer and we have to traverse the path further upwards to grant it. Previously, the layer masks were stored as arrays mapping from access right indices to layer_mask_t. The layer_mask_t value then indicates all layers in which the given access right is still (tentatively) denied. This patch introduces struct layer_access_masks instead: This struct contains an array with the access_mask_t of each (tentatively) denied access right in that layer. The hypothesis of this patch is that this simplifies the code enough so that the resulting code will run faster: * We can use bitwise operations in multiple places where we previously looped over bits individually with macros. (Should require less branch speculation and lends itself to better loop unrolling.) * Code is ~75 lines smaller. Other noteworthy changes: * In no_more_access(), call a new helper function may_refer(), which only solves the asymmetric case. Previously, the code interleaved the checks for the two symmetric cases in RENAME_EXCHANGE. It feels that the code is clearer when renames without RENAME_EXCHANGE are more obviously the normal case. Tradeoffs: This change improves performance, at a slight size increase to the layer masks data structure. This fixes the size of the data structure at 32 bytes for all types of access rights. (64, once we introduce a 17th filesystem access right). For filesystem access rights, at the moment, the data structure has the same size as before, but once we introduce the 17th filesystem access right, it will double in size (from 32 to 64 bytes), as access_mask_t grows from 16 to 32 bit [1]. Link: https://lore.kernel.org/all/20260120.haeCh4li9Vae@digikod.net/ [1] Signed-off-by: Günther Noack <gnoack3000@gmail.com> Link: https://lore.kernel.org/r/20260206151154.97915-5-gnoack3000@gmail.com [mic: Cosmetic fixes, moved struct layer_access_masks definition] Signed-off-by: Mickaël Salaün <mic@digikod.net>
1 parent 45f2a29 commit 65b691f

9 files changed

Lines changed: 272 additions & 341 deletions

File tree

security/landlock/access.h

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,30 @@ union access_masks_all {
6161
static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
6262
sizeof(typeof_member(union access_masks_all, all)));
6363

64-
typedef u16 layer_mask_t;
65-
66-
/* Makes sure all layers can be checked. */
67-
static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
64+
/**
65+
* struct layer_access_masks - A boolean matrix of layers and access rights
66+
*
67+
* This has a bit for each combination of layer numbers and access rights.
68+
* During access checks, it is used to represent the access rights for each
69+
* layer which still need to be fulfilled. When all bits are 0, the access
70+
* request is considered to be fulfilled.
71+
*/
72+
struct layer_access_masks {
73+
/**
74+
* @access: The unfulfilled access rights for each layer.
75+
*/
76+
access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
77+
};
6878

6979
/*
70-
* Tracks domains responsible of a denied access. This is required to avoid
71-
* storing in each object the full layer_masks[] required by update_request().
80+
* Tracks domains responsible of a denied access. This avoids storing in each
81+
* object the full matrix of per-layer unfulfilled access rights, which is
82+
* required by update_request().
83+
*
84+
* Each nibble represents the layer index of the newest layer which denied a
85+
* certain access right. For file system access rights, the upper four bits are
86+
* the index of the layer which denies LANDLOCK_ACCESS_FS_IOCTL_DEV and the
87+
* lower nibble represents LANDLOCK_ACCESS_FS_TRUNCATE.
7288
*/
7389
typedef u8 deny_masks_t;
7490

security/landlock/audit.c

Lines changed: 25 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -180,38 +180,21 @@ static void test_get_hierarchy(struct kunit *const test)
180180

181181
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
182182

183+
/* Get the youngest layer that denied the access_request. */
183184
static size_t get_denied_layer(const struct landlock_ruleset *const domain,
184185
access_mask_t *const access_request,
185-
const layer_mask_t (*const layer_masks)[],
186-
const size_t layer_masks_size)
186+
const struct layer_access_masks *masks)
187187
{
188-
const unsigned long access_req = *access_request;
189-
unsigned long access_bit;
190-
access_mask_t missing = 0;
191-
long youngest_layer = -1;
192-
193-
for_each_set_bit(access_bit, &access_req, layer_masks_size) {
194-
const layer_mask_t mask = (*layer_masks)[access_bit];
195-
long layer;
196-
197-
if (!mask)
198-
continue;
199-
200-
/* __fls(1) == 0 */
201-
layer = __fls(mask);
202-
if (layer > youngest_layer) {
203-
youngest_layer = layer;
204-
missing = BIT(access_bit);
205-
} else if (layer == youngest_layer) {
206-
missing |= BIT(access_bit);
188+
for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) {
189+
if (masks->access[i] & *access_request) {
190+
*access_request &= masks->access[i];
191+
return i;
207192
}
208193
}
209194

210-
*access_request = missing;
211-
if (youngest_layer == -1)
212-
return domain->num_layers - 1;
213-
214-
return youngest_layer;
195+
/* Not found - fall back to default values */
196+
*access_request = 0;
197+
return domain->num_layers - 1;
215198
}
216199

217200
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
@@ -221,50 +204,39 @@ static void test_get_denied_layer(struct kunit *const test)
221204
const struct landlock_ruleset dom = {
222205
.num_layers = 5,
223206
};
224-
const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
225-
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT(0),
226-
[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT(1),
227-
[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = BIT(1) | BIT(0),
228-
[BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = BIT(2),
207+
const struct layer_access_masks masks = {
208+
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
209+
LANDLOCK_ACCESS_FS_READ_DIR,
210+
.access[1] = LANDLOCK_ACCESS_FS_READ_FILE |
211+
LANDLOCK_ACCESS_FS_READ_DIR,
212+
.access[2] = LANDLOCK_ACCESS_FS_REMOVE_DIR,
229213
};
230214
access_mask_t access;
231215

232216
access = LANDLOCK_ACCESS_FS_EXECUTE;
233-
KUNIT_EXPECT_EQ(test, 0,
234-
get_denied_layer(&dom, &access, &layer_masks,
235-
sizeof(layer_masks)));
217+
KUNIT_EXPECT_EQ(test, 0, get_denied_layer(&dom, &access, &masks));
236218
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE);
237219

238220
access = LANDLOCK_ACCESS_FS_READ_FILE;
239-
KUNIT_EXPECT_EQ(test, 1,
240-
get_denied_layer(&dom, &access, &layer_masks,
241-
sizeof(layer_masks)));
221+
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
242222
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE);
243223

244224
access = LANDLOCK_ACCESS_FS_READ_DIR;
245-
KUNIT_EXPECT_EQ(test, 1,
246-
get_denied_layer(&dom, &access, &layer_masks,
247-
sizeof(layer_masks)));
225+
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
248226
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
249227

250228
access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
251-
KUNIT_EXPECT_EQ(test, 1,
252-
get_denied_layer(&dom, &access, &layer_masks,
253-
sizeof(layer_masks)));
229+
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
254230
KUNIT_EXPECT_EQ(test, access,
255231
LANDLOCK_ACCESS_FS_READ_FILE |
256232
LANDLOCK_ACCESS_FS_READ_DIR);
257233

258234
access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR;
259-
KUNIT_EXPECT_EQ(test, 1,
260-
get_denied_layer(&dom, &access, &layer_masks,
261-
sizeof(layer_masks)));
235+
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
262236
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
263237

264238
access = LANDLOCK_ACCESS_FS_WRITE_FILE;
265-
KUNIT_EXPECT_EQ(test, 4,
266-
get_denied_layer(&dom, &access, &layer_masks,
267-
sizeof(layer_masks)));
239+
KUNIT_EXPECT_EQ(test, 4, get_denied_layer(&dom, &access, &masks));
268240
KUNIT_EXPECT_EQ(test, access, 0);
269241
}
270242

@@ -370,9 +342,6 @@ static bool is_valid_request(const struct landlock_request *const request)
370342
return false;
371343
}
372344

373-
if (WARN_ON_ONCE(!!request->layer_masks ^ !!request->layer_masks_size))
374-
return false;
375-
376345
if (request->deny_masks) {
377346
if (WARN_ON_ONCE(!request->all_existing_optional_access))
378347
return false;
@@ -406,12 +375,12 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
406375
if (missing) {
407376
/* Gets the nearest domain that denies the request. */
408377
if (request->layer_masks) {
409-
youngest_layer = get_denied_layer(
410-
subject->domain, &missing, request->layer_masks,
411-
request->layer_masks_size);
378+
youngest_layer = get_denied_layer(subject->domain,
379+
&missing,
380+
request->layer_masks);
412381
} else {
413382
youngest_layer = get_layer_from_deny_masks(
414-
&missing, request->all_existing_optional_access,
383+
&missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
415384
request->deny_masks);
416385
}
417386
youngest_denied =

security/landlock/audit.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ struct landlock_request {
4343
access_mask_t access;
4444

4545
/* Required fields for requests with layer masks. */
46-
const layer_mask_t (*layer_masks)[];
47-
size_t layer_masks_size;
46+
const struct layer_access_masks *layer_masks;
4847

4948
/* Required fields for requests with deny masks. */
5049
const access_mask_t all_existing_optional_access;

security/landlock/domain.c

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -182,32 +182,36 @@ static void test_get_layer_deny_mask(struct kunit *const test)
182182
deny_masks_t
183183
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
184184
const access_mask_t optional_access,
185-
const layer_mask_t (*const layer_masks)[],
186-
const size_t layer_masks_size)
185+
const struct layer_access_masks *const masks)
187186
{
188187
const unsigned long access_opt = optional_access;
189188
unsigned long access_bit;
190189
deny_masks_t deny_masks = 0;
190+
access_mask_t all_denied = 0;
191191

192192
/* This may require change with new object types. */
193-
WARN_ON_ONCE(access_opt !=
194-
(optional_access & all_existing_optional_access));
193+
WARN_ON_ONCE(!access_mask_subset(optional_access,
194+
all_existing_optional_access));
195195

196-
if (WARN_ON_ONCE(!layer_masks))
196+
if (WARN_ON_ONCE(!masks))
197197
return 0;
198198

199199
if (WARN_ON_ONCE(!access_opt))
200200
return 0;
201201

202-
for_each_set_bit(access_bit, &access_opt, layer_masks_size) {
203-
const layer_mask_t mask = (*layer_masks)[access_bit];
202+
for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) {
203+
const access_mask_t denied = masks->access[i] & optional_access;
204+
const unsigned long newly_denied = denied & ~all_denied;
204205

205-
if (!mask)
206+
if (!newly_denied)
206207
continue;
207208

208-
/* __fls(1) == 0 */
209-
deny_masks |= get_layer_deny_mask(all_existing_optional_access,
210-
access_bit, __fls(mask));
209+
for_each_set_bit(access_bit, &newly_denied,
210+
8 * sizeof(access_mask_t)) {
211+
deny_masks |= get_layer_deny_mask(
212+
all_existing_optional_access, access_bit, i);
213+
}
214+
all_denied |= denied;
211215
}
212216
return deny_masks;
213217
}
@@ -216,28 +220,28 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
216220

217221
static void test_landlock_get_deny_masks(struct kunit *const test)
218222
{
219-
const layer_mask_t layers1[BITS_PER_TYPE(access_mask_t)] = {
220-
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0) |
221-
BIT_ULL(9),
222-
[BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = BIT_ULL(1),
223-
[BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = BIT_ULL(2) |
224-
BIT_ULL(0),
223+
const struct layer_access_masks layers1 = {
224+
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
225+
LANDLOCK_ACCESS_FS_IOCTL_DEV,
226+
.access[1] = LANDLOCK_ACCESS_FS_TRUNCATE,
227+
.access[2] = LANDLOCK_ACCESS_FS_IOCTL_DEV,
228+
.access[9] = LANDLOCK_ACCESS_FS_EXECUTE,
225229
};
226230

227231
KUNIT_EXPECT_EQ(test, 0x1,
228232
landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
229233
LANDLOCK_ACCESS_FS_TRUNCATE,
230-
&layers1, ARRAY_SIZE(layers1)));
234+
&layers1));
231235
KUNIT_EXPECT_EQ(test, 0x20,
232236
landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
233237
LANDLOCK_ACCESS_FS_IOCTL_DEV,
234-
&layers1, ARRAY_SIZE(layers1)));
238+
&layers1));
235239
KUNIT_EXPECT_EQ(
236240
test, 0x21,
237241
landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL,
238242
LANDLOCK_ACCESS_FS_TRUNCATE |
239243
LANDLOCK_ACCESS_FS_IOCTL_DEV,
240-
&layers1, ARRAY_SIZE(layers1)));
244+
&layers1));
241245
}
242246

243247
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */

security/landlock/domain.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,7 @@ struct landlock_hierarchy {
122122
deny_masks_t
123123
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
124124
const access_mask_t optional_access,
125-
const layer_mask_t (*const layer_masks)[],
126-
size_t layer_masks_size);
125+
const struct layer_access_masks *const masks);
127126

128127
int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy);
129128

0 commit comments

Comments
 (0)