From nobody Sat Sep 26 14:38:30 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 2926145DF7F for ; Mon, 31 Aug 2026 14:59:44 +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=1788188385; cv=none; b=P4PS0vHNUGFdLkPyhIB1l0rk6I/nFfrxKHP4l6razYgq+dcQSzFq+1DiuaJfe0zgWb0Tfxp84Pi5T7Yk35PXkJXxSl6S2InB+wefVO1K1cLDlgFeZ8R74IVayVf6iCaIByTJNNyiTNPHAzZuaKy2ps4sK1YI1lDHHHyhqI3gDBU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188385; c=relaxed/simple; bh=87J7Hy9oybrEQ8VoZHjr4Dl0KQ9+NG/q3OT8MdhIFgQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GtEOqqktj5QzaUikfGOgcZSO9uRD3UX8gtjtGOI4lNmxHsVEptv0LjIBDem7ql6y6AgvB3+0B8pNG+uljhUDzjYZS4JYmh39rNg7djfOSgnf9IOf3CWR0TXGGy8908iELOVZVF3CkqklLiMXUX3rUw2OleEEkODuTlXgP1NzUOk= 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=Zi2w2VQ6; 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="Zi2w2VQ6" Received: by mail-yw1-f174.google.com with SMTP id 00721157ae682-8200b55dc47so32151927b3.3 for ; Mon, 31 Aug 2026 07:59:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188383; x=1788793183; 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=mg1kKbDlTHOBJYnbAI3hLPf56gY3s9FmGuWH1KCaD1k=; b=Zi2w2VQ6xP3dTX57tl7ETXF19+gE7d6zgWqXy2oYWHdGXyWmJx5rOiHRLfzJSKy4gd FbvQOtBgzeduQQhaikTHfz7n4XOiQtLoIzFiOaRhQ+p4vYOsvqnW1woF9YarkPG1I0/K isxXbPMRtgJConu8CKWgn819sE3+9bjDUSHQyNAb20/SVMZfS8dfV6NDXxz5FJ/7C8Oz C5m44rbloUA3Jbzdw8ja/3Y6l2fXYfNQteAWZ8DfgtAm/IlTaYM8RulXdQcrcb2hccfT kambp5tNIFWg49Q2iYaSmlLZ/UHXFGcsjujpj3795gHh4J15FEB2MLrrzTkkSSC/vbpt FGxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188383; x=1788793183; 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=mg1kKbDlTHOBJYnbAI3hLPf56gY3s9FmGuWH1KCaD1k=; b=O6M+DlaD93lxRNX9TE49Nm4ePu0SZDUPhHPE1yB5AYEr2JdwbMH/ZKMQGevqVQFmX+ /srwKnaYudnJ1VoEejMiUcsiswKCWXbZd0yGJ8w9qFhUdwGMHsXdnvUMgp904etLXQd6 SxV8Ag7vlqmClTlwkS96huxmC507rltEdePleudzBiR9tyB5BgINNVdGdBgphhAAd9Ye J5mzGDu1FP91Z62F+I1dckJNLfb15SwVG/O/lyC1rARviLfFYyWf2/bbuyb7lAZVmwSS 8lsP2BGM+6yScxkfaxbXOQSmFg0LmJVVXqaiUd0Q+nysjBpmsB9E1bi/Ngxl18NLdWDM RkhQ== X-Forwarded-Encrypted: i=1; AKwUvBzJlbJm92CBAPw/2EW7afWXbOYFsHsnkESoBJ3hCquZlsl9/Uy7NUUowwrO7YF8u7Yo8vy9Zph9lGEVeS4=@vger.kernel.org X-Gm-Message-State: AFuF++lwun88KWpmKuV9693TH2yeToSkDTkC2CBRHAm/rsnllCDjK4kG rdEToFntIsY6qH62zof8IjzEdHfX13jmJNHYOH2xfPU5Baeh9+N+jq1L X-Gm-Gg: AYBFou17UdYVV86cHn6IPUiwgjyX93TT0SZgPuQwOF0df4ebhg1jy0oIeACqquwmyaz cf3n93OQbMPOWQoYLjWjNrL4oPRg7Xxo5OwKqIGlLXE8WtF1ycAub2POFC2WKuxs4IZBs/0CX79 Xj2dwur4+hLjPQ8d94g7bF3/lfXlmzoDjP87XQJZf+gulF2L9Ik8jSIeQuTBJZY6HMy+UKTRz9B SaHsqNY5yzZ4caAddlYHVo7hXcH5sUIo8BftL+UkO+ZiPNljk06TFDPGmyjQkTS/ee2QU/yDGzb ZhTSrJHulH3egQ6GQSHcC9bv5YYTPq6R8V0QfNbVliKNNgGwlU3InuJ/q69X5YLDyQGUDkPfmTC nSi22IenssAOOsh058mz0zl1dF4zXsuT+0cTb6w8n0TKFwmosz3Rss1v6qbN0UtiiEgDR1Q0/Ny 1pNHqGEmCGR2vMpJexZ/eSqgtdUymNuR2fJdLwWE1ubB/zT+IKCJKCIM9srl95WhMr9vkz3QMey XXsZKUD/ePzPm0ayJWNCno= X-Received: by 2002:a05:690c:4005:b0:856:98e2:6e9b with SMTP id 00721157ae682-85d69640f36mr78852557b3.5.1788188383108; Mon, 31 Aug 2026 07:59:43 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.07.59.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 07:59:42 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess , Casey Schaufler Subject: [PATCH v2 01/15] lsm: Add the LSM policy object lifetime hooks Date: Mon, 31 Aug 2026 10:58:43 -0400 Message-ID: <20260831145858.3869191-2-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 1433809bb166..d06744d72e04 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 Sat Sep 26 14:38:30 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 AAC044A5C49 for ; Mon, 31 Aug 2026 14:59:48 +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=1788188390; cv=none; b=a8wc7yYDh3fJNtpCzOTV6dqOfG9d2/qtWQ43Qr2QvZXaq/g+DxSFrXpdXo7GbSyzDl5/5TRwJZRaW6nOb57XD6F0LXwdDQLOh7qqhlFgSW0PwOaIZu7Yq/3HXf00PEWHDYOvN3ZkudlvhbbyltOVOhEW6kZgJQ3UYrrTLWqZmP0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188390; c=relaxed/simple; bh=lBMOIU6CWsLN3ziU8VrmYYDs3RoekpMxdj/Po6RCFC8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=eOucBgwI8xn7/OZHwRrmIemAJsPbPnSiUdzVHNZsDQywJC63szLpEJtztGslTWPRj9dsOf6uBtmHGIXLW1P2X4KcOGWXyKOtssfW7tAhD6O672/3CogSo5waHT5llypUL0eiXaXD8La3ZY3jkz38nCFRXy+L1jcx3zIcz/kNP3g= 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=BQFP4e4u; 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="BQFP4e4u" Received: by mail-yw1-f178.google.com with SMTP id 00721157ae682-81f3b227a4aso45516907b3.1 for ; Mon, 31 Aug 2026 07:59:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188387; x=1788793187; 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=1usyBp9aft3dyeY3uN5coYzOfoJwuHJfSY/skJ61OQQ=; b=BQFP4e4ubiv8+GA7M1P3gvEPOxGXLUlMVfV95F9jdsRVKt/d35M0gLgII2tVJFbmzn jrE5vIMuUN8U/3x2QopqPZunQWc/ruGOIDVAvNcxVTCyvoT00oADPNbhz6XpaP4JzDH2 3uGYdwby9MXwtvdhyVDgsqpZgzm44Fl8tieZuaL3CKOy44agzIBIEb1k4u0MAiupoCAy +RH/PFNxL35sMZrQmvVPt3qVTureDGQeka8xegkWJXXVhcuu1jyOD4MA9krQpRVG5uk+ JfVpsMkdMzgKJ7dM3nhQLFhRgRYoOKnILFopmIzZfPsBtaP6WaMPzd5MSjgUR5DrXq5D kVNA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188387; x=1788793187; 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=1usyBp9aft3dyeY3uN5coYzOfoJwuHJfSY/skJ61OQQ=; b=TMtIalvHH/S24tp52zVay1cjQsYFNmLbUul6wEOcf2EBjSHVUpxW8ZethNTUI+MQTX SShslh6BJ+3fu5/whxrtV5EB9QyLNBGfRxFVnEKKWWTAFOM5HUmHUxS3ULapQvNMMzwT Unw8MEehG9lxMk0qd3r+PgIB3ewaWOvsu0nOb91kyU6Mhqsk1OtNBgkupvV1gMOE7OVc 6Vmg7roioHdO0vNyT9cpjRg5ME9Ii6lsrJsaNjRgLLQ22u380O6NGSeZcV8PcdJrjLYq wUOv3oim+qRFnnu/Nj6AHS+RfauGAtpA9Up5elAN/gsMPTwWIzMKfMoEuJ1SbNNSXbHe YaXg== X-Forwarded-Encrypted: i=1; AKwUvByKjDCx74M8DdTt0a7Rbu43UNH78/wTuFL0s8Y7NubgJ5q/UUSL2tClqgsIYNYEMkz+CzcRque168dMGt8=@vger.kernel.org X-Gm-Message-State: AFuF++m1LQ6xNRoac+9wL4gqlOcZJrrgiXvaFrT10PSy6EKHnxJ+EE7v iRFRNyXKLLqwx69SfAFC0cYmwdzHkxT9jN12PBq3Gszr2wAlZmzom3zb X-Gm-Gg: AYBFou05aY43OhvR7yG1YCY2mQdop0DQ+DgjmAaEaoYMvI7XJPnTZFUImifccRHngZY jAzs2KhXgeR/cQlX1xAo75+EkTY1X+DyisiUzU3rOch4ACLaVks3RjeC+0huKS0/allgYaa91Jb lLqdnySy5aRAR40D+77QIogs7dKc6TSLvfQIhJqOVr+3MY/MlG22iU0TPLgB9FZsqUn7Q/07epX 8H3UsQfH0/3GiMRX2Ruw30Fsrv+8urCXgJ8HXrlW4hB5JaW2Y9qrGRB5fTJ06ZfqKgM+yrwkBfP 38KqLK1JXyZuLl3XyzzsF9V6z8zDQepRUlk7ISJ3jcuDQ9ycKkkKaCYo/brd2oTyt/VqDB76YMr 0HzhXjc5sJZWJp4MxKZ5AnVVnHS6O4Q6ou6yZM4FAclB9/eIH+yqoavqFp1zOADwONh3cmKAygd xaD9m4djN3/8OT638hXwvYrwD3KZv44SCEnU/MWr+juezS9kqvf4/igmL4jo8dujHn12i/uX0MO yAGURYL4obahOcExdvO3QQ= X-Received: by 2002:a05:690c:4021:b0:845:ddf4:a3a7 with SMTP id 00721157ae682-85d6cbd8637mr82959027b3.16.1788188387414; Mon, 31 Aug 2026 07:59:47 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.07.59.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 07:59:46 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess , Casey Schaufler Subject: [PATCH v2 02/15] lsm: Add the bprm_apply_policy_object LSM hook Date: Mon, 31 Aug 2026 10:58:44 -0400 Message-ID: <20260831145858.3869191-3-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 d06744d72e04..d5859553f7f8 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 Sat Sep 26 14:38:30 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 92D754A5C49 for ; Mon, 31 Aug 2026 14:59:53 +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=1788188395; cv=none; b=CxFu0g1vn3hAtGU4tZ1dLtebZzM+lbO88PYS4MLtB62b4r4HP/I3Fj5DvTESz6hKFoU7m1KpFjQa0MdlzjU6VtG7GL1RHH/rSf7N4N1ZleQYvxDfi7hrwoXEt2FZUcNj95R6BoG9YLSv3Dp/YWWaXQ2uNl3tQP3x/tmwx/euNIk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188395; c=relaxed/simple; bh=6UZqgo1pNVnRYUaLOBFpn2TGeofhqUttyRB0yoGXOFw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=RaLLsK0n0EfVtxyjTgYt0tyoIugZDJdKl//vYiln0KjV9mOjxNSSCJRQGh3gcr48MxoSRKHFNS/me8D7jP6O+25lSuNICRdc3FdTjlfFZwVpUEa3cEKad2wlsdZSnRz8iDQqVYaDlpGf6swLViOIdjLdUq4Q0fPRph9+A6e1ECw= 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=nYkvfYRh; 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="nYkvfYRh" Received: by mail-yw1-f171.google.com with SMTP id 00721157ae682-867a943b149so8556047b3.1 for ; Mon, 31 Aug 2026 07:59:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188392; x=1788793192; 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=8vHIsNy/IN9t6970qY2+L8dSJ+0VAmbxT4VuLQXYbHc=; b=nYkvfYRh2kCVnaN0Tj2EhYB4/lWIH1hkSdlsFcbqjPxAEq5YMsvux0IdUOlHxORVhs OPs3zXVqr52d5DJJWQV/h/YDwG8JbGlo84KsmHEdYhfT8aRnxlKCATTYmLtY0PPyBFMX bDRFKFrunByFw4eiLcdi+Dtc0rIwgI7JDRAOt57HgFtVMiW1uwzxt0yHh1ysFckqKFeC Wx9K/9lPtaqDpXnkXMk6eTU70OnEU5y68Q2MGJGsPd53v5keBmKqsby9d9vyH4R3kW+J 9VD4atWFGTx8rkwuin7omcU8cAbc19CluS1sDkXBGVopeKcIJ4TIvTWp+bVM6VvWfXaX S92A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188392; x=1788793192; 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=8vHIsNy/IN9t6970qY2+L8dSJ+0VAmbxT4VuLQXYbHc=; b=NS0j402CtK3TtAjfJFGO3bDBtjLHSWH6ua9pnIoQLj9u5fnCPz21pPrDdTWVjLjX9v N1Jq77LJzpwTjgPltkld8GYGMrTsB6WaDkjTATez+CNmeIUkszwXOyDbKMdC+Xgxmguy bgtr1ukw0O0a5BEaQQb/8jSNvBHZ8ZcwUz4/ZtLoGMvnm70QfbBYCp95xm2uxeeTqhVF 5euQAkE+K3XiUkL/NSwheUKz/+czlcHVDXBdrDsNyFzRVQ0jrIkMRW3l5dXNxf9VpjE5 O27tH6wbErocjEx6gyESWKBPsn6D/AYfjCqwHjRx43MjrEzWyyybTZ5l/aNHB9QKLWSx y51Q== X-Forwarded-Encrypted: i=1; AKwUvBydU2TAAELFROo01Z4PCuPHkiONOW/S/lm7GPQbmsr+9WHUWTdV8bUSHmDzPEgz0BOc4jdxYs4fHykZVxE=@vger.kernel.org X-Gm-Message-State: AFuF++nCdRrtwExWj11bHEyV+zt4Itaq4D2R9IdOfnwgEdhulKPCd4Yc XfGoMKkFDRzK4uLMcb70FsGYjdFERy8k2wFrLA3E1uVyYW8awhpxI+UH X-Gm-Gg: AYBFou2gb6mMT9Mz9FPEH+DrBmlojAghODnbkLf4+3DiMXRqwd0/ZjIs6wLYcVajy6K 20AGz+0Br4erOH632ypzhxKWKNloTM30VKoeU57jaPq9vmwaq4gD4DyuPofaESTNIsHryGpdSVj ekl6UXxBPq3SgdG8RoJPNNz+Xjwz7rRBTWQgH1M1ydHeuCWfbawDPQDDO1NoG4wJz1RUMAqdLxr kbnm2S65tR5oDFdTVO0J69Q78mAsFw0/dlfz1IEOhiqJVzCmN1K2ArCpRvKfbU3do/hUS0pAsml 16kw6kVzAbvv41HmBd+2/01iSBuMIeIcp2sZ7FtyAH+lkeGyPYgYMLAq1QBnfVhi5/FXAN9+vGo dFKnVuBaRPlt+SnKte4Nt0qZUuG/30M7EY6S/1/ZlI3Zsf24B9muaThymS4swWKKJcR+euVM/M3 r8+3G7Ug8fXaeCLCPw6J/J60Fvx+ieLEiCO5VPVKY+RkJwHILYfDyAxpnQ2qjopKwDQopBF6H0Z OWesKxZqfjVKCD8w2ZZ3fcuQ1UYAAdXiQ== X-Received: by 2002:a05:690c:c4e6:b0:7fe:4069:d3fe with SMTP id 00721157ae682-868747fe33emr6595577b3.30.1788188392315; Mon, 31 Aug 2026 07:59:52 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.07.59.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 07:59:52 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess , Casey Schaufler Subject: [PATCH v2 03/15] lsm: Move the lsm_for_each_hook() macro to security/lsm.h Date: Mon, 31 Aug 2026 10:58:45 -0400 Message-ID: <20260831145858.3869191-4-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 71aea8fdf014..0a6fa21cc31b 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 Sat Sep 26 14:38:30 2026 Received: from mail-yw1-f181.google.com (mail-yw1-f181.google.com [209.85.128.181]) (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 4D62D4A6CDB for ; Mon, 31 Aug 2026 14:59:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188400; cv=none; b=BRodU4F1OoUd26igwvA8/z+g3eid1CvFF2mgXWomiy1/+VRZ8fz0o+eMgdXpsLEnx3+hPxYzdmzo38aLF4rfhSq6VsDv2i9gFhMlDzKos9aX9ssq/m2YugEFJNvZdNMjBi4pfhsyFYUrRtJlc/AxKUKzQY/yNibbcIJ65VXqhhg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188400; c=relaxed/simple; bh=xzF999ibqg4mw3Kbq/zTe/hVGJfzKbE0cK8gaGiljdg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=saV5AalPkAfg244LSTSKoV7wkiy00XOZlXgmKJvJKKZ2HxSAFc+2u3tmwkPoxbmZkQmX12MZmQRUkq2VEd28Jc1Cshk5QsqC+tw1QMlCKoeT74dwMDMu8G3BAWlsBoKOX8m2VrYkKT/5nZkHENjpZKqc9/Csf3HION6TshJATGo= 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=knWJMLeP; arc=none smtp.client-ip=209.85.128.181 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="knWJMLeP" Received: by mail-yw1-f181.google.com with SMTP id 00721157ae682-836c8bdac50so37665847b3.0 for ; Mon, 31 Aug 2026 07:59:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188397; x=1788793197; 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=CuP/HSiHorbIS7zM6H5giX6A7B2bIefcFZ0dGi8JhTg=; b=knWJMLePsGezU9SC3FmP7QKkENKt554Gq1XxmU4QKQ4WcpnEzvtvbo/08Hrx8JeCye IQQgbx77Og2IuYQ5UJl+Psbigr6QjSaUbqoTKAhxjdEUjvXQGd9CWsE0z/7AJUETNqyC aTB36OpSKgfZfK2OkqoztKqGCGN+LUoCVUilHChbEpq12TQ3x3Jw7lQ7uiTWIDsHCPLl nXbqeoSRXYXbiphon+tP2G3vffRqK8iQ7976SnK1u88VYch+uGPxkRlYxIdm7dX1yVMK cagDmTCWobvLa/wC5+2vmsA6LAoQKn+C3BZxAZahUSFOYPvT3zwq3VhXkf8DOdVo4yEl FkmA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188397; x=1788793197; 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=CuP/HSiHorbIS7zM6H5giX6A7B2bIefcFZ0dGi8JhTg=; b=iUJFx482cAa+P4ixbhrTWytphkasWmUx3YW3ZrG2kzOhRmwzmX14czheF/Adzr4dDb zZFsGtxH82qXCHQnuPceecvMxfnNJCa8dXtZQI9PBgnItdP2EjdLL2XijqK7XWoLqkjj 8Fivt4xs7sEtit7hNZOfE5R44kgNBbuN/+hGpirfX/nQKJ1SvX116ILDMaU+Zh2LNqie Mqq0Ntnumbi60jh+lymMd1SW5LUEUPSixhZ/wSCd5mBnfjpCmzMWZVmPP+Tyb5Nstipg SL6w5EeJdSsp4RT3ImR7GmQCLOjdl4EphJDyUhe3yMTZbHeNqxs7FQMKcgcN4s2wUH2J wjTA== X-Forwarded-Encrypted: i=1; AKwUvBxGHrZytv9cvB9+b0xZtsx7VDTNXn9OyrDVIkVzDcmgitvPu0kV/KGUhr8uWu/KURplD+M8unJ1rk6t/Z4=@vger.kernel.org X-Gm-Message-State: AFuF++k+mjN/xQRoqikC5TRHx3/MTHHA9uURQMMg8apzBJIA4iPlv0zY Ubkwmj/Yz1hZ13Umlp6F08WiuHMC34hddmF++45nj2naxERSrAXmtyIi X-Gm-Gg: AYBFou06BpXPe4TL+OUE4EMzkbkciqqTMY0TXIZu5P842hxCPBfEns0N7H7IMK2NA52 Iz74oqkcp8IfJP0zU1FRsjnmzGqosUgM8dcaZbUNE+pzNxSmXZvRMCnzdidO8cdr81C5f1Pkvpt YOGHk3Vtx0QIdiiNznPfohDA/6SKjIa3/1rkYKzboh616Hk9qqHzE8jRmeQNY4ZoiwlfgsNbayK uG5YpEO4b3PA8cLtzC4x9CtS3+lpvoDm1Lq8xFXS4rxTd5j5LjXdl/tpIpST7xqHXb16E8o5yg1 wnebf123IOlx4tx65RezpsxPsoEQ9dL1s8zfVLqPbHNqCIvfbtFONr+v7sldVQrbxZh4NIDru0v 8Qs2sisKBJrCi6KB6JkQ7kGueZoyEPVBf3YOVj1ywKNdB15c8kbFEgNAbZjncYS/YS0j1r8hYkU PXzkmDmrnByPsav9lO1v+Po6MaiDRcW4yLx7xAhK5Gl2ZGIapQFDGHUAGRBoV8LxL4h649IznjR d1GWQN76qxmI5IJvS2ZSAk= X-Received: by 2002:a05:690c:38a:b0:858:ad19:324d with SMTP id 00721157ae682-868703e165fmr7145687b3.3.1788188397150; Mon, 31 Aug 2026 07:59:57 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.07.59.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 07:59:56 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 04/15] lsm: Add the bpf_lsm_policy_release kfunc and policy object destructor Date: Mon, 31 Aug 2026 10:58:46 -0400 Message-ID: <20260831145858.3869191-5-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 f5301c30ea91..2af6a25a1399 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5037,6 +5037,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 Sat Sep 26 14:38:30 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 A096D4F6470 for ; Mon, 31 Aug 2026 15:00:04 +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=1788188406; cv=none; b=MtpkV1GpDcQ9UFolqvVuSsee5r++UQaTo0rTzRpUWpYiDNbIRocFGqza/HWq3GxV0ItWS+ObRcGmHR6UZFm4uYbDTt8wDCyLmde118JAIb2jWXKFWYDrUVdq/CIvYuE1qLNToaCAA398Y2RLTB1kq185DvW4snq9vHXUNYksU3I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188406; c=relaxed/simple; bh=b13IPf/84INpWuDI29q/RKRtrh98z2Lf1mSfFGTrgeM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=RH3d5qfmqoDHbUOdpK3IGRih5nuZOLcdJXdty/RiNf2keFU2h+Z56ypFDRWSoKIeLkidKt0QhuLEVgUGjJ/QN2StpvZ1VvgHiAWDmdbbn7q8lp4HpulT0rnvEJ83uDccF5p3+8nXXv0h1/0KVYmgk16HS+2ZcHm/Mwy8qp8z100= 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=kZbge3x0; 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="kZbge3x0" Received: by mail-yw1-f170.google.com with SMTP id 00721157ae682-8588583a7c3so43999067b3.2 for ; Mon, 31 Aug 2026 08:00:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188403; x=1788793203; 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=rCFWN9c5DyvZlo6DEGKf40uISp6POIHTrkimV4mL3IQ=; b=kZbge3x06aKFzXNvf6cyjoA7yBvs5Ly+cX1WlfmDUoSnwqQih1TnNj2pK1iRia+5wb lpUl6uE5rzbRx4elvQdOtyUEerK2r9vxqATKZs9flWkMvmC+qATzcrIZtsx4Jyw1Fzx1 zSxDQfo3FRwq9HCH5tgP3KdEXN2s8opCR8CsdY4PxE2GUYQbrQDPH271O9EBtuT0xk8C H+v95cOXvlRk0zUrAZy7RY+IjGRqcddqiSgdsAiKs8ykI8P6VsFqWA1ew9em/Zi+yy9v Z4039trmXpZocgF+eJtBnNQH+RMVI+Asn/VuBcbyjXi7ohefY91/w6SduZvFGRHFa7kb VzUQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188403; x=1788793203; 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=rCFWN9c5DyvZlo6DEGKf40uISp6POIHTrkimV4mL3IQ=; b=LIXgaTmBz1HtV4FEofgclWCMSqzqxqomgcAAf42hEK5pnM9EgYE3AEXsxrT9IINa9h jILr9LVyR0JtQDufg69uyNyIY0nlVkK5rn2eg+YHc/EqM8+EgUoaHgk2YgwAYwMPcBcK Mk5Z6y9mVOChVddhwGr7vPg4FgZ+nN7jATq81lFbi9u09KAg93XkNe0eBRrHRg9GrUGP Mi+wAtGJi25azqSiWVWB6IxF+IhuAss/T1GrEOf14HuiaMvzvmM+SVQ8jOyMxG00SI/a 6sNi019dXPgdGkRcR858fs+579f2eX6jaWhuR6m1QY3iwQswXS+5VtJd8srz00RD3Jt7 YxpA== X-Forwarded-Encrypted: i=1; AKwUvBz0biWrzlpo/CBaiwvjKA0UmavenH1tNbMG58x4Rge8OQsej26u9LKlYXvPshdIitb0cBfn7QShxCiDZ14=@vger.kernel.org X-Gm-Message-State: AFuF++l+eBRu4gXCdq6SpCQAH9FNfVThIkcUv1XqoUUR+2crqcTREa02 bIzBTmsgi45H2g2yBRvjw6mHg/xQPFWz2eFxt9z361IQM+sO2fEjbRVr X-Gm-Gg: AYBFou2eR0Xz1+BMDGI7RsYQpNSBCOncJPONWri9pEhY51JcGyMIOxkc/nhHYiomj6P HVKOx7ug06VJ0sx9knTKrGjRMbf6YvuJ9V01sID6wCTS1znQzwXtZTXRPdUXvuTBpGlGAI1Eb21 kpf10idzLc3IjCckLB2cG2168Bal1GkXAjFPseQEZS+XdCbGmX5siNht0Pfq1/YF9cUMSHkxKNf bN/Y6bvnzDZiohamtXcj1U8Wxii6jEIfl3ZexBwvXZXFJBsXkP0neovb3iL437VO3lQbeWbN/Ve pN3FSm+gsVXdQqXfgVadXy5T6/e2AevCjvTBAB98uFdsdDQUO2Uzhtb072yCYoxYFzsL1UaztI9 R4fTllb1y6F5eg6pbOuyOZeKdHDjw5Z4mKTTuwWExWwNiAuGA3JMxvfmwry/YEK7ej227UUyRev yM/3rwbvDoTkFWxf+At5AxrxZ5scz/06LlfUriNRMnm1wI3hnB+x3Yip7CSdm5mwrsEtTCCkmAO Zxt1KEn4W87Ddpebbfmg2c= X-Received: by 2002:a05:690c:93:b0:862:65f4:c8bb with SMTP id 00721157ae682-86265f4cb64mr46763897b3.3.1788188402897; Mon, 31 Aug 2026 08:00:02 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:02 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 05/15] lsm: Add the bpf_lsm_policy_from_fd kfunc Date: Mon, 31 Aug 2026 10:58:47 -0400 Message-ID: <20260831145858.3869191-6-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 Sat Sep 26 14:38:30 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 6346D4FB9B5 for ; Mon, 31 Aug 2026 15:00:10 +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=1788188412; cv=none; b=h8+w3vELKR+jASA5jYBfUPR0J/wRcKEhNzIs2I3/FKCXsk/qz5A8F7H0KDl6zZgz3kp1oQVWWI4h5YnEe7LTTDFpFZk0H+MGOmh4Qot81b+7QdjUWlyUpCC+LRTcgycVsJIDTGYMZ4TqNNKbeYuD5zFhLrSLRdC5JHRJ+mFuzqg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188412; c=relaxed/simple; bh=ZSuaHxdeSmdK1XVdgW9I78FY4WiLUsxPZgAk1jSE970=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WjQ4A9azTZ7x4a/pIfmzHRwJ5SU9nQr9XAWWF3zyqkMl9VQg/vomwZ0wwNVM2ifApNToFZiXpvJBJVvV67nH9fGpxu52zIqvLfNxczrzdID3rJgGczCHO0lpf1UPf8ZsL40fAw99eETK+DGh5NVuHgD94Xq7ds4ZMD8hto0fEnE= 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=Y2/Vm0nU; 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="Y2/Vm0nU" Received: by mail-yw1-f178.google.com with SMTP id 00721157ae682-8664769db58so10149467b3.3 for ; Mon, 31 Aug 2026 08:00:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188409; x=1788793209; 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=xKtb+s5mvwWHR2Dm56oQU87T2PX0PUH0OEkGW4x95GI=; b=Y2/Vm0nUmzXzhAQjDw/2Y5pc/FwY5dIj308vVT12yAbmD6fVCV68CiTqI/nRoKK+to fhf7XtN5CWyG/ydMSnekty57XGpqqI8mDNg+OMiJarRYCjocUGVwFypCNmj96Q3PZjuf mPZ7yqpuqyPWnTfzje3Q2SjPE3qM0Mbq+wT8XfEXr4d3mnVWgt9UIlcyOQcJgrD9yWpu 1LbqAdnvzdIWd5x7BiesnOp/b6SWJWrEbLMaHLryZ7VczXWEIRBpfcWd0fDWNCaDRfjx 1SvaajZ5iaOmCXDr+DHbtTpm3RQxW7pY7QMMMeAuWMGdHbavFNsE+hAugDlJ0apH2uQB Vs/w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188409; x=1788793209; 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=xKtb+s5mvwWHR2Dm56oQU87T2PX0PUH0OEkGW4x95GI=; b=NTWTXw66m8ZSIeWTyOUmXEPeQVycnjtnrYOf+T2PR8JROcvCsKjUZl7UfPDUOTi/OV xtL6Uy8jHiAAFZR34WTJZGDsQXnR9urIDe/T/LMuxujsSRu8ICl4EJOGszY2KP8hu4Mz EQjtgBOBTh8OEAyEi57WZ3GxYjvQEGAt/G9pnDFz5mmpootwGgq941Jb6JsacZQYGUy/ +1j/HqAizvQgzwXlh96BN7GCMjkN6jWR0PMA+t5PmW+8jhtfqnUMND5D6UfosO3FVLtd MaMEvSqXiTfp7TASa4jYji8KkR1rdmVFlWvhd2lzYZcMHy3aloDOdCdmu8us6ZUVqS5C 2mww== X-Forwarded-Encrypted: i=1; AKwUvBy5o1HgPtsDKP/o+C6povRGYb6e9V0jlWANOorsrDvGGYnEHu43jIWtZC8ZeGuGUYZqcGtjHmgv6/+WsaI=@vger.kernel.org X-Gm-Message-State: AFuF++kjsIcZ/Uu6Qv/AY2HykKkdE7MrA4/yLEvaIe6ByGrqrXk2cF7K msWgj/UHYHp1X3HE5vxW8+aMXu+YQoaqew2YRCKstxHpDNOYFj5UmlGr X-Gm-Gg: AYBFou3/Wt9yUGBan1pMWCD+ScV5vYT8owGe21IZD/0xkiBOPKfBJvynXrPBKAoogqw fFnY2aKhPEwUsubP++j4z60TypzUuRmRudLCOwrtFAGH7yhfq5iuhkj8cD4x+rdLjZoXXNSeIe7 7Ij+vKlJF8+Pbdcs7znLiGgiUQF/lSxOKjI2W3j9X6n73DvcUXgFJI4rMpegLkg/ix3lJTB0hx6 wHjeRQve8DB1Uw/n9VjRVKPDA7St0JC0vhSz0bsHxgCGgOuQ18LtNEZbMQI+sV3yU5oPkUkQ008 hrideVHyirFjXfEN/mrwBje7ZM80KW/fMrvf8iyGVBpTzDvjfVmXrBn+Itj39pUo/YLXEztTE2z b3x9+OG+FBQUQdx/osbHlLcqqiEK11t98dN/rxlSNHTlsMLZ4OxXBmBGZ+9mB3f1jy3hG0/dUO5 qyVz/+PUczVI2LIFeU4qORHarXpKFJjymVLb3Yuk/LS45gJ4BjDI9BHs1cCQZ1LoY3D0yWLHaaz SCBZ6ulzrS4gniZN/qhdtg= X-Received: by 2002:a05:690c:c6c5:b0:85c:79d2:91a4 with SMTP id 00721157ae682-85d6b275eacmr80105927b3.17.1788188408827; Mon, 31 Aug 2026 08:00:08 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:08 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 06/15] lsm: Add the bpf_lsm_policy_acquire kfunc Date: Mon, 31 Aug 2026 10:58:48 -0400 Message-ID: <20260831145858.3869191-7-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 7aa47342dc65..ba9972c572e1 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -4394,6 +4394,9 @@ BTF_ID(struct, task_struct) #ifdef CONFIG_CRYPTO BTF_ID(struct, bpf_crypto_ctx) #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 Sat Sep 26 14:38:30 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 180AD4FB9A5 for ; Mon, 31 Aug 2026 15:00:16 +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=1788188419; cv=none; b=Mw808pis7HtjDHQbkwXInmRJX7hJFPUgeUxXYo2fXp5U8k1M2uuQvlg9gNTEP8twtjexcCqyC7QnqesLQlkb58xp83qd8O6dtAsXu6bSsXS0ftp8YbbZ/+9KCE5YUEnRFxALqM6lY6CHpNxOWD4OIGQMOHVzLVgBtQYxFvQTanU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188419; c=relaxed/simple; bh=mBguI3lxWh98IPKMLMiJfZb8ZI8JMn8qUfhfKB7O+cg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=CKEP8Ompe3Attsmt27F/EaeZCxn/5L+ATYhUgGgnYxm3suP3Jb0yNZ7IbZSaT4yBUrn8w9uykSY2u0PynQp0D6pcpy2rRp+Tw0odTfHWDpVl90V8rFExkExzmeqgSpU+ZIfuzsrnX/jJgmx5dSbKDk7t1NPBQnQvS2KhmB8APCA= 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=boAMSZEm; 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="boAMSZEm" Received: by mail-yw1-f171.google.com with SMTP id 00721157ae682-8615bb9d857so9482257b3.2 for ; Mon, 31 Aug 2026 08:00:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188415; x=1788793215; 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=JNBFYVAoUCaDrX6UYDOJH0oSYKJlB1jRjXPAi4Ru5wU=; b=boAMSZEmx4LSTuyQmyphqrdYrGkYSrIsuhsKPapEWUX8A1aWzZb6VtRH2JNee6KamT PSBqtY+VDNfHLY8h7vGc9D5Z4HAxsxatcfpo4AsNj50e6YMnQlhsq7bKrADZ6rZU0Rc3 Zyei8la9BngfC7/JVNBIX9zTktXanfVGcZZIB3tNZ9cn4/h1/pb1CPPmTo2OOu/wUfVt Fec3+Cca/eFaGV6Y9D4Bo3UEgJGaoQjVwCKywHr80LsNQhzbQ3DxAIS+oQFzGhmsecpZ laDie4g6FXjuw7g19AjZyXfj/kWWdbZdA47JFlENLWq+yBC3EOoDgdrQ0JDN6QBNSt1e qYEg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188415; x=1788793215; 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=JNBFYVAoUCaDrX6UYDOJH0oSYKJlB1jRjXPAi4Ru5wU=; b=hmrvgCKasQntpPZYGjA9OOXLV2Ams3uwb2i6lAupZh/buNfkJPH4cP3KUmydqZXlps IqE9Eml5bg9TES+A1+3859BfNDQUhYaepZ6RO/zsLgCEMeZNbRUBxrfg7xVwNJAoKcaG CaKhew1tK4UaxNfGyiU1TGYCHnJuhqPVYq5IaPi7ozQoxEgFpo4/EIee7MDoDHYW8DjG K7hQPGIqSiEclJMqZh0MCa+paHRbOKuokIMEqVHNJpFC4Q9Vf6/3U5ZOnVVprgwe9Vqk DXm5KXa6CBWMg8hpjhTUq8fKRz9xayOddtyqJ0BLx3rwwhyDREXE0hwCdQOEDVIZcdz3 /cLw== X-Forwarded-Encrypted: i=1; AKwUvBydu4WY9r2S9rfbjDoM/6zNfN3bgCQgnZLwCREC6AdVttvivHalWCxPzS4//H1yPP0JwdcNwcQHP2Dv/qI=@vger.kernel.org X-Gm-Message-State: AFuF++msw9hWdJcbrcRN9P+3USYolnelgWc5iHRVe82WOAg3g45zkLFy aQWXu4W5W2Z5AZ0HKeRZIAl1j7PsJfpJkUF/ORjyzf9t/fkJoVDc9gyq X-Gm-Gg: AYBFou0bV5fwXNzBBZfmbmkO1cOI9e+f2LRhfyta7NEgss/2WOzrjSCacdxHy0K+4ex z+oixaop56Bm1Jo+qA5R7njjNmhbEpv4fyy7zfHdnuqAUnZbDyh7BPbILDyZVrEG2TOsQprxxQG qH6+5/uQebsZESagVPHENxaJiogfRSKx+ILh0b/qVGBX8mF/y9FPvh52ySbsYnaNjnoVrmy0+5x fqkrkRmJpMrqKq1vs2uro+bMfFqxm+PnaE/8OiR5dYarei/AB/OlwUa4abT2x6lGT/8tY4lOse/ lON4XUOFNzVISIEReSOFrhEHssBrcoIY5vLX4vp6bJ5BUUIu9DqejUsGEgCObuuS2e7FXwzGGcR iQLa1yEbxUxrjlGGQuCbedTA4YEpVH1+Vv+I8egtWitZX7BVrqFJDwtiJeqLpjcF20dXUGgeOTN xboAEYsFR/O7xY0lLVg48qJ5g+oQWRTt9G/DEElqCz8Xhr3ajyU7PHNM62d0KhIxyR03BbpjHkw HNWOByf9/MMrY4uRmc3x20= X-Received: by 2002:a05:690c:102:b0:867:ae30:4ea8 with SMTP id 00721157ae682-867ae307bcamr12393917b3.34.1788188414813; Mon, 31 Aug 2026 08:00:14 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:13 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 07/15] lsm: Add the bpf_lsm_policy_apply_bprm kfunc Date: Mon, 31 Aug 2026 10:58:49 -0400 Message-ID: <20260831145858.3869191-8-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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. Cc: Paul Moore Cc: KP Singh Signed-off-by: Justin Suess --- security/bpf_lsm_kfuncs.c | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/security/bpf_lsm_kfuncs.c b/security/bpf_lsm_kfuncs.c index 43a4bf57fd31..743752b5852e 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,8 @@ BTF_ID_LIST_SINGLE(bpf_lsm_policy_from_fd_ids, func, bp= f_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. */ static int bpf_lsm_policy_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id) @@ -141,11 +198,26 @@ 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]) { + /* + * BPF_LSM_CGROUP programs run under classic + * RCU and cannot sleep. + */ + if (prog->expected_attach_type =3D=3D BPF_LSM_CGROUP) + return -EACCES; + + if (!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 Sat Sep 26 14:38:30 2026 Received: from mail-yw1-f181.google.com (mail-yw1-f181.google.com [209.85.128.181]) (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 4A68B4FECE9 for ; Mon, 31 Aug 2026 15:00:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188422; cv=none; b=HjheHvnDaD6kUAPDrvF/0gxBTViN91pbgf8iGape5MPDKYRCQ3gWexveeuM8Km2mXALrtTkfwdrRNX1W9kJezeIQx6ylmjfdqinakRLt1RE6cEeiUgAYB89wV2g/zYE3Nx0DUjmsChgNMOVCNh+DoYg3+IKDj7BVsXIX4FOv/JQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188422; c=relaxed/simple; bh=fPvKQuDavmYJkKbFES3kYx/V3SZddiqGrWAf9c0rtvE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qOb/lO69VIPP52bV04VaCUNKNoDACiwNX3OhJpN+FfCbWSKZ/flhvs26W1fL4sJ9KJICC3tEfrnBUCRqPRhq5j/GwpVDx9QI7JY2O3Au7/Kp5Dmi7Qr3fEx4NxPNgFxUASXcDLoRm71Xq0RQ+NPAzqq6vyqPyb+dxC2ZFKRJ8CY= 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=lrN7h+zN; arc=none smtp.client-ip=209.85.128.181 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="lrN7h+zN" Received: by mail-yw1-f181.google.com with SMTP id 00721157ae682-85aeb506b78so45714617b3.3 for ; Mon, 31 Aug 2026 08:00:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188420; x=1788793220; 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=J9vg9u75aczYS2NdokKyQvYBzY1IUPtCFLk4j1zgxQI=; b=lrN7h+zN+/T3izk1jddtcvNzFhT/58FdaKvN7kxOYh1avtFZOzoE6g48uXPKNH7Rcy HLXeVYwLJ08nrALkdQhQgQEqEFCGaROxV2hCf4527rEOvhOBSblg8Eaj6dTVRVjjH9ve k/ntKYu+O3ZOdJ4n4nXDm8hVq9HxDoXjcIw+v7zx7wFriGr72CmHDz35MbS8HNoII5ZY 8PyKR4/NurYfYDDmqj7gcrD8D/hjIbU7tB6vIElmbm15WP9vbNG1/jWxhPyOxCjOHTky HZvDc75B69/XihDxNbrH27nekxIt0kfYO/tsUNVBlFQgdU+uRfNoNQw2SJszQi4TwzRU 8Vjg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188420; x=1788793220; 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=J9vg9u75aczYS2NdokKyQvYBzY1IUPtCFLk4j1zgxQI=; b=S8zP2wKUku4tKm2YQqHxvSLfpvSbx8tg+1V+BU69hL1uMXajdjhQFoOpJ2hnE/3qeP SEtqBYi+I839koVCskwlM/7JdiSpRmtMxmOE/qTfpQ+vbhhRVHrLnV3JtaUJEQH/aJ14 nBs94DoQXGQK4T8IR+gWdIavSmGb5UbpZh5gRqKYaJPXyNkwREbqfN4eq77V6dD+Inkf fGRLRh6hJlfH2gOnpMFtRRhBQQTDza0JM2eEP+Xo754AoHdqRT49p+2apHnXvwj12TSQ jFDfSP2VzPox/DHy4h4FVTMNdVhxacy+cWdgBWpfs/s+KzKUUI+dcBGc0ef+34JUgrzR XPnA== X-Forwarded-Encrypted: i=1; AKwUvBzP2sp1CH8lNk723hxQjhU37Nz/gQwLG1WaUEsy+iJAXyWvVxPQD+kjkz8kvg6E67ea40vEIFPfMeXFyZg=@vger.kernel.org X-Gm-Message-State: AFuF++nwjqoweO1Gxx8QWXB/xOF1aoT2FUzX7T23xMDDoUT9PwkrsFoH NtHikSf4dbf/N84pok2FB5vMKBIisagYSPLzMDDYz/6lX++8JfkPa8/7 X-Gm-Gg: AYBFou2LnK+xpDdfXtzdPffBR07GH/DBMIdM+nBQcs8T3TgPVDaDpz/YHVp4tko74NV /aHaClBuuZLMIx2BR4tfonCOVL3PyWc/iGKtFcdnWG21Zyu5aU0P7Rd0+PhwH8eB+WMOb7UXGxk IOJ10nlA1LzUzztNghO0fE9mAUmVSet8ggJPA/VIWCVH+DnOZluZcBaHwI0wNtAN6HJDJjL08Tp ssoGfEHcORArK0bBCPs1hVSPF+o/umL2wKDECoDb90Ggy4UI8/jtAxu7iD/h9jlxHZhzA+O8v2g c+XA0lVA0cWhD1321gbuiRsrs8HpaVqKjL3BhJFUVv3j7txTKWxaU+z6DM83B8jpbUQXzV1gG+J MF7KMCEmOHid6YX7oJO7tz4/j6g385kIlMgRkstR9XiB2Qo+IWTTOfe1t5xOUpVjRQSfOwVpgyz DX6aVvuojezfuxZYzKfVH1K6GHvSvpc5W8EX5r+l7/ltdXHnf2GRd3ANcF1oP140YDJeczckSh1 iz+bIJsOoirt4zqlA/A47w= X-Received: by 2002:a05:690c:c3f1:b0:81e:98d3:cac7 with SMTP id 00721157ae682-8686f52fe19mr6867327b3.12.1788188419607; Mon, 31 Aug 2026 08:00:19 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:19 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess , Casey Schaufler Subject: [PATCH v2 08/15] lsm: Document the LSM policy object interface Date: Mon, 31 Aug 2026 10:58:50 -0400 Message-ID: <20260831145858.3869191-9-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 Sat Sep 26 14:38:30 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 E02424A8411 for ; Mon, 31 Aug 2026 15:00:26 +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=1788188430; cv=none; b=qUL7VHEFCCWiJdADr1lvu+JbBZ+VwHpB5Lq/WSlDwC+V677q2tb5/izrSnC7fib4LoJqyY2EPIAmeXppX4bR0Q6a4E18BKBbMl1xVxLv1P7/tpHoG0IPgQNJvreQsUR8029PNXaT3VEluXPkM4mvjVNgyOPTwdqcmpB60wRhRS0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188430; c=relaxed/simple; bh=KJmAMn9kyZlisXvbTbDVwQ8wzaahdzZjp32Cx71hT9Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=DSOTjfvdRzd1FD+BAcHv5/AOdmrRzXu7iK6AlW9LX7vaaLKSB92YMQlauI3O4/6pMIIBPEU0XydFGe0OWEmlaGKJBKnCQM3+S3TM0nVU7PMnnvnu5UHgwkO9QtaFVAO3Veeb0QWh2MzA22KD6rKjtTNrPwfdn0/lfHRc2Xm8eWg= 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=iasmlXfc; 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="iasmlXfc" Received: by mail-yw1-f178.google.com with SMTP id 00721157ae682-8549a96e93cso20830317b3.3 for ; Mon, 31 Aug 2026 08:00:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188426; x=1788793226; 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=0HdDzHGCpm+jxHHcKn7aF3osQpvVTCYyJTtW+VBgeM8=; b=iasmlXfcIQvkWIy4Q3+gCBHK5OhpLF6ESTfTNWXYZlSqRyNBXqDTdEFmwUyrjg0VWP +99EeCAHNtDbXSIfKv3uCElmQ7uyIT3zkTbtVzKGK0WzZa2XFURUTIh1jBK3NHeqGT+q Z0gdAxvE4aEvalZfPvlZwevFpubCM6BpDWw2XOkjQx0dWRAOkTrfT9TlKIG5Q4yhlZJe KqeGAgAH1p/B7WG5QGoTAwN1mw3L15SbJeTFbLAAgGSVuv8mWdrkkMmygBthn0JsHlqD Izp2iz96n6kd/JTCMpbIooS8jdeblIQ47jbV7FhHGiXuXfE+jUvgXTRC8mJ9HZnM5/LL 3YbA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188426; x=1788793226; 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=0HdDzHGCpm+jxHHcKn7aF3osQpvVTCYyJTtW+VBgeM8=; b=cKE7i0IzuJLITphwmraQs4brQJpRiekNC1YgUHAVPIm5YnPXkqmhbLpkL5jKoqChEy sihPVd816cz1+Hw6sc9fvRs8g3re9KrqcNtOcmA17eaQJAbGVNnzl/lXBTD2095xJzLH i19HwAGGrHadiRml+Us+XO1TpM0oVrq5Rcphhw7VHVW4EzUpmhWo9/hfQLBaujr+vx4V 4IMGk7h0WdxzU3fIaw8mek8xQnkYuHUwEACurPO6Ug922BJTulX/F59CE//7ER2Dioga fY0JIiC92kHOs9ZFngiwP0yknJ6xSz9UQGu4TLZJUd2/y5KrnrAuZXWB3gkaxotDTyI0 m1NA== X-Forwarded-Encrypted: i=1; AKwUvBzQumPC3qJeDNxsfbU8i65u/ar1p9wVSm3xAIbobJnVXiARD3kJwuMKK5Qr8ZrD5CP+ESUfpZu1sScMooo=@vger.kernel.org X-Gm-Message-State: AFuF++l/zQipXXcADH+9gysjBuDmKIr9xFYg3dQ1ER+Kw7GUtwpof5RY ihKVDBfXD4yCqUNCwa/WbpkZCy0uzSCwWBt+ziM0kV++Mrq47rVJzrBN X-Gm-Gg: AYBFou21Uf0U8SJABb5hGA4UGoyhaMTDLUhUxP8DL9AFB/MMxXzqL0xzH2M04HBRKig Ed1d1rBRgnVS/5sWjTdKk3IMUFFPdwo4SBxaJlG0+j0uzHWVLU/Wf4OX6dGdwMEmk5/PlBcN9js MAhnT0fPer6AXiBKF8IDYGT5z0r/kqjHwS4R+ZwAGOdwR3EPNY2oIhNDNcb848B8wx243/h9MzX D9hynkRXJV8vU5mztZNCL7Zx/GsAkU2IJa1pNZKW8ed3hs9AMvZGsTZ3sp4VhjOJgo1ouGWBfzP 0qkjrRhjs7Pw1N2y1BK5rBZM6D/LenZ7gnizT7mAC2L1nPLm6uSlzPzMW7vPoTdf8qRyYisD5Bm GXVzvKz60bFiDJT5PtdP/UX0KWJ/+O6Zb0ywnA8odCLJahhFf5ChHRD9/PebM8kkyHqSNLFy9yp 3j3emJ3lqTYRnalZKi0oBmW4TwVLt36tTOw+kElvr9tJ/WO75zec/m7HZFSI8ib4Nyzt5X24APv tHZsAFjEVMc2gWOwYWqbONRtCDAd8WVYg== X-Received: by 2002:a05:690c:f15:b0:833:a89f:6cab with SMTP id 00721157ae682-85d660d94aamr105891687b3.2.1788188425564; Mon, 31 Aug 2026 08:00:25 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:24 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 09/15] selftests/bpf: Add tests for the LSM policy object kfuncs Date: Mon, 31 Aug 2026 10:58:51 -0400 Message-ID: <20260831145858.3869191-10-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- .../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 Sat Sep 26 14:38:30 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 1C9494A8420 for ; Mon, 31 Aug 2026 15:00:30 +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=1788188431; cv=none; b=akVHGEi1LE43T+s0BUC3Ouk/gXylBp6QX1IRjOdQ+v6Lw1rLMJU/cZmP6zL+1HWWZ6BiKnhAOWpN4IiE6bSeOpcyg4LN7SbpI+qZxfF8RCrXz67nkmWHcvOzXLtk71OtAy/Bacbh5lPxSwfmYg7PgaSL65JD+G+ywY4sw/KaEvg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188431; c=relaxed/simple; bh=T5//L0yJEU3CVN1o5jxNYrK6S5XFuiGgpBvi0jp/cJ8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=j1+wE5f4iUUutuA/N2yzjlMKNClxs/q8+dSvGCYmeay8xNMCvw7Vrqg0PFYS+RKH2pT2WVsMuVuh0NnaAXvmZOzvbccvcZwDPCdqN7lEOpeq09vgI78k0QmqLCK7LmTv+FWbys7WF4d/mcM+V5/ofBDva9IttDIWgRXU3p2NFbI= 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=pwHXeLt7; 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="pwHXeLt7" Received: by mail-yw1-f174.google.com with SMTP id 00721157ae682-861feb6d61bso16008437b3.2 for ; Mon, 31 Aug 2026 08:00:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188429; x=1788793229; 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=Ewv3B6+QarCZOHtRgYa+vZ91GvJ2yanwE+qIJXxDYtc=; b=pwHXeLt7ZqCYDiNGZxJbgDt5S0lrMpY1WNrVPZ0gMBd1EHvN1FRIs2URmTzC1nBftV +E/gOXn+4ncnWTvG8OtQQdYIORFurWSOorqvJIfB1syAWYZCCtJIIqWNSk02M3rdt5yf fjPFY0vGV3q/gv8qCSd7ipGWPNFwO71BbAd5oW/tv5T/ME6z12EtRHh0pJTJsvotXXGA aucCPfztdXwfFGDauZqv+DaWc2/fPPDFpy8EPaZ2EOjEQOL/e0+PNZ9/RFPCdCL4qP1L hbjL9deuCwKvQ5OaiCP/sRb4tPnuNy/0mD+n23XaqskXce1Ks1YMyo0KFVrtwBHogOkQ a8xg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188429; x=1788793229; 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=Ewv3B6+QarCZOHtRgYa+vZ91GvJ2yanwE+qIJXxDYtc=; b=HXIuqWiFNv7PF3IAAHmmWlJ2qbTcbEcdzlCgRfRRjtF97VgXO0ZnO0KvcUCClkzRLE 9xrf4xErS8u2xLBlMkNK2YObRC6CxEMhGYwdndzS8B8DJd7fcbB2a3F0BxU/a7JPr2pP LFRiZUlxP66QyTbg9WHBGb/EuJDpUNf9Qe8g2u8Ej+V3m1fJKkXtT5ZzUYq1XYNmXXK1 uwgx0SaDXTBH5GPks0rKOlCjoiRLcCo/bF8PRtZQhmWlgkXlADjeKqkze1X7oZsiS50d Hv7pbDVlDtvvVfDOBBe7LJUsgsoyuZlqp5I0D12WEJFAL8M2EIETsOkxmEMQh7FtxKvj Wq9w== X-Forwarded-Encrypted: i=1; AKwUvBzlVx8Iagx0GP+36Dc9QrAEN/ZS7fKf2ySx2UJkTM2mCVJIcT4OB/j6vbn0xegwzJOvAZFYJvau2jhdWnQ=@vger.kernel.org X-Gm-Message-State: AFuF++n+VsCQbsbAGTVhe7itM8SlLbiXpBuMb0IEfeNBeqM9O3FKQ0a4 wqm43KQsUzhiGr2MO9Wa3jKBjZKLzQNZizeMmcyFCKEGisJbdaeHF1ky X-Gm-Gg: AYBFou2hIE4rqe0L3Uk7gqxB/Oy3dMzXAIdHjRmRPHNLBSaTQaRI+C2V3TGLfJ/HEsb Htr909F+FuyE4ubR2dPi+/Rsi5bJER1/qDxwCfqFYEswZQ2YWW9edll2fLhITrLmOSCsySJ/ozb p9hk6f/aKesZwArXKfeaD4OIkInIW0s79SONNT98GXMoZRW9CvKAXe0Wzm65mvV98em5Sei5ZMI vu79L2c8IvPPG0aKDA0YBmOVPYhrEtcEEe4oY1BB2/I6P4EW9Uil10ggNwyYKYoAoRZOSuXX+Lw +nsjHO0WoUpwVsrXIOdaup0BO13+wlq+83ISt/OhRQSUN6B22x9wRC4yWpALYF2HwJySK7tpwSe BZ+blC11mYT+7w/2xtrbhlVB4BNHRKx2N9AglEpA8U7GjHTv1jVDmOkgzDnCXN+XgXfqD5zliJ6 ZLIyeF8we24IkHTnBlSR6VrGPADLH6T1Sh4LmtUjPBvk3o3hd5JURC3KG1Cczqz5zhSgRoMv08F RSUV6BqvKyyUK66L24NohI= X-Received: by 2002:a05:690c:a7c2:b0:822:ee08:749 with SMTP id 00721157ae682-8686ed6877emr7079577b3.6.1788188428958; Mon, 31 Aug 2026 08:00:28 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:28 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 10/15] landlock: Expose the ruleset fd lookup to the rest of Landlock Date: Mon, 31 Aug 2026 10:58:52 -0400 Message-ID: <20260831145858.3869191-11-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 Sat Sep 26 14:38:30 2026 Received: from mail-yw1-f179.google.com (mail-yw1-f179.google.com [209.85.128.179]) (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 E6AF14A8420 for ; Mon, 31 Aug 2026 15:00:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188436; cv=none; b=lujNG48ejStmxxEgKjKOYJKBBPLuTCYOoyaAeB1PaX/3Hrm6V/Lttd6CU1KfnJtWpdPqk5jxYObz+eZyvRvEyq0ZmN/+3ibrjOFROm2QO88frwJj8auOtqltYJINMxAhkcHCufRsM67jxeR/DQTjaKX1DU6aOcMKsRi8z4XX/4c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188436; c=relaxed/simple; bh=ggkEpSln1XdPPecX8WAdnU8H+3V7doqITlU0A4IOYnk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=JUA6gqcpaoAMP9QiNBcN/X2HZsCAifYTNTcepoijIWJsbTfrqj+2Uy3lCIm0Z852K/5WnSB0teAXRuTHdPRlVaN8jbD9iz77l+62WkbD++ZQaOxpuQB8crEkeMz8qXTh7YNVKyZhuyXQU7buyyPr/jASLDJDXhj/fsCOMs5UPJc= 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=EAgEYSjc; arc=none smtp.client-ip=209.85.128.179 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="EAgEYSjc" Received: by mail-yw1-f179.google.com with SMTP id 00721157ae682-8664769db58so10157427b3.3 for ; Mon, 31 Aug 2026 08:00:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188434; x=1788793234; 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=37FOvEpP7fLV8sjizV5fXkE6iW+/lc3VdHmJ4Pl4zDM=; b=EAgEYSjc1NkxvfltXvWKtiJ0cbxgAVEkB1HPcve4EKuDPHykku/eq7I77n6Rk8yP5X Zvtz/k7znuEkgaxoiPM1bNPolUV0aAGlm/rSDJaDOTVDToU4A8IiLzeVhFl1VRtXKWv4 yIWNBq+eQ+GtgqUJsrGbQ9UFa2QW44rQSmWm2Xzf1eh3P5/yJ9geAq3QJN89G04+VbT5 lF9XCboLSCNN+kBi0KKhSkiHmhTRrkwHTZ1YD43JCsGTsg72+F0GNBertKQVrxlrtAOV CpKfIAY404F+gRgY+aWDGhrBvYWHFldnjAC0iDsnDasD7yVmgj64okYQm/wYJVMlPm5f 2FjA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188434; x=1788793234; 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=37FOvEpP7fLV8sjizV5fXkE6iW+/lc3VdHmJ4Pl4zDM=; b=d5dw18r59JtqOEc7jSFRSqF1nKghQsHg76t422aQM8Xtuz1YYPjdE3Ipttneg25M44 lICchOW5gRKpoQe92KOsqx/aITxbnuslLdmlee+eZ6pgbu/IMRC+tIgF62EQOVmo+Cxj jDH+oDdjrtXPCLf5tLXt2wVDP/0Wuz2eRFyLQy/gHxm1kdkJBcdVioMLCk2NHb2nVwHN KN44gZuGN1ibFGJkEwYk9QTEv4lPmUA24WgDD4iWSQtQzJdC4HVKu+srKq7/XHAKYpZB RkkEnLndywni5PXEWmIRmeQSiEOlb279upN4rCiKv/SfK7Y3MrvNvSInnNTvtogLXjuS SHbQ== X-Forwarded-Encrypted: i=1; AKwUvByDq8uEXNZRT1pkYBb4z1GvVVo32GIEf0nazAFnq1geCc43FBLiyl/c6VkqIaTlmkEr6mHeYcHV9I4M7c8=@vger.kernel.org X-Gm-Message-State: AFuF++mqQcDsDHcp2lTGH3AJfw3+VR9rOZGTpXEvVvw0F+hTKOL6YxXj vSCvyWscHKzFXt5mggsKzFDm5DvzLjOqCVljTGSLjou7s0gtB5hYt+nK X-Gm-Gg: AYBFou3hOSMMYbENnMFq3ViHUHd3sN9ESlrPdJQ8etjyjv/h7d9JuvbebjSSHzRDcty t2UBp8c75watfyckfhYmOJRDXWOoeWbRge6Ej03VgHIRJcWeMtA72w4a5MHicKnZvlda7MFrsGJ Njra8MnLbVoUJlb7PexbPrp9H+soKlhh5EcjERI9k8yG/g34tdlhUl6uCkEsYW4omSyKyRQ3ivo DjeKV6IM+U1Hyg8Cx1ki/6XItTZHHLTY5K7iGMuqWvukhrkOQ2HOITSC5zWKsvRfhe4j+xdJQXP Ddm+mHHofa9iB/6b6GgTcCCIP7E0LZOC9YE4Bst7msEgNAWaHNktFE3KPWenfKuj57RqDFws++6 xyBObgit9qQc5DNfdXBKZA9k1cFfL2RnV+WaEnnm2NaWz0Ow5jpCRpMx2G1iobvmtHGORUweX71 U+ov8XPyWvKbeNUIraJFMQvuFoCt9GwS0hOfHXYXAWGz32xgP8uY6TUaXv2d43fYmznYLUbRSWt AwC02QAdKW76squrYiGR/I= X-Received: by 2002:a05:690c:6311:b0:862:cd8e:78fb with SMTP id 00721157ae682-862cd8e9648mr42595017b3.2.1788188433555; Mon, 31 Aug 2026 08:00:33 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:33 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 11/15] landlock: Factor the credential restriction out of landlock_restrict_self() Date: Mon, 31 Aug 2026 10:58:53 -0400 Message-ID: <20260831145858.3869191-12-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 Sat Sep 26 14:38:30 2026 Received: from mail-yx1-f44.google.com (mail-yx1-f44.google.com [74.125.224.44]) (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 0C6CB501F36 for ; Mon, 31 Aug 2026 15:00:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188441; cv=none; b=ITIuyCtX0+3zzx5+VNIqY5Vya4HxQzCsEHHp7Aj56L/9R+yPLgPE1HEGdQKq8rF3fv/aw6/iJm/N5zWCrzR8AooFV6C3yjG7AC7DqXQdR1YBZWUIgnBF8ll6xLoAzFZhqZi/pR0jbdWdM1w98jZOMIBwB0U57s6FEYmt7gS7lRg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188441; c=relaxed/simple; bh=9K/6PJaguZoiDf+6wZGGUbNkFvvbD+lozD8PmC2yt9M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=giEY7Hl32gCP/uoLMWa0hsuEdBYxJdwL+g7XhSkWecvY7BBBFv4R32E51L7Wh24m7JWfV8F7urmT6eIioTMEmDSpueX2aOMS5K4xYBtVkZhhPtpTdH9w/ftuvSnRAgvx8MtyyniRRxWooVSPnhq5fstCIMZwba9v69aG70jN5ug= 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=RqEu4qbu; arc=none smtp.client-ip=74.125.224.44 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="RqEu4qbu" Received: by mail-yx1-f44.google.com with SMTP id 956f58d0204a3-66c7127a73dso3238528d50.2 for ; Mon, 31 Aug 2026 08:00:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188439; x=1788793239; 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=HDGtPCx5xovkSN4f72iXHLyepfibwkWfrygjFuBGvns=; b=RqEu4qbuUxIWQUo6AUoEiJHxOsfHVpT0UJaRiltMiLwaPIXp+I2B9ODBSxep0nZMXq Onh0evcehQ59jb8+vmhkP4tq+1OQte0e8V67QDnhP6pE6DIZJMUB76+XF+EQF6FBSjKO uF8zYBSQLEYmXsh4v4N6tFnMcw3AlT9tc4isdMyyuX4Oq4o4Lt3ApLsj0eKMW1yvDXWG N33L9VulW0YdVO2FyVx4xzbJRb5clMAocVLqpnxOepzw75ohF0C4biWJ7RX+mRB2Oadc EvuhcrU3WiR1kxw0tRPmGSm5z6ytdMNYTtwYfq6v51+kSM6/w9cr6tblQrFCxwaJauJV 7uaQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188439; x=1788793239; 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=HDGtPCx5xovkSN4f72iXHLyepfibwkWfrygjFuBGvns=; b=KQt696Qr/6FuCmCkeeJ698qV/UBn4L8+w0eIUbYRysyMMJnlj1FbS6oaFRzClt9jFa 9V94uBLwQvMUMYCXWqnnhGX/7FmGHq239QHKoNgPpzwJ6uAgl9MNtU8VyqXtel0cf2do gTGRN0vrgmX7IKXHT95gvY/f5ys/N2Xfve1SEyPopvF2fCdUf4bWT1928Cpbdo+Tj7Ly KV2zu9gaGS/u8T2ovrbYMQ6hSvdWLGy+NXpvVgglHj/Z6nWUsChVhXBiVrJNDOV1TZDx I8S2zAaHuZmOnsR4rnAjTQFEtB7VnSKw9HbqkZ/Rthmi9FFZVzgijsP7qMkXesbrcV5d FNNQ== X-Forwarded-Encrypted: i=1; AKwUvBzUh9XNxWKyt64lOQ20F/rFzmculrgVmWnwjAJ9aqpv2BwnWyhUk+6FoEwIHrJZJtydg4XjLNQoJ4eGgWI=@vger.kernel.org X-Gm-Message-State: AFuF++kv6AMFLAEEW3nG92u4qM6dm5orR41MPxeGG0PUYLXXCEFKhq66 HqYnZXLvCb4EfcCaLuoMFoH9GN5pd1DWrT6tL+2uzfkL4m6PpjVW2h/R X-Gm-Gg: AYBFou3DLU+Rb6yyRCG7gPK2rOsFsKfUKibS2e9U1daxlZWqn/CXdxCYqO/xEerRSW2 ufTEvkYu4cZPiFuotnWhpROJ3h+JWJBhNru7JuXcszNlgxp+ghRUQvsfCIulj+iOSp0Ciog/osX sRjFt+hFhL2Y8ICcAr9Lv9Ig6p62trL5LJRt+5TbRBVawRIGO7gavsfemACd/aSSFyq0BD7F5JM 4JIUpKAJlFeODS3JDPW7Kz+4wRASZ/xptWtV6fcWNXz3GDcBFJyNt5MUG1QcjKvZ5dVJ2WhZ8Rb UiFh766dsrcPMIWRW3AyCK9y2Nbee4eS4Fvq3X95gZMB7X+Pzf8X0KmqzYuFqRx0aXGhk8XK+Iz MdeYrHjeRyPG7kFe6z84JHg/q744IfHZ9qATzM9V5lfVb+gczvKgQtN6tibQEduOXEUDMt3IcPC 3B9HCsFfyHFbK8FaFunHcAw9B0N34huDuWeISQSSWgELl4cJ1y61oigCA9Nj1BM+DImqcFEcHkG hjPzS7y1qzVVAvYg5hv4js= X-Received: by 2002:a53:d045:0:20b0:66c:c683:5bdb with SMTP id 956f58d0204a3-66e4c78b3b5mr6500975d50.49.1788188438576; Mon, 31 Aug 2026 08:00:38 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:38 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 12/15] landlock: Free rulesets after an RCU grace period Date: Mon, 31 Aug 2026 10:58:54 -0400 Message-ID: <20260831145858.3869191-13-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 Sat Sep 26 14:38:30 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 CCB56501F36 for ; Mon, 31 Aug 2026 15:00:46 +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=1788188448; cv=none; b=EocAcnkLt+Z0f4gwshCQNPD+ghYjLxRCHwH8Q/h+GVJBsF+s00HMKBGJWq1Cl/AY7Yrw9aaZg0ccql7bdhUboHqg0rbGAIdDRIyNWp/scSwMTnCBjHULK2+u0VHTqaABwBC0LHtaw7mu0x8168Lk8qdHMeQm3A1ni+pkGMS+it4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188448; c=relaxed/simple; bh=y8vKV0EbX3uwzWTuiC7zAtR24C/exA4HkGBQ/YwAlKE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=MoA+77u68snvsV2fz3z8m6hUWH/5Xno4LCFs2qvltx0l9YgKpnFhG40LA/E4KuOwrP39TpwWzsREyAfrGUnA1Gq5/5vnXsEQLyYJ/UagsMz+aoXMWw+Au5jbo7g3eTI92cwUtrwEtdu2yHRtxf7aQM9NBIupBA6Zzuw7rjnzszc= 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=GJsupqP7; 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="GJsupqP7" Received: by mail-yw1-f170.google.com with SMTP id 00721157ae682-861feb6d61bso16014527b3.2 for ; Mon, 31 Aug 2026 08:00:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188446; x=1788793246; 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=RiftMX2c5AE5hdKP1oNX+6YGtpBCsKTcAAXYc0whhFw=; b=GJsupqP7En+Cx56W2MKVbSdRhVzoYyZGBitZyyiJv6M9H4+seB26qk+15gJyo/63ir CCXE6kumBUmHea3+2NTaycsOlMpRKZU6OBwKgOB4wzJNdACDYFdLOCDuJq7W0ZBB+46J Lwlwp3vSO4NwFUvIuO3JqQIQ88PBi6d97yg91EiSo9spQv40jSbZpN8ZBDtBjBiaEt9W qF98xP1CBaN9RXON833dgJyljbC9JxyHUViFt75msox3n8fDRrNAF17lFtUfb1/ynHbA r891FnEK6x/x0Ogg8t8WgfJhIMQZ34xllg0dbCFg8oUztBoImL9hAS8CfJUAHEMVBMIv I6/g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188446; x=1788793246; 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=RiftMX2c5AE5hdKP1oNX+6YGtpBCsKTcAAXYc0whhFw=; b=jncAheVhMcXukYmlfW0VVSXlLt6Hw27r1BmCpUKyT/ByIY6RnqBp2y1G6y6N36jVxt zA4PKvg/vn2Q/IeBUcSW/nJvQfJJ6Wc6y6+7c00aLWQ0A42+fOomiMM+1iYpvwMXKoO3 nVk7FCPFYh3Y77JCB4gpIdXicY2mxcK8DT9aWcMIKgvBXwQzT+nUGKOMZ9+i5Ecztbum 7JCGbWXpKRsjqJ/1aa7L8Cp+UqKs05YHDvayQR7DnCi7F9N0gpenzkQNODDZeD/+wLpJ mHcZf5XsIE/MdpEcaoIav2RHnHsVSjMeX0al44bji5OJybrPp9CHuA4G12gNrbzzXM2V kkPw== X-Forwarded-Encrypted: i=1; AKwUvBxHmgLG06i2tTwtd9ipKpA706gqpM7kV0MiKYpQ0NHhPcGKxTvnMd+j5xXkns7IuwetIPJf/z1780WeZG4=@vger.kernel.org X-Gm-Message-State: AFuF++m7IH6rMCFZOrSk+rpc2L6bGDrFecrurqe/mHR6NxN+y03Crs6i eX3vg3zql3BG7IsF/4j9/wqk7yJyWeSiWxg4NbX7zwQPM3S4pBlsgQes X-Gm-Gg: AYBFou08vv5/VaLuEq0rkeYTBxtl28J3ilh61u4Pxs9wPhV3PBJkti0QW3xHFAFfEVv Y96fyPPDaVQ1BxV4t98gVOAXxj/c97Y83GD0MRhUQymoB8mvn0HlywbggtYYuad9Kl5UfRUN9r8 oU5axSx8feMIvdtVP+dKp/4LqaHZ48Z5yv1Zpd4ZuO5tL7QJJbc6EOlWicx52sjmv4H2MtvH6M0 fbZBeUO5dRj1eTxTELRMMhQNrSSirvBz5MObbH1PRmWmzbXfU9fLoG372exGryrkWQI3iRLAJlV WBJO7BLGyYGPP5ruP4EfWnfK0lc6NB4H5LcjgOd1rjSaHA4mhFb0ReyWhAPHe7IjxwGUk4w1VDS NaT51WtE5wv4taEpejKCcYQQC37/BcdS3p/o3O3f1KZ0GayaXoME8fIPBlDisP3aj10YWmjFWGx LKQMJEabNSAghnUNJXREVQonu5/sx3Z13Sc0TSdj+i2uE5QJpodxwSgEMYrK2Afs8nBpu83ngAF UKIhGalxabMW9z4x3m5zb8= X-Received: by 2002:a05:690c:a7c2:b0:822:ee08:749 with SMTP id 00721157ae682-8686ed6877emr7087787b3.6.1788188444975; Mon, 31 Aug 2026 08:00:44 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:42 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 13/15] landlock: Implement the LSM policy object hooks Date: Mon, 31 Aug 2026 10:58:55 -0400 Message-ID: <20260831145858.3869191-14-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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 Sat Sep 26 14:38:30 2026 Received: from mail-yw1-f182.google.com (mail-yw1-f182.google.com [209.85.128.182]) (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 64567503BD1 for ; Mon, 31 Aug 2026 15:00:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188454; cv=none; b=k2nIj1UW68xJ9yeZpBlvey4/kWzufxTMw4O+yYaZzCydazqJWZuQrWBKnGK5nWJTM/+7DK1A0JY7UU4JMbVjMv9qprcyNVJl45xE/Y2Ebu4BTXkOGrf+fIjiEr8o+8dv64JTJdbIAul2B4mqKKiqO0bnXzfxtid13suKDv8iRUY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188454; c=relaxed/simple; bh=K8bob/0alFROS+/jtb/M2iJVIOfkWpfvlsD9YGEeofQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=N6Wn5X0U/BVDpuuMMdaSSQC8RpC9t1Sp2vfii6Bq9jOEREdxaS+nh4SqANZlyqTFVBKxSOf18349Cqcx+0jC8JvIUD9kwRsJUp86rKuikByHoi6UVXtBXu/y3+etPwnp7geJZ+FBeI1wl6qC1/UjNmxFNhaDRDlvZEV39lsnIbk= 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=H9gmwejv; arc=none smtp.client-ip=209.85.128.182 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="H9gmwejv" Received: by mail-yw1-f182.google.com with SMTP id 00721157ae682-85a50f6a7f7so40542227b3.2 for ; Mon, 31 Aug 2026 08:00:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188451; x=1788793251; 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=2QDe4ORKgIW9X7wqWVheNUlHrEl6DClMeGSUdm41tlw=; b=H9gmwejvzsKDmUCvpwmKwiHAc7WIRB1yYzH++qRpgMEKLlHNzMHoR8xbx0g7Zw8iiS 6tDLX/U3gkKP99GzNnNR3LdMZSpbH8IwSxzxvS0BIct+e+n2xfuX3VGXoMs55JxZ8a2E UtjaPq28nrcrtuC2xUdWe+xfr6PWjQGiDUwkzSZTMjSAI6975NKqTIvjTaXtuG856laO slNcMoWn6KDd4Pp6N4fJ/cNQFw6/StnqPtrt5TKPETdJDjIusEeZPajgYFgOjmsLV/jX CJtxSlOqRM3T4w1vXCFUYoqP1JXTCbEyZOqVuHGhKpv0E0kWdIWDMh3CTeDxvc8VCD4P dorg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188451; x=1788793251; 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=2QDe4ORKgIW9X7wqWVheNUlHrEl6DClMeGSUdm41tlw=; b=oDdVCs1YJnCaJmMuzFeb8K33onxWzQHq6Z5DrTI8lKKtrH6P19AIYRdaLEHkkybl8S B3g8sZLDCshnpD2yH3YiBpJ0I+GGNB/7Mm/LyrocY9aDI0J0ZL0jLHPVioW23MtJ4xMG hTtIWBqgfWnPlRuF6YrnPsiga79a/CIddAehPElrHw34RcbCyg5sPcWqtCCR9XAlrgbS iWZ0/Fqdp6gB6Gwt8ql9NHv+Ws80yNUDcT1wGZgOL7KGXtrPqcAP+gsdi5hZla4isuX1 bQVbV7weOP7KtaBMnua72WBhPj9UXWzdAsHrZQfMb2vTSBeMz5RtE8VI9kGISYg84ocF mY8w== X-Forwarded-Encrypted: i=1; AKwUvBxA5Y+ofikMw9Pr8EiuKA4884ssd1L/ch88OeVbQ6SmJ3vX8KT5RNRQOsK51EXlootF7v5xxmKVCvY03ZY=@vger.kernel.org X-Gm-Message-State: AFuF++moUzRBt3gPKpsBUi5Fh6NiR0TaetZWptJYgVWXz4qRFAksbqxF gWI5lCf1TPbMlSv4I2RMrZHWWurBl+In6A5AGORTHaI9o1QGbKuRiP4s X-Gm-Gg: AYBFou3X7xortXlxYiPzNw6fwaBHeaTkqX7zcuA+E8b9VfM+/QYJkQk+4yqIqeYDpRA Hvino5wF8cHzLk9zuf5i83nm8IueKg1IFpRS7MssE/p/5aPJ7A9X77JMSPFP9Saovj2xZxINRjX 4OIADXuKtX0h0haUTfm0NRjXG9u9u6/3UuobBvOh4YxPLFzTlgqBXfXu+6cGNrMWlNDfudeSnIV j/9Frc4+rUrMfA+qY1Q30G983723FNU4N5KyJYWcXi+4Y4n7EtzVhvu6Vp9oMtgcA78Y+vKX5/A Vj54urOZd7ZZa2rIsXTTiGdBq03nXquXo2iorIeG3TLCHyulcJkFg23tGylOAdxiITIO4OLeWHq sP+OHB4pdbFX+BODquIAUSRL0TzZlVdkgp3iId3BfBLjlpGCvVgy7v65MsH+Z0zrlXBXw6GA2X4 ltohSFAgBnas2N0bwz9DyYBQyxf7DIplvyZ3CGO5UwG6ixyjlUzdpIunh1ATOEm6YFNx7m+/RBT M0Obuqmy56rW3+rqY8tn9Q= X-Received: by 2002:a05:690c:c3b7:b0:868:505e:b97a with SMTP id 00721157ae682-868505ed0ccmr9531957b3.10.1788188450680; Mon, 31 Aug 2026 08:00:50 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:50 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 14/15] selftests/bpf: Test the LSM policy object kfuncs with Landlock Date: Mon, 31 Aug 2026 10:58:56 -0400 Message-ID: <20260831145858.3869191-15-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- tools/testing/selftests/bpf/config | 1 + tools/testing/selftests/bpf/config.x86_64 | 2 +- .../bpf/prog_tests/lsm_policy_landlock.c | 522 ++++++++++++++++++ .../selftests/bpf/progs/lsm_policy_landlock.c | 142 +++++ 4 files changed, 666 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 ea7044f30adc..2fa734497461 100644 --- a/tools/testing/selftests/bpf/config +++ b/tools/testing/selftests/bpf/config @@ -120,6 +120,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 42ad817b00ae..13ca4906b67f 100644 --- a/tools/testing/selftests/bpf/config.x86_64 +++ b/tools/testing/selftests/bpf/config.x86_64 @@ -126,7 +126,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..9270b39f5e3a --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/lsm_policy_landlock.c @@ -0,0 +1,522 @@ +// 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")) + 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 Sat Sep 26 14:38:30 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 D096E503BEB for ; Mon, 31 Aug 2026 15:00:58 +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=1788188460; cv=none; b=hOMQloEqQJJIYTIWHYsylDHCqK9EEuaP64VT7LFthzP7BKaC/pm4keApIPG9elhuS6DC9ZGHuEjNEKIgoYGhoEZTqYl+SGVLs3Ek15yQuuRPcUGPDRnSUtMsXaqjdb2XuFUHybNpwvRuNf35MsKc2mAZquMLNZ/RFOEu2W6UyaI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788188460; c=relaxed/simple; bh=WptSfOqEMNBmGXPbTwBGhZeeMOjidJMhs+vE6dbvvzI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=tbg21CtLLpU419jrGM+HpnjrhGOH6sv/yLLgydR2ZE4fPrt0CDBYFOErd16CrWdb9yxhoLWpjMDzSR8p9RdgVVCQmjb6TF85SiDYPmzTIITpYorgcmFpo8auLt+bBN/gW1gUBeezGNaP73U64+QSHJA3UxOKyR7/Jev2Jsx55lQ= 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=qdR5VD8i; 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="qdR5VD8i" Received: by mail-yw1-f176.google.com with SMTP id 00721157ae682-867a943b149so8584547b3.1 for ; Mon, 31 Aug 2026 08:00:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788188458; x=1788793258; 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=P+ivS8VeeEbj61xzPS0u7NgZI1YqhiDthhnP+E6PAls=; b=qdR5VD8iNcuRx462EBfWJeyu9x0tUaxVsaqQz2xAiVhTvDSQIpM7/pmzrnUU8neMFL YB4HR3mtnwD/I2GaMDdu6y3tnVwTlP5RgSdsFD5iwnxYHWjz8eZU8XgaMso3ZYlvA4kj FsfD014PI8/5djnkc/5jobgKsGtbpz1YZkaQ7PnoqsvguMfecefjNBBZzjC7UxNILmLe w+n8IkIHZRmk24UTzXLjBWIXTedT+fdu6JJc6xBbgZyKLDPTnXTP9HuH/x4ERS8jPVHW 54o/elhfn4l10v0XQv2/TeCrht1jl8WG69jGmgsRqqXXU+yyZniYa3w8OzmgChTiDDoE JY1w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788188458; x=1788793258; 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=P+ivS8VeeEbj61xzPS0u7NgZI1YqhiDthhnP+E6PAls=; b=tG3aZN+GROOLxgi9QP1ovYf72zmNa1VdBdt9ijzfbMe1Pq5CGJUj1y7+TnWqvupC04 hX34kiY+nMEdhTFIzWm6Vap+qNkbBTDRqsdzDCbIDyt+QgzfExXNbOrdp0ZRqPXc1BKX iGLal5hEm+oMthzIqTv9DCPIi1/naWisqv35aoAge2r4jayCrNBuru4cF4v0cVvvoxP0 PHGeMga9v4OXjMUgpElF+4Zm4qapwBBKpfqPU1KUvk6mE/qQRQQo4a2eVgQz7fC5QzSw +stJNud+QNf21ZoZucG3GJQoOfpqEX8hb3kOKXOyDtJoXSZD3naYI6SiM5l0LBpXYZJ4 HrfA== X-Forwarded-Encrypted: i=1; AKwUvBxCQq30jKewCgwX0bh2JEj02m0ROwoPN6k1ZL2R3QIThKAx81ZaSl+FO0TfctvXsx+oisHsNgVAP4588T4=@vger.kernel.org X-Gm-Message-State: AFuF++kV+BD0WYaiqwLW0eISmglnKJaV8ouqsTp3zyVKlbtpkvBOiPXg /bgsm0+ZZTfAVZNApERIcYQi+HLxIIlPiPW2tAJfkuQMz6+wms1INW5c X-Gm-Gg: AYBFou2Tbke9alngg+HBIEmsx/dcd7Po+Z59h29ZPQm+GCULASD5cczd1DXCkzRvnzI neXTnQ7BPw1lCDT9vAeYy0zHR8ra6nhmCt9QYn3XbR4aHBfvZ3ewQNY97R6gl4XSUXBmsgqk2FC 8Y+pN4irb/fGquth1tcKNK2dFOYcgFrvXc9QEKP091NVXRfJagSSxT1bbNYU9/8zy4LJ5L0OA1p KNxwODb89HiBeYe8U+EwhHTFrvJWNma6kKDtA4btMdtFSVvUsJ7YptLBV8okZ3Lz/v2KONsUV0w 4kFWQukkLJskokpiEegYieysrlSSdcN+IFZcxcXpTALKtXLDf3XvD2yy/KiJBjSRSB3pt/7LW4Z +eYgBT+/NZqvO7lpuwDPie8N1xDezO+nJSpxmYxMiwuY1JXok2/2CHi34f7JMshw+EhPD9L1rGV VWNOyrO9q+TwFixpG+uwOn6AJYUoWw7WerAJ/AmK8Lla09ayZBqqXK+Ip2H37EaJpbaWZDSdwmN mvI6wzGwby17KLiP8iueSs= X-Received: by 2002:a05:690c:47c5:b0:81d:edf2:dad8 with SMTP id 00721157ae682-868726e607emr7223467b3.22.1788188457650; Mon, 31 Aug 2026 08:00:57 -0700 (PDT) Received: from zenbox ([2600:1700:18fb:6011:f6fc:b424:b1bb:6ff0]) by smtp.gmail.com with ESMTPSA id 00721157ae682-85e58666e15sm53903827b3.0.2026.08.31.08.00.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 31 Aug 2026 08:00:56 -0700 (PDT) From: Justin Suess To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org, kpsingh@kernel.org, paul@paul-moore.com, mic@digikod.net, viro@zeniv.linux.org.uk, brauner@kernel.org, kees@kernel.org Cc: gnoack@google.com, jack@suse.cz, song@kernel.org, yonghong.song@linux.dev, martin.lau@linux.dev, m@maowtm.org, bpf@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, Justin Suess Subject: [PATCH v2 15/15] landlock: Document the BPF policy interface Date: Mon, 31 Aug 2026 10:58:57 -0400 Message-ID: <20260831145858.3869191-16-utilityemal77@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260831145858.3869191-1-utilityemal77@gmail.com> References: <20260831145858.3869191-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 --- 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