On Wed, Oct 7, 2026 at 9:28 AM Abel Vesa <[email protected]> wrote:
>
> 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
Thanks for the patch.
Would you consider this a bug in clang, or an error in how the
__counted_by attribute is applied?
If the former, then a bug report against upstream llvm-project/ and a
link to that bug report would be appreciated. Then Bill can take a
look.
Or if this was a known issue specific to clang 20, then we should have
a link to the fix (or consider bumping counted_by support to the
compiler version that is bug free).
> 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]>
>
--
Thanks,
~Nick Desaulniers