With Clang 20 and CONFIG_FORTIFY_SOURCE, stacking Landlock domains can
trigger a fortify panic in the handled_masks copy in inherit_ruleset():
__fortify_panic
landlock_merge_ruleset
__arm64_sys_landlock_restrict_self
Clang miscalculates the location of the __counted_by counter in the
anonymous structure nested inside the domain's union. In an arm64 build,
__builtin_dynamic_object_size(domain->handled_masks, 1) reads offset 40
(the first handled_masks entry) instead of offset 36 (num_layers).
Because the destination domain is zero-initialized and its masks have not
yet been populated, FORTIFY sees a zero-sized destination and rejects the
copy of the parent's layers.
Move num_layers and handled_masks to the top level of landlock_domain so
that Clang uses the correct counter.
Assisted-by: LLM
Fixes: bd3a19800dd1 ("landlock: Add counted_by in landlock_domain")
Reported-by: Konrad Dybcio <[email protected]>
Co-developed-by: Konrad Dybcio <[email protected]>
Signed-off-by: Konrad Dybcio <[email protected]>
Signed-off-by: Abel Vesa <[email protected]>
---
Move the domain layer counter and flexible array out of the anonymous
union to work around Clang 20 reading the wrong counter for __counted_by.
This fixes the FORTIFY panic when copying the parent domain's layer stack.
Based on next-20260930. Validated arm64 Landlock object builds with
Clang 20 and GCC 14, with CONFIG_FORTIFY_SOURCE enabled, and checked the
counter load in Clang-generated code. Not boot-tested.
---
security/landlock/domain.h | 48 +++++++++++++++++++---------------------------
1 file changed, 20 insertions(+), 28 deletions(-)
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index caa3d19d2c43..5e65ab004f62 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -216,37 +216,29 @@ struct landlock_domain {
/**
* @work_free: Enables to free a domain within a lockless
* section. This is only used by landlock_put_domain_deferred()
- * when @usage reaches zero. The fields @usage, @num_layers and
- * @handled_masks are then unused.
+ * when @usage reaches zero. The field @usage is then unused.
*/
struct work_struct work_free;
- struct {
- /**
- * @usage: Number of credentials referencing this
- * domain.
- */
- refcount_t usage;
- /**
- * @num_layers: Number of layers that are used in this
- * domain. This enables to check that all the layers
- * allow an access request.
- */
- u32 num_layers;
- /**
- * @handled_masks: Contains the subset of filesystem and
- * network actions that are restricted by a domain. A
- * domain saves all layers of merged rulesets in a stack
- * (FAM), starting from the first layer to the last one.
- * These layers are used when merging rulesets, for user
- * space backward compatibility (i.e. future-proof), and
- * to properly handle merged rulesets without
- * overlapping access rights. These layers are set once
- * and never changed for the lifetime of the domain.
- */
- struct access_masks
- handled_masks[] __counted_by(num_layers);
- };
+ /**
+ * @usage: Number of credentials referencing this domain.
+ */
+ refcount_t usage;
};
+ /**
+ * @num_layers: Number of layers that are used in this domain. This
+ * enables to check that all the layers allow an access request.
+ */
+ u32 num_layers;
+ /**
+ * @handled_masks: Contains the subset of filesystem and network actions
+ * that are restricted by a domain. A domain saves all layers of merged
+ * rulesets in a stack (FAM), starting from the first layer to the last
+ * one. These layers are used when merging rulesets, for user space
+ * backward compatibility (i.e. future-proof), and to properly handle
+ * merged rulesets without overlapping access rights. These layers are
+ * set once and never changed for the lifetime of the domain.
+ */
+ struct access_masks handled_masks[] __counted_by(num_layers);
};
static inline access_mask_t
---
base-commit: 6c2cb8b8b843d216ab549b678a0d8831c43153e0
change-id: 20261007-b4-landlock-fix-domain-fortify-panic-510367f8cfe3
Best regards,
--
Abel Vesa <[email protected]>