From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yw1-f177.google.com (mail-yw1-f177.google.com [209.85.128.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BF9923C0621 for ; Wed, 9 Sep 2026 19:37:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982668; cv=none; b=AeFqPBsnKhjcyLoYW+v7xXOb2ImULohC1PPBtCzlsefKk1YjzpJZ3WPobZ9RJeY3vPtcM1aYcZwHkCLmlz4FnST6iTtzgBuYiSrkx+X5njo0dliUJXqpTuPeTgBhzA9W1ven0nlYCAC7io7UkqJwPqkLW0gSIqKYSKT7YwzFxiI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982668; c=relaxed/simple; bh=UJsvgSwI/yDoGvCnBHSm45ONH9EJW/+m6j0M3SFXO1I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ffHkZVPxcaHMzaGXyzwRl88PGsarpbxyxQFO/Hsn1niP/4BrvoeW8vAJfXzM3pRUnGcN9p622FNK4jVgi2KbEBsUoXLAeqRTlutq2H/0zJTaP7WitRi3XAL/rRvAKrnNZTBigK8qrVRukAMwTdonLNC3wUJr+15+JmvDbacIgss= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=HgN+MOSs; arc=none smtp.client-ip=209.85.128.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="HgN+MOSs" Received: by mail-yw1-f177.google.com with SMTP id 00721157ae682-87005a0e052so77760177b3.2 for ; Wed, 09 Sep 2026 12:37:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982665; x=1789587465; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=bDAYkmuiKuVgFzeoHlRDXquGRrGJTz+CZkotRusPofE=; b=HgN+MOSsbIV2uR0LhkUrMnE8a6nVaExxLy+8dJ+C0zLGgJQyqIMymSImp/nz7obWlD Ev7OrSq34nxtrYl5/aQCGVPugb33ERiQ3K2SrgIHOkcs0mSC/ojZwJpueNevlTTW1dHr bfO4rFX3sQn6Hw7mPURe0UV6jY1lC45B6bEJuwTSBySeNrUIqLw4hAsG4hSZTRNWx1VX B6WoQ1khM3P9xDisOTmtApbekwjwHdmGzTi+qplWuClXLsuPsQvI9OwBo6ntEUWY6eoA 7WTfyRsVLJD5RoJKtwZ3Pv5bWQ029eEDC474LEfcGw+OVzmfqgXvGojoY89iwPPfIWz2 clWA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982665; x=1789587465; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=bDAYkmuiKuVgFzeoHlRDXquGRrGJTz+CZkotRusPofE=; b=qrPgbs6W/XWWt0BLDNFnD4J2K3P6JJiDDc/YIq/okcfqFM9w6/fSoaefklH+xzGt5O aq9tEJ42u7ADp/V3nIo0ryMwNPIMQWpkfk5Wip/ghPhRAE0HFIik57pd4vgJXE8i3aRi rmCVQLkyUmyPkvHK3YbjG+f5YGmPG9xaRH3Em5zf6djFB8X4lVYTWioBE3lPR7qUsjSg pvibpoKYwwqbjcmssr2cX8tj12j1ZL+Ak2QrrSEA0I/Et7KfI9JDmnb350sLPvFNNxu7 RzzTxiMWfj6dTdUyuwNUAq7vRNdijq37IF02S0nAWutE1lfKBNAByUdGgiDxwEFdFcSa gapA== X-Forwarded-Encrypted: i=1; AKwUvBxmx+tHmJFkRIF7kfIV35QEvk+L2n6uMOTD99gtUG5hmmdk/uWiL2EcjWeLPoaZBmq5rjpLNQRJNoATZXY=@vger.kernel.org X-Gm-Message-State: AFuF++lWePmPRxqvhtO6eH/bVE9a1uOWQwSURkMpZFth/WaR5ZFXpiaW LxYJTnrgVCJWvdc2S7+c+abt+rEYUhagvTGua9cH254PrsSoPdtH0Fbz X-Gm-Gg: AYBFou2A2T+npenXS6AnTe3IS5mACxvPc+o5tBiV23953TTSB3n54tKplhYqKwSAmur gjVLiDPeazGpG0LkCC8xRbFPueoUOsLhzW9ktbHefvWDGEU8YEBaHdIH2skod4u6DBhXXavRqfO V7K9hqML7fiIpIZxAW68lY8opokl6c+mNFwjzRrPm5YsgliIyUpGgDioVWEltYgHl+rYYcmv+Lp 6s6+CEjJgMlgRdIpS5L1eONKFHBivEpjZm8bBLCe5DPbUdNFiOLr87dggnnt8pURxVylI8LAhgB HTfyuxRxxFgRa3mMcPUlqd92lY69uLAsF1hjh3CY6BDectET2fRAjN40Zt3uN1B8cSe47qWF3qx IWjhLIW0ICErfuPyVgYeoNStFpumcUekv+067i/5+7DiCeZ73Z/Iet0Sxgz7yA+Z3ppIL5NPYhP nLgMkXFLlV8MOC05QrhxzGXvGT04F9an8DzATzw68Y/wsCvZgnDGo3QQaypRofoWKStprB+CSeR lHa9fTo0+0/xWempAcXSaRVAVUQ8FlsxPIUnyvDg3M= X-Received: by 2002:a05:690c:6a01:b0:81e:abe2:9a3b with SMTP id 00721157ae682-8712314032cmr152621237b3.10.1788982660585; Wed, 09 Sep 2026 12:37:40 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.37.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:37:40 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 01/15] lsm: Add the LSM policy object lifetime hooks Date: Wed, 9 Sep 2026 15:37:04 -0400 Message-ID: <20260909193719.518517-2-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add struct lsm_policy_object, the identity an LSM embeds in a policy object it shares with BPF programs, and the three hooks managing such an object's lifetime: policy_object_from_fd(fd, &object) policy_object_get(object) policy_object_put(object) The object records the owning LSM's LSM_ID_* value. The BPF kfuncs built on these hooks dispatch each call on an object to the one LSM matching its lsmid, which resolves the containing object with container_of(); the framework never interprets an object beyond its lsmid. The type field discriminates between the owning LSM's own policy object kinds and is private to it, with 0 reserved as "unset" so a zeroed, untagged object fails every type check. from_fd has no object to route by: the fd refers to a file set up through the owning LSM's own userspace interface, so the fd itself identifies its LSM. The framework offers the fd to every implementation in turn; an LSM declines a fd that is not one of its policy objects with -EOPNOTSUPP, and any other error is a definitive translation failure. The hooks back referenced BPF kptrs, which imposes the same lifetime contract on every implementation: from_fd returns a reference on a live object, get acquires with inc-not-zero semantics and fails with -ENOENT once the count dropped to zero, put may be called from contexts that cannot sleep (BPF drives it from map destructors), and the containing object is freed only after an RCU grace period, as programs load policy object kptrs from maps under RCU and may examine an object concurrently with its last put. The hooks are excluded from the "bpf" LSM's attachment points. The object-routed hooks are unreachable there, as LSM_ID_BPF policy objects cannot exist; for from_fd, whose walk visits every implementation, a BPF program cannot fill the object out parameter, so an attachment returning 0 would hand the caller an uninitialized pointer. Cc: Paul Moore Cc: Casey Schaufler Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. include/linux/lsm_hook_defs.h | 4 ++++ include/linux/security.h | 11 +++++++++++ kernel/bpf/bpf_lsm.c | 3 +++ 3 files changed, 18 insertions(+) diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 65c9609ec207..d7684407737a 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -452,6 +452,10 @@ LSM_HOOK(int, 0, bpf_token_create, struct bpf_token *t= oken, union bpf_attr *attr LSM_HOOK(void, LSM_RET_VOID, bpf_token_free, struct bpf_token *token) LSM_HOOK(int, 0, bpf_token_cmd, const struct bpf_token *token, enum bpf_cm= d cmd) LSM_HOOK(int, 0, bpf_token_capable, const struct bpf_token *token, int cap) +LSM_HOOK(int, -EOPNOTSUPP, policy_object_from_fd, int fd, + struct lsm_policy_object **object) +LSM_HOOK(int, -EOPNOTSUPP, policy_object_get, struct lsm_policy_object *ob= ject) +LSM_HOOK(void, LSM_RET_VOID, policy_object_put, struct lsm_policy_object *= object) #endif /* CONFIG_BPF_SYSCALL */ =20 LSM_HOOK(int, 0, locked_down, enum lockdown_reason what) diff --git a/include/linux/security.h b/include/linux/security.h index 153e9043058f..5e423bea080e 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -168,6 +168,17 @@ struct lsm_prop { struct lsm_prop_bpf bpf; }; =20 +/* + * Identity of a policy object an LSM shares with BPF programs, + * embedded in the LSM's own object. @lsmid identifies the owning + * LSM; @type discriminates that LSM's policy object types, with 0 + * reserved as "unset". + */ +struct lsm_policy_object { + u64 lsmid; + u32 type; +}; + extern const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1]; =20 /* These functions are in security/commoncap.c */ diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c index 82c5988417a0..f762cac6838b 100644 --- a/kernel/bpf/bpf_lsm.c +++ b/kernel/bpf/bpf_lsm.c @@ -56,6 +56,9 @@ BTF_ID(func, bpf_lsm_xfrm_decode_session) #endif BTF_ID(func, bpf_lsm_ismaclabel) BTF_ID(func, bpf_lsm_file_alloc_security) +BTF_ID(func, bpf_lsm_policy_object_from_fd) +BTF_ID(func, bpf_lsm_policy_object_get) +BTF_ID(func, bpf_lsm_policy_object_put) BTF_SET_END(bpf_lsm_disabled_hooks) =20 /* List of LSM hooks that should operate on 'current' cgroup regardless --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yw1-f176.google.com (mail-yw1-f176.google.com [209.85.128.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 293513C10A4 for ; Wed, 9 Sep 2026 19:37:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982666; cv=none; b=WPWpHVMv+mcDplCffu7jTOitD+jzFzlsELUYjfGhamnKv9PEQ7TuGsfl8ExL0sQw29dNyFryv0ykq/Qy+JeH2DdChCG1uDc+RptR8DxWH9kHi1e1oaSfRfAjPCzkhp7HuS8+V8vaQj4HcVatmLwNtktL5bDUeYKDkYfhKmTQPlc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982666; c=relaxed/simple; bh=1LzvLV16xeBAkstMGb/MXqY0fnWj9soFnay87GQLE4I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lUbUKX9EmjsUA6seHnKZWfT8SagjDJWHrGxnK1TVvFbWpL5MT861Xp4xd5nafCJNAJ0vuOONu0pWGHYVOUbLnd6xvyZAe+B9qhz43s6SWbBDlBdjwz0Y8AjEb2nFbfwP0bWoXf0az+ciJ9UJc37+UHOyTV7mjj1lrBGze8lP9xI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=JQUApE5n; arc=none smtp.client-ip=209.85.128.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="JQUApE5n" Received: by mail-yw1-f176.google.com with SMTP id 00721157ae682-81ecf499af9so89482357b3.1 for ; Wed, 09 Sep 2026 12:37:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982664; x=1789587464; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=gtEYMRE/KFKlT1/9bXmERUsL0M9lvoNkItLmndtSgzs=; b=JQUApE5n99dyerryUAtbX4T3wFcxmhOhMp/XL6CtqilC1uucsGziGBY91J92vSLZYN IrH3V72+1yBLHLbRpaR2zVW4nPnAhnTz7NAd0Tqb/e3dESNrpkfXaSHk2PeuOKG1JbN0 qKMO8FARWnzIvT0LLqBtlYlEDnHxUKlNzhlrF93aZnh5AzfTJzENv+jHeMxbAKckSiCt B3TmVeeSZHEtWYwdqgslVE30woIUNnuhIP5cYTZHKaEH/4Mke+OO0ZiM+9iOVvkWb9tO aX7valwJl06aescUImrzZC7zlSFMEUR9lE9ZtsDs3EYv/lZiHWoTKbVrJn2F/mKBS5Wt iIOA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982664; x=1789587464; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=gtEYMRE/KFKlT1/9bXmERUsL0M9lvoNkItLmndtSgzs=; b=heQbOEDyOoPceCl7eKkrI+wnSVVZFAgTRGWf2UU6SrQLVNYexrUbzxeNYX2aAeDoMU OlU8c5jz456aT4h0Y8Eu1iD9NvKSwRKVR1HDa4Slebcj5YyGhjSaLYwZkclJGHMA86Bq rn4zqtFwbRCEGhqHEX5KXcUcJyMxp8WYux4ssSZZ2CtDRVQLtJtn+OXuAV4oy+zvDIYo kXauzNsosfEaRdxPJf27iMkbyvCfAdYAhrwFN3HQp/bcE1u0OlCnlTrIpNTrZFvqMEnp Xt9obPfBswMLptvL4ROu2Vrb5IRSxkdteeHx5jJ49MwwwVzln2yug3OsqVrgJyaoP+R9 n25g== X-Forwarded-Encrypted: i=1; AKwUvBwHUPVWJUdqyC8f/WFOWA/68179agZyTlOP9yDd2LdzivUpsvWqZWYnBMI/bTwXVkDFnO1yGq36kef6jN8=@vger.kernel.org X-Gm-Message-State: AFuF++lFz0lcypOlwRs+zc3q8C7V84iyVIbN7CbfFM2AdNrk8RqzZe48 vEX6QzyvB11itd8fLNILn8O6i4yrVBKLohsd/jBW1eGuHA60s9qhfuLQ X-Gm-Gg: AYBFou1wPTGvAzoMk4ANxU1ZYIsx/E4ldAtyz2movyX+pkgKTOVonjbdYEDBZo/ZKhz fPoEPE0Xy7/nGTW5pNUz76tDqg/hk2GwxlsBa2qW9RH5P7KDq9C79JZjYqDZgdFH5N9ggaUEEVz ljxie+Jta5+qb+3dvCDPKQs3wss+JhmGPM3oOAnUy+8sRBcBwO9gPlMwxwLBnRCIJEOnUDwr2SY 2wTqmbVU23GKEZMr+WfPjEE3FxZF8nVU+57BQtvVxQSHUVq3L/6cN0kSqxyLr/FCM/DEVwPE866 DItuvKdL2MQJEpiOwaKToQGDMuDUVnUBnpmerlaFg6X3+ThaXctkwICyiBaDIpTKNbkhvvfLk2U xwg6KhzUSkMzIAlgv5BeoRkdXPZSllM3zntoajeMVvhZrdfdgFAwGGTX5WltUDEWrKEZd0zuebl iV9ZxMZ7gaLFFw3EvBY5imR49nqIILZamI5lSfhzo/uCOxYifnopBkanDVzgW79OVvl//oh6iFW 7z2whZ83lBXXhGRYxriU/OqAuzsx7+b X-Received: by 2002:a05:690c:c501:b0:871:a319:3ee8 with SMTP id 00721157ae682-871a31948c5mr132151937b3.10.1788982663710; Wed, 09 Sep 2026 12:37:43 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.37.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:37:43 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 02/15] lsm: Add the bprm_apply_policy_object LSM hook Date: Wed, 9 Sep 2026 15:37:05 -0400 Message-ID: <20260909193719.518517-3-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add the first policy object operation, applying a policy to the credentials prepared for an execution: bprm_apply_policy_object(bprm, object, flags) The hook is only called between the preparation and the commitment of the bprm's credentials, i.e. from a bprm_creds_for_exec() or bprm_creds_from_file() context, where the executed task can still be arranged to start confined by the policy. How the policy composes with restrictions the credentials already carry, and the meaning of @flags, are defined by the implementing LSM, which must reject unsupported flags with -EINVAL. An LSM with no notion of applying a policy object to an execution does not implement the hook, and the calling kfunc fails with -EOPNOTSUPP. Like the lifetime hooks, this hook is excluded from the "bpf" LSM's attachment points, as the targeted dispatch makes an attachment there unreachable. Cc: Paul Moore Cc: Casey Schaufler Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. include/linux/lsm_hook_defs.h | 2 ++ kernel/bpf/bpf_lsm.c | 1 + 2 files changed, 3 insertions(+) diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index d7684407737a..ddb15bea383e 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -452,6 +452,8 @@ LSM_HOOK(int, 0, bpf_token_create, struct bpf_token *to= ken, union bpf_attr *attr LSM_HOOK(void, LSM_RET_VOID, bpf_token_free, struct bpf_token *token) LSM_HOOK(int, 0, bpf_token_cmd, const struct bpf_token *token, enum bpf_cm= d cmd) LSM_HOOK(int, 0, bpf_token_capable, const struct bpf_token *token, int cap) +LSM_HOOK(int, -EOPNOTSUPP, bprm_apply_policy_object, struct linux_binprm *= bprm, + struct lsm_policy_object *object, u32 flags) LSM_HOOK(int, -EOPNOTSUPP, policy_object_from_fd, int fd, struct lsm_policy_object **object) LSM_HOOK(int, -EOPNOTSUPP, policy_object_get, struct lsm_policy_object *ob= ject) diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c index f762cac6838b..f3de70511c0f 100644 --- a/kernel/bpf/bpf_lsm.c +++ b/kernel/bpf/bpf_lsm.c @@ -56,6 +56,7 @@ BTF_ID(func, bpf_lsm_xfrm_decode_session) #endif BTF_ID(func, bpf_lsm_ismaclabel) BTF_ID(func, bpf_lsm_file_alloc_security) +BTF_ID(func, bpf_lsm_bprm_apply_policy_object) BTF_ID(func, bpf_lsm_policy_object_from_fd) BTF_ID(func, bpf_lsm_policy_object_get) BTF_ID(func, bpf_lsm_policy_object_put) --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yx2-f12.google.com (mail-yx2-f12.google.com [74.125.224.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AA3D43C3443 for ; Wed, 9 Sep 2026 19:37:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.140 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982671; cv=none; b=Dv/H+T7NWigNTFNEhhPW8kLGqeFCuZtD4wFFl5dvFodmQKTNBTLUp8Q7rq2yKy7dnKtdRDtTLG+2SCMKSMJ5F8KvA9UX24tG0mUGV98ffUuNuiTF0/OXn4q8sP2gcjAo2QBzKsLbfA0qQz1C0chTJdTecEb2SK+N5J8v4uEA24U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982671; c=relaxed/simple; bh=9ahnDuIzIlnNHViEcr2anfgH+TEO7ySvpOYjKqJILUY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BRlmqTMHCn/2MCdQ11YwVu9aH+cwWF7epcJSmoZr3T8lvRUbVtdKlvaDqBa1S2O0mveolqDYNOGLeCAl8thP9h68xwk11g5iz9d3afLleWPJDN/DdPvaHpoRigZyrvjsrO7Q8SpqtjyXc3A9lSTDWzq62KPUMQEhlOXWFjXIyhs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=M2pOVb/r; arc=none smtp.client-ip=74.125.224.140 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="M2pOVb/r" Received: by mail-yx2-f12.google.com with SMTP id 00721157ae682-85d46e4cdcaso14468577b3.3 for ; Wed, 09 Sep 2026 12:37:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982667; x=1789587467; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=zGIeBhiHsvfHjC9J6yImwRdInjaib4hfso2CvsNy6M8=; b=M2pOVb/rAfpf8c9Bwl119L9N7ddnxvT5Lbk9i6+QWTaEubnmJWawrFUNA50Uo7qUPV /aIeqtSlCiMonnO5wF9h7AWiL9dEk7o3Y5Y8rRbdoQxxavkeE5CGX5k+ttq3Q/CGH+SV Ku1ktKHEp7r1wvAJ+K++lW3QeNhT2WaEQ5bk4KOuPpNbq3GvWbN41+M4rkBjEYwZEWkJ 0UVumj99TnBBTgLSYkD/6RiJEMew+Lr2sRmDSQZl/8SVJmqfD67f0b6BOiyfgfCyHFwu p93lA954WmeNCqAVjLy4zJI0iEnJ2xpIq6gSEVw4v2G3Zj/r1IwR2+FEw+FRjFsRIM/y 9mNw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982667; x=1789587467; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=zGIeBhiHsvfHjC9J6yImwRdInjaib4hfso2CvsNy6M8=; b=YPRat1RVN48j2IOQPcDqW3ZWT9GTrZVqtrXNzIaB4yWw5bMhDacZ2XIaDOZEAmxeti 44YNyvfalMA1nirFEnShN8DradMKMui5lOg6RkH1P/EKNIsT+SaC5nNpNjLh/3KoSc6x BFMYJWbIBDHFBpY9r7RgU6i1pqpZ12oJMKW4ILlaQHWfUvGifVPc+c1lmZOiiNf+KEW5 hxf6Wo4c30DE6pvqBtavtKGQeYInUbPTvpnkc7e/s1ickggq5doEdSdMvqJE+7lVLamd m3nvIcMSbd04EjFu4TYPMXYP8+GaNmZ4em/o2jPkx816Xehy0Zgg+I+8UfLuPj9UmYBp G7+Q== X-Forwarded-Encrypted: i=1; AKwUvBy9QzZc0gQRdSBlJrWSS/BGZChXc2l4b2xwWuQef8uSn2GB2hnEpW/YMXOazdtlWXFdnryOmvx9wuPr4fw=@vger.kernel.org X-Gm-Message-State: AFuF++nM3SPulgXw9MYq+LU2IX5nDy9KNU7bXcmbJertvA1NTCZANZ+R bHs27gzu4ALKLIjuefL/s3A156+ns3zhg4ibAu5VN1JXC7838VX3XQc9 X-Gm-Gg: AYBFou2APcUbCNBV5RTReWfDshZmo41yOCbC71cuSqdWE+4aTN9iW0r5wyNxInneMOr 2D7FcoDGV9dEG7KIZf2gPD8UmIkgLSn48yD/Gwzc7IvRV0tRWd9WNVaSToxG2jlacLf/WbLZZBE SkhEJ2D11GP6ikTj+WhLSzEuf9Vzoqy6Esf3WHt1fWG2qeLLdUdKERyC5BOh+vkWgNTF4Ms58pN 9/2NSHZ7D41Sap1Gb078PDeEQZiK1FjsKf+fg0aBry4rzag4PCLmSvOsPRTHax7p4kmfvPydejz Das9qOkd9uGb0AE96vvs3yaLKpuNIdvAk0VBxsfO8Lrq+hsZOB/sl1H2Wn+W4dvK/zKwXWZZQpJ HxMxc/cRw/0gYArnf4+famRa7XyIpfsV9t5/7l2ZAE9kkmyoXs0cueSycitXHyPS1+bck2FrlPA VnXwmxaUoMAsK2MVKAIUYuPY2iYZLJ+lL6MvbnXShTmJnCD4Dk3HD1UNZgY4KMNmB7k90TJfT2L xC4v48mtfhv5G/19SztxLYYsL7lIE/2 X-Received: by 2002:a05:690c:7091:b0:872:1e2e:b496 with SMTP id 00721157ae682-87f29821932mr39000317b3.31.1788982667401; Wed, 09 Sep 2026 12:37:47 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.37.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:37:47 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 03/15] lsm: Move the lsm_for_each_hook() macro to security/lsm.h Date: Wed, 9 Sep 2026 15:37:06 -0400 Message-ID: <20260909193719.518517-4-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Move the lsm_for_each_hook() iterator from security/security.c to security/lsm.h, verbatim: a following commit adds a user outside security.c, the file implementing the LSM policy object kfuncs. No functional change. Cc: Paul Moore Cc: Casey Schaufler Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. security/lsm.h | 6 ++++++ security/security.c | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/security/lsm.h b/security/lsm.h index 32f808ad4335..264ae63290a8 100644 --- a/security/lsm.h +++ b/security/lsm.h @@ -24,6 +24,12 @@ extern bool lsm_debug; extern unsigned int lsm_active_cnt; extern const struct lsm_id *lsm_idlist[]; =20 +/* Iterate over the active implementations of a given hook */ +#define lsm_for_each_hook(scall, NAME) \ + for (scall =3D static_calls_table.NAME; \ + scall - static_calls_table.NAME < MAX_LSM_COUNT; scall++) \ + if (static_key_enabled(&scall->active->key)) + /* LSM blob configuration */ extern struct lsm_blob_sizes blob_sizes; =20 diff --git a/security/security.c b/security/security.c index 2ee276ab15c5..74f98ef8025e 100644 --- a/security/security.c +++ b/security/security.c @@ -495,11 +495,6 @@ OUT: \ RC; \ }) =20 -#define lsm_for_each_hook(scall, NAME) \ - for (scall =3D static_calls_table.NAME; \ - scall - static_calls_table.NAME < MAX_LSM_COUNT; scall++) \ - if (static_key_enabled(&scall->active->key)) - /* Security operations */ =20 /** --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yx2-f12.google.com (mail-yx2-f12.google.com [74.125.224.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E7B683C1404 for ; Wed, 9 Sep 2026 19:37:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.140 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982676; cv=none; b=n6O0ij3sBQEchahOSZfusTA17n+60JOiJxpeJp1qK9nDeo7HIVelgHh0r0mgfYVG9BtYeaoZxTlqyODeYjQChHRmYxfBybDHzYsTG/FmiegopdIDfSZbbUlgoTiozc5zCyvPsdtxlbKz900cNzCpE2O9E3xESaHzolLEhKeVo20= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982676; c=relaxed/simple; bh=5JeaYKVSzKXAX/sgZsANqhlDhCRYedCYJj80Zm93r/Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=EI6X+A4T9WMe+s1WXmrEzs7z0fbUR9j6CELTnG37wYKlrSDbo6AWvw8cYdCnIG/2kONHg+GfjPkWyxWplpEAK7wXNkvKiOtmQCqYiUDDh5eOYvFM1ZAoPIArdJKOZD6JO6DqKx13bzdFnKV/bSGJyzTlIOXNZ1XgDNPUJY0kt2s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=OIId4AkZ; arc=none smtp.client-ip=74.125.224.140 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="OIId4AkZ" Received: by mail-yx2-f12.google.com with SMTP id 00721157ae682-85d46e4cdcaso14469697b3.3 for ; Wed, 09 Sep 2026 12:37:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982672; x=1789587472; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=F6UQVnWZ6sTXYVTmbh7VH9y6ww3yNiInSoSyLyLDAvU=; b=OIId4AkZstPKXJVDjd4u2DZy7RqEwTgJ3fDQGoGniYfF7jN70SB1tifbY/sLGHP/Gy FZyah/11agw+U3lH9fhANZC/0CyA7EMOk0UL/dGZxGWUB9MNBBhsiocrhjeOzCJ/qZHW oWc4sKx7mgtcfWLtTg43oqSLeZu2Pclz+BTruKttyXwFGc0XTW4KHCjz4OsOsph7jIVv JVLqdOZNu5RpPrntaLMdo9lCTCOSQbdDS+IR3fJB5ZDCUO1z5wny3RzDEkuB93DR93KO +jrVX7GDkgqLNsOPzpFKloqILgCLEeU9/hAngMM9h/pYDPkCYCI79A1F4Zg3W/j8RlKu nl1g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982672; x=1789587472; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=F6UQVnWZ6sTXYVTmbh7VH9y6ww3yNiInSoSyLyLDAvU=; b=ZlLG6AsgPPwmB4xjM+lqfi1zxTIJDUOoCrp7yptSIGB23SzmOgAUs04KOE8fXf8ezf fg4ALPz1Gxj5bWskfoWSKulATaC3POE8v5oLo+mPRGkwaWKxpPr8sUIRmgPc+PhAWmbu 5qvzBdNekuZwpWw2rl7QNAUBYes1/LvWFwit5cma7cpj+SDg5IXgMlsLHv0x3b+0ifRJ mgXSRccMIstP7DkB9rIqUlpWQZDv5q6BImdf2jlffZkcZop8MpPgrukQIGG+NiJ1NXBZ JvH5fJ4S1MYycQnBM7UH7kpW9SxrLU1TVAw5vOkZ6JhCMv3UyKhXZKTfkGykIZ7ovkdE 7vAg== X-Forwarded-Encrypted: i=1; AKwUvBxm7FqeZU4ZO+5+GlqyMkzrvjjOEKW6T/paeBV0x6X7ZtkFICv4pF2xqNyHwgWR2SUTM4QcJoCx3XSGnb0=@vger.kernel.org X-Gm-Message-State: AFuF++muwOZq2WoSW6QuTCRBeDdyrS1kz5S8scoHqpabw2CFzsOq1No0 waABUOhZGhmhMwCBKtg5wdmqYs6I9PTzWXZLw0eU1latrr1KUaa/YmoE X-Gm-Gg: AYBFou1h8+egcJuIN9y1JJ9htiepbFKoeS0LKak4vewf4hjlRrOI3nYdq+vyaGepNaZ yZwgdNZ5YDME23WW8FNVPG3OfX0mHEKvRLurjsSeXEj2GKu9+3Hwpiwdus2HF4ax5TqN2jgapaM SIHcBVM9uHUWBmbphKGhORf4E5rMm7V/SLhJc10hgHMCcFxnP4pCAo3Cu5EINTJniLfy0URxm92 4vGSJ5BZpfdjgRmJHRcugTQLI7BHXpTdebTwWZBFeAVYtV+7WmvyDk2zTiaLvFeK6HnHUq9geOR 8/f1R1cNFQUXETOK2ASw8eldrc/u5I/LkmJEBVliGy1GvCy/mjfDXNauTZckaQRyod64p8/Wkjr SK1OCFfhaxL2CJLF+jPBLixt+/MU/9vPngk16f37ucXf4uBKDG8bp/TmnmuV/xVf7qfmOK5aPeG 6n+xt5ZxebGQgfS/DuWR8gA1LGGJDibkakGH0A6Ik1KRIwsFX2hbtRvGD5HsB2+TjAQGHk/m/0R 91Z9GPjcWNdwfSguDJ7IYQQEUz3nbsW X-Received: by 2002:a05:690c:c6ce:20b0:816:b469:ccec with SMTP id 00721157ae682-87f245c27e6mr31944217b3.15.1788982672495; Wed, 09 Sep 2026 12:37:52 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.37.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:37:52 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 04/15] lsm: Add the bpf_lsm_policy_release kfunc and policy object destructor Date: Wed, 9 Sep 2026 15:37:07 -0400 Message-ID: <20260909193719.518517-5-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add security/bpf_lsm_kfuncs.c, the home of the kfuncs exposing LSM policy objects to BPF programs, with the first of them: bpf_lsm_policy_release(object) KF_RELEASE The kfuncs are the LSM framework's own BPF interface: there is no per-LSM kfunc and no intermediate security_*() layer. Each kfunc walks the matching hook's implementation list and calls the one registered by the LSM whose lsmid the policy object carries. Calling a kfunc for an LSM that is not active or has no policy object support fails at runtime rather than hiding the kfunc at verification time, so BPF program loading is independent of the boot-time LSM configuration. A policy object reference is meant to be handed over through a map kptr field, so also register a destructor for struct lsm_policy_object: map-held references are dropped on map teardown, possibly from a context that cannot sleep, which the policy_object_put() hook contract accounts for. For the same reason the kfunc is not KF_SLEEPABLE, and the filter adds no per-kfunc rule: releasing a reference must be allowed wherever one can be held. The filter itself is needed because BPF_PROG_TYPE_LSM and BPF_PROG_TYPE_SYSCALL, the two registered program types, share their kfunc lookup buckets with other program types. Cc: Paul Moore Cc: KP Singh Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. MAINTAINERS | 1 + security/Makefile | 2 +- security/bpf_lsm_kfuncs.c | 98 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 security/bpf_lsm_kfuncs.c diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb07770..ba816d7ee127 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5081,6 +5081,7 @@ F: kernel/bpf/bpf_lsm.c F: kernel/bpf/bpf_lsm_proto.c F: kernel/trace/bpf_trace.c F: security/bpf/ +F: security/bpf_lsm_kfuncs.c =20 BPF [SELFTESTS] (Test Runners & Infrastructure) M: Andrii Nakryiko diff --git a/security/Makefile b/security/Makefile index 4601230ba442..a9364ea9828b 100644 --- a/security/Makefile +++ b/security/Makefile @@ -23,7 +23,7 @@ obj-$(CONFIG_SECURITY_LOADPIN) +=3D loadpin/ obj-$(CONFIG_SECURITY_SAFESETID) +=3D safesetid/ obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) +=3D lockdown/ obj-$(CONFIG_CGROUPS) +=3D device_cgroup.o -obj-$(CONFIG_BPF_LSM) +=3D bpf/ +obj-$(CONFIG_BPF_LSM) +=3D bpf/ bpf_lsm_kfuncs.o obj-$(CONFIG_SECURITY_LANDLOCK) +=3D landlock/ obj-$(CONFIG_SECURITY_IPE) +=3D ipe/ =20 diff --git a/security/bpf_lsm_kfuncs.c b/security/bpf_lsm_kfuncs.c new file mode 100644 index 000000000000..e1190215d477 --- /dev/null +++ b/security/bpf_lsm_kfuncs.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* BPF kfuncs exposing LSM policy objects. */ + +#include +#include +#include +#include +#include +#include +#include + +#include "lsm.h" + +__bpf_kfunc_start_defs(); + +/** + * bpf_lsm_policy_release - Release a policy object reference + * @object: policy object to release + * + * Release an acquired reference on a policy object. + */ +__bpf_kfunc void bpf_lsm_policy_release(struct lsm_policy_object *object) +{ + struct lsm_static_call *scall; + + lsm_for_each_hook(scall, policy_object_put) { + if (scall->hl->lsmid->id !=3D object->lsmid) + continue; + scall->hl->hook.policy_object_put(object); + return; + } + /* A held reference implies the owning LSM implements the hook. */ + WARN_ON_ONCE(1); +} + +/* Destructor for referenced lsm_policy_object kptrs. */ +__bpf_kfunc void bpf_lsm_policy_release_dtor(void *object) +{ + bpf_lsm_policy_release(object); +} +CFI_NOSEAL(bpf_lsm_policy_release_dtor); + +__bpf_kfunc_end_defs(); + +BTF_KFUNCS_START(bpf_lsm_policy_kfunc_ids) +BTF_ID_FLAGS(func, bpf_lsm_policy_release, KF_RELEASE) +BTF_KFUNCS_END(bpf_lsm_policy_kfunc_ids) + +BTF_ID_LIST(bpf_lsm_policy_dtor_ids) +BTF_ID(struct, lsm_policy_object) +BTF_ID(func, bpf_lsm_policy_release_dtor) + +/* + * BPF_PROG_TYPE_LSM and BPF_PROG_TYPE_SYSCALL share their kfunc + * lookup buckets with other program types, so restricting the policy + * kfuncs requires a filter. + */ +static int bpf_lsm_policy_kfunc_filter(const struct bpf_prog *prog, + u32 kfunc_id) +{ + if (!btf_id_set8_contains(&bpf_lsm_policy_kfunc_ids, kfunc_id)) + return 0; + + switch (prog->type) { + case BPF_PROG_TYPE_SYSCALL: + case BPF_PROG_TYPE_LSM: + return 0; + default: + return -EACCES; + } +} + +static const struct btf_kfunc_id_set bpf_lsm_policy_kfunc_set =3D { + .owner =3D THIS_MODULE, + .set =3D &bpf_lsm_policy_kfunc_ids, + .filter =3D bpf_lsm_policy_kfunc_filter, +}; + +static int __init bpf_lsm_policy_kfunc_init(void) +{ + const struct btf_id_dtor_kfunc bpf_lsm_policy_dtors[] =3D { + { + .btf_id =3D bpf_lsm_policy_dtor_ids[0], + .kfunc_btf_id =3D bpf_lsm_policy_dtor_ids[1], + }, + }; + int ret; + + ret =3D register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, + &bpf_lsm_policy_kfunc_set); + ret =3D ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, + &bpf_lsm_policy_kfunc_set); + return ret ?: register_btf_id_dtor_kfuncs(bpf_lsm_policy_dtors, + ARRAY_SIZE(bpf_lsm_policy_dtors), + THIS_MODULE); +} +late_initcall(bpf_lsm_policy_kfunc_init); --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yw1-f177.google.com (mail-yw1-f177.google.com [209.85.128.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7C7273C585E for ; Wed, 9 Sep 2026 19:37:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982679; cv=none; b=jPZXYndSiqcXE2kVgqdg8sj9WOdDYzVJA0z/MNIiM5BKunXVOEx4ZNPAJP6g8mRpubgsevXciOWZ4qVhnEEGeF0f7X/U9o7r/MqlqnBpXyW6/7m+zUGFJ/1NCdGbwpZsitDI9e5X9+J+4ErtoWjUjuRenmGQxDWzXQWNfUdpUAc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982679; c=relaxed/simple; bh=/dxijAUwePg6/FhEMbyE1waXEgJ1mB/8UILjJnXwrrE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=REWBwyK1Y5tMyWPtTI38t8unq84MEUHcH7nh5gu1U5z9zaTzj8p3NHKIce3LE5HS6+WaHR3KIkVWRL/MiYdd5LyBVXltk0JKbmAcHVLWwW7OV4bC0mb83GRiTtSe9blQ+QCwFQwx+mNw76fxDTZiogJTY5BIQkRM81oOobLIjwU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=r+IHGWER; arc=none smtp.client-ip=209.85.128.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="r+IHGWER" Received: by mail-yw1-f177.google.com with SMTP id 00721157ae682-87f4e914a45so22212497b3.2 for ; Wed, 09 Sep 2026 12:37:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982676; x=1789587476; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=B0X6CzMR8wIDenHZSYS+S86AsXPtgp2BAuIVL5oI510=; b=r+IHGWERI82Em0oYYhWNrktbDSRcbZ3tTYrvh5ZXtVWGP5E8N7oNzwzGhsx6FhdgXo 7aeFrjoFQSptKmDgvRZ6kyZUq6DjCEFjpe+s7wXHGSt8teduQMt3XLDgM9tFnA6IhGbD pAg6HuIwADPLgF6le9JwKhBINTingozwaTs56WrSvgr+LcHRNRHSbgVwz/R+dyfH3ViD e+kc5Y7Di7UreSGAvRcYMGBlTLTA3uaPym8Vf3FudhMxAb752r9Hdz7yTn2GJHDEOuiz zu4UatWH2x4p19iDCd+Ymkz7ZKOa+uRAKCDZo+JbjuDe/cKGPxjxTGn4GnaOUvNnEb13 sNcQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982676; x=1789587476; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=B0X6CzMR8wIDenHZSYS+S86AsXPtgp2BAuIVL5oI510=; b=nSpP5IAyw8thRwN8dz7Fw6Pdbs5v+e70+wMo/pBR0TUFOhWmsJXWKP+g6ZPOPbTmoH z+g0l0WTZEyyi9SNm5JG/rNkxDg/POfBJE3H/gJL1lGsJNGk6K8TyIwkU5y0RCq/bZ1N 6flOEgZNYs8yedudkqu5iBDcTid+7B3tV6H5i/bd4rci7VU6RWGaIU3aGDNlfCfmFuqc SzdC8/QwmVwq3YBSNfjlQi3UXkMf2myFuNBLqsbQWv0XzbDVHXTIz4dK/0r56/z/ukNN RcpGtA9PVhykfpgiA6A/TvbW42PNLcBbKa3BVY1tvaf/aNPcTjfxt7iqOcADhT2W/0JW XJ/g== X-Forwarded-Encrypted: i=1; AKwUvBwHa8lpDEZZSANZPBC+H1XA4xrmfBsbR7eQYTPC4eS8nM9aShSRXNX2NmzWKw9+mH9lE3yWCKsfmE/X5VY=@vger.kernel.org X-Gm-Message-State: AFuF++l692S8GAV0cEvr/MqRsqBJ4hq3DA9EUXEub4dRpCvlqyEh79hd RLEu47a1Otbv/h2jz0LcdqzwxEc8pmJF+J2XvrzRil4M660O6E6D+Srw X-Gm-Gg: AYBFou3pQ6tLwK5K+QWqLEMAr1bHT56bOz9Lxv+Q7uT+vJw/FJCU2tkstkjgsjcvvgV bGQqS4l5uhplNJ35+/aHEcjUKy1xVLlsnoDMBSRskA7EFyYcb5gE43ZRd3ilMG3q7Wyl9R86luV bbIoBt5u/08ZePc1ZjOwYePWQTjXnvwIIt/iMB5KVZlFcv/o93BfMXPnYR4gRzIPrNHbhq35hg0 jsoBE7rthHV5kZr4FCX8BcN3xd5Ts0ltPHyEXqATN4gTTIfpQy6gBvd8jEjz38Vao2sFYxFXx1H RGd9uv4/SwS1pCFYlKAOF42IuW/vbB+c4aOaAvtqWizuCa8cWmtI3dVst4qyedBu++XDmVqmVDx JRP7rZKA19aMnBDmGjz5eXVNFFOrBrpaxzaTgfuOLdWGXQ1grH9cm6XHLHNQeHp7v1cPsF7vv3h RbCatT7V3zLIg+kFmfBqcuBjE/5pqPgbl11PPOePcn5OJ1cdFuhvuycBST1mA/RD9qxAbGl2jfl icYry5H0Zvi26B+WF3ivnFK/9p9qc89 X-Received: by 2002:a05:690c:e254:10b0:873:5ddf:d869 with SMTP id 00721157ae682-8735ddfdaaamr92262297b3.52.1788982676015; Wed, 09 Sep 2026 12:37:56 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.37.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:37:55 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 05/15] lsm: Add the bpf_lsm_policy_from_fd kfunc Date: Wed, 9 Sep 2026 15:37:08 -0400 Message-ID: <20260909193719.518517-6-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add the kfunc translating a file descriptor into a referenced policy object: bpf_lsm_policy_from_fd(fd, flags) KF_ACQUIRE|KF_RET_NULL|KF_SLEEPABLE No argument names an LSM: a policy object fd refers to a file set up through the owning LSM's own userspace interface so the fd itself identifies the LSM asked to translate it. The kfunc offers the fd to every policy_object_from_fd implementation in turn until one claims it. Following the convention of the lsm_*(2) syscalls, @flags belongs to the framework and is reserved: the kfunc returns NULL for @flags !=3D 0. A policy object fd is only meaningful in the fd table of the process that set the object up, while an LSM program runs in the context of the task it mediates, so the filter makes this kfunc exclusive to syscall programs (BPF_PROG_TYPE_SYSCALL), which run in the context of the task invoking them. The acquired object may be released with bpf_lsm_policy_release(). Cc: Paul Moore Cc: KP Singh Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. security/bpf_lsm_kfuncs.c | 53 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/security/bpf_lsm_kfuncs.c b/security/bpf_lsm_kfuncs.c index e1190215d477..988dcd6f4dd9 100644 --- a/security/bpf_lsm_kfuncs.c +++ b/security/bpf_lsm_kfuncs.c @@ -14,11 +14,50 @@ =20 __bpf_kfunc_start_defs(); =20 +/** + * bpf_lsm_policy_from_fd - Get an LSM policy object from a fd + * @fd: file descriptor referring to a policy object, resolved in the + * file descriptor table of the task running the program + * @flags: reserved for future use, must be 0 + * + * Translate @fd, as set up through the owning LSM's own userspace + * interface, into a referenced policy object. The fd identifies the + * LSM asked to translate it: each LSM recognizes its own fds and + * declines every other. Only syscall programs may call this kfunc: + * they run in the context of the task invoking them, where the fd is + * meaningful. The reference must be released with + * bpf_lsm_policy_release(). + * + * Return: A referenced policy object, or NULL if @flags is not 0, if + * no enabled LSM recognizes @fd as one of its policy objects, or if + * the recognizing LSM fails to translate it. + */ +__bpf_kfunc struct lsm_policy_object *bpf_lsm_policy_from_fd(int fd, u32 f= lags) +{ + struct lsm_static_call *scall; + struct lsm_policy_object *object; + int err; + + if (flags) + return NULL; + + lsm_for_each_hook(scall, policy_object_from_fd) { + err =3D scall->hl->hook.policy_object_from_fd(fd, &object); + if (err =3D=3D -EOPNOTSUPP) + /* Not this LSM's fd: let another claim it. */ + continue; + if (err) + return NULL; + return object; + } + return NULL; +} + /** * bpf_lsm_policy_release - Release a policy object reference * @object: policy object to release * - * Release an acquired reference on a policy object. + * Release a reference acquired with bpf_lsm_policy_from_fd(). */ __bpf_kfunc void bpf_lsm_policy_release(struct lsm_policy_object *object) { @@ -44,6 +83,8 @@ CFI_NOSEAL(bpf_lsm_policy_release_dtor); __bpf_kfunc_end_defs(); =20 BTF_KFUNCS_START(bpf_lsm_policy_kfunc_ids) +BTF_ID_FLAGS(func, bpf_lsm_policy_from_fd, + KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_lsm_policy_release, KF_RELEASE) BTF_KFUNCS_END(bpf_lsm_policy_kfunc_ids) =20 @@ -51,10 +92,14 @@ BTF_ID_LIST(bpf_lsm_policy_dtor_ids) BTF_ID(struct, lsm_policy_object) BTF_ID(func, bpf_lsm_policy_release_dtor) =20 +BTF_ID_LIST_SINGLE(bpf_lsm_policy_from_fd_ids, func, bpf_lsm_policy_from_f= d) + /* * BPF_PROG_TYPE_LSM and BPF_PROG_TYPE_SYSCALL share their kfunc * lookup buckets with other program types, so restricting the policy - * kfuncs requires a filter. + * kfuncs requires a filter. A policy object fd is only meaningful in + * the fd table of the task that set the object up: the fd kfunc is + * exclusive to syscall programs, which run in that task's context. */ static int bpf_lsm_policy_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id) @@ -64,7 +109,11 @@ static int bpf_lsm_policy_kfunc_filter(const struct bpf= _prog *prog, =20 switch (prog->type) { case BPF_PROG_TYPE_SYSCALL: + return 0; case BPF_PROG_TYPE_LSM: + if (kfunc_id =3D=3D bpf_lsm_policy_from_fd_ids[0]) + return -EACCES; + return 0; default: return -EACCES; --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yw1-f172.google.com (mail-yw1-f172.google.com [209.85.128.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6A1C03BFE33 for ; Wed, 9 Sep 2026 19:38:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982683; cv=none; b=PFSXYOUaaSmdLlRKuL2vEvGoOdKZ7hVc02JzNqtBIKYebhEhpDRJkz02mHAktoivBJWViEgbPLzlzdAMBPbgZD6lb8ePdIr2CGlYlfn/dsHS3Tshzrs+wkABLUIvxT95Ydv3j/tJCfPeIbisr7mHMvi6bcNjzuf/pJ0oJvbDjF8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982683; c=relaxed/simple; bh=BExP6Uc0CAqt4PblTM1vCa+4tjOKYHzmWABKQ8gVc50=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=VjuVr/oH6vzpLTTAyPkeXBTYakFdRBa2Awg1q4k4XqJ1bcgsXBOkWuSDpAKlxnKTz7ItaNUdJSfySkv7bzdQbBXe3TlUPaPUijorlTOSIj8T7yfJt7nuL1N1qaYIhh66vcJkGXdudMLB/4rDYtQZpsSHz3OtH3te924eUfQwxQk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=J/y+X3/K; arc=none smtp.client-ip=209.85.128.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="J/y+X3/K" Received: by mail-yw1-f172.google.com with SMTP id 00721157ae682-836c4474028so63217967b3.0 for ; Wed, 09 Sep 2026 12:38:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982680; x=1789587480; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=/JJ/f47awfp4tzzCzgVlCBrXa77/+V7TaH/K3Hj08jM=; b=J/y+X3/KmzNOU4d4WZpf8v544xqstJlaUVOln4L+uebNmOzQSjR7d+B42fPeobQK/k V9hS26KQXcB57idq/0UlZ1wgHlUAgoeaUeqSDpOnDSzHU8anhI2GY8QWfRBk6YFXVCjT fMZJDodD6a+qe0sjUFsM3NclNZPeKShCDlfJNvYxZYGlCNt5etRDneZqyfjEgJA/Mnjs mi3BZy5Afqnw+NM/LrqDqhsRz0+8E5ygi6L842av6nSVD0+GJ3uZiDvjDH3z4fJSK0tl w30EJkF13YUzi9CkfTn/25mqr0zE0YO9U5rQ4JlwoCoCExJX9s1M3FN7/hVVBS8eGgY8 Ncqg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982680; x=1789587480; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=/JJ/f47awfp4tzzCzgVlCBrXa77/+V7TaH/K3Hj08jM=; b=fRx87sDP+83D4LMQta+Sc6bVkWMxH7xIAVdAn1WY0HtsoqujNLTpRbKeNTO/GhWjZF /GXr/uS69UWXw1LIiu0L6GaIV/GqZKhvDJztkEiAJ5Ckk6m2CkK9b7sNLmiDbd6UPIUg LYPF6kZVncCNznKxgL/wZHURfB+E+I3fY0ta8s+jt8BdH2SmwiXL62UWlXICyLNCP/pB C6WQv603rY401lQRNG6AhPgnBHAyqhrCLfJ7a3zPC2XqSEYP10CYz5X5BlESeJIahFWK wtuJXUlmq3t1E5O1O/AdpwgGfF+R0gRntmRgROGxtG+V8hrV+2JKQwJG8+MbNYCkssUr znVw== X-Forwarded-Encrypted: i=1; AKwUvByVfvtvLjqPerJhcT5W2GyJk4YsKRmIdC5bx1zjt9U5I5ARQHwAOCZAJdiGWbWYsP6FAqO4tFZpDUJztGk=@vger.kernel.org X-Gm-Message-State: AFuF++n1JuzmPJHjaDj+mjvdz6Lz2kJpnieP52hXeHXFeYI4Xs6aX8Um ZTxJCykNqygJhqzGG1YVUVykTaf6/3jKijGwPVTWSugrXvLIh9iKBOfX X-Gm-Gg: AYBFou0v39PKRe7H+E+HM0qMzz6NQazOgYtcoXhis+LKOil0x1+SmXVvvKb7U9GxK/Q JR5Yn9ai87vTS4RCgf4pM352Puz+aSrgprOyYlIUTuopoYTOPYUUZ3i0GjJG9OYYLMsrUmjeUIJ HOuUCCinj01qA6IltkRzf5fkfKVhUThZT2MJ0pAsmXWf7bNoNA3IPGw1n7/hxE0EAF3GoGKfxd+ 1ZmyNxLedc2wSp1Q7f+c4Qs2MX20LPz5F2xU1OLXQAoekh7iNAn9Qq2Dd/R8kiZHbiw0LQMUW5d 1E/2DkMFRuLWsw+yjIv26H5qm+PT+PQ6r/44HWKO7QbxH9dNAINbvXiAud863ggjHNTv5wSM/NX UAYwnSSMIGtyW68jD9cm7y0dn0R9E8rseADfKyxu/Ba59rMgQdeAqx9r9kKS5cybZgKea7W26f2 +km41A58zej61T7FgwNfZyETNEnrVBYLDgXdtKpl9+v5hqR1P4PzcDrqXLSQRgQrz7jyQ8n5Of3 3n7OuCg/05nOh6NqxlXUr6ZBf/lBQRO X-Received: by 2002:a05:690c:e3c1:b0:86f:c1db:14c7 with SMTP id 00721157ae682-8712259664fmr133356567b3.7.1788982680038; Wed, 09 Sep 2026 12:38:00 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.37.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:37:59 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 06/15] lsm: Add the bpf_lsm_policy_acquire kfunc Date: Wed, 9 Sep 2026 15:37:09 -0400 Message-ID: <20260909193719.518517-7-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add the kfunc acquiring a reference on a policy object the program does not own: bpf_lsm_policy_acquire(object) KF_ACQUIRE|KF_RCU|KF_RET_NULL bpf_kptr_xchg() is the only way to take an owned pointer out of a map kptr field, and it empties the slot: concurrent executions of an enforcement program would race for the one stored reference. Modeled after bpf_task_acquire(), this kfunc removes the exclusivity: a program loads the kptr field with a plain read under bpf_rcu_read_lock(), acquires its own reference through the policy_object_get hook, and leaves the map slot untouched. The acquired reference survives bpf_rcu_read_unlock(), carrying over to a sleepable bpf_lsm_policy_apply_bprm() call, and is released with bpf_lsm_policy_release(). Adding struct lsm_policy_object to the verifier's rcu_protected_types set makes the plain load yield an RCU-protected pointer instead of an untrusted one. This is where the policy object contract's RCU requirements become load-bearing: the kfunc and the get hook examine the object concurrently with a possible last put, which is safe because implementations free only after an RCU grace period and acquire with inc-not-zero semantics. A failed get makes the kfunc return NULL, per KF_RET_NULL. The kfunc does not sleep and is meaningful wherever a policy object pointer can be loaded, so the filter adds no per-kfunc rule. Cc: Paul Moore Cc: KP Singh Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. kernel/bpf/verifier.c | 3 +++ security/bpf_lsm_kfuncs.c | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 9e79750e2480..d1af0090d38f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -4660,6 +4660,9 @@ BTF_ID(struct, bpf_crypto_ctx) #ifdef CONFIG_INET BTF_ID(struct, bpf_ksock) #endif +#ifdef CONFIG_BPF_LSM +BTF_ID(struct, lsm_policy_object) +#endif BTF_SET_END(rcu_protected_types) =20 static bool rcu_protected_object(const struct btf *btf, u32 btf_id) diff --git a/security/bpf_lsm_kfuncs.c b/security/bpf_lsm_kfuncs.c index 988dcd6f4dd9..43a4bf57fd31 100644 --- a/security/bpf_lsm_kfuncs.c +++ b/security/bpf_lsm_kfuncs.c @@ -14,6 +14,36 @@ =20 __bpf_kfunc_start_defs(); =20 +/** + * bpf_lsm_policy_acquire - Acquire a reference on a shared policy object + * @object: RCU-protected pointer to a policy object, e.g. loaded from + * a map kptr field under bpf_rcu_read_lock() + * + * Acquire a reference of its own on a policy object the program does + * not own, so that any number of concurrent program executions can + * use the object shared through one map kptr field, without emptying + * it as bpf_kptr_xchg() would. The returned reference stays valid + * after bpf_rcu_read_unlock() and must be released with + * bpf_lsm_policy_release(). + * + * Return: A referenced policy object, or NULL if the object's + * reference count concurrently dropped to zero. + */ +__bpf_kfunc struct lsm_policy_object * +bpf_lsm_policy_acquire(struct lsm_policy_object *object) +{ + struct lsm_static_call *scall; + + lsm_for_each_hook(scall, policy_object_get) { + if (scall->hl->lsmid->id !=3D object->lsmid) + continue; + if (scall->hl->hook.policy_object_get(object)) + return NULL; + return object; + } + return NULL; +} + /** * bpf_lsm_policy_from_fd - Get an LSM policy object from a fd * @fd: file descriptor referring to a policy object, resolved in the @@ -57,7 +87,8 @@ __bpf_kfunc struct lsm_policy_object *bpf_lsm_policy_from= _fd(int fd, u32 flags) * bpf_lsm_policy_release - Release a policy object reference * @object: policy object to release * - * Release a reference acquired with bpf_lsm_policy_from_fd(). + * Release a reference acquired with bpf_lsm_policy_from_fd() or + * bpf_lsm_policy_acquire(). */ __bpf_kfunc void bpf_lsm_policy_release(struct lsm_policy_object *object) { @@ -83,6 +114,7 @@ CFI_NOSEAL(bpf_lsm_policy_release_dtor); __bpf_kfunc_end_defs(); =20 BTF_KFUNCS_START(bpf_lsm_policy_kfunc_ids) +BTF_ID_FLAGS(func, bpf_lsm_policy_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NU= LL) BTF_ID_FLAGS(func, bpf_lsm_policy_from_fd, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_lsm_policy_release, KF_RELEASE) --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yx2-f12.google.com (mail-yx2-f12.google.com [74.125.224.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 056A83C9EF6 for ; Wed, 9 Sep 2026 19:38:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.140 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982687; cv=none; b=Kj2mx8Vb9YGPj79+ZM8trQSCNgEakK1CN2r1GC68rsrXaEeycNDR9BmZyziPhopw7KafGLcGEpNsBp9qSux2Q5tvhzhtPbdQB0HlVqqCje42aBzsvoltlA3AO8i2E0pYzP18F8zWFtRJdWrz2UmPIp1/6apidDp9ypMcg45ONMI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982687; c=relaxed/simple; bh=3UdmNZbim5XGw6/6tAVi8wsONhfQi1lHvtpKuyErfvM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BYJ3chflDHu6mMcHEMWYAhpt/PlZPYWtiBVgFN0nU1iVGPgP6QBhZ/iUNCuyZx1L0Wi3kmujkY5+tQEsXmot6Pg9EtAO/L4pGSW8rEUU0+xg4jxO3c8mJk+Z2EkDWE7GUT9wQ6rCJ5+6zBlm7TubU/sVlU998QOIQCkLywyrlVg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=SV40quUM; arc=none smtp.client-ip=74.125.224.140 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="SV40quUM" Received: by mail-yx2-f12.google.com with SMTP id 00721157ae682-85d46e4cdccso15647107b3.1 for ; Wed, 09 Sep 2026 12:38:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982684; x=1789587484; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=EkCEHurDTlOHDlrBUCUNQwXmIR2I592Pn8PkaAk7zk8=; b=SV40quUM2PemiShLAriX8Uu8SxRM3gM9ezwq09rWE7WCz1bwOsw5mYcNfk8Ez8oo0x eiAXdBMiYA8rRzsVx+2q2mL5nG2llDGViuebV4a9DNtvdBiTXxENdorMWy41RdlFgqBC KELGl6BQZkGHk7YQR1zoHqUkqUldqrFu8AqXH73WkP2AvZIxVnwn9R8QNHimJ8Jl13f5 fOQ3S9W+zQa7LImWk3Nm0IQbmAWzsXemX11IaAHEKAFIOvRklPWk6MrUULUmFgcscpH5 naWzdroQSnPf/xf5IPywLPawOtHVxsn+vXxWquaAtXDyg948zwbj8IjGYEXuRVtOPMjY 2FvA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982684; x=1789587484; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=EkCEHurDTlOHDlrBUCUNQwXmIR2I592Pn8PkaAk7zk8=; b=GtR3jOVGaf2YCk4HbeDj4Gl+O7xZPO9COpXGmTi269U0puCJV2FYmjWT78HUBB0Kgs 001jNA6nALxMgNU+JfKN8bP+uwzsFjv3Ffoyj0J5gLj/l53YoCQq7OA8L6S7qGB+61NP FahoX4II/3lmW2NCPPsVuZ493O8T2b2QPz18XiZ4QFQoyy9h/H/1PE/SC8JaYVtSXNUV L3ZIdUd1+rjXPLH9eEHzTHqS1dreCLahv2USnb8cNiPUmFjIqLSi563bS0csw44Tzxny lnQbFnOdfjA5YiPH02r1YjK91sSD3KoKD+YCG1tvMOpxXGhwj3MxE7H+GgtmMMyd/48o 2g7Q== X-Forwarded-Encrypted: i=1; AKwUvBxSNaG/uKuMWElfh6b9DkxNmyj+B+VW8DUfyf3IWYb+32K1jAfz4GrgEKIs5rSRJWi9Ac5AzkIzydp3e+c=@vger.kernel.org X-Gm-Message-State: AFuF++lpVD8upSfMNmRg8wkQ1phEmUFzcKHdquum5A/T5Lawmuu64pq7 JOiVJgOzReFQb8+0Y4MXpydJf9Q/FN8HQEMZQ3PG7aIpGythLUtIYjxc X-Gm-Gg: AYBFou1wNAvmdPNG+njkTMbeE/h+ZAHUOodQ7Yw8glIMAJkdNxorlGX9Wv6fnk9Cbws LJFrmDT/utmLsLyqGRMz6QXpJZvUlTeYr0Z3MQ9sTzxvA1ruRwiVdwudTLpU1JJsonYr+wELQqX dHCsWcRlNwSyuVCBE1W6Rb1DO5jXWAvZBg4rTpxkx7gwa3KotBMmmjG2H7HTEYJ1sxVr3tp85rj Qpyp7om7NamhY/0gUsrnRKpi+7GV9hMYmyUqlzp8YgvQ576SKnmlMOCLF9jEtEO1guKRLgszKzK w7Gj0QwI0lG9JT6fLCPd7JM2ngdELwhSJJyKwlWUJnVJzQS59sNAoXDZ9ZpbrJqqAYGT0RTCB7j o+EHax1e/GND93z1tiHjSgItw6bFGn/vs2OeYrHgBJVGxydocIjaaGmEUqML258JyfIkStA8rpJ +thS1oicP3OhuhT3WeFfPMvchQpyYysomAQbbihRUMxNfSbDpIWAapi15ZT2BGjTSyCnL3YuZs4 2rQwUDWIneS1N0WJfaZnX58JfCcRwMg X-Received: by 2002:a05:690c:6610:b0:881:392b:23db with SMTP id 00721157ae682-881392b4020mr15749527b3.11.1788982683732; Wed, 09 Sep 2026 12:38:03 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.38.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:38:03 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 07/15] lsm: Add the bpf_lsm_policy_apply_bprm kfunc Date: Wed, 9 Sep 2026 15:37:10 -0400 Message-ID: <20260909193719.518517-8-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add the kfunc applying a policy object to an execution: bpf_lsm_policy_apply_bprm(object, bprm, flags) KF_SLEEPABLE It asks the LSM owning @object, through the bprm_apply_policy_object hook, to restrict the credentials prepared in @bprm, so that the executed task starts confined by the policy. The meaning of @flags and the composition with restrictions the credentials already carry are the owning LSM's; an LSM without execution policy support makes the call fail with -EOPNOTSUPP. The kfunc runs the hook in a root memcg charging scope: the policy restricts the execution on behalf of the BPF program, not of the mediated task, so what the owning LSM allocates to compute it, e.g. Landlock's merged domain, is not charged to the task the program supervises. The filter makes the kfunc exclusive to the sleepable LSM programs attached to the bprm_creds_for_exec() or bprm_creds_from_file() hooks, the only contexts where the bprm's credentials are prepared but not yet committed. The verifier's argument typing does not draw this boundary on its own: a trusted struct linux_binprm pointer is also available at the other bprm hooks -- bprm_check_security(), which runs per binfmt while an interpreter may still rewrite the execution, and bprm_committing_creds()/bprm_committed_creds(), which run at or past the point of no return, where the prepared credentials are frozen or installed -- and to tp_btf programs via the sched_prepare_exec and sched_process_exec tracepoints, which resolve kfuncs from the same registration bucket as LSM programs. The attach-point filter, not the argument type, is the authorization boundary. No filter case is needed for BPF_LSM_CGROUP programs: since commit 5b038319be44 ("bpf: Reject sleepable BPF_LSM_CGROUP programs at load time") they cannot be sleepable, so KF_SLEEPABLE already excludes them. Cc: Paul Moore Cc: KP Singh Signed-off-by: Justin Suess --- Notes: v2->v3: - Drop the BPF_LSM_CGROUP case from the kfunc filter: since commit 5b038319be44 ("bpf: Reject sleepable BPF_LSM_CGROUP programs at load time") such programs cannot be sleepable, so KF_SLEEPABLE already excludes them from the apply kfunc. - Document, in the commit message and the filter comment, why the attach-point filter rather than the verifier's argument typing is the authorization boundary. security/bpf_lsm_kfuncs.c | 68 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/security/bpf_lsm_kfuncs.c b/security/bpf_lsm_kfuncs.c index 43a4bf57fd31..857e9a316d1c 100644 --- a/security/bpf_lsm_kfuncs.c +++ b/security/bpf_lsm_kfuncs.c @@ -2,16 +2,25 @@ =20 /* BPF kfuncs exposing LSM policy objects. */ =20 +#include #include #include #include #include #include #include +#include +#include #include =20 #include "lsm.h" =20 +/* The sleepable LSM hooks bpf_lsm_policy_apply_bprm() may be called from.= */ +BTF_SET_START(bpf_lsm_policy_bprm_hooks) +BTF_ID(func, bpf_lsm_bprm_creds_for_exec) +BTF_ID(func, bpf_lsm_bprm_creds_from_file) +BTF_SET_END(bpf_lsm_policy_bprm_hooks) + __bpf_kfunc_start_defs(); =20 /** @@ -44,6 +53,49 @@ bpf_lsm_policy_acquire(struct lsm_policy_object *object) return NULL; } =20 +/** + * bpf_lsm_policy_apply_bprm - Apply a policy object to exec credentials + * @object: policy object to apply + * @bprm: execution context providing the prepared credentials to + * restrict + * @flags: flags defined by the LSM owning @object + * + * Ask the LSM owning @object to restrict the credentials prepared in + * @bprm with it, so that the executed task starts confined by the + * policy. How the policy composes with restrictions the credentials + * already carry, and the meaning of @flags, are defined by the owning + * LSM. @object is only borrowed: the caller keeps its reference. + * The hook runs in a root memcg charging scope: policy the LSM + * computes on behalf of the program is not charged to the mediated + * task. + * + * Return: 0 on success, -EOPNOTSUPP if the LSM owning @object does + * not support applying policy to an execution, -EINVAL on unsupported + * @flags, other negative values on LSM-specific failures. + */ +__bpf_kfunc int bpf_lsm_policy_apply_bprm(struct lsm_policy_object *object, + struct linux_binprm *bprm, u32 flags) +{ + struct lsm_static_call *scall; + struct mem_cgroup *old_memcg; + int err; + + lsm_for_each_hook(scall, bprm_apply_policy_object) { + if (scall->hl->lsmid->id !=3D object->lsmid) + continue; + /* + * The hook runs on behalf of the BPF program, not of the + * mediated task: charge its allocations to the root memcg. + */ + old_memcg =3D set_active_memcg(root_mem_cgroup); + err =3D scall->hl->hook.bprm_apply_policy_object(bprm, object, + flags); + set_active_memcg(old_memcg); + return err; + } + return -EOPNOTSUPP; +} + /** * bpf_lsm_policy_from_fd - Get an LSM policy object from a fd * @fd: file descriptor referring to a policy object, resolved in the @@ -115,6 +167,7 @@ __bpf_kfunc_end_defs(); =20 BTF_KFUNCS_START(bpf_lsm_policy_kfunc_ids) BTF_ID_FLAGS(func, bpf_lsm_policy_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NU= LL) +BTF_ID_FLAGS(func, bpf_lsm_policy_apply_bprm, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_lsm_policy_from_fd, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_lsm_policy_release, KF_RELEASE) @@ -124,6 +177,8 @@ BTF_ID_LIST(bpf_lsm_policy_dtor_ids) BTF_ID(struct, lsm_policy_object) BTF_ID(func, bpf_lsm_policy_release_dtor) =20 +BTF_ID_LIST_SINGLE(bpf_lsm_policy_apply_bprm_ids, func, + bpf_lsm_policy_apply_bprm) BTF_ID_LIST_SINGLE(bpf_lsm_policy_from_fd_ids, func, bpf_lsm_policy_from_f= d) =20 /* @@ -132,6 +187,12 @@ BTF_ID_LIST_SINGLE(bpf_lsm_policy_from_fd_ids, func, b= pf_lsm_policy_from_fd) * kfuncs requires a filter. A policy object fd is only meaningful in * the fd table of the task that set the object up: the fd kfunc is * exclusive to syscall programs, which run in that task's context. + * Applying policy to an execution is exclusive to the sleepable bprm + * LSM hooks the operation is specified for. The bprm argument's type + * alone does not draw that boundary: other bprm hooks and the tp_btf + * exec tracepoints, which share the LSM programs' kfunc bucket, also + * provide a trusted bprm pointer outside the window where the + * prepared credentials may still be updated. */ static int bpf_lsm_policy_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id) @@ -141,11 +202,18 @@ static int bpf_lsm_policy_kfunc_filter(const struct b= pf_prog *prog, =20 switch (prog->type) { case BPF_PROG_TYPE_SYSCALL: + if (kfunc_id =3D=3D bpf_lsm_policy_apply_bprm_ids[0]) + return -EACCES; return 0; case BPF_PROG_TYPE_LSM: if (kfunc_id =3D=3D bpf_lsm_policy_from_fd_ids[0]) return -EACCES; =20 + if (kfunc_id =3D=3D bpf_lsm_policy_apply_bprm_ids[0] && + !btf_id_set_contains(&bpf_lsm_policy_bprm_hooks, + prog->aux->attach_btf_id)) + return -EACCES; + return 0; default: return -EACCES; --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yx1-f45.google.com (mail-yx1-f45.google.com [74.125.224.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D63AB3C98BA for ; Wed, 9 Sep 2026 19:38:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982691; cv=none; b=E3i3KMTWjiqEKRP2I7cH4jkpYYtwSOxR5ByaTkYgOfnn2ABcaekNJEjR+3hUbDDv7iqYI8CuG2uBz/Kj3AueuZ01b3u8yetI4QOnFxVcoY58uuGnbbO6bF8YPrsCKGt6T6H6UFIIzHJUkeQ7uZYtrFYGQCOSE15X+GNvpaGYY/I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982691; c=relaxed/simple; bh=gK5MnwxSIYAl7ZmjTB1FKOm55yKxqXXey3cseNQLAt4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=uMdz2X4bSexpADm71dYpHwZKFNxgeoq7QY4Sr9xxEjdsX1SkD7UAh7yUGf/CQQY3FFZMZ/n93xW3+cP56D7QaAukgbwUwo7XzCY9Zt6m5tDfxRF6EIMFRlNG96Bs8EarOg3c3burhhUoog8twCYWK985g7FnEc2DYu7jiMbGQao= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=mCl06AXg; arc=none smtp.client-ip=74.125.224.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="mCl06AXg" Received: by mail-yx1-f45.google.com with SMTP id 956f58d0204a3-66e63c2c9e1so148260d50.0 for ; Wed, 09 Sep 2026 12:38:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982687; x=1789587487; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=ZqUYtQxdenK0ojTeBznvGiMR4AWulNf4CY2WGcBzpw0=; b=mCl06AXgDgW+7opueNvlzgNxuRKgcNQjkHarp1U5ZlThuNwTckUAgcCJ7Vkq3qcowb YLFR06YqtXIMFPXny0RuBidOdhMUPcpSE47WQU8TXGcHfXyaJonC5RLjPQIxMME50OEV I3Wrgl1pkpy9M+n3tCL20TQNn1FxHTYRmC+bfEweZTJLn2u6b2xqXdOYWJwb5mEE8j8V xC/wwFTBJ+0ZMlmXKQKJjKrXfsKza4XiCV/a9nKnc5wsUT5W0qDNOfAVnA3aTWclZi1B Bv3xFOxDQzIBTiMKMDyWLcg0semiYSKOWIeItVGAGAJzb8V0K69Qul6doa9hdsLOjiZB JWjQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982687; x=1789587487; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=ZqUYtQxdenK0ojTeBznvGiMR4AWulNf4CY2WGcBzpw0=; b=qCzdWwcX+/NM6GZlinJd/UZ/nCFCpF0n00FC2YHGEyMrw28tIQNZpYQtBoJAcXzDAD EpzzANWTVnnpzTQ+E40NfisSIoCmEnRdsBkTMZ/agM5HTcHEyi5SR5xU2NyYeqra0SWa l/wZjCiQ2OvZWGieWNVSVGFv8xF9Epa2CA0n6WloDaqWt+HEEOAUmFq/T1XzXT/erdPk RiMO8LhgpQkfI9vvHrTKhNsvwDhbtexgpxP5FVnGePZ6FE+/mgIsKztuFZt/co+ynSnX uOC8v9rNTIYxm9V8Zh6m3EOfiH1HYRPe2Br8j618jy55r4j6gI2W45yvoVPC9pb7nelB ZkHA== X-Forwarded-Encrypted: i=1; AKwUvBypyB7ZqIxIcWLTxFNIu3CQz4PUDk3BzDfsa3X41dE52Dc4MQyAa2bPwoND8zZ4jJJknJrAiBHtyNRAJSI=@vger.kernel.org X-Gm-Message-State: AFuF++m2A6vxiU0Rg//ozgSBLcjUwTJK35SsBgGIZ3aHwX9eUA0deckp AnWg3E84D5f7IK5e5TVKLOk2D3xOnxn/UFG3BJI+voHTGvQjYY+8qKVo X-Gm-Gg: AYBFou33BC3BZAcEcdu2XtGB7NL6bzBFqdVvlpAakPnf0kl3dArwesb/ZFmkiGFL0aY 5U/CkSEjWwZKExqfgK/ljOFz8JX97UgsJtI0xt/hxSDLFRyrP2+Ddy35FjG5mNTzvRbr8yFky7m I539UuyTL0cBXJLS/i3lqt0Ex9GIZZlP9idH3vcqaxWcfCoPmFlfqKX5LnEopQbWbWx+dT4K17Q LWwPbZX53wamAawZyHbq7wq67Ypqy74DXHhVQLt+tAK8YEZ8RwKfNpOXWJJGKLnH6VMkiA4fkUL 0RgH1afv5kAzM8qAVDwzEvhVtnsZJRDnRJ+onkxjBCPpepxom3stmkwD83swFUi1Oy17QDLOqqh GywGso96DGh1gWM7JQvY5BRJ2sCQMlJnEGmokQ7bXiIJhX7AQhivPl4Iz5xV4uzAR/mptWc3z6K mVYIEkB8W8+q1iVGaB7B7EMfpPGEgMa9zaMZWzUtxTHN+oBQvNP4BzSUMxCATbcH8HTLUwo585y ExI1KV8Ir4bh9QE7io6dSOhwOISGzZx X-Received: by 2002:a05:690c:9c0d:b0:861:850e:dd59 with SMTP id 00721157ae682-882018855cemr4754927b3.9.1788982686908; Wed, 09 Sep 2026 12:38:06 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.38.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:38:06 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 08/15] lsm: Document the LSM policy object interface Date: Wed, 9 Sep 2026 15:37:11 -0400 Message-ID: <20260909193719.518517-9-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Describe the split of responsibilities in lsm-development.rst: the LSM framework owns the BPF-facing kfuncs, an LSM opts in by embedding struct lsm_policy_object, tagged with its lsmid and an LSM-private type, and implementing ordinary LSM hooks, and the lifetime contract those hooks must satisfy comes from BPF's execution model. Cc: Paul Moore Cc: Casey Schaufler Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. Documentation/security/lsm-development.rst | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Documentation/security/lsm-development.rst b/Documentation/sec= urity/lsm-development.rst index 5895e529da7f..190d9b8f2346 100644 --- a/Documentation/security/lsm-development.rst +++ b/Documentation/security/lsm-development.rst @@ -15,3 +15,52 @@ see ``security/security.c`` and associated structures: =20 .. kernel-doc:: security/security.c :export: + +LSM policy objects and BPF kfuncs +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D + +The LSM framework implements an interface for individual LSMs to +expose their policy through BPF kfuncs and kptrs. An LSM may not +export any kfunc or other BPF interface directly. + +An LSM opts in by embedding ``struct lsm_policy_object`` in one of +its own objects, setting its ``lsmid`` to the LSM's ``LSM_ID_*`` +value and its ``type`` to a nonzero value of the LSM's choosing, and +implementing the policy object hooks (``policy_object_from_fd``, +``policy_object_get``, ``policy_object_put``, and per-operation hooks +such as ``bprm_apply_policy_object``) like any other hook, resolving +the containing object with ``container_of()``. The ``type`` namespace +is private to the owning LSM, which uses it to tell its own policy +object kinds apart; the framework never interprets it, and 0 is +reserved as "unset". + +The kfuncs, defined once in ``security/bpf_lsm_kfuncs.c``, dispatch +each call on an object to the single LSM matching its ``lsmid``. The +fd translation has no object yet: the framework offers the fd to +every ``policy_object_from_fd`` implementation in turn, and an LSM +declines a fd that is not one of its own with ``-EOPNOTSUPP``; any +other error fails the translation. A program that expects a policy of +a specific LSM can read the returned object's ``lsmid``. Either way, +a BPF program reaches an LSM the same way every other kernel caller +does, through an LSM hook, while the BPF verifier tracks the object +as a referenced kptr. + +An LSM opting in must satisfy the lifetime contract that BPF's +execution model imposes: the containing object is reference counted, +``policy_object_get`` acquires with inc-not-zero semantics and fails +once the count dropped to zero, ``policy_object_put`` may be called +from contexts that cannot sleep (map destructors), and the object's +memory is freed only after an RCU grace period, as programs load +policy object kptrs from BPF maps under RCU. Hooks for operations an +LSM does not provide are simply not implemented: the corresponding +kfunc then fails with ``-EOPNOTSUPP`` at runtime. Whether the LSM +providing an operation is built in and active is likewise a runtime +property: the kfuncs are always registered when ``CONFIG_BPF_LSM`` is +enabled, so BPF program loading is independent of the boot-time LSM +configuration. + +.. kernel-doc:: security/bpf_lsm_kfuncs.c + :identifiers: bpf_lsm_policy_acquire + bpf_lsm_policy_apply_bprm + bpf_lsm_policy_from_fd + bpf_lsm_policy_release --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yx2-f12.google.com (mail-yx2-f12.google.com [74.125.224.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5B43C3CC323 for ; Wed, 9 Sep 2026 19:38:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.140 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982693; cv=none; b=LzyJtj8JaQsU4SjGN9n7kfpjIJrNXEa2QU//0FCMnV6GT6kbMP8zhbmXmkMAROAg7VfjfgfntDNoaK3bDlrvA5XRQi1zQ/RE7K02ySYwsCPAyLB/h/0M85n2ocyVi8dtRotUioQEl98Sk4i3b1HBs1mYSmg3FikjeQs3TCaoeX0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982693; c=relaxed/simple; bh=bRYuGW2zUXXwDnDiHOCFcw1PiQe9SH8nYMOt5euiY8A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=gJp82274lxEMH2neNKIubOKif++TELC3SFv8ovFCZ784ifEX03u+WMhuWDnn8U0Bm228Fu6WO9Qk516esDpGHsuXRK8hx6omZgnpz1/f/f5+nKybTyzpLDS9jeFePTC51QiAVZeiqYsAdt68lnGVwVwHyNptK8kyIzITmhOJJ54= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=LJqFnATu; arc=none smtp.client-ip=74.125.224.140 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="LJqFnATu" Received: by mail-yx2-f12.google.com with SMTP id 956f58d0204a3-66e4aae3149so1507533d50.2 for ; Wed, 09 Sep 2026 12:38:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982690; x=1789587490; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=/Vzz2Syeb86gTKcafHfzV5LcScc4P7BtAEH15H3VzJw=; b=LJqFnATu1xNe+Z4kHbQEF3uYZXvNH+t/60SDggXQQeNEfoxBbuqFzbPhAML5XYKTOW SCMjh6akZPcTygcXrR9bhEFNOo3yZoa4k25ObELtmH0+M/uvRVxzTnIBDeh2PFuG54dy 0a2gJMK2gC1BdxUEyLXHjOsMBw7XDCYoYMZUrmvDyy2AMCYgZ8vgoXwZsschOUQ6JvVk TUv36lRYih/3qz09tGDhqsWHJmm48vM89vIwdoqDOmFIyN+zfHDaObuBpwfsyY+taGUN XGD4533dV6PJWHGQGjTNqGFLDQjCYI0meevmZ7Sg2dLrJ8/gqROsWcKwrdsoKYNOydDo sfIw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982690; x=1789587490; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=/Vzz2Syeb86gTKcafHfzV5LcScc4P7BtAEH15H3VzJw=; b=sjfo0ewkHzgeBwO92THKWUIQx9IkFsV6iMZAJ3hGdxxEJenU8lKIpCQhrVOkdy1rck XYCxT/GbTSA4PlF7JhJ1ssOQ1gyoPfAnAOrxX4hut+0nPSem013mfvKL1DFdi5DM8E9/ ZRo3zRxuzWNVY6z6QbtRT1BrwFLvD1jSTNIbB9rx1Apjb/6BjuOhG2LfPYRX/tgwFb5b /E/fpxC6jouwAb3XUipldBBva5S/vm8e85NBNaj+ik0qLD/wFRU6e1PxQBik4SVF+8ge gv9s2yIwegMQ/zhluTmezlR7mjlBkEuozXkgYLmaD8WqsB8QC58bUEhnJ9EHCOrfw2/F pKMw== X-Forwarded-Encrypted: i=1; AKwUvBzYDtXX6eXDYcIYPYNREVJbb3LN2Rmr3tZzAAfAIQfgyM0/LX90038TqA95won5UufmWOEXggbkyHER0Js=@vger.kernel.org X-Gm-Message-State: AFuF++mmpVXqwBSJMeOJMgHbCrz+TBYBjGWLLkENGR2YmG8vF9WVgEem 1bYfwgwWRaxwNxCTZ6pREmF5JLkwOx4KiOsicjZvc3GQuE6nZUtdsVTp X-Gm-Gg: AYBFou08Cw9BO0xXTOqETPrGR8skUUXvRtoek8NRSc5t9i2kFUy0fmDWH0y2AU0R5kj Kkjr61JDEv9t5ImYZsL2bH5RGxf7eUgTebz8bTSJTzs5aB4dl1CBAwppWw+56kPTYxS+dUuoeyS aEP4FyTLBgIYkQklcYjXSTK5EIYTegSYTH4X2o/UzraXf3mI99S1g16d2rTrbXXfQxa7mMpQoER Jv6Q/BUFba5qnWmuoGMFnwk2Y97oi/My/mc9U7fS90Q8vh1C1L6gjfrjgjsAMGX4mhAqizoJbnZ X1bDEAOA8JgVvH23ckTdzIyxeseM/8StXqeQskS9fOIduwrOCK5GYfkZU6+NV3ht6WXx0Alsq20 JaFFeNLA43BzUb00hCAjjLLvdGdpvRxU0dlOymRsJwDIvqX9Ess3ivON/dJSB5zv14Q1Wp8Jgbd AdhpcnkPSiHHuuqKFDo6HXu8LMYs3ocryEL13Pxmc+FBY37+BwC7EQFSNv93tCZGdS6GFedM5Vu t/POtXEg2SE7srasu0RZ13rciAkWOlT X-Received: by 2002:a05:690e:4806:b0:66f:7728:b3ce with SMTP id 956f58d0204a3-671036ef4b8mr2163028d50.30.1788982690169; Wed, 09 Sep 2026 12:38:10 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.38.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:38:09 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 09/15] selftests/bpf: Add tests for the LSM policy object kfuncs Date: Wed, 9 Sep 2026 15:37:12 -0400 Message-ID: <20260909193719.518517-10-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Test the properties of the policy object interface that hold independently of any LSM implementing the hooks. The failure programs pin down the verifier-side contract: the kfuncs are rejected in tracing programs, the fd kfunc in LSM programs, the apply kfunc in syscall programs, on non-bprm LSM hooks and in non-sleepable programs, leaked references fail verification, and a kptr loaded outside an RCU read-side section cannot be acquired. The syscall program checks the runtime contract of bpf_lsm_policy_from_fd(): a bad fd, a fd that is no LSM's policy object, and a nonzero value of the reserved flags all resolve to NULL. Exercising the kfuncs against an LSM actually providing policy objects is left to that LSM's own tests. Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. .../bpf/prog_tests/lsm_policy_kfuncs.c | 54 ++++++ .../selftests/bpf/progs/lsm_policy_kfuncs.c | 52 ++++++ .../bpf/progs/lsm_policy_kfuncs_failure.c | 154 ++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/lsm_policy_kfunc= s.c create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_kfuncs.c create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_fai= lure.c diff --git a/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c b/t= ools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c new file mode 100644 index 000000000000..9f4ffb5f47be --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/lsm_policy_kfuncs.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright =C2=A9 2026 Justin Suess */ + +#include +#include +#include + +#include "lsm_policy_kfuncs.skel.h" +#include "lsm_policy_kfuncs_failure.skel.h" + +/* + * Runtime contract of bpf_lsm_policy_from_fd(), independent of any + * LSM implementing the policy object hooks: a bad fd, a fd that is no + * LSM's policy object, and a nonzero value of the reserved flags all + * resolve to NULL. + */ +static void test_from_fd_null(void) +{ + LIBBPF_OPTS(bpf_test_run_opts, opts); + struct lsm_policy_kfuncs *skel; + char tmp_path[] =3D "/tmp/lsm_policy_kfuncs_XXXXXX"; + int tmp_fd, err; + + tmp_fd =3D mkstemp(tmp_path); + if (!ASSERT_GE(tmp_fd, 0, "mkstemp")) + return; + + skel =3D lsm_policy_kfuncs__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) + goto out_close; + skel->bss->plain_fd =3D tmp_fd; + + err =3D bpf_prog_test_run_opts(bpf_program__fd(skel->progs.check_from_fd), + &opts); + if (!ASSERT_OK(err, "check_from_fd_run") || + !ASSERT_OK(opts.retval, "check_from_fd_retval")) + goto out_destroy; + + ASSERT_TRUE(skel->bss->got_null_for_bad_fd, "bad_fd_null"); + ASSERT_TRUE(skel->bss->got_null_for_plain_fd, "plain_fd_null"); + ASSERT_TRUE(skel->bss->got_null_for_bad_flags, "bad_flags_null"); +out_destroy: + lsm_policy_kfuncs__destroy(skel); +out_close: + close(tmp_fd); + unlink(tmp_path); +} + +void test_lsm_policy_kfuncs(void) +{ + if (test__start_subtest("from_fd_null")) + test_from_fd_null(); + RUN_TESTS(lsm_policy_kfuncs_failure); +} diff --git a/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs.c b/tools/= testing/selftests/bpf/progs/lsm_policy_kfuncs.c new file mode 100644 index 000000000000..f084ccfcde91 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright =C2=A9 2026 Justin Suess */ + +#include +#include + +char _license[] SEC("license") =3D "GPL"; + +extern struct lsm_policy_object * +bpf_lsm_policy_from_fd(int fd, u32 flags) __ksym; +extern void bpf_lsm_policy_release(struct lsm_policy_object *object) __ksy= m; + +int plain_fd; +bool got_null_for_bad_fd; +bool got_null_for_plain_fd; +bool got_null_for_bad_flags; + +/* + * Runs in the test runner's context through BPF_PROG_RUN, where + * @plain_fd is meaningful. + */ +SEC("syscall") +int check_from_fd(void *ctx) +{ + struct lsm_policy_object *object; + + /* A fd not open in this task's fd table must resolve to NULL. */ + object =3D bpf_lsm_policy_from_fd(-1, 0); + if (!object) + got_null_for_bad_fd =3D true; + else + bpf_lsm_policy_release(object); + + /* + * A valid fd that is not any LSM's policy object must be + * declined by every LSM and resolve to NULL. + */ + object =3D bpf_lsm_policy_from_fd(plain_fd, 0); + if (!object) + got_null_for_plain_fd =3D true; + else + bpf_lsm_policy_release(object); + + /* The flags are reserved: any nonzero value must resolve to NULL. */ + object =3D bpf_lsm_policy_from_fd(plain_fd, 1); + if (!object) + got_null_for_bad_flags =3D true; + else + bpf_lsm_policy_release(object); + + return 0; +} diff --git a/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c = b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c new file mode 100644 index 000000000000..04080838aefd --- /dev/null +++ b/tools/testing/selftests/bpf/progs/lsm_policy_kfuncs_failure.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright =C2=A9 2026 Justin Suess */ + +#include +#include +#include +#include "bpf_misc.h" + +char _license[] SEC("license") =3D "GPL"; + +extern struct lsm_policy_object * +bpf_lsm_policy_acquire(struct lsm_policy_object *object) __ksym; +extern int bpf_lsm_policy_apply_bprm(struct lsm_policy_object *object, + struct linux_binprm *bprm, + u32 flags) __ksym; +extern struct lsm_policy_object * +bpf_lsm_policy_from_fd(int fd, u32 flags) __ksym; +extern void bpf_lsm_policy_release(struct lsm_policy_object *object) __ksy= m; +void bpf_rcu_read_lock(void) __ksym; +void bpf_rcu_read_unlock(void) __ksym; + +struct policy_slot { + struct lsm_policy_object __kptr *object; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, int); + __type(value, struct policy_slot); +} policy_map SEC(".maps"); + +/* + * The LSM policy kfuncs are limited to LSM and syscall programs by + * the BPF-side kfunc filter: a tracing program calling one must fail + * verification. + */ +SEC("tp_btf/task_newtask") +__failure __msg("calling kernel function bpf_lsm_policy_from_fd is not all= owed") +int BPF_PROG(tracing_prog, struct task_struct *task, u64 clone_flags) +{ + struct lsm_policy_object *object; + + object =3D bpf_lsm_policy_from_fd(-1, 0); + if (object) + bpf_lsm_policy_release(object); + return 0; +} + +/* + * The fd kfunc is exclusive to syscall programs: it must be rejected + * in an LSM program, even on an allowed hook. + */ +SEC("lsm.s/bprm_creds_for_exec") +__failure __msg("calling kernel function bpf_lsm_policy_from_fd is not all= owed") +int BPF_PROG(lsm_get, struct linux_binprm *bprm) +{ + struct lsm_policy_object *object; + + object =3D bpf_lsm_policy_from_fd(-1, 0); + if (object) + bpf_lsm_policy_release(object); + return 0; +} + +/* + * The enforcement kfunc is exclusive to the sleepable bprm LSM + * hooks: it must be rejected in a syscall program. + */ +SEC("syscall") +__failure __msg("calling kernel function bpf_lsm_policy_apply_bprm is not = allowed") +int syscall_restrict(void *ctx) +{ + return bpf_lsm_policy_apply_bprm(NULL, NULL, 0); +} + +/* + * Any LSM attach point other than the sleepable bprm hooks must be + * rejected for the enforcement kfunc. + */ +SEC("lsm.s/file_open") +__failure __msg("calling kernel function bpf_lsm_policy_apply_bprm is not = allowed") +int BPF_PROG(wrong_hook, struct file *file) +{ + return bpf_lsm_policy_apply_bprm(NULL, NULL, 0); +} + +/* + * The enforcement kfunc may sleep: a non-sleepable program on an + * allowed hook must be rejected. + */ +SEC("lsm/bprm_creds_for_exec") +__failure +__msg("program must be sleepable to call sleepable kfunc bpf_lsm_policy_ap= ply_bprm") +int BPF_PROG(nonsleepable_prog, struct linux_binprm *bprm) +{ + return bpf_lsm_policy_apply_bprm(NULL, bprm, 0); +} + +/* An acquired policy object reference must be released before returning. = */ +SEC("syscall") +__failure __msg("Unreleased reference") +int leak_policy(void *ctx) +{ + bpf_lsm_policy_from_fd(-1, 0); + return 0; +} + +/* + * A kptr loaded outside an RCU read-side critical section is + * untrusted: the acquire kfunc must reject it. + */ +SEC("lsm.s/file_open") +__failure __msg("must be a rcu pointer") +int BPF_PROG(acquire_untrusted, struct file *file) +{ + struct lsm_policy_object *object; + struct policy_slot *slot; + int key =3D 0; + + slot =3D bpf_map_lookup_elem(&policy_map, &key); + if (!slot) + return 0; + + object =3D slot->object; + if (!object) + return 0; + + object =3D bpf_lsm_policy_acquire(object); + if (object) + bpf_lsm_policy_release(object); + return 0; +} + +/* A reference acquired from a shared policy object must be released too. = */ +SEC("lsm.s/file_open") +__failure __msg("Unreleased reference") +int BPF_PROG(leak_shared_policy, struct file *file) +{ + struct lsm_policy_object *object; + struct policy_slot *slot; + int key =3D 0; + + slot =3D bpf_map_lookup_elem(&policy_map, &key); + if (!slot) + return 0; + + bpf_rcu_read_lock(); + object =3D slot->object; + if (object) + object =3D bpf_lsm_policy_acquire(object); + bpf_rcu_read_unlock(); + return 0; +} --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yw1-f178.google.com (mail-yw1-f178.google.com [209.85.128.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5FE9A3AB5AC for ; Wed, 9 Sep 2026 19:38:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982697; cv=none; b=XqtBzLXe2nEyU7AZwAZ6hve3Eux80gYIJtqrPQVueZeKrLe1Ie2f1sQZepVs3L2wyCXyN/XrVuO3E1g3Hmm+xZ+trQye7sRyJooDIrSEW4rAQt93aeHwY2DId6cqI7Zd/Xj7gQD6SBCTbF3y8tyQZ+P0kUgJK+cHQYNscQ9ycUw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982697; c=relaxed/simple; bh=Rd/Pd3Leo+E3YU6ucU4gtKFQXVKmN2yo/RXforbd1n0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=lTdvRPArUTikW1eEgXvWZpdOxO+7HFFGJd1ASd4VrUpQy8kqMEMAnutSe1BSv5OdSxAtN28aP2jGe7IME2mz9Uvv30pmwdFQk2tVNDGdYvGOuN1+ulkQlnSyM5EOL5PTFKcM4mqdkuB5Q4Dk8w9IuSjJlViPNtZHZRrHxE8NSaA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=H3kK0VBE; arc=none smtp.client-ip=209.85.128.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="H3kK0VBE" Received: by mail-yw1-f178.google.com with SMTP id 00721157ae682-8588583a7c3so67386997b3.2 for ; Wed, 09 Sep 2026 12:38:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982693; x=1789587493; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=os/pHxblfNO+1KqvqowSwfs/2gIGC0QX3UM4Kl6LzkE=; b=H3kK0VBE6GSq1egktkw4gh3qWBPWAIz0pn4FXTJACwpoWKFfEVs4k0mwZWenVfvHBv VRcbGOcpogc6z0kwCpop6taSb3Qblqkxve/IBAiyAGkddfSMAZ+Kth82G3udwfQUCJKP sI6E2ukVrqoKAVDI2VkPdoGKgoiJVHmnn9qm3Qhy/h9URxHmP5YR2YqSIt3CBi3HrXw1 Re+hEkQPCr3apnonPNQ9P7d2XDYK3WW4D/UwzIGbMaVsElNvtBRcvjE5rlKInfrcbab8 tvYd4V5NOITqQRIYjUKWyc2k2U0iVlfrdVIquB3GVeUlIJm6YmimjRCQYBqebUlXt5AL Xx+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982693; x=1789587493; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=os/pHxblfNO+1KqvqowSwfs/2gIGC0QX3UM4Kl6LzkE=; b=lVDsDKIjVUTq4UC9WcOkLfuf8yqx5HdE7msaO94BZEH3aD14YtH1tCCBl8xtG+/Oyy jjoGS9qJ5r/XPG6R0UZ8NIiSTfzl8LQtC9QmyqNuo6Io194CoPGTeVD6Ou2DCSXrBOsA nDNNptFvWqhOxz+1rGgPuruEp5wj8TMByKa7dvNOtMY1MXfJJKbzeAPFJYNDY38OUtvO 0XhdIxzVkcarDQ7ZsFxzxMJ2vupOZZ/wZ5rxYkL5HB6FDlfkMBXo+MT2Pl0wFPY7MH0R j49AVfE3pn/ttacKCnmD/H5erx6p2PyXihQ3HPHdeWqzaYh671QdV5s9zFLTuHjkVARF z4Cw== X-Forwarded-Encrypted: i=1; AKwUvBywWC1rcgjAORToV6unmSEuL/IP6/sTXLvY8rOzYYnKakmyyeMwM0qW+ZIycAhaeHk+FL3QMfQ0j6Pc2Yo=@vger.kernel.org X-Gm-Message-State: AFuF++loOUthcBOhfxuMSzUdYDH3upjfeSb7RvvoG7K+PavaapEZrl/c jvUPmdp9xKKsecer8RPRAU31d32iYerQduo4tgfgRADZ+oaSSlTZCs11 X-Gm-Gg: AYBFou1ouVpo6eyRtkKUf9mE9eg01sIXWUJXilptoIM3jiICR/hUscrhAoDmPvIPQRv z5oEr5k24xgQBqppyVMkGJsyiS0c+WCn9JoWTk+HfEJkpr6/SWyL4R++JLNKlFgz41DuaU1f/Hh 6iJHPhOJ4lBoD6J0ioNGPOwsKWmu9kwBlXKr3xKbDKA3kx4pQaBaJR79gsbVH8wBFSZjKt+i4Le ebzDy9Hfeyuxff/by3ndp7NwfK15A6g26IzzcReHMykeNjdoS6NoyS+yfnJftoK0EP33EnCCSEf Wlcjhu3ifJgOCx6LWx9k3yRGfOMz+lyKMA/NjMmqJCqOys4OokZasEB5uOWLaH+VKpjYgAxZur/ CxB+JyHitl5I7Bg/mVbSaIZFjoqilaDfqhsxscQlPg8mdykbBIKd0sMyZD8v1yfGPQ+GYD6AUkw hF98I4Eta/u9SpbQb7vjNxk0kmg1s3hPeiMPpmrIEkl85ZgWHd/aOts9Bcdrj5osk6D6F7QbXud 8Wk0WiMftVVpTaXFxx95o3d8V3pitQz7Xx3rtL0WrI= X-Received: by 2002:a05:690c:9:b0:87f:bf8d:3377 with SMTP id 00721157ae682-87fbf8d35efmr29761407b3.63.1788982692601; Wed, 09 Sep 2026 12:38:12 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.38.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:38:12 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 10/15] landlock: Expose the ruleset fd lookup to the rest of Landlock Date: Wed, 9 Sep 2026 15:37:13 -0400 Message-ID: <20260909193719.518517-11-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Rename get_ruleset_from_fd() to landlock_get_ruleset_from_fd() and give it external linkage within Landlock, declared in ruleset.h next to the other ruleset lifetime helpers. A following commit implements the LSM kfunc policy hooks, which need to translate a ruleset fd into a landlock_ruleset reference from outside syscalls.c. No behavioral change. Cc: Micka=C3=ABl Sala=C3=BCn Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. security/landlock/ruleset.h | 3 +++ security/landlock/syscalls.c | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index b536fa0425b7..b58e3d9846af 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -214,6 +214,9 @@ int landlock_store_rule(struct landlock_rules *const ru= les, =20 void landlock_free_rules(struct landlock_rules *const rules); =20 +struct landlock_ruleset *landlock_get_ruleset_from_fd(const int fd, + const fmode_t mode); + /** * landlock_get_rule_root - Get the root of a rule tree by key type * diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 1d02d57f4c48..cb294a3582ae 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -305,8 +305,8 @@ SYSCALL_DEFINE3(landlock_create_ruleset, * Returns an owned ruleset from a FD. It is thus needed to call * landlock_put_ruleset() on the return value. */ -static struct landlock_ruleset *get_ruleset_from_fd(const int fd, - const fmode_t mode) +struct landlock_ruleset *landlock_get_ruleset_from_fd(const int fd, + const fmode_t mode) { CLASS(fd, ruleset_f)(fd); struct landlock_ruleset *ruleset; @@ -486,7 +486,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_f= d, return -EINVAL; =20 /* Gets and checks the ruleset. */ - ruleset =3D get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE); + ruleset =3D landlock_get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE); if (IS_ERR(ruleset)) return PTR_ERR(ruleset); =20 @@ -585,7 +585,8 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, rule= set_fd, const __u32, (flags & ~LANDLOCK_RESTRICT_SELF_TSYNC) =3D=3D LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) { /* Gets and checks the ruleset. */ - ruleset =3D get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ); + ruleset =3D landlock_get_ruleset_from_fd(ruleset_fd, + FMODE_CAN_READ); if (IS_ERR(ruleset)) return PTR_ERR(ruleset); } --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yw1-f177.google.com (mail-yw1-f177.google.com [209.85.128.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 43F9B3C0613 for ; Wed, 9 Sep 2026 19:38:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982703; cv=none; b=aetLp1tO34aSft6uaJc22sc8qZh8tICfitaYXThTdWM2K3/SaLpOlUY6XSotrQapL9Jj1izyXrTXrr6p8OtnkG/xWzDuvOF1D2bBXcXTODilqHQa0YxW4AUgVkP1/eUBGoxH8YHfPr4irxP15dUfooR5ERVRLL0evOuV9SpMIis= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982703; c=relaxed/simple; bh=A6yjzTdbHZqnguHBbm4vFsZKuEWYm2FWBIz+/7/XZos=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=u9vS1QGx7U42Qn7UjlOxzZSmhzrXZb0nkP5xWekLAcMpNuiydadLKir0xVYP/DpgKChc/PHF+P7HgEYvqowa52Ox4e7ShHRlRx8+eXpWq35vUMHjJ9+KL3B8Evmo7wruiXJZkkV7h6VHpBti7qaTwOiuElhNWcmLLn8+Pv/R7Wk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=haKPfA+B; arc=none smtp.client-ip=209.85.128.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="haKPfA+B" Received: by mail-yw1-f177.google.com with SMTP id 00721157ae682-861f30636f9so98982487b3.0 for ; Wed, 09 Sep 2026 12:38:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982697; x=1789587497; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=yPGhSQERTfDV/m5o0iCkbt26gt+YkajcCjMPDdzinZQ=; b=haKPfA+BtZW94SFI+Mc7CoY26tfA31Wp5QtqY0UYBWPhzkPh1E2V0cd4fbgVwAmaaJ jnRPw1qvpl3iT8bAOoLgF8OQdmnc2C19I845ykkhuAdDpyz1em62QH/vDEWbLA3FSrw7 8q7TG9BMjPXufDOnmjYBOvndFhpUPoLRzViWDT3G4Dod2vWX0Y0CuSqc3eBNdS8BX1Pc H6hQnDY1FbHlzParduuxujOMSltMtinkixo1r/UQ2Ur8fp216HAUeSTsXzjeo3iHnf1q DJPlS10ShEvzb4LlnnpyDbCwPIyrr+nsl42ckov2apuy19CFTIR4RV2InBIogsAFj+4A 0ifQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982697; x=1789587497; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=yPGhSQERTfDV/m5o0iCkbt26gt+YkajcCjMPDdzinZQ=; b=Mg0oBYJ3hTsF9SpoCXKIuGMgHm9X6zz2HTAMZgpNjMfFvgJpo3RM2PeGYVkgmjsjI8 zzLfqDJBwetKSDDy+BX+JWW6UyXHl1HHHiclDAZm3odgx1/wrH3+TeDqOSzi7HKLZukx oTwFu58tMuNGijSTVfkD1wEtodxH9M1g6SJ/gXZb/SjgrB40CZgje03L4DBmGDSoZGyd 2bLMXKGWQjHBRH1Ai9Z+nw1M1ZFde1Hln4BG26I+A2yZBLmpSCWPH1KvFOzcVsmL+i6x wIYDKKi8AJ9o32TqtOBbKo1QnDI56FcjJakaTfHn4Zu6cNCpVyiZc9Z7BRSgid6eq2+S P9ow== X-Forwarded-Encrypted: i=1; AKwUvBxzN9eNPOAe+PBkE8zdTRXMoU6AuySs6L6JrhTPSP/x2edexskV4ny38h+wyVKRkqCZR4pklhTiYFbIwgE=@vger.kernel.org X-Gm-Message-State: AFuF++k1EEigJ4aLY52LnpQBzhA9Pz4UE+BKyKkBcopY1RlYWu3xZHgU WSc4L86r8HXAlflvVra/+eAbkGbWwbKuNrAlTXqDjOE6+dGVcikRYsuL X-Gm-Gg: AYBFou0xKYuFLC0D+I7KB4wnU9XKQH+1gFc3qccmTM3rv6S5N+pAP0In9dB5LWUfbvc N5OJHLNRkGu94LK1CXAUv/SokTH6ljCB32bLldG9odBcf5OHk/NTtkzwfi/wuZR3kPEphAeZUaF +/dl1e01G2JKoXddLkKCqvZLsXWJ/kXlWEeEkAD1zNIZB6PiMFZJzkyxxFncz1ySK/JJiZd8RZz A2O4MaDUnRFnVVSuWPY/nrfHgTIwn8nCCRwV2HGxDknTSOB7fLWxidhLjh7XzC0O2Rf10JO8NwE h1liGZnx5wGko2j2A+sI/4HvqdPrpeP3xP2pViTX7Hwjn5QCkomU2b0OjWktEUuVAiz+ajqAVAe Nm+XASCBycT+8Y9rRooT5RkXqt9uPj/VA3MgqDJ7D4GadmydNpJrTsXbVJINFbNi34wZb4GwxYm UVlNvkbJE+Faecn/dNlOjHotEzqGOD219o+RTjKpcQrAREcv5NGirDRMiIM9ZIgHueUa/a5MLPk JeTUTAfHfV4IKU4uZBUJU4R4pdm/O1Y X-Received: by 2002:a05:690c:6513:b0:873:5c7b:c0fb with SMTP id 00721157ae682-8735c7bc1b0mr114372877b3.51.1788982696408; Wed, 09 Sep 2026 12:38:16 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.38.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:38:16 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 11/15] landlock: Factor the credential restriction out of landlock_restrict_self() Date: Wed, 9 Sep 2026 15:37:14 -0400 Message-ID: <20260909193719.518517-12-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Split the core of landlock_restrict_self() into two credential helpers: landlock_prepare_restriction() - translate the landlock_restrict_self(2) flags, merge the ruleset with the credentials' domain, and configure the new domain's log state, producing a struct landlock_restriction: the complete new state that the enforcement gives to a credential. landlock_apply_restriction() - enforce a computed restriction on credentials exclusively owned by the caller. This step cannot fail, so a caller may run it past its last point of failure. The merge runs under @ruleset->lock and the landlock_create_domain trace event is emitted before the lock is released, exactly as in the syscall before this change: the event still observes the ruleset snapshot that was merged, and it still fires before any thread-sync wait. Committing the hierarchy out of LANDLOCK_LOG_UNCOMMITTED moves along with it, so every domain computed by landlock_prepare_restriction() has its create/free trace events balanced, whether or not it ends up enforced. The syscall behaves exactly as before: prepare and apply run back to back on the prepared credentials. The no_new_privs/CAP_SYS_ADMIN precheck, the flag mask check, the TSYNC handling, and the landlock_enforce_domain trace event are syscall policy and stay in place. The point of the split is that application is decoupled from computation: a following commit restricts an execution from a BPF kfunc by staging a prepared restriction in the binprm credentials and applying it at the exec point of no return, with the flag translation, domain merge, and log configuration in one shared place. The restriction records the flags it was computed with instead of translating them into per-flag fields: consumers read the staged flags at application time, so a future flag that must be honored at enforcement travels with the restriction automatically, with no per-flag plumbing in the callers. Cc: Micka=C3=ABl Sala=C3=BCn Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. security/landlock/cred.c | 132 +++++++++++++++++++++++++++++++++++ security/landlock/cred.h | 31 ++++++++ security/landlock/syscalls.c | 96 ++++--------------------- 3 files changed, 178 insertions(+), 81 deletions(-) diff --git a/security/landlock/cred.c b/security/landlock/cred.c index 03449c26247e..f02706f12c7d 100644 --- a/security/landlock/cred.c +++ b/security/landlock/cred.c @@ -8,14 +8,146 @@ */ =20 #include +#include #include +#include +#include #include +#include +#include =20 #include "common.h" #include "cred.h" +#include "domain.h" #include "ruleset.h" #include "setup.h" =20 +#include + +/** + * landlock_prepare_restriction - Compute a credential restriction + * + * @llcred: Landlock credentials to restrict: provides the parent domain a= nd + * the previous log configuration. Not modified. + * @ruleset: Ruleset to enforce, or NULL for a log-configuration-only chan= ge. + * @flags: landlock_restrict_self(2) flags. The caller is responsible for + * validating them against the set of flags it supports. + * @restriction: Computed restriction. On success, holds a reference on + * @restriction->domain (if any), which + * landlock_apply_restriction() transfers to the restricted + * credentials. + * + * The restriction builds on @llcred's current state: the caller must apply + * it to (or stage it for) these same credentials. + * + * Return: 0 on success, -errno on failure. + */ +int landlock_prepare_restriction( + const struct landlock_cred_security *const llcred, + struct landlock_ruleset *const ruleset, const u32 flags, + struct landlock_restriction *const restriction) +{ + struct landlock_domain *new_dom; +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + /* Translates "off" and "on" flags to booleans. */ + const bool log_same_exec =3D + !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF); + const bool log_new_exec =3D + !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON); + const bool prev_log_subdomains =3D !llcred->log_subdomains_off; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + + *restriction =3D (struct landlock_restriction){ + .flags =3D flags, + }; + + if (!ruleset) + return 0; + + mutex_lock(&ruleset->lock); + new_dom =3D landlock_merge_ruleset(llcred->domain, ruleset); + if (IS_ERR(new_dom)) { + mutex_unlock(&ruleset->lock); + return PTR_ERR(new_dom); + } + /* + * Emits the domain-creation event while @ruleset->lock is still + * held, right after the merge, so an eBPF program attached to + * the tracepoint reads the exact ruleset that was merged into + * the domain: a consistent snapshot that a concurrent + * landlock_add_rule() (which holds the same lock) cannot + * modify. + * + * This must not be delayed past the return of this function. + * Holding @ruleset->lock across + * landlock_restrict_sibling_threads() would hang: a sibling + * thread blocked in landlock_add_rule() on the same + * @ruleset->lock cannot run the task_work that thread-sync + * waits for (the lock wait is uninterruptible). Emitting here + * keeps the lock off the thread-sync path. + * + * The trade-off is that the event fires for a domain that may + * never be enforced: a later (rare) thread-sync failure or an + * aborted execution drops it. Those paths free the domain, + * which emits the matching free_domain event so the create/free + * pair stays balanced. + */ + trace_landlock_create_domain(new_dom, ruleset); + mutex_unlock(&ruleset->lock); + +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + new_dom->hierarchy->log_same_exec =3D log_same_exec; + new_dom->hierarchy->log_new_exec =3D log_new_exec; + /* + * The creation event fired above, so move the domain out of + * LANDLOCK_LOG_UNCOMMITTED: its free_domain event must fire + * too, even if the domain is dropped before being enforced. + * Audit logging may still be disabled (DISABLED); tracing + * observes it anyway. + */ + if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains) + new_dom->hierarchy->log_status =3D LANDLOCK_LOG_DISABLED; + else + new_dom->hierarchy->log_status =3D LANDLOCK_LOG_PENDING; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + + restriction->domain =3D new_dom; + return 0; +} + +/** + * landlock_apply_restriction - Enforce a computed restriction on credenti= als + * + * @llcred: Landlock credentials to restrict, exclusively owned by the cal= ler + * (prepared and not yet committed). + * @restriction: Restriction computed by landlock_prepare_restriction() + * against the same credential state; its domain reference is + * transferred to @llcred. + * + * Cannot fail, so that a caller may apply a restriction past its last poi= nt + * of failure, e.g. an exec point of no return. + */ +void landlock_apply_restriction(struct landlock_cred_security *const llcre= d, + struct landlock_restriction *const restriction) +{ +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + if (restriction->flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF) + llcred->log_subdomains_off =3D true; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + + if (!restriction->domain) + return; + + /* Replaces the old domain. */ + landlock_put_domain(llcred->domain); + llcred->domain =3D restriction->domain; + restriction->domain =3D NULL; + +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + llcred->domain_exec |=3D BIT(llcred->domain->num_layers - 1); +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +} + static void hook_cred_transfer(struct cred *const new, const struct cred *const old) { diff --git a/security/landlock/cred.h b/security/landlock/cred.h index a5ff9957949a..88fa97fc3bd2 100644 --- a/security/landlock/cred.h +++ b/security/landlock/cred.h @@ -21,6 +21,29 @@ #include "ruleset.h" #include "setup.h" =20 +/** + * struct landlock_restriction - Computed credential restriction + * + * The result of landlock_prepare_restriction(): the new state that + * enforcing a ruleset with a set of landlock_restrict_self(2) flags + * gives to a credential, decoupled from its application. It is + * enforced with landlock_apply_restriction(), either right away + * (landlock_restrict_self(2)) or after a staging period (restriction + * of an execution). + */ +struct landlock_restriction { + /** + * @domain: New domain to enforce, owning a reference. NULL if the + * restriction only carries a log configuration change. + */ + struct landlock_domain *domain; + /** + * @flags: landlock_restrict_self(2) flags the restriction was + * computed with, validated by the caller. + */ + u32 flags; +}; + /** * struct landlock_cred_security - Credential security blob * @@ -152,6 +175,14 @@ landlock_get_applicable_subject(const struct cred *con= st cred, return NULL; } =20 +int landlock_prepare_restriction( + const struct landlock_cred_security *const llcred, + struct landlock_ruleset *const ruleset, const u32 flags, + struct landlock_restriction *const restriction); + +void landlock_apply_restriction(struct landlock_cred_security *const llcre= d, + struct landlock_restriction *const restriction); + __init void landlock_add_cred_hooks(void); =20 #endif /* _SECURITY_LANDLOCK_CRED_H */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index cb294a3582ae..9451376ccf50 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -9,7 +9,6 @@ =20 #include #include -#include #include #include #include @@ -31,7 +30,6 @@ #include =20 #include "cred.h" -#include "domain.h" #include "fs.h" #include "limits.h" #include "net.h" @@ -546,10 +544,9 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, rul= eset_fd, const __u32, struct landlock_ruleset *ruleset __free(landlock_put_ruleset) =3D NULL; struct landlock_domain *new_dom =3D NULL; struct cred *new_cred; - struct landlock_cred_security *new_llcred; + struct landlock_restriction restriction; bool process_wide; - bool __maybe_unused log_same_exec, log_new_exec, log_subdomains, - prev_log_subdomains; + int err; =20 if (!is_initialized()) return -EOPNOTSUPP; @@ -568,13 +565,6 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, rul= eset_fd, const __u32, !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) return -EPERM; =20 - /* Translates "off" flag to boolean. */ - log_same_exec =3D !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF); - /* Translates "on" flag to boolean. */ - log_new_exec =3D !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON); - /* Translates "off" flag to boolean. */ - log_subdomains =3D !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF); - /* * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with * -1 as ruleset_fd, optionally combined with @@ -596,85 +586,29 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ru= leset_fd, const __u32, if (!new_cred) return -ENOMEM; =20 - new_llcred =3D landlock_cred(new_cred); - -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - prev_log_subdomains =3D !new_llcred->log_subdomains_off; - new_llcred->log_subdomains_off =3D !prev_log_subdomains || - !log_subdomains; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ - /* * The only case when a ruleset may not be set is if * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set (optionally with * LANDLOCK_RESTRICT_SELF_TSYNC) and ruleset_fd is -1. We could * optimize this case by not calling commit_creds() if this flag was * already set, but it is not worth the complexity. + * + * There is no possible race condition while copying and manipulating + * the current credentials because they are dedicated per thread. */ - if (ruleset) { - /* - * There is no possible race condition while copying and - * manipulating the current credentials because they are - * dedicated per thread. - */ - mutex_lock(&ruleset->lock); - new_dom =3D landlock_merge_ruleset(new_llcred->domain, ruleset); - if (IS_ERR(new_dom)) { - mutex_unlock(&ruleset->lock); - abort_creds(new_cred); - return PTR_ERR(new_dom); - } - /* - * Emits the domain-creation event while @ruleset->lock is still - * held, right after the merge, so an eBPF program attached to - * the tracepoint reads the exact ruleset that was merged into - * the domain: a consistent snapshot that a concurrent - * landlock_add_rule() (which holds the same lock) cannot - * modify. - * - * This must come before the thread-sync wait below. Holding - * @ruleset->lock across landlock_restrict_sibling_threads() - * would hang: a sibling thread blocked in landlock_add_rule() - * on the same @ruleset->lock cannot run the task_work that - * thread-sync waits for (the lock wait is uninterruptible). - * Emitting here keeps the lock off the thread-sync path. - * - * The trade-off is that the event fires for a domain that a - * later (rare) thread-sync failure aborts. That path emits the - * matching free_domain event so the create/free pair stays - * balanced (see the thread-sync error path below). - */ - trace_landlock_create_domain(new_dom, ruleset); - mutex_unlock(&ruleset->lock); - -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - new_dom->hierarchy->log_same_exec =3D log_same_exec; - new_dom->hierarchy->log_new_exec =3D log_new_exec; - /* - * The creation event fired above, so move the domain out of - * LANDLOCK_LOG_UNCOMMITTED: its free_domain event must fire - * too, even if a thread-sync failure aborts it below. Audit - * logging may still be disabled (DISABLED); tracing observes it - * anyway. - */ - if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains) - new_dom->hierarchy->log_status =3D LANDLOCK_LOG_DISABLED; - else - new_dom->hierarchy->log_status =3D LANDLOCK_LOG_PENDING; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ - - /* Replaces the old (prepared) domain. */ - landlock_put_domain(new_llcred->domain); - new_llcred->domain =3D new_dom; - -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - new_llcred->domain_exec |=3D BIT(new_dom->num_layers - 1); -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + err =3D landlock_prepare_restriction(landlock_cred(new_cred), ruleset, + flags, &restriction); + if (err) { + abort_creds(new_cred); + return err; } =20 + new_dom =3D restriction.domain; + landlock_apply_restriction(landlock_cred(new_cred), &restriction); + if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) { - const int err =3D landlock_restrict_sibling_threads( - current_cred(), new_cred, flags); + err =3D landlock_restrict_sibling_threads(current_cred(), new_cred, + flags); if (err) { /* * Thread-sync failed (rare), so the new domain is --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yw1-f174.google.com (mail-yw1-f174.google.com [209.85.128.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CC77B3C1D4D for ; Wed, 9 Sep 2026 19:38:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982705; cv=none; b=gaI6+gWeVSbR39GBIW76BxG++DNj3hOc5A2HQlfXCsJDR5d5yNb4RYUyl1/4sSTVkxJz4foQFcBBWo3TqD1J1z1xWna6SO6H/h5clAltAQTpu6kWrL1rHbO+yCXPbIk+t2XRvwy2+Q+yL7QXS+IY7q8WQ/uDdSlWlUcKeGJkA8w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982705; c=relaxed/simple; bh=lZBPKN9tIckw18hI3pUXLnkD8axvL7yGCPUsmHMAcok=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=USZD+mfqlNPRKbUL2kVj0OCEbP83yGpf26Ub1HCAhCereSFMkd9mgjM6AQcG7nA014uSU+OsL9vZWzxhbGTdY27+JH0B7ZCgTilesIoe3dEEUMEaVh9VbdAhSrwRh0iGEpx4pC2+zNRDvJfmkVWmHc6lccdcJis4OIY7v9nkivE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=K90a1jZw; arc=none smtp.client-ip=209.85.128.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="K90a1jZw" Received: by mail-yw1-f174.google.com with SMTP id 00721157ae682-871c8a36fe3so73623877b3.1 for ; Wed, 09 Sep 2026 12:38:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982700; x=1789587500; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=6LXObdENkcO6lDYtxSgA/vA2hc5vAc6IcovovYK/Cr4=; b=K90a1jZwUx1X0o+np8ih7KyaX4bQp4Gn+IC+5AU+2tOwsUiWPNoREutI1UKRVMrkBr Txc0x9QuMifJYuTvMEgbiOaL9NYN+yGz/eFBg7tExxdhXUpfHZ+ltfr4Xqh+9eQClo0K ksiGZU7F/mDHm4nEG6Y/iG6Bu9dk8KgqXF7Ny4jlgYLT+K+ro5l9bUJUrLnymgIpqmLX V91Nykh6zqLu9EbMxR2LBlmUxSqGn5JlbR+Hpvcak+AOEPqH9RgHHGw8IffeVlFnBMKz vG2WJGTW1dKLkqnHsZ5vCmslqVXx01dfhimvs0A+I3P+Sb9e5VNQJVbN0rjuVlrBSafQ h05A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982700; x=1789587500; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=6LXObdENkcO6lDYtxSgA/vA2hc5vAc6IcovovYK/Cr4=; b=AAHUnssvjr04+MM/SwJ58Fx+gd/5m9VtuDVp7Ytd1P43iTSueSIJotbQSeTVjc+2bb p4hsBzw1rueMbpVlqCuVYcbNixA/TuOL9de4QlmdmnIaKfmznQjT3TMwmVAz6uJwKssN JZL+SWNv7lDIgJu+/uL91bXJFsa8GDFx8YpFV4EsuviI9Od3zFe7/etLtCgSZCRi2KMV lfZC+BG789/vB8kwK2du+EUChUHU9ePa1J7NbdzX/mw6+Mo+7zxhimDleNxZnnBge3aP ZGysxf0cdMKiONRkw7IwNvbqiDYTMK2RRUSIfOJpxvuqNC9DFewU/LhixfebhNmrQeDH 55Vg== X-Forwarded-Encrypted: i=1; AKwUvBwf6pz/v0o0X1b9ghWoJQDgXoaPGEY++F3UteYO0C0DBk+15IUOhidRIovxpzy6wRADVHkym4UYZ5JJkc8=@vger.kernel.org X-Gm-Message-State: AFuF++k+uP8j/4ls8g1L+Ti+hKjfrZPeOQsCfIqX//J1lExDiUQ9xHTP iBLjtgSWlU2m7TbzGVp2x5TICMQSCQDrGfgnBtuFKqXFdtNM8yE3h1d4 X-Gm-Gg: AYBFou3LSUrYOAash80I2NG7iJttHSy+sQZEp6/E9oQvoz2i0edM93Abli6evDGnDs/ 5+7rVZZmTshuv/bvKqL4KOHVmDjuXgExIsFJOzlcj2aVz1JZuZ81FdH0xh29pwz66fzVptCd2kC C4MIM0YgKK9R3IyPDcKYJhzqT2UhD9HosCUFrFx+OASiElC6sBKLVFHpPlEqaAKnBCN1f4PuRWY h/0oqH7qKh4NDmO6Oc2Fov43YYa9kJMxMvYNhwHqhrn+0ahjoNVIt4dgHxJd2dXeP7dnk58fqft nhqXmDPqaK3d1Nhlwu/G06xuLtmbwB1nq2BuSLOyb5/iwISFJsyYrTnkOSEQk/5MaF03ZfldiOR wvZyOzt4hjl24J1DvvNhSTbCkd3Y7n8frCE648ab7ZxXaYnPhOLWspbKZjJ7SgLna6JTk5b0WZ5 a9g3EYG+jOpFFr4fn/NqbwTZKTC/VR6fcMWlF/ScG9CwkbrF1ZHA+omwZlUlCmOHVEGG0HNeoSF sYvUbHqSax0z3SYfY55p2JUB2kubd+VSdEet6TJeXE= X-Received: by 2002:a05:690c:288:b0:87e:2480:df7e with SMTP id 00721157ae682-87e2480e524mr54158337b3.49.1788982700039; Wed, 09 Sep 2026 12:38:20 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.38.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:38:19 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 12/15] landlock: Free rulesets after an RCU grace period Date: Wed, 9 Sep 2026 15:37:15 -0400 Message-ID: <20260909193719.518517-13-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Defer every ruleset free behind an RCU grace period, and keep the fields that stay readable while a free is pending out of the union that overlays the deferred-free work item. The policy_object_get LSM hook lets a caller holding only an RCU-protected pointer to a ruleset (e.g. loaded from a BPF map kptr field under rcu_read_lock()) race a refcount_inc_not_zero() against the drop of the last reference. For that to be sound, the ruleset's memory, and its reference count in particular, must remain valid until every RCU reader that could still observe the pointer is done: free the ruleset through queue_rcu_work(), which waits for a grace period before running the free work. The work item is overlaid with the fields that no one may touch once @usage reaches zero: @lock, @quiet_masks and @handled_masks. @usage itself stays outside the union so a racing reader observes zero instead of the work item's bytes, and the tracing fields @version and @id stay outside too because the landlock_free_ruleset trace event reads them when the queued work finally runs. Since queueing the work never sleeps, the might_sleep() annotation is dropped: a following commit releases ruleset references from BPF object destructors that cannot sleep. Cc: Micka=C3=ABl Sala=C3=BCn Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. security/landlock/ruleset.c | 24 ++++++++++++++--- security/landlock/ruleset.h | 54 ++++++++++++++++++++++++------------- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 0d07707523cd..00a6b9938fd1 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -21,6 +21,7 @@ #include #include #include +#include #include =20 #include "access.h" @@ -346,9 +347,26 @@ static void free_ruleset(struct landlock_ruleset *cons= t ruleset) kfree(ruleset); } =20 +static void free_ruleset_work(struct work_struct *const work) +{ + struct landlock_ruleset *ruleset; + + ruleset =3D container_of(to_rcu_work(work), struct landlock_ruleset, + work_free); + free_ruleset(ruleset); +} + +/* + * RCU readers (cf. the policy_object_get LSM hook) may call + * refcount_inc_not_zero() on a ruleset they hold no reference to: the mem= ory + * must survive a grace period after the last put. Queueing the free also + * makes this callable from contexts that cannot sleep (cf. the + * policy_object_put LSM hook). + */ void landlock_put_ruleset(struct landlock_ruleset *const ruleset) { - might_sleep(); - if (ruleset && refcount_dec_and_test(&ruleset->usage)) - free_ruleset(ruleset); + if (ruleset && refcount_dec_and_test(&ruleset->usage)) { + INIT_RCU_WORK(&ruleset->work_free, free_ruleset_work); + queue_rcu_work(system_dfl_wq, &ruleset->work_free); + } } diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index b58e3d9846af..1465f8a5c464 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -15,6 +15,7 @@ #include #include #include +#include =20 #include "access.h" #include "limits.h" @@ -157,12 +158,10 @@ struct landlock_ruleset { */ struct landlock_rules rules; /** - * @lock: Protects against concurrent modifications of @rules, if @usage - * is greater than zero. - */ - struct mutex lock; - /** - * @usage: Number of file descriptors referencing this ruleset. + * @usage: Number of file descriptors referencing this ruleset. Kept + * outside the union with @work_free: RCU readers may still call + * refcount_inc_not_zero() while a queued free waits out the grace + * period. */ refcount_t usage; =20 @@ -175,22 +174,41 @@ struct landlock_ruleset { */ u32 version; /** - * @id: Unique identifier for this ruleset, used for tracing. + * @id: Unique identifier for this ruleset, used for tracing. Kept + * outside the union with @work_free: the free_ruleset trace event + * reads it after the free has been queued. */ u64 id; #endif /* CONFIG_TRACEPOINTS */ =20 - /** - * @quiet_masks: Stores the quiet flags for an unmerged ruleset. For a - * merged domain, this is stored in each layer's struct - * landlock_hierarchy instead. - */ - struct access_masks quiet_masks; - /** - * @handled_masks: Contains the subset of filesystem and network actions - * that are handled by this ruleset. - */ - struct access_masks handled_masks; + union { + /** + * @work_free: Enables to free a ruleset after an RCU grace + * period, within a lockless section. This is queued by + * landlock_put_ruleset() when @usage reaches zero. The + * fields @lock, @quiet_masks and @handled_masks are then + * unused. + */ + struct rcu_work work_free; + struct { + /** + * @lock: Protects against concurrent modifications of + * @rules, if @usage is greater than zero. + */ + struct mutex lock; + /** + * @quiet_masks: Stores the quiet flags for an unmerged + * ruleset. For a merged domain, this is stored in each + * layer's struct landlock_hierarchy instead. + */ + struct access_masks quiet_masks; + /** + * @handled_masks: Contains the subset of filesystem and + * network actions that are handled by this ruleset. + */ + struct access_masks handled_masks; + }; + }; }; =20 struct landlock_ruleset * --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yw1-f171.google.com (mail-yw1-f171.google.com [209.85.128.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AC5023D9DB0 for ; Wed, 9 Sep 2026 19:38:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982709; cv=none; b=I67P/HWR3qR6LQP8mvqMGtA5mqAqn0KD20VW2SLLAa2IBxMlEONhPFdCG8O3yFS6b7BOUm81PtmKzeygwM+Sk7PC6qR9Lmmc0p3blB/DslZp5kuKohFewpCnMWJujGBxi8U7AUKOZt/wynEvZJFm0urm7JcENGtto3wtKI7Hw5g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982709; c=relaxed/simple; bh=GVQa37PtHTlxgvyLGvpWACiTjTDsHznsn6e2nf6Q868=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=llBKymL1XLcF4XOj06u0SGK5jykzemkrN9p3YymWOflSa2H6RpUbGfl/GvO4bKilfo4t13NcKWHjNCfDwHG+Oe+7BB94GNxKczlqR7h9jxEJwpZ3k8YbC9swit+eJHIo3YWOaqS2aD0Jw8AoxPmzBZvle5drh4hEx0KV7a2+7eo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=KMvDAAgB; arc=none smtp.client-ip=209.85.128.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="KMvDAAgB" Received: by mail-yw1-f171.google.com with SMTP id 00721157ae682-8588583a7c3so67389277b3.2 for ; Wed, 09 Sep 2026 12:38:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982704; x=1789587504; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=H3JvvSRMnHbVw1Zr9Fg0dlqwvarfrIGvlpSZqWlrGmY=; b=KMvDAAgBTt5nV0zKP5BQ5+5OBO7etrZ4Agz4HJsHvOOxG7SnhfVgFMD4Ts72eM55eW pVIuYB5tDY4ueNP4VMgBj0pDUsvNQ7rDFY4bX7R9C3R8KwGwJv9Pgzilp7TM0kpNOyPE qVWe1dgBJmP6LYvPmWyek84aEpTBtm6opIePwREInyvSsm21zRNbRuZQCOSX+EoIcfTq vQFvJbovR1NwZoksYumHYw4C7hE7s/MOtLX6ABYc4XpavyFHPOgUgY+f0UyEhRizFoQa 6WexKI4bMe/3ThAu2IS9LxBnztVC5m/OuC6paFImJmObfPCN83vO1nLwyJxYWkDBST7V 0UFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982704; x=1789587504; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=H3JvvSRMnHbVw1Zr9Fg0dlqwvarfrIGvlpSZqWlrGmY=; b=hLvmRJ4KQ0oyQ89AbG+8TyG598nHshKAp5uWXHODzZzX5rHHzboG8j3bb3uK6n9vvW +sswnTPXbWoHHgOYEY4txJyvpzkYgHm9jLDzuwXlZkvMwTs0rpBe3YNXonGWS0Vll+4l h4HIuCfl69tGyW8CQGrB8lnAaCz8dsFEulZ920pnu+Nu6GMaGKRVywzxeE9lmpxJJdA+ 1ZHBs26QoTRHC+w2tD1kML03syVXu+9aianZZ1cNT9x6gXE5/6//dv9/Y7e/gNoc5h3d x26XK+QHXLouxIXVRYeFH0BwlCxumheRDNOLMoDhmp2u398tG+56mB9489RpTia9qw1d OS7g== X-Forwarded-Encrypted: i=1; AKwUvByZhQnwtH+awOK+g+p7mL24XHFS3SFYcNVYCbkz5VNa2U03DBD1TVBKp9F7qvpEyjH2/65DFn+ZZPuC4Rw=@vger.kernel.org X-Gm-Message-State: AFuF++nUpDAzanL+F3t5994jxNEAVl++fuQu6MYemBmeDplFsbpLpleu MgfGfe2Pv1VQjrz6z7LerGdEgsdQYZB6Kf0KjPB0z9o0q5lr2iRU1Ewy X-Gm-Gg: AYBFou14PIuaQDYk1LE0qMlWKdWmJU2vnmw2jOgnEdoRPZCFvMok38gz0v5sk2b73K3 KO/LVcRS3syGACUIcL5PmF9Ob0l494uZSFU1Q6Bh0FiKhSYowg9lf+9pHNxTog1N9IQpkJ4LuV1 jaEfNmzBrHGemyJIXmNQziH4h4bx2Auui0dWGQMmux3G9LEyc5rOK2ulvcUNV3K+YaO3+gF4OoI +rgoMqTO4FoynTejFnTwF+9ZfCcY64mj1zJ7nIT6VvvmG2Jlm6NrAgccznHiDRlWXwBHejQ7BGW vZIy6zV33Z63U5rISq/aGoLLqICll9YNsMTHC2kRzY1MCMTSaJPjHdG26ShDnd7sQWdw2PN6oxc S1b9vwQWi+aQeRLO0stKkajApGsyTfOum5fmB5adeCAReiZBZXZH1XLsug25KDZo3RfOSK9h87H ujdiSHBH3aiECfLyRHCNfYbW13P9frig5vA5CZHEkGIkf4b7g/hS5P1/nFB3R6IoTeCYpPxn5vm 5qjPku555HUpNMNZTDaXyXKDPWzGPuX X-Received: by 2002:a05:690c:e28a:10b0:873:5bb2:6c0a with SMTP id 00721157ae682-8735bb26e53mr88038077b3.52.1788982704120; Wed, 09 Sep 2026 12:38:24 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.38.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:38:23 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 13/15] landlock: Implement the LSM policy object hooks Date: Wed, 9 Sep 2026 15:37:16 -0400 Message-ID: <20260909193719.518517-14-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Implement the generic LSM hooks exposing Landlock rulesets as policy objects to BPF. The new code is gated on CONFIG_BPF_LSM, the only configuration where the kfuncs calling the hooks exist. struct lsm_policy_object is embedded in struct landlock_ruleset and tagged with LSM_ID_LANDLOCK and LANDLOCK_POLICY_TYPE_RULESET at ruleset creation; the hooks resolve the ruleset with container_of() and no Landlock type crosses the LSM boundary. The type namespace is private to Landlock and routes the container_of() resolution once several policy object types exist: the consuming hooks reject an object of an unexpected type with -EINVAL (the put hook, which cannot fail, warns instead), while from_fd only produces objects and needs no check. The embedded object sits outside the union overlaying the deferred-free work item: an RCU reader may still read its identity while a queued free waits out the grace period. - policy_object_from_fd() translates a ruleset fd, created with landlock_create_ruleset(2) and populated with landlock_add_rule(2), into an owned ruleset reference, validated the same way as for the Landlock syscalls (ruleset file type, FMODE_CAN_READ). A fd referring to a file that is not a Landlock ruleset is declined with -EOPNOTSUPP instead of the syscall's -EBADFD: it may be another LSM's policy object, and the decline lets the framework offer it to the LSM it belongs to. - policy_object_put() releases such a reference. The hook may be reached from BPF object destructors that cannot sleep, which is fine: ruleset puts queue the free as RCU work. - policy_object_get() acquires an additional reference for a caller that only holds an RCU-protected pointer, e.g. loaded from a BPF map kptr field under rcu_read_lock(). The reference is taken with refcount_inc_not_zero(); the racing reader's access to the ruleset memory is safe because rulesets are freed after an RCU grace period. - bprm_apply_policy_object() shares the landlock_restrict_self(2) path: it calls landlock_prepare_restriction() on the credentials prepared in the binprm and stages the computed restriction (the merged struct landlock_domain) in their Landlock blob. The flags are the landlock_restrict_self(2) flags; only LANDLOCK_RESTRICT_SELF_TSYNC is rejected with -EINVAL because the restriction targets the execution, not the calling threads. LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS is honored, not ignored: the syscall implements it directly in landlock_restrict_self(), so the staged path reads it back from the staged restriction's flags and sets no_new_privs on the executing task at application time. The executed program then starts with no_new_privs set, binding it and all its descendants; the current execution's privilege computation is unaffected, as the bprm credentials (including any setuid elevation) were computed before bprm_committing_creds(). The staged restriction is enforced by a bprm_committing_creds() hook with the same landlock_apply_restriction() call as the syscall, past the exec point of no return: an execution either starts confined by the domain or, if it fails earlier, leaves the calling task untouched. The applied layer is accounted in domain_exec so the LOG_SAME_EXEC and LOG_NEW_EXEC audit flags follow the executed program. There is no no_new_privs/CAP_SYS_ADMIN precondition here: gating who may load a policy-applying BPF program is the BPF attachment's privilege model. The application emits the landlock_enforce_domain trace event, keeping the event's invariant that every enforcement falls between the domain's create_domain and free_domain events. The process is single-threaded past de_thread(), so the single event concludes the operation with complete =3D=3D 1 and process_wide =3D=3D 1. no_new_privs reports the post-flag state; on this path a value of 0 carries no authorization meaning and only tells the observer that the confined program can still elevate through future setuid execs, which the event's documentation now spells out. The staged restriction's lifetime is fully covered: a second bprm_apply_policy_object() call on the same execution releases and replaces the previously staged restriction, an execution failing before the point of no return releases it through hook_cred_free(), and the application clears the staged domain so committed task credentials never carry one. landlock_cred_copy(), now also used by hook_cred_transfer(), upholds that invariant by never copying a staged restriction. Cc: Micka=C3=ABl Sala=C3=BCn Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. include/trace/events/landlock.h | 15 +++- security/landlock/Makefile | 2 + security/landlock/bpf.c | 152 ++++++++++++++++++++++++++++++++ security/landlock/bpf.h | 21 +++++ security/landlock/cred.c | 16 ++-- security/landlock/cred.h | 16 ++++ security/landlock/limits.h | 4 + security/landlock/ruleset.c | 6 ++ security/landlock/ruleset.h | 22 +++++ security/landlock/setup.c | 2 + 10 files changed, 245 insertions(+), 11 deletions(-) create mode 100644 security/landlock/bpf.c create mode 100644 security/landlock/bpf.h diff --git a/include/trace/events/landlock.h b/include/trace/events/landloc= k.h index f82588f6f90e..012ab9dcccb2 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -500,13 +500,22 @@ TRACE_EVENT(landlock_create_domain, * enforcement time: 1 if set (by a prior * :manpage:`prctl(2)` %PR_SET_NO_NEW_PRIVS or by * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS), 0 if the domain - * was enforced with %CAP_SYS_ADMIN instead. + * was enforced with %CAP_SYS_ADMIN instead, or without + * either precondition on the BPF exec path (see below). * * Emitted for each thread sys_landlock_restrict_self() enforces the * domain on, in that thread's own context, right after its * commit_creds(), so it fires only once the thread is irreversibly - * enforcing the domain (aborted operations emit none). Not - * balanced; every enforcement falls between the domain's + * enforcing the domain (aborted operations emit none). Also emitted + * at execve(2)'s point of no return when a BPF-staged policy object + * is applied to the execution (see bpf_lsm_policy_apply_bprm()): the + * process is single-threaded after de_thread(), so the single event + * has @complete =3D=3D 1 and @process_wide =3D=3D 1. On that path, + * @no_new_privs =3D=3D 0 carries no authorization meaning (the authority + * is the privilege to attach the BPF program, not + * no_new_privs/%CAP_SYS_ADMIN); it only tells the observer that the + * confined program can still elevate through future setuid execs. + * Not balanced; every enforcement falls between the domain's * landlock_create_domain and landlock_free_domain events. * * @complete =3D=3D 1 && @process_wide =3D=3D 1 means the whole process is diff --git a/security/landlock/Makefile b/security/landlock/Makefile index 2711f4876939..606bc91522e2 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -20,3 +20,5 @@ landlock-$(CONFIG_SECURITY_LANDLOCK_LOG) +=3D \ landlock-$(CONFIG_AUDIT) +=3D audit.o =20 landlock-$(CONFIG_TRACEPOINTS) +=3D trace.o + +landlock-$(CONFIG_BPF_LSM) +=3D bpf.o diff --git a/security/landlock/bpf.c b/security/landlock/bpf.c new file mode 100644 index 000000000000..28dc68013251 --- /dev/null +++ b/security/landlock/bpf.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - LSM policy object hooks + * + * Copyright =C2=A9 2026 Justin Suess + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bpf.h" +#include "cred.h" +#include "domain.h" +#include "limits.h" +#include "ruleset.h" +#include "setup.h" + +#include + +static int hook_bprm_apply_policy_object(struct linux_binprm *bprm, + struct lsm_policy_object *object, + u32 flags) +{ + struct landlock_cred_security *bprm_llcred =3D landlock_cred(bprm->cred); + struct landlock_ruleset *ruleset; + struct landlock_restriction restriction; + int err; + + if (object->type !=3D LANDLOCK_POLICY_TYPE_RULESET) + return -EINVAL; + + ruleset =3D container_of(object, struct landlock_ruleset, policy_object); + + /* + * landlock_restrict_self(2) flags minus TSYNC, which targets + * the calling threads, not the execution. + */ + if ((flags | LANDLOCK_MASK_RESTRICT_BINPRM) !=3D + LANDLOCK_MASK_RESTRICT_BINPRM) + return -EINVAL; + + err =3D landlock_prepare_restriction(bprm_llcred, ruleset, flags, + &restriction); + if (err) + return err; + + /* + * Replaces (and releases) a previously staged restriction. + * Nothing is enforced until bprm_committing_creds(); a failed + * execution drops the staged restriction in hook_cred_free(). + */ + landlock_put_domain(bprm_llcred->staged.domain); + bprm_llcred->staged =3D restriction; + return 0; +} + +static void hook_bprm_committing_creds(const struct linux_binprm *bprm) +{ + struct landlock_cred_security *bprm_llcred =3D landlock_cred(bprm->cred); + struct landlock_domain *domain =3D bprm_llcred->staged.domain; + + if (!domain) + return; + + /* Set first so the enforcement event reports the post-flag state. */ + if (bprm_llcred->staged.flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) + task_set_no_new_privs(current); + + /* The application clears @staged's domain pointer. */ + landlock_apply_restriction(bprm_llcred, &bprm_llcred->staged); + + /* + * Past de_thread(), the process is single-threaded: this single + * event both concludes the operation and covers the whole + * process. + */ + trace_landlock_enforce_domain(domain, true, true, + task_no_new_privs(current)); +} + +static int hook_policy_object_from_fd(int fd, struct lsm_policy_object **o= bject) +{ + struct landlock_ruleset *ruleset; + + ruleset =3D landlock_get_ruleset_from_fd(fd, FMODE_CAN_READ); + if (IS_ERR(ruleset)) { + if (ruleset =3D=3D ERR_PTR(-EBADFD)) + return -EOPNOTSUPP; + return PTR_ERR(ruleset); + } + + *object =3D &ruleset->policy_object; + return 0; +} + +/* + * The caller holds no reference, only an RCU-protected pointer: the + * RCU-deferred ruleset free keeps the memory valid for the + * inc_not_zero() race against a concurrent last put. + */ +static int hook_policy_object_get(struct lsm_policy_object *object) +{ + struct landlock_ruleset *ruleset; + + if (object->type !=3D LANDLOCK_POLICY_TYPE_RULESET) + return -EINVAL; + + ruleset =3D container_of(object, struct landlock_ruleset, policy_object); + if (!refcount_inc_not_zero(&ruleset->usage)) + return -ENOENT; + return 0; +} + +static void hook_policy_object_put(struct lsm_policy_object *object) +{ + struct landlock_ruleset *ruleset; + + /* + * The type routes the container_of() resolution once several + * policy object types exist; only rulesets are referenced today. + */ + if (WARN_ON_ONCE(object->type !=3D LANDLOCK_POLICY_TYPE_RULESET)) + return; + + ruleset =3D container_of(object, struct landlock_ruleset, policy_object); + + /* + * May be reached from BPF object destructors that cannot sleep, + * which is fine: the put queues the free as RCU work. + */ + landlock_put_ruleset(ruleset); +} + +static struct security_hook_list landlock_hooks[] __ro_after_init =3D { + LSM_HOOK_INIT(bprm_apply_policy_object, hook_bprm_apply_policy_object), + LSM_HOOK_INIT(bprm_committing_creds, hook_bprm_committing_creds), + LSM_HOOK_INIT(policy_object_from_fd, hook_policy_object_from_fd), + LSM_HOOK_INIT(policy_object_get, hook_policy_object_get), + LSM_HOOK_INIT(policy_object_put, hook_policy_object_put), +}; + +__init void landlock_add_bpf_hooks(void) +{ + security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), + &landlock_lsmid); +} diff --git a/security/landlock/bpf.h b/security/landlock/bpf.h new file mode 100644 index 000000000000..7c0f199c630a --- /dev/null +++ b/security/landlock/bpf.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - LSM policy object hooks + * + * Copyright =C2=A9 2026 Justin Suess + */ + +#ifndef _SECURITY_LANDLOCK_BPF_H +#define _SECURITY_LANDLOCK_BPF_H + +#include + +#ifdef CONFIG_BPF_LSM +__init void landlock_add_bpf_hooks(void); +#else /* CONFIG_BPF_LSM */ +static inline void landlock_add_bpf_hooks(void) +{ +} +#endif /* CONFIG_BPF_LSM */ + +#endif /* _SECURITY_LANDLOCK_BPF_H */ diff --git a/security/landlock/cred.c b/security/landlock/cred.c index f02706f12c7d..8e5b1b6c165e 100644 --- a/security/landlock/cred.c +++ b/security/landlock/cred.c @@ -151,11 +151,7 @@ void landlock_apply_restriction(struct landlock_cred_s= ecurity *const llcred, static void hook_cred_transfer(struct cred *const new, const struct cred *const old) { - const struct landlock_cred_security *const old_llcred =3D - landlock_cred(old); - - landlock_get_domain(old_llcred->domain); - *landlock_cred(new) =3D *old_llcred; + landlock_cred_copy(landlock_cred(new), landlock_cred(old)); } =20 static int hook_cred_prepare(struct cred *const new, @@ -167,10 +163,14 @@ static int hook_cred_prepare(struct cred *const new, =20 static void hook_cred_free(struct cred *const cred) { - struct landlock_domain *const dom =3D landlock_cred(cred)->domain; + struct landlock_cred_security *const llcred =3D landlock_cred(cred); + + landlock_put_domain_deferred(llcred->domain); =20 - if (dom) - landlock_put_domain_deferred(dom); +#ifdef CONFIG_BPF_LSM + /* Releases a restriction staged for an aborted execution. */ + landlock_put_domain_deferred(llcred->staged.domain); +#endif /* CONFIG_BPF_LSM */ } =20 #ifdef CONFIG_SECURITY_LANDLOCK_LOG diff --git a/security/landlock/cred.h b/security/landlock/cred.h index 88fa97fc3bd2..9a2971197892 100644 --- a/security/landlock/cred.h +++ b/security/landlock/cred.h @@ -59,6 +59,16 @@ struct landlock_cred_security { */ struct landlock_domain *domain; =20 +#ifdef CONFIG_BPF_LSM + /** + * @staged: Restriction staged by the bprm_apply_policy_object() hook, + * owning its domain reference, applied at bprm_committing_creds(). + * Only ever set on credentials prepared for an execution; committed + * task credentials never carry a staged restriction. + */ + struct landlock_restriction staged; +#endif /* CONFIG_BPF_LSM */ + #ifdef CONFIG_SECURITY_LANDLOCK_LOG /** * @domain_exec: Bitmask identifying the domain layers that were enforced= by @@ -99,6 +109,12 @@ static inline void landlock_cred_copy(struct landlock_c= red_security *dst, *dst =3D *src; =20 landlock_get_domain(src->domain); + +#ifdef CONFIG_BPF_LSM + /* Only bprm credentials own a staged restriction: never copied. */ + WARN_ON_ONCE(src->staged.domain); + dst->staged =3D (struct landlock_restriction){}; +#endif /* CONFIG_BPF_LSM */ } =20 static inline struct landlock_domain *landlock_get_current_domain(void) diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 1a7c5fb8f6fd..9aeb99b2f173 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -37,6 +37,10 @@ #define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS #define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - = 1) =20 +/* Subset of the restrict-self flags applicable to an execution. */ +#define LANDLOCK_MASK_RESTRICT_BINPRM \ + (LANDLOCK_MASK_RESTRICT_SELF & ~LANDLOCK_RESTRICT_SELF_TSYNC) + /* clang-format on */ =20 #endif /* _SECURITY_LANDLOCK_LIMITS_H */ diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 00a6b9938fd1..6de326b71a5a 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -23,6 +23,7 @@ #include #include #include +#include =20 #include "access.h" #include "id.h" @@ -51,6 +52,11 @@ landlock_create_ruleset(const access_mask_t fs_access_ma= sk, mutex_init(&new_ruleset->lock); new_ruleset->rules.root_inode =3D RB_ROOT; =20 +#ifdef CONFIG_BPF_LSM + new_ruleset->policy_object.lsmid =3D LSM_ID_LANDLOCK; + new_ruleset->policy_object.type =3D LANDLOCK_POLICY_TYPE_RULESET; +#endif /* CONFIG_BPF_LSM */ + #if IS_ENABLED(CONFIG_INET) new_ruleset->rules.root_net_port =3D RB_ROOT; #endif /* IS_ENABLED(CONFIG_INET) */ diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 1465f8a5c464..22edb140bb7f 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -15,6 +15,7 @@ #include #include #include +#include #include =20 #include "access.h" @@ -146,6 +147,16 @@ struct landlock_rules { u32 num_rules; }; =20 +#ifdef CONFIG_BPF_LSM +/* + * Landlock's lsm_policy_object types. The namespace is private to + * Landlock; 0 stays reserved as "unset". + */ +enum landlock_policy_type { + LANDLOCK_POLICY_TYPE_RULESET =3D 1, +}; +#endif /* CONFIG_BPF_LSM */ + /** * struct landlock_ruleset - Landlock ruleset * @@ -157,6 +168,17 @@ struct landlock_ruleset { * @rules: Red-black tree storage for rules. */ struct landlock_rules rules; + +#ifdef CONFIG_BPF_LSM + /** + * @policy_object: Identity under which the ruleset is handed out + * to BPF programs as a referenced kptr: the LSM policy kfuncs + * dispatch back to Landlock through its lsmid. Kept outside the + * union with @work_free: RCU readers may read its lsmid while a + * queued free waits out the grace period. + */ + struct lsm_policy_object policy_object; +#endif /* CONFIG_BPF_LSM */ /** * @usage: Number of file descriptors referencing this ruleset. Kept * outside the union with @work_free: RCU readers may still call diff --git a/security/landlock/setup.c b/security/landlock/setup.c index 47dac1736f10..3b7e18edadfb 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -11,6 +11,7 @@ #include #include =20 +#include "bpf.h" #include "common.h" #include "cred.h" #include "errata.h" @@ -68,6 +69,7 @@ static int __init landlock_init(void) landlock_add_task_hooks(); landlock_add_fs_hooks(); landlock_add_net_hooks(); + landlock_add_bpf_hooks(); landlock_init_id(); landlock_initialized =3D true; pr_info("Up and running.\n"); --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yw1-f171.google.com (mail-yw1-f171.google.com [209.85.128.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 067703DAAB6 for ; Wed, 9 Sep 2026 19:38:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982714; cv=none; b=Ke9/5pNymlCXbTCzn2l3xTI2ksZAttaXie3KQdbl7Y5OCNoUhMtfUugDDh6S4Wu8FZ2d7yqnmEoZdItuOHTRTYj0+0zkkpBY3dxkeS35upC6HGx/e2uFs7mHH4GUAc5MXj5RuhyYbxJn1b8GGr6IryLn77B7dI2XcRe1LU+Vg0E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982714; c=relaxed/simple; bh=td8/lLBXlZW0QdNATZMz/HxISs9wVcTx3cj3xeWsnxQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=LH8UDP1sW22swI4hHLBH7E3mg0XiLjMUH6jsV1Xtwr98Za2/4xJk2ueih9d235yYxixSJdHowJ0cRuM6bDJ3jFGCxk/IDhmki7ftbzrA1V6LzHh4jG+LEMJ8OTkqhtqQvanF7/TVOGxd2sXQxjM/8BrRPkp6UVbkEwIiUjrVQgY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=TU0VgpKM; arc=none smtp.client-ip=209.85.128.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="TU0VgpKM" Received: by mail-yw1-f171.google.com with SMTP id 00721157ae682-86c0e4dd49cso84931257b3.3 for ; Wed, 09 Sep 2026 12:38:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982709; x=1789587509; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=/ozf45W5n4HZ3ueQQr/XzBXeJTkMQpO9cGnym8VL9+4=; b=TU0VgpKMJ6aHCN4r0Aqs36B0kRd8JmyCMypgjZkYJSTdckeMXx6869a5XmjMiLr4+/ 3BC04Gj84f/o8tF+IdRh8ofUZ7rDT1F3t0e2F3JxfHFSFQXnH3A7a7zcXcuyjMl6tsf9 erLd/s7XJnpsHvMhq0d6pxGGJMcN2/Xx3rswTxx3AaZw1xnFVMETJVqH2lAit7qncNcN XmFCP7oH6pxelcdB+tm9Rmq5sACjAPkRvZ/fnLfZnYMiqnFvOeQ5wWb+meoA5S1ZcWDn geMNWptfJdnGc847tV5JBhqArt+hF1mAQISgH6joncyTK2OQKAM5rXBrpP+HlNFvPdfL kcXA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982709; x=1789587509; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=/ozf45W5n4HZ3ueQQr/XzBXeJTkMQpO9cGnym8VL9+4=; b=AwRkgpeVdYJF7TPFfI7irI5s2g8CUE0iYrjSf4hk61j3hTstJ8Ntt7dk/bjGNeuF2Z jwjkJRfgzI4Wah12ZraaQKAP0mSGXx2PLfpODLrDH2dP2y2QJDAMKeCOLU1n3HrZz2mT iEUJY+KoANVIwIkOMALEz+U0M7Pfc1eKV3z86T8C25bLSGB85xKa3D+yKaHg8lySerzc mwDYHqOc+WhyWbpy833YGm3ERtRvdvaj45fZubcTMRCVSrPdXXo81cqD7p4mcAxsyBbn RI2EebH6YCFwbIfCaGSGmoBG3iUuhvcImXyAeiA6lAxZn0/jSton+mgvV4xnDZICzPP5 JiTg== X-Forwarded-Encrypted: i=1; AKwUvBwRs+fp9zFAIgtqDFeDNbSvd1+G+XnOOyNXkx13f/ew0YzdgI0nTqZr0YrOn0KeABbG/5FfPJliwxcwNG4=@vger.kernel.org X-Gm-Message-State: AFuF++kJ4vFgoHeaf08eqF8BJ4kY/6LFYPAUXzc99rTtwD8W07T9uoJ2 UWItkUzWB6Gzk/nC8zOlo0+3DqOJ6NAg9HjAmOahREvqXloqwhrOHGsN X-Gm-Gg: AYBFou0y3RbEKwe8pgCDrvcn/fojAVxr7oiSHtchH/12cl0K7t75nLzoeO/nR/9CBJ+ j1WajaF04F6UoWZ99NhAgXQGE3FVztJ5zhNIqS/aoOzFTmXqLOQ5LL7IuOctocQTk+HRX3fSZMp 1SwMuyXlT7fWzTyG7//7dycsNgPtDuJGzWL/xoxzcEX5A6jR+sk4iVwFwoIJQ+itaxiuXy1XXhT 1yKmhIkrSwIJHdCBLjsz7iyr7+KEZHACq+mA9oDPBZFSx+JYX2nvpcL4pWQnrw1rCG3bXm0EIoe 71AilxKqTEmsLkRrWtMMcZZHZ+Y8ksengXk75Pa1dx8QWkziE4QuBxx4FyVUuNPUnvZLYno5E0a EIcWDvHO0l5PzRpifitQGXy+P4D0hGTYj/3Dhh93r+fA8LZwVQiJ+tiB5zG1O2lLfpA+/bb0SC5 +qGEouVW55LoHxlSkm0MZu/yMtkp6yshddZLPT7AkjYTVHXnFsfhFP6i0cD1SXICZQAF1k8Afc6 J0njwK5fm2f64LCqKxL0kwze3T9oAb4 X-Received: by 2002:a05:690c:3683:b0:820:b89:52c6 with SMTP id 00721157ae682-8712a418f03mr147939227b3.33.1788982708611; Wed, 09 Sep 2026 12:38:28 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.38.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:38:28 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 14/15] selftests/bpf: Test the LSM policy object kfuncs with Landlock Date: Wed, 9 Sep 2026 15:37:17 -0400 Message-ID: <20260909193719.518517-15-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Exercise the policy object kfuncs against an LSM actually providing policy objects, complementing the LSM-independent tests of the verifier-side and from_fd contracts. The programs mirror the intended usage: a syscall program acquires a Landlock ruleset with bpf_lsm_policy_from_fd() and parks it in a map kptr field; an LSM program on bprm_creds_for_exec() loads the field under bpf_rcu_read_lock(), takes its own reference with bpf_lsm_policy_acquire(), and applies the ruleset with bpf_lsm_policy_apply_bprm(). The prog_tests runner checks that: - a monitored execution starts confined (a handled-but-not-allowed write fails) while an unmonitored one is untouched, - two concurrent monitored executions are both restricted from the one shared map slot, - the landlock_restrict_self(2) log flags are accepted while LANDLOCK_RESTRICT_SELF_TSYNC is rejected with -EINVAL and leaves the execution unrestricted, - LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS makes the executed program start with no_new_privs set, while an execution without it stays non-nnp, - a second apply call on the same execution replaces the staged restriction rather than failing, - an execution failing past the bprm hook (ENOEXEC) discards the staged restriction and leaves the caller unconfined, - the object's identity is BTF-readable off the trusted kptr: the LSM-private type tag is nonzero, and the lsmid is LSM_ID_LANDLOCK with the fd alone having routed the translation, - the bprm application emits a single landlock_enforce_domain event, observed by a tp_btf program: complete =3D=3D 1, process_wide =3D=3D 1, a= nd no_new_privs reporting the post-flag state. Signed-off-by: Justin Suess --- Notes: v2->v3: - Fix a pipe fd leak on the fork() error path of test_restrict_binprm_discard(). tools/testing/selftests/bpf/config | 1 + tools/testing/selftests/bpf/config.x86_64 | 2 +- .../bpf/prog_tests/lsm_policy_landlock.c | 525 ++++++++++++++++++ .../selftests/bpf/progs/lsm_policy_landlock.c | 142 +++++ 4 files changed, 669 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/bpf/prog_tests/lsm_policy_landl= ock.c create mode 100644 tools/testing/selftests/bpf/progs/lsm_policy_landlock.c diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/b= pf/config index 2f79688dcf7c..42e20d245f90 100644 --- a/tools/testing/selftests/bpf/config +++ b/tools/testing/selftests/bpf/config @@ -122,6 +122,7 @@ CONFIG_SAMPLES=3Dy CONFIG_SAMPLE_LIVEPATCH=3Dm CONFIG_SECURITY=3Dy CONFIG_SECURITYFS=3Dy +CONFIG_SECURITY_LANDLOCK=3Dy CONFIG_SYN_COOKIES=3Dy CONFIG_TEST_BPF=3Dm CONFIG_UDMABUF=3Dy diff --git a/tools/testing/selftests/bpf/config.x86_64 b/tools/testing/self= tests/bpf/config.x86_64 index 523e0d29bbd4..2c4d857f69f5 100644 --- a/tools/testing/selftests/bpf/config.x86_64 +++ b/tools/testing/selftests/bpf/config.x86_64 @@ -125,7 +125,7 @@ CONFIG_LEGACY_VSYSCALL_NONE=3Dy CONFIG_LOG_BUF_SHIFT=3D21 CONFIG_LOG_CPU_MAX_BUF_SHIFT=3D0 CONFIG_LOGO=3Dy -CONFIG_LSM=3D"selinux,bpf,integrity" +CONFIG_LSM=3D"landlock,selinux,bpf,integrity" CONFIG_MAC_PARTITION=3Dy CONFIG_MAGIC_SYSRQ=3Dy CONFIG_MCORE2=3Dy diff --git a/tools/testing/selftests/bpf/prog_tests/lsm_policy_landlock.c b= /tools/testing/selftests/bpf/prog_tests/lsm_policy_landlock.c new file mode 100644 index 000000000000..f01ae5be8aef --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/lsm_policy_landlock.c @@ -0,0 +1,525 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright =C2=A9 2026 Justin Suess */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lsm_policy_landlock.skel.h" + +/* Fallbacks for old system headers. */ +#ifndef LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON +#define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (1U << 1) +#endif +#ifndef LANDLOCK_RESTRICT_SELF_TSYNC +#define LANDLOCK_RESTRICT_SELF_TSYNC (1U << 3) +#endif +#ifndef LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS +#define LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS (1U << 4) +#endif +#ifndef LSM_ID_LANDLOCK +#define LSM_ID_LANDLOCK 110 /* uapi/linux/lsm.h */ +#endif + +struct policy_test_env { + struct lsm_policy_landlock *skel; + char tmp_path[64]; + int tmp_fd; + int ruleset_fd; +}; + +static int create_ruleset(void) +{ + const struct landlock_ruleset_attr attr =3D { + .handled_access_fs =3D LANDLOCK_ACCESS_FS_WRITE_FILE, + }; + + return syscall(__NR_landlock_create_ruleset, &attr, sizeof(attr), 0); +} + +static void reset_prog_state(struct lsm_policy_landlock *skel) +{ + skel->bss->called =3D false; + skel->bss->no_policy =3D false; + skel->bss->restrict_err =3D -1; + skel->bss->restrict2_err =3D -1; + skel->bss->restrict_ok_count =3D 0; + skel->bss->kfunc_flags =3D 0; + skel->bss->double_call =3D false; + skel->bss->monitored_pid =3D 0; + skel->bss->monitored_pid2 =3D 0; + skel->bss->enforce_domain_id =3D 0; + skel->bss->enforce_count =3D 0; + skel->bss->enforce_complete =3D false; + skel->bss->enforce_process_wide =3D false; + skel->bss->enforce_no_new_privs =3D false; +} + +/* + * Runs the syscall program that acquires the ruleset from + * @ruleset_fd, in the runner's fd table, and parks it in the map kptr + * slot for the LSM program. + */ +static int load_ruleset_into_map(struct lsm_policy_landlock *skel) +{ + LIBBPF_OPTS(bpf_test_run_opts, opts); + int err; + + err =3D bpf_prog_test_run_opts(bpf_program__fd(skel->progs.load_policy), + &opts); + if (!ASSERT_OK(err, "load_policy_run")) + return -1; + if (!ASSERT_OK(opts.retval, "load_policy_retval")) + return -1; + /* Landlock's type tag, read off the trusted kptr, is never 0. */ + ASSERT_NEQ(skel->bss->policy_type, 0, "policy_type_nonzero"); + /* The fd, not a kfunc argument, routed the call to Landlock. */ + ASSERT_EQ(skel->bss->policy_lsmid, LSM_ID_LANDLOCK, "policy_lsmid"); + return 0; +} + +/* + * Creates the target tmp file and the ruleset, loads and attaches the + * skeleton, and parks the ruleset in the map. Returns 0 on success; + * on failure (or skip), the caller must still run teardown_env(). + */ +static int setup_env(struct policy_test_env *env) +{ + env->skel =3D NULL; + env->ruleset_fd =3D -1; + strcpy(env->tmp_path, "/tmp/lsm_policy_landlock_XXXXXX"); + env->tmp_fd =3D mkstemp(env->tmp_path); + if (!ASSERT_GE(env->tmp_fd, 0, "mkstemp")) + return -1; + + env->ruleset_fd =3D create_ruleset(); + if (env->ruleset_fd < 0) { + if (errno =3D=3D EOPNOTSUPP || errno =3D=3D ENOSYS) + test__skip(); + else + ASSERT_GE(env->ruleset_fd, 0, + "landlock_create_ruleset"); + return -1; + } + + env->skel =3D lsm_policy_landlock__open_and_load(); + if (!ASSERT_OK_PTR(env->skel, "skel_open_and_load")) + return -1; + env->skel->bss->ruleset_fd =3D env->ruleset_fd; + reset_prog_state(env->skel); + + if (!ASSERT_OK(lsm_policy_landlock__attach(env->skel), + "skel_attach")) + return -1; + + return load_ruleset_into_map(env->skel); +} + +static void teardown_env(struct policy_test_env *env) +{ + lsm_policy_landlock__destroy(env->skel); + if (env->ruleset_fd >=3D 0) + close(env->ruleset_fd); + if (env->tmp_fd >=3D 0) + close(env->tmp_fd); + unlink(env->tmp_path); +} + +/* + * Forks a child that blocks on a pipe, then execs @path with @argv. + * Returns the child's pid, or -1 on error. @release_fd receives the + * pipe's write end: release_exec_child() lets the child exec, after + * its pid has been published to the BPF program. + */ +static pid_t spawn_exec_child(const char *path, char *const argv[], + int *release_fd) +{ + int pipe_fds[2]; + char buf =3D 0; + pid_t pid; + + if (!ASSERT_OK(pipe(pipe_fds), "pipe")) + return -1; + + pid =3D fork(); + if (!ASSERT_GE(pid, 0, "fork")) { + close(pipe_fds[0]); + close(pipe_fds[1]); + return -1; + } + if (pid =3D=3D 0) { + close(pipe_fds[1]); + read(pipe_fds[0], &buf, 1); + close(pipe_fds[0]); + execv(path, argv); + exit(127); + } + close(pipe_fds[0]); + *release_fd =3D pipe_fds[1]; + return pid; +} + +static void release_exec_child(int release_fd) +{ + char buf =3D 0; + + write(release_fd, &buf, 1); + close(release_fd); +} + +/* Returns the child's exit status, or -1 on error. */ +static int wait_exec_child(pid_t pid) +{ + int status; + + if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid")) + return -1; + if (!ASSERT_TRUE(WIFEXITED(status), "child_exited")) + return -1; + return WEXITSTATUS(status); +} + +static int run_exec_child(struct lsm_policy_landlock *skel, + bool monitored, const char *shell_cmd) +{ + char *argv[] =3D { "sh", "-c", (char *)shell_cmd, NULL }; + int release_fd; + pid_t pid; + + pid =3D spawn_exec_child("/bin/sh", argv, &release_fd); + if (pid < 0) + return -1; + skel->bss->monitored_pid =3D monitored ? pid : 0; + release_exec_child(release_fd); + return wait_exec_child(pid); +} + +/* + * Exit codes: 4 =3D unexpected write outcome, 0 =3D everything as + * expected. + */ +static void format_child_cmd(char *cmd, size_t len, bool expect_write_ok, + const char *tmp_path) +{ + if (expect_write_ok) + snprintf(cmd, len, "echo x > %s || exit 4; exit 0", tmp_path); + else + snprintf(cmd, len, + "if echo x > %s 2>/dev/null; then exit 4; fi; exit 0", + tmp_path); +} + +static void test_restrict_binprm(void) +{ + struct policy_test_env env; + struct lsm_policy_landlock *skel; + char cmd[256]; + int ret; + + if (setup_env(&env)) + goto out; + skel =3D env.skel; + + /* Control: an unmonitored execution may write to the tmp file. */ + reset_prog_state(skel); + format_child_cmd(cmd, sizeof(cmd), true, env.tmp_path); + ret =3D run_exec_child(skel, false, cmd); + if (!ASSERT_EQ(ret, 0, "control_child_exit")) + goto out; + ASSERT_FALSE(skel->bss->called, "control_not_monitored"); + + /* + * A monitored execution starts landlocked: the ruleset handles + * LANDLOCK_ACCESS_FS_WRITE_FILE without any rule, so the write + * must fail. + */ + reset_prog_state(skel); + format_child_cmd(cmd, sizeof(cmd), false, env.tmp_path); + ret =3D run_exec_child(skel, true, cmd); + if (!ASSERT_EQ(ret, 0, "restricted_child_exit")) + goto out; + ASSERT_TRUE(skel->bss->called, "lsm_prog_called"); + ASSERT_FALSE(skel->bss->no_policy, "ruleset_in_map"); + ASSERT_EQ(skel->bss->restrict_err, 0, "restrict_binprm"); + + /* The audit log flags of landlock_restrict_self(2) apply too. */ + reset_prog_state(skel); + skel->bss->kfunc_flags =3D LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON; + format_child_cmd(cmd, sizeof(cmd), false, env.tmp_path); + ret =3D run_exec_child(skel, true, cmd); + if (!ASSERT_EQ(ret, 0, "log_flags_child_exit")) + goto out; + ASSERT_EQ(skel->bss->restrict_err, 0, "log_flags_restrict_binprm"); + + /* + * LANDLOCK_RESTRICT_SELF_TSYNC targets the calling threads, not + * an execution: the kfunc must reject it and the execution must + * stay unrestricted. + */ + reset_prog_state(skel); + skel->bss->kfunc_flags =3D LANDLOCK_RESTRICT_SELF_TSYNC; + format_child_cmd(cmd, sizeof(cmd), true, env.tmp_path); + ret =3D run_exec_child(skel, true, cmd); + if (!ASSERT_EQ(ret, 0, "tsync_child_exit")) + goto out; + ASSERT_TRUE(skel->bss->called, "tsync_prog_called"); + ASSERT_EQ(skel->bss->restrict_err, -EINVAL, "tsync_rejected"); + + /* + * A second call on the same execution replaces the staged + * domain (and releases the first one): the result is a single + * restriction, not an error. + */ + reset_prog_state(skel); + skel->bss->double_call =3D true; + format_child_cmd(cmd, sizeof(cmd), false, env.tmp_path); + ret =3D run_exec_child(skel, true, cmd); + if (!ASSERT_EQ(ret, 0, "double_child_exit")) + goto out; + ASSERT_EQ(skel->bss->restrict_err, 0, "double_restrict_first"); + ASSERT_EQ(skel->bss->restrict2_err, 0, "double_restrict_second"); +out: + teardown_env(&env); +} + +/* + * Two monitored executions, released together, must both be + * restricted from the one shared map kptr slot. + */ +static void test_restrict_binprm_concurrent(void) +{ + struct policy_test_env env; + char *argv[4]; + int release_fds[2] =3D { -1, -1 }; + pid_t pids[2] =3D { -1, -1 }; + char cmd[256]; + int i; + + if (setup_env(&env)) + goto out; + + format_child_cmd(cmd, sizeof(cmd), false, env.tmp_path); + argv[0] =3D "sh"; + argv[1] =3D "-c"; + argv[2] =3D cmd; + argv[3] =3D NULL; + + for (i =3D 0; i < 2; i++) { + pids[i] =3D spawn_exec_child("/bin/sh", argv, &release_fds[i]); + if (pids[i] < 0) + goto out_kill; + } + + env.skel->bss->monitored_pid =3D pids[0]; + env.skel->bss->monitored_pid2 =3D pids[1]; + + /* Releases both children only once both pids are published. */ + for (i =3D 0; i < 2; i++) { + release_exec_child(release_fds[i]); + release_fds[i] =3D -1; + } + + for (i =3D 0; i < 2; i++) { + ASSERT_EQ(wait_exec_child(pids[i]), 0, + "concurrent_child_exit"); + pids[i] =3D -1; + } + + ASSERT_FALSE(env.skel->bss->no_policy, "ruleset_in_map"); + ASSERT_EQ(env.skel->bss->restrict_ok_count, 2, + "both_execs_restricted"); + +out_kill: + for (i =3D 0; i < 2; i++) { + if (pids[i] > 0) { + kill(pids[i], SIGKILL); + waitpid(pids[i], NULL, 0); + } + if (release_fds[i] >=3D 0) + close(release_fds[i]); + } +out: + teardown_env(&env); +} + +/* + * Checks that a staged restriction is discarded, and the staged + * domain released, when the execution fails after the bprm hook: the + * calling task must not end up landlocked. + */ +static void test_restrict_binprm_discard(void) +{ + struct policy_test_env env; + char garbage_path[] =3D "/tmp/lsm_policy_garbage_XXXXXX"; + int garbage_fd, pipe_fds[2]; + char buf =3D 0; + pid_t pid; + + if (setup_env(&env)) + goto out; + + /* + * An executable file that no binfmt handler accepts: the exec + * fails with ENOEXEC after bprm_creds_for_exec() has run. + */ + garbage_fd =3D mkstemp(garbage_path); + if (!ASSERT_GE(garbage_fd, 0, "mkstemp_garbage")) + goto out; + if (!ASSERT_EQ(write(garbage_fd, "junk\n", 5), 5, "write_garbage") || + !ASSERT_OK(fchmod(garbage_fd, 0700), "chmod_garbage")) { + close(garbage_fd); + goto out_unlink; + } + close(garbage_fd); + + if (!ASSERT_OK(pipe(pipe_fds), "pipe")) + goto out_unlink; + + /* + * Cannot use spawn_exec_child(): the same process must test its + * write access after the failed exec. + */ + pid =3D fork(); + if (!ASSERT_GE(pid, 0, "fork")) { + close(pipe_fds[0]); + close(pipe_fds[1]); + goto out_unlink; + } + if (pid =3D=3D 0) { + char *argv[] =3D { "garbage", NULL }; + int fd; + + close(pipe_fds[1]); + read(pipe_fds[0], &buf, 1); + close(pipe_fds[0]); + execv(garbage_path, argv); + /* + * The failed execution must leave no trace: no + * Landlock domain, i.e. writing must still work + * (exit 6). + */ + fd =3D open(env.tmp_path, O_WRONLY | O_TRUNC); + if (fd < 0) + exit(6); + close(fd); + exit(0); + } + close(pipe_fds[0]); + env.skel->bss->monitored_pid =3D pid; + release_exec_child(pipe_fds[1]); + + ASSERT_EQ(wait_exec_child(pid), 0, "discard_child_exit"); + ASSERT_TRUE(env.skel->bss->called, "lsm_prog_called"); + ASSERT_EQ(env.skel->bss->restrict_err, 0, "restrict_binprm"); +out_unlink: + unlink(garbage_path); +out: + teardown_env(&env); +} + +/* + * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS makes the executed program start + * with no_new_privs set; without it, a non-nnp parent's execution + * stays non-nnp. Child exit code 5: unexpected NoNewPrivs value. + */ +static void test_restrict_binprm_nnp(void) +{ + static const char nnp_cmd[] =3D + "grep -q '^NoNewPrivs:[[:space:]]*%d' /proc/self/status || exit 5"; + struct policy_test_env env; + struct lsm_policy_landlock *skel; + char cmd[sizeof(nnp_cmd)]; + int ret; + + if (setup_env(&env)) + goto out; + skel =3D env.skel; + + if (!ASSERT_OK(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0), + "runner_not_nnp")) + goto out; + + reset_prog_state(skel); + snprintf(cmd, sizeof(cmd), nnp_cmd, 0); + ret =3D run_exec_child(skel, true, cmd); + if (!ASSERT_EQ(ret, 0, "no_flag_child_exit")) + goto out; + ASSERT_EQ(skel->bss->restrict_err, 0, "no_flag_restrict_binprm"); + + reset_prog_state(skel); + skel->bss->kfunc_flags =3D LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS; + snprintf(cmd, sizeof(cmd), nnp_cmd, 1); + ret =3D run_exec_child(skel, true, cmd); + if (!ASSERT_EQ(ret, 0, "nnp_flag_child_exit")) + goto out; + ASSERT_EQ(skel->bss->restrict_err, 0, "nnp_flag_restrict_binprm"); +out: + teardown_env(&env); +} + +/* + * The bprm application emits landlock_enforce_domain, observed here by + * a tp_btf program: the single event concludes the operation + * (complete =3D=3D 1), covers the whole post-de_thread() process + * (process_wide =3D=3D 1), and reports the post-flag no_new_privs state. + */ +static void test_restrict_binprm_trace(void) +{ + struct policy_test_env env; + struct lsm_policy_landlock *skel; + char cmd[256]; + int ret; + + if (setup_env(&env)) + goto out; + skel =3D env.skel; + + if (!ASSERT_OK(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0), + "runner_not_nnp")) + goto out; + + reset_prog_state(skel); + format_child_cmd(cmd, sizeof(cmd), false, env.tmp_path); + ret =3D run_exec_child(skel, true, cmd); + if (!ASSERT_EQ(ret, 0, "trace_child_exit")) + goto out; + ASSERT_EQ(skel->bss->restrict_err, 0, "restrict_binprm"); + ASSERT_EQ(skel->bss->enforce_count, 1, "one_enforce_event"); + ASSERT_TRUE(skel->bss->enforce_complete, "enforce_complete"); + ASSERT_TRUE(skel->bss->enforce_process_wide, "enforce_process_wide"); + ASSERT_NEQ(skel->bss->enforce_domain_id, 0, "enforce_domain_id"); + ASSERT_FALSE(skel->bss->enforce_no_new_privs, "enforce_nnp_off"); + + reset_prog_state(skel); + skel->bss->kfunc_flags =3D LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS; + format_child_cmd(cmd, sizeof(cmd), false, env.tmp_path); + ret =3D run_exec_child(skel, true, cmd); + if (!ASSERT_EQ(ret, 0, "trace_nnp_child_exit")) + goto out; + ASSERT_EQ(skel->bss->enforce_count, 1, "one_enforce_event_nnp"); + ASSERT_TRUE(skel->bss->enforce_no_new_privs, "enforce_nnp_on"); +out: + teardown_env(&env); +} + +void test_lsm_policy_landlock(void) +{ + if (test__start_subtest("restrict_binprm")) + test_restrict_binprm(); + if (test__start_subtest("restrict_binprm_concurrent")) + test_restrict_binprm_concurrent(); + if (test__start_subtest("restrict_binprm_discard")) + test_restrict_binprm_discard(); + if (test__start_subtest("restrict_binprm_nnp")) + test_restrict_binprm_nnp(); + if (test__start_subtest("restrict_binprm_trace")) + test_restrict_binprm_trace(); +} diff --git a/tools/testing/selftests/bpf/progs/lsm_policy_landlock.c b/tool= s/testing/selftests/bpf/progs/lsm_policy_landlock.c new file mode 100644 index 000000000000..231b24b87dd4 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/lsm_policy_landlock.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright =C2=A9 2026 Justin Suess */ + +#include +#include +#include + +char _license[] SEC("license") =3D "GPL"; + +extern struct lsm_policy_object * +bpf_lsm_policy_acquire(struct lsm_policy_object *object) __ksym; +extern int bpf_lsm_policy_apply_bprm(struct lsm_policy_object *object, + struct linux_binprm *bprm, + u32 flags) __ksym; +extern struct lsm_policy_object * +bpf_lsm_policy_from_fd(int fd, u32 flags) __ksym; +extern void bpf_lsm_policy_release(struct lsm_policy_object *object) __ksy= m; +void bpf_rcu_read_lock(void) __ksym; +void bpf_rcu_read_unlock(void) __ksym; + +struct policy_slot { + struct lsm_policy_object __kptr *object; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, int); + __type(value, struct policy_slot); +} policy_map SEC(".maps"); + +int monitored_pid; +int monitored_pid2; +int ruleset_fd; +u32 kfunc_flags; +bool double_call; +u32 policy_type; +u64 policy_lsmid; +bool no_policy; +int restrict_err; +int restrict2_err; +int restrict_ok_count; +bool called; +u64 enforce_domain_id; +int enforce_count; +bool enforce_complete; +bool enforce_process_wide; +bool enforce_no_new_privs; + +/* + * Runs in the test runner's context through BPF_PROG_RUN, where + * @ruleset_fd is meaningful. + */ +SEC("syscall") +int load_policy(void *ctx) +{ + struct lsm_policy_object *object, *old; + struct policy_slot *slot; + int key =3D 0; + + slot =3D bpf_map_lookup_elem(&policy_map, &key); + if (!slot) + return 1; + + object =3D bpf_lsm_policy_from_fd(ruleset_fd, 0); + if (!object) + return 2; + + /* + * The object's identity is BTF-readable off the trusted kptr: a + * program that expects a policy of one specific LSM can check + * the lsmid the fd resolved to. + */ + policy_type =3D object->type; + policy_lsmid =3D object->lsmid; + + old =3D bpf_kptr_xchg(&slot->object, object); + if (old) + bpf_lsm_policy_release(old); + return 0; +} + +SEC("lsm.s/bprm_creds_for_exec") +int BPF_PROG(restrict_exec, struct linux_binprm *bprm) +{ + struct lsm_policy_object *object; + struct policy_slot *slot; + int pid =3D bpf_get_current_pid_tgid() >> 32; + int key =3D 0; + + if (pid !=3D monitored_pid && pid !=3D monitored_pid2) + return 0; + + called =3D true; + + slot =3D bpf_map_lookup_elem(&policy_map, &key); + if (!slot) + return 0; + + /* + * RCU load + acquire instead of bpf_kptr_xchg(): the slot is + * never emptied, so concurrent executions can share it. + */ + bpf_rcu_read_lock(); + object =3D slot->object; + if (object) + object =3D bpf_lsm_policy_acquire(object); + bpf_rcu_read_unlock(); + + if (!object) { + no_policy =3D true; + return 0; + } + + restrict_err =3D bpf_lsm_policy_apply_bprm(object, bprm, kfunc_flags); + if (!restrict_err) + __sync_fetch_and_add(&restrict_ok_count, 1); + if (double_call) + /* Replaces the domain staged by the first call. */ + restrict2_err =3D bpf_lsm_policy_apply_bprm(object, bprm, + kfunc_flags); + + bpf_lsm_policy_release(object); + return 0; +} + +SEC("tp_btf/landlock_enforce_domain") +int BPF_PROG(on_enforce_domain, struct landlock_domain *domain, bool compl= ete, + bool process_wide, bool no_new_privs) +{ + int pid =3D bpf_get_current_pid_tgid() >> 32; + + if (pid !=3D monitored_pid && pid !=3D monitored_pid2) + return 0; + + __sync_fetch_and_add(&enforce_count, 1); + enforce_domain_id =3D domain->hierarchy->id; + enforce_complete =3D complete; + enforce_process_wide =3D process_wide; + enforce_no_new_privs =3D no_new_privs; + return 0; +} --=20 2.55.0 From nobody Fri Sep 25 18:21:00 2026 Received: from mail-yw1-f170.google.com (mail-yw1-f170.google.com [209.85.128.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DBA883C1D4D for ; Wed, 9 Sep 2026 19:38:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982716; cv=none; b=Wdrl2LPpiOKY6sI2h0BUoQw6ycB3aY7EHXOZJcVw/IZ/a179HL9HiGPNur7LSfx2fZsV5G21CltNqfbAbDZSLyEKuFRTnWRx+MMiJCApAp5mh8shfwrhFhDR7x34e0lrXV6M7NvEBqAwRSMJ2pkJKcQU5RUdlEhHi/OzFcNtYJQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788982716; c=relaxed/simple; bh=waHQHiFfqMIJ1vdwpMD2w/AcnmEjSVLl6pRymyrAAEA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=aKpgmoJwOCEKc8uln8qH+WnsZP4cqD49375HVPQeeWBSbV2UWyspFBdhWYkrO6myHKgN8vFjnu9kr8jN/PBENpeCDlT2MXW/7THhtjYUylXGVlJQG24Y4D4oQ7KSOve9VcxdAljJHGLdkqWns/gmsrLpE8oViNBgdKjUYMvzdGw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=RennIaof; arc=none smtp.client-ip=209.85.128.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="RennIaof" Received: by mail-yw1-f170.google.com with SMTP id 00721157ae682-8623b1e7cb2so45764757b3.1 for ; Wed, 09 Sep 2026 12:38:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788982713; x=1789587513; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=evloMT+R7+GF/RTy6Xj0kggqlRDodR8G5+ybCy69Si4=; b=RennIaof8bDg1nyCeSmOO/nBVNKQvsg6zxKyFVvh6XJVN42lXt3u8hAF+CxYuFJd/E cqIkqbpYtty7EUeYmAsfub28euj5mEKyb5mfqmxsrP29IysLnHeW+7oiPB9kwkedcNTO gX2mo7+UoA1dVhq3ljI4VKrzds9z2cboEUHp78cpZCPjPRU0VXBk0I40V/sNFVoF1F9M MC2yCUxmWTDTwu6thElzBwS5JGVqshXbFP3okzf27IJ4Hm9S4FHaNDw+RHzwW3NQcac/ Ul5TrYDn0aP0FlL39f8oD6r+wUSCnVlBleOlo9nordQz57wqxWG0X5oGgFpP8/P+kOvy nn8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788982713; x=1789587513; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=evloMT+R7+GF/RTy6Xj0kggqlRDodR8G5+ybCy69Si4=; b=paj8lLqzUNlnQ2K81wJDTz0E9r7qLOibE27nNXh8jcb9OjuSrrVKtYchQAPGd9cSQ4 hmZZ33fs6j8uBaivMKNlOE+5mp6HPoIiA8Orm7dBCojpkJkgSwelk8KddwpxBZRuim53 CEK+Qy/ULIYL7Un7TtSca68azdsPhV7nXdzW5zYt+w/4Ld4WC8D6EK0doNSUce3rnlqX 3xwQkRgWR4n8VNwPQ27NoaQ+Gu2M52RC7S2yf2SH6oC3BUIRUv0lwztNvJDN9ubVgLKX Bj9MUA4BZF66UhtPIkGQMBfzYhqT3A5bZr8MlCDzJLgXCZkh9c28okS0zG2elUvHoDP/ DqQQ== X-Forwarded-Encrypted: i=1; AKwUvByi8Kd5fy0GKsZg8pZYiHkmhdJ5zcvussSvHKh37ubLnL6S9WiB9RMoi55diwrgV6qaWqkqrk5WUAlhpM0=@vger.kernel.org X-Gm-Message-State: AFuF++lnQkbs13nvTcD05rvNkAPLUTI98H5qsziSjuMjVllNQGBzxW69 Kd0NZuRChrRe09n/Wzivbcne+cNCDuUNrg65msfKFBYwaoKQ5PP1O+EWoEzi8kA0 X-Gm-Gg: AYBFou1UuhNehAmf6y+hdUHeTDjBkQ6fSMiN3LoudzCbRC5prdqJguYpmxnMZ53/1mQ gbD8u42lopNy7h711ETezkLlPEFddlR2/1bwpPabSeHuLrsrLkdDJM7vWycPxDEqbLouA/emgaj J0P6VYczVdvLri9hWH9aqvRuecX8gWEqI2JiOrXRHXPTNMAUOk2th39QnWgc5Cj77LBrovvBZgD PIapwB/CEkVvmyKGgnRUqmNTTJ7fh11+WKg/NMLNVcqtIXlhFsnlfzzpdl38/vTfo8TH9tuCCEP kpLlBGB9eir4FQ87rt8+VDamQ3GIougYKTl1dr3KsrAk3+4j+V6KEJMDZvzfhxkYMJnuc7S7V6z QrdMWNZ65Ib3TDp89JtzImV6NkoKsuTZy/yOlyvcrQfFZz/kIUOK88IqJC+HS/dWN0WOpmvRBfO +PJw8sTtzK/24uWvpslMDlXZBtNPp6WYHulmWwm4rBCcX2LGki+njXBzNq0N206Kr2lvr5mZvRD rLFwHh/tkEYfMW9DUGWUwOeITkb0dWX X-Received: by 2002:a05:690c:6508:b0:873:5c0f:28d with SMTP id 00721157ae682-8735c0f03eamr136509517b3.55.1788982712679; Wed, 09 Sep 2026 12:38:32 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:bae:bfc2:7e96:e5c8]) by smtp.gmail.com with ESMTPSA id 00721157ae682-871493155d3sm115277577b3.16.2026.09.09.12.38.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 12:38:32 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, matt@bobrowski.net, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: casey@schaufler-ca.com, gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com, jolsa@kernel.org, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH bpf-next v3 15/15] landlock: Document the BPF policy interface Date: Wed, 9 Sep 2026 15:37:18 -0400 Message-ID: <20260909193719.518517-16-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909193719.518517-1-utilityemal77@gmail.com> References: <20260909193719.518517-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Describe how the generic LSM policy kfuncs apply to Landlock rulesets: the program-side flow, the staged restriction and its trace event, the LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS semantics on an execution, and why the bprm path carries no no_new_privs/ CAP_SYS_ADMIN precondition. Note the new emission point in the trace events overview. Cc: Micka=C3=ABl Sala=C3=BCn Signed-off-by: Justin Suess --- Notes: v2->v3: - No change. Documentation/security/landlock.rst | 38 +++++++++++++++++++++++++ Documentation/trace/events-landlock.rst | 5 +++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Documentation/security/landlock.rst b/Documentation/security/l= andlock.rst index 2d6e1076484e..ddc0d149ae8f 100644 --- a/Documentation/security/landlock.rst +++ b/Documentation/security/landlock.rst @@ -130,6 +130,44 @@ The reasoning is: restrictions, because access within the same scope is already allowed based on ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX``. =20 +BPF kfuncs +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +BPF programs can apply a userspace-created Landlock ruleset to an +execution, through the generic LSM policy kfuncs (see +Documentation/security/lsm-development.rst). A syscall program +(``BPF_PROG_TYPE_SYSCALL``), running in the context of the process +that set the ruleset up, acquires the ruleset with +``bpf_lsm_policy_from_fd()`` and typically hands it over through a +map kptr field; a sleepable LSM BPF program attached to the +``bprm_creds_for_exec`` or ``bprm_creds_from_file`` hooks then +enforces it on an execution with ``bpf_lsm_policy_apply_bprm()``. + +The restriction is staged in the Landlock blob of the credentials +prepared for the execution and committed past the exec point of no +return, so a failed execution leaves the calling task untouched. The +commitment emits the ``landlock_enforce_domain`` trace event (see +Documentation/trace/events-landlock.rst). The kfunc flags take the +``landlock_restrict_self(2)`` flags with their usual semantics, with +the exception of ``LANDLOCK_RESTRICT_SELF_TSYNC``, which is rejected: +the restriction targets the execution, not the calling threads. + +``LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS`` makes the executed program +start with no_new_privs set, binding it and all its descendants. It +does not affect the current execution's privilege computation: the +bprm credentials, including any setuid elevation, are computed before +the flag is set. + +Unlike ``landlock_restrict_self(2)``, the bprm path has no +no_new_privs/``CAP_SYS_ADMIN`` precondition: a task that is not +no_new_privs can carry a BPF-applied domain, and its setuid +executions still elevate while confined. This is sound because +attaching the BPF program is itself privileged, granting the same +power as the syscall's ``CAP_SYS_ADMIN`` carve-out. + +Executions may run concurrently: each takes its own reference on +the shared ruleset with ``bpf_lsm_policy_acquire()``. + Tests =3D=3D=3D=3D=3D =20 diff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/= events-landlock.rst index af9267cca47d..6cec3194af0b 100644 --- a/Documentation/trace/events-landlock.rst +++ b/Documentation/trace/events-landlock.rst @@ -34,7 +34,10 @@ Landlock trace events are organized in four categories: - ``landlock_add_rule_fs``: a filesystem rule is added to a ruleset - ``landlock_add_rule_net``: a network port rule is added to a ruleset - ``landlock_create_domain``: a new domain is created from a ruleset -- ``landlock_enforce_domain``: a domain is enforced on a thread +- ``landlock_enforce_domain``: a domain is enforced on a thread. Also + emitted at ``execve(2)``'s point of no return when a BPF program has + staged a policy on the execution (see the BPF kfuncs section of + Documentation/security/landlock.rst) =20 **Denial events** are emitted when an access is denied: =20 --=20 2.55.0