* [ANN] Linux Security Summit Europe 2026 CfP
From: Reshetova, Elena @ 2026-05-22 5:56 UTC (permalink / raw)
To: linux-security-module@vger.kernel.org
Cc: Linux Security Summit Program Committee, Friend,
linux-integrity@vger.kernel.org, lwn@lwn.net,
linux-crypto@vger.kernel.org, keyrings@vger.kernel.org,
linux-coco@lists.linux.dev, kernel-hardening@lists.openwall.com
====================================================================
ANNOUNCEMENT AND CALL FOR PARTICIPATION
LINUX SECURITY SUMMIT EUROPE 2026
Thursday, 8 October
Prague, Czechia
====================================================================
DESCRIPTION
Linux Security Summit Europe (LSS-EU) 2026 is a technical forum for
collaboration between Linux developers, researchers, and end-users.
Its primary aim is to foster community efforts in deeply analyzing and
solving Linux operating system security challenges, including those in the
Linux kernel.
This year LSS-EU is a single day event happening right after Linux Plumbers 2026
https://lpc.events/
Proposals to LSS-EU should be submitted via:
https://events.linuxfoundation.org/linux-security-summit-europe/program/cfp/
SUGGESTED TOPICS
* Access Control
* Case Studies
* Cryptography and Key Management
* Emerging Technologies, Threats & Techniques
* Hardware Security
* IoT and Embedded Security
* Integrity Policy and Enforcement
* Open Source Supply Chain for the Linux OS
* Security Tools
* Security UX
* Linux OS Hardening
* Virtualization and Containers
DATES TO REMEMBER:
* CFP Close: Sunday, 28 June at 11:59 PM CEST (UTC +2) / 2:59 PM PDT (UTC -7)
* CFP Notifications: Tuesday, 14 July
* Schedule Announced: Wednesday, 15 July
* Event Date: Thursday, 8 October
WHO SHOULD ATTEND
We're seeking a diverse range of attendees and welcome participation by
people involved in Linux security development, operations, and research.
LSS is a unique global event that provides the opportunity to present and
discuss your work or research with key Linux security community members and
maintainers. It's also useful for those who wish to keep up with the latest
in Linux security development and to provide input to the development
process.
MASTODON
For event updates and announcements, follow:
https://social.kernel.org/LinuxSecSummit
#linuxsecuritysummit
PROGRAM COMMITTEE
The program committee for LSS 2026 is:
* James Morris, Microsoft
* Serge Hallyn, Geico
* Paul Moore, Microsoft
* Stephen Smalley, NSA
* Elena Reshetova, Intel
* John Johansen, Canonical
* Kees Cook, Google
* Casey Schaufler
* Mimi Zohar, IBM
* David A. Wheeler, Linux Foundation
The program committee may be contacted as a group via email:
lss-pc () lists.linuxfoundation.org
^ permalink raw reply
* [PATCH v3] keys/trusted_keys: move TPM-specific fields into trusted_tpm_options
From: Srish Srinivasan @ 2026-05-22 8:16 UTC (permalink / raw)
To: linux-integrity, keyrings
Cc: James.Bottomley, stefanb, jarkko, zohar, nayna, rnsastry,
linux-kernel, linux-security-module, ssrish
The trusted_key_options struct contains TPM-specific fields (keyhandle,
keyauth, blobauth_len, blobauth, pcrinfo_len, pcrinfo, pcrlock, hash,
policydigest_len, policydigest, and policyhandle). This leads to the
accumulation of backend-specific fields in the generic options structure.
Define a trusted_tpm_options structure and move the TPM-specific fields
there. Store a pointer to trusted_tpm_options in trusted_key_options's
private.
No functional change intended.
Signed-off-by: Srish Srinivasan <ssrish@linux.ibm.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
---
This patch depends on 9ec4175a30eb ("KEYS: trusted: Debugging as a
feature"), which is in linux-tpmdd/master but not yet in mainline.
Please apply on top of linux-tpmdd/master.
base-commit: 67657fb65aa9f2d7dd46235246b1677792bd103e
Changelog:
v3:
- Exclude the preparatory clean up patch as the problem has been addressed
in commit 9ec4175a30eb ("KEYS: trusted: Debugging as a feature")
v2:
- Exclude the bug-fix patch as it has already been applied to 6.19-rc7
- Rename instances of trusted_tpm_options from tpm_opts to private
- Use pr_debug and KERN_DEBUG for logging debug messages (preparatory clean
up patch)
- Address other minor comments from Jarkko
include/keys/trusted-type.h | 11 ---
include/keys/trusted_tpm.h | 14 ++++
security/keys/trusted-keys/trusted_tpm1.c | 95 ++++++++++++++---------
security/keys/trusted-keys/trusted_tpm2.c | 51 ++++++------
4 files changed, 102 insertions(+), 69 deletions(-)
diff --git a/include/keys/trusted-type.h b/include/keys/trusted-type.h
index 9f9940482da4..3db61b57cf73 100644
--- a/include/keys/trusted-type.h
+++ b/include/keys/trusted-type.h
@@ -39,17 +39,6 @@ struct trusted_key_payload {
struct trusted_key_options {
uint16_t keytype;
- uint32_t keyhandle;
- unsigned char keyauth[TPM_DIGEST_SIZE];
- uint32_t blobauth_len;
- unsigned char blobauth[TPM_DIGEST_SIZE];
- uint32_t pcrinfo_len;
- unsigned char pcrinfo[MAX_PCRINFO_SIZE];
- int pcrlock;
- uint32_t hash;
- uint32_t policydigest_len;
- unsigned char policydigest[MAX_DIGEST_SIZE];
- uint32_t policyhandle;
void *private;
};
diff --git a/include/keys/trusted_tpm.h b/include/keys/trusted_tpm.h
index 0fadc6a4f166..355ebd36cbfd 100644
--- a/include/keys/trusted_tpm.h
+++ b/include/keys/trusted_tpm.h
@@ -7,6 +7,20 @@
extern struct trusted_key_ops trusted_key_tpm_ops;
+struct trusted_tpm_options {
+ uint32_t keyhandle;
+ unsigned char keyauth[TPM_DIGEST_SIZE];
+ uint32_t blobauth_len;
+ unsigned char blobauth[TPM_DIGEST_SIZE];
+ uint32_t pcrinfo_len;
+ unsigned char pcrinfo[MAX_PCRINFO_SIZE];
+ int pcrlock;
+ uint32_t hash;
+ uint32_t policydigest_len;
+ unsigned char policydigest[MAX_DIGEST_SIZE];
+ uint32_t policyhandle;
+};
+
int tpm2_seal_trusted(struct tpm_chip *chip,
struct trusted_key_payload *payload,
struct trusted_key_options *options);
diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
index 13513819991e..21360a41d290 100644
--- a/security/keys/trusted-keys/trusted_tpm1.c
+++ b/security/keys/trusted-keys/trusted_tpm1.c
@@ -49,15 +49,17 @@ enum {
#ifdef CONFIG_TRUSTED_KEYS_DEBUG
static inline void dump_options(struct trusted_key_options *o)
{
+ struct trusted_tpm_options *private = o->private;
+
if (!trusted_debug)
return;
pr_debug("sealing key type %d\n", o->keytype);
- pr_debug("sealing key handle %0X\n", o->keyhandle);
- pr_debug("pcrlock %d\n", o->pcrlock);
- pr_debug("pcrinfo %d\n", o->pcrinfo_len);
+ pr_debug("sealing key handle %0X\n", private->keyhandle);
+ pr_debug("pcrlock %d\n", private->pcrlock);
+ pr_debug("pcrinfo %d\n", private->pcrinfo_len);
print_hex_dump_debug("pcrinfo ", DUMP_PREFIX_NONE,
- 16, 1, o->pcrinfo, o->pcrinfo_len, 0);
+ 16, 1, private->pcrinfo, private->pcrinfo_len, 0);
}
static inline void dump_sess(struct osapsess *s)
@@ -631,6 +633,7 @@ static int tpm_unseal(struct tpm_buf *tb,
static int key_seal(struct trusted_key_payload *p,
struct trusted_key_options *o)
{
+ struct trusted_tpm_options *private = o->private;
struct tpm_buf tb;
int ret;
@@ -641,9 +644,10 @@ static int key_seal(struct trusted_key_payload *p,
/* include migratable flag at end of sealed key */
p->key[p->key_len] = p->migratable;
- ret = tpm_seal(&tb, o->keytype, o->keyhandle, o->keyauth,
+ ret = tpm_seal(&tb, o->keytype, private->keyhandle, private->keyauth,
p->key, p->key_len + 1, p->blob, &p->blob_len,
- o->blobauth, o->pcrinfo, o->pcrinfo_len);
+ private->blobauth, private->pcrinfo,
+ private->pcrinfo_len);
if (ret < 0)
pr_info("srkseal failed (%d)\n", ret);
@@ -657,6 +661,7 @@ static int key_seal(struct trusted_key_payload *p,
static int key_unseal(struct trusted_key_payload *p,
struct trusted_key_options *o)
{
+ struct trusted_tpm_options *private = o->private;
struct tpm_buf tb;
int ret;
@@ -664,8 +669,8 @@ static int key_unseal(struct trusted_key_payload *p,
if (ret)
return ret;
- ret = tpm_unseal(&tb, o->keyhandle, o->keyauth, p->blob, p->blob_len,
- o->blobauth, p->key, &p->key_len);
+ ret = tpm_unseal(&tb, private->keyhandle, private->keyauth, p->blob,
+ p->blob_len, private->blobauth, p->key, &p->key_len);
if (ret < 0)
pr_info("srkunseal failed (%d)\n", ret);
else
@@ -702,6 +707,7 @@ static const match_table_t key_tokens = {
static int getoptions(char *c, struct trusted_key_payload *pay,
struct trusted_key_options *opt)
{
+ struct trusted_tpm_options *private = opt->private;
substring_t args[MAX_OPT_ARGS];
char *p = c;
int token;
@@ -717,7 +723,7 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
if (tpm2 < 0)
return tpm2;
- opt->hash = tpm2 ? HASH_ALGO_SHA256 : HASH_ALGO_SHA1;
+ private->hash = tpm2 ? HASH_ALGO_SHA256 : HASH_ALGO_SHA1;
if (!c)
return 0;
@@ -731,11 +737,11 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
switch (token) {
case Opt_pcrinfo:
- opt->pcrinfo_len = strlen(args[0].from) / 2;
- if (opt->pcrinfo_len > MAX_PCRINFO_SIZE)
+ private->pcrinfo_len = strlen(args[0].from) / 2;
+ if (private->pcrinfo_len > MAX_PCRINFO_SIZE)
return -EINVAL;
- res = hex2bin(opt->pcrinfo, args[0].from,
- opt->pcrinfo_len);
+ res = hex2bin(private->pcrinfo, args[0].from,
+ private->pcrinfo_len);
if (res < 0)
return -EINVAL;
break;
@@ -744,12 +750,12 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
if (res < 0)
return -EINVAL;
opt->keytype = SEAL_keytype;
- opt->keyhandle = handle;
+ private->keyhandle = handle;
break;
case Opt_keyauth:
if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE)
return -EINVAL;
- res = hex2bin(opt->keyauth, args[0].from,
+ res = hex2bin(private->keyauth, args[0].from,
SHA1_DIGEST_SIZE);
if (res < 0)
return -EINVAL;
@@ -760,21 +766,23 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
* hex strings. TPM 2.0 authorizations are simple
* passwords (although it can take a hash as well)
*/
- opt->blobauth_len = strlen(args[0].from);
+ private->blobauth_len = strlen(args[0].from);
- if (opt->blobauth_len == 2 * TPM_DIGEST_SIZE) {
- res = hex2bin(opt->blobauth, args[0].from,
+ if (private->blobauth_len == 2 * TPM_DIGEST_SIZE) {
+ res = hex2bin(private->blobauth, args[0].from,
TPM_DIGEST_SIZE);
if (res < 0)
return -EINVAL;
- opt->blobauth_len = TPM_DIGEST_SIZE;
+ private->blobauth_len = TPM_DIGEST_SIZE;
break;
}
- if (tpm2 && opt->blobauth_len <= sizeof(opt->blobauth)) {
- memcpy(opt->blobauth, args[0].from,
- opt->blobauth_len);
+ if (tpm2 &&
+ private->blobauth_len <=
+ sizeof(private->blobauth)) {
+ memcpy(private->blobauth, args[0].from,
+ private->blobauth_len);
break;
}
@@ -792,14 +800,14 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
res = kstrtoul(args[0].from, 10, &lock);
if (res < 0)
return -EINVAL;
- opt->pcrlock = lock;
+ private->pcrlock = lock;
break;
case Opt_hash:
if (test_bit(Opt_policydigest, &token_mask))
return -EINVAL;
for (i = 0; i < HASH_ALGO__LAST; i++) {
if (!strcmp(args[0].from, hash_algo_name[i])) {
- opt->hash = i;
+ private->hash = i;
break;
}
}
@@ -811,14 +819,14 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
}
break;
case Opt_policydigest:
- digest_len = hash_digest_size[opt->hash];
+ digest_len = hash_digest_size[private->hash];
if (!tpm2 || strlen(args[0].from) != (2 * digest_len))
return -EINVAL;
- res = hex2bin(opt->policydigest, args[0].from,
+ res = hex2bin(private->policydigest, args[0].from,
digest_len);
if (res < 0)
return -EINVAL;
- opt->policydigest_len = digest_len;
+ private->policydigest_len = digest_len;
break;
case Opt_policyhandle:
if (!tpm2)
@@ -826,7 +834,7 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
res = kstrtoul(args[0].from, 16, &handle);
if (res < 0)
return -EINVAL;
- opt->policyhandle = handle;
+ private->policyhandle = handle;
break;
default:
return -EINVAL;
@@ -837,6 +845,7 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
static struct trusted_key_options *trusted_options_alloc(void)
{
+ struct trusted_tpm_options *private;
struct trusted_key_options *options;
int tpm2;
@@ -849,14 +858,23 @@ static struct trusted_key_options *trusted_options_alloc(void)
/* set any non-zero defaults */
options->keytype = SRK_keytype;
- if (!tpm2)
- options->keyhandle = SRKHANDLE;
+ private = kzalloc_obj(*private);
+ if (!private) {
+ kfree_sensitive(options);
+ options = NULL;
+ } else {
+ if (!tpm2)
+ private->keyhandle = SRKHANDLE;
+
+ options->private = private;
+ }
}
return options;
}
static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob)
{
+ struct trusted_tpm_options *private = NULL;
struct trusted_key_options *options = NULL;
int ret = 0;
int tpm2;
@@ -874,7 +892,8 @@ static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob)
goto out;
dump_options(options);
- if (!options->keyhandle && !tpm2) {
+ private = options->private;
+ if (!private->keyhandle && !tpm2) {
ret = -EINVAL;
goto out;
}
@@ -888,20 +907,22 @@ static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob)
goto out;
}
- if (options->pcrlock) {
- ret = pcrlock(options->pcrlock);
+ if (private->pcrlock) {
+ ret = pcrlock(private->pcrlock);
if (ret < 0) {
pr_info("pcrlock failed (%d)\n", ret);
goto out;
}
}
out:
+ kfree_sensitive(options->private);
kfree_sensitive(options);
return ret;
}
static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob)
{
+ struct trusted_tpm_options *private = NULL;
struct trusted_key_options *options = NULL;
int ret = 0;
int tpm2;
@@ -919,7 +940,8 @@ static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob)
goto out;
dump_options(options);
- if (!options->keyhandle && !tpm2) {
+ private = options->private;
+ if (!private->keyhandle && !tpm2) {
ret = -EINVAL;
goto out;
}
@@ -931,14 +953,15 @@ static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob)
if (ret < 0)
pr_info("key_unseal failed (%d)\n", ret);
- if (options->pcrlock) {
- ret = pcrlock(options->pcrlock);
+ if (private->pcrlock) {
+ ret = pcrlock(private->pcrlock);
if (ret < 0) {
pr_info("pcrlock failed (%d)\n", ret);
goto out;
}
}
out:
+ kfree_sensitive(options->private);
kfree_sensitive(options);
return ret;
}
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index 6340823f8b53..94e01249b921 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -24,6 +24,7 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
struct trusted_key_options *options,
u8 *src, u32 len)
{
+ struct trusted_tpm_options *private = options->private;
const int SCRATCH_SIZE = PAGE_SIZE;
u8 *scratch = kmalloc(SCRATCH_SIZE, GFP_KERNEL);
u8 *work = scratch, *work1;
@@ -46,7 +47,7 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
work = asn1_encode_oid(work, end_work, tpm2key_oid,
asn1_oid_len(tpm2key_oid));
- if (options->blobauth_len == 0) {
+ if (private->blobauth_len == 0) {
unsigned char bool[3], *w = bool;
/* tag 0 is emptyAuth */
w = asn1_encode_boolean(w, w + sizeof(bool), true);
@@ -69,7 +70,7 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
goto err;
}
- work = asn1_encode_integer(work, end_work, options->keyhandle);
+ work = asn1_encode_integer(work, end_work, private->keyhandle);
work = asn1_encode_octet_string(work, end_work, pub, pub_len);
work = asn1_encode_octet_string(work, end_work, priv, priv_len);
@@ -102,6 +103,7 @@ static int tpm2_key_decode(struct trusted_key_payload *payload,
struct trusted_key_options *options,
u8 **buf)
{
+ struct trusted_tpm_options *private = options->private;
int ret;
struct tpm2_key_context ctx;
u8 *blob;
@@ -121,7 +123,7 @@ static int tpm2_key_decode(struct trusted_key_payload *payload,
return -ENOMEM;
*buf = blob;
- options->keyhandle = ctx.parent;
+ private->keyhandle = ctx.parent;
memcpy(blob, ctx.priv, ctx.priv_len);
blob += ctx.priv_len;
@@ -233,6 +235,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
struct trusted_key_payload *payload,
struct trusted_key_options *options)
{
+ struct trusted_tpm_options *private = options->private;
off_t offset = TPM_HEADER_SIZE;
struct tpm_buf buf, sized;
int blob_len = 0;
@@ -240,11 +243,11 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
u32 flags;
int rc;
- hash = tpm2_find_hash_alg(options->hash);
+ hash = tpm2_find_hash_alg(private->hash);
if (hash < 0)
return hash;
- if (!options->keyhandle)
+ if (!private->keyhandle)
return -EINVAL;
rc = tpm_try_get_ops(chip);
@@ -268,18 +271,19 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
goto out_put;
}
- rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+ rc = tpm_buf_append_name(chip, &buf, private->keyhandle, NULL);
if (rc)
goto out;
tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT,
- options->keyauth, TPM_DIGEST_SIZE);
+ private->keyauth, TPM_DIGEST_SIZE);
/* sensitive */
- tpm_buf_append_u16(&sized, options->blobauth_len);
+ tpm_buf_append_u16(&sized, private->blobauth_len);
- if (options->blobauth_len)
- tpm_buf_append(&sized, options->blobauth, options->blobauth_len);
+ if (private->blobauth_len)
+ tpm_buf_append(&sized, private->blobauth,
+ private->blobauth_len);
tpm_buf_append_u16(&sized, payload->key_len);
tpm_buf_append(&sized, payload->key, payload->key_len);
@@ -292,14 +296,15 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
/* key properties */
flags = 0;
- flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH;
+ flags |= private->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH;
flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT);
tpm_buf_append_u32(&sized, flags);
/* policy */
- tpm_buf_append_u16(&sized, options->policydigest_len);
- if (options->policydigest_len)
- tpm_buf_append(&sized, options->policydigest, options->policydigest_len);
+ tpm_buf_append_u16(&sized, private->policydigest_len);
+ if (private->policydigest_len)
+ tpm_buf_append(&sized, private->policydigest,
+ private->policydigest_len);
/* public parameters */
tpm_buf_append_u16(&sized, TPM_ALG_NULL);
@@ -373,6 +378,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
u32 *blob_handle)
{
u8 *blob_ref __free(kfree) = NULL;
+ struct trusted_tpm_options *private = options->private;
struct tpm_buf buf;
unsigned int private_len;
unsigned int public_len;
@@ -392,7 +398,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
}
/* new format carries keyhandle but old format doesn't */
- if (!options->keyhandle)
+ if (!private->keyhandle)
return -EINVAL;
/* must be big enough for at least the two be16 size counts */
@@ -433,11 +439,11 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
return rc;
}
- rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+ rc = tpm_buf_append_name(chip, &buf, private->keyhandle, NULL);
if (rc)
goto out;
- tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth,
+ tpm_buf_append_hmac_session(chip, &buf, 0, private->keyauth,
TPM_DIGEST_SIZE);
tpm_buf_append(&buf, blob, blob_len);
@@ -481,6 +487,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
struct trusted_key_options *options,
u32 blob_handle)
{
+ struct trusted_tpm_options *private = options->private;
struct tpm_header *head;
struct tpm_buf buf;
u16 data_len;
@@ -502,10 +509,10 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
if (rc)
goto out;
- if (!options->policyhandle) {
+ if (!private->policyhandle) {
tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT,
- options->blobauth,
- options->blobauth_len);
+ private->blobauth,
+ private->blobauth_len);
} else {
/*
* FIXME: The policy session was generated outside the
@@ -518,9 +525,9 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
* could repeat our actions with the exfiltrated
* password.
*/
- tpm2_buf_append_auth(&buf, options->policyhandle,
+ tpm2_buf_append_auth(&buf, private->policyhandle,
NULL /* nonce */, 0, 0,
- options->blobauth, options->blobauth_len);
+ private->blobauth, private->blobauth_len);
if (tpm2_chip_auth(chip)) {
tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, NULL, 0);
} else {
--
2.51.0
^ permalink raw reply related
* Re: [PATCH v4 1/7] lsm: Add granular mount hooks to replace security_sb_mount
From: Christian Brauner @ 2026-05-22 9:15 UTC (permalink / raw)
To: Song Liu
Cc: linux-security-module, linux-fsdevel, selinux, apparmor, paul,
jmorris, serge, viro, brauner, jack, john.johansen,
stephen.smalley.work, omosnace, mic, gnoack, takedakn,
penguin-kernel, herton, kernel-team
In-Reply-To: <20260515200158.4081915-2-song@kernel.org>
On Fri, 15 May 2026 13:01:52 -0700, Song Liu <song@kernel.org> wrote:
> [...]
>
> Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com> # for selinux only
> Signed-off-by: Song Liu <song@kernel.org>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Please cleanly separate the preparatory work for introducing the new
hooks from any changes to fs/namespace.c
Once you have all of the new machinery in place, switch fs/namespace.c
over to the new hooks.
This will make it way easier to review and easier to distribute the
--
Christian Brauner <brauner@kernel.org>
^ permalink raw reply
* Re: [PATCH v3] keys/trusted_keys: move TPM-specific fields into trusted_tpm_options
From: Jarkko Sakkinen @ 2026-05-22 12:41 UTC (permalink / raw)
To: Srish Srinivasan
Cc: linux-integrity, keyrings, James.Bottomley, stefanb, zohar, nayna,
rnsastry, linux-kernel, linux-security-module
In-Reply-To: <20260522081637.189546-1-ssrish@linux.ibm.com>
On Fri, May 22, 2026 at 01:46:37PM +0530, Srish Srinivasan wrote:
> The trusted_key_options struct contains TPM-specific fields (keyhandle,
> keyauth, blobauth_len, blobauth, pcrinfo_len, pcrinfo, pcrlock, hash,
> policydigest_len, policydigest, and policyhandle). This leads to the
> accumulation of backend-specific fields in the generic options structure.
>
> Define a trusted_tpm_options structure and move the TPM-specific fields
> there. Store a pointer to trusted_tpm_options in trusted_key_options's
> private.
>
> No functional change intended.
>
> Signed-off-by: Srish Srinivasan <ssrish@linux.ibm.com>
> Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
> ---
I'm supporting this change yes but before spreading tpm_buf all over the
place I'd like to get this merged:
https://lore.kernel.org/linux-integrity/20260522013555.1063716-1-jarkko@kernel.org/T/#u
It'd need tested-by's most importantly. There's another patch shoe-horning
buffers a bit upgrading tpm_buf so I thought now would be good tiem to
get this finally applied.
Possible merge conflicts for +1 change of this should be fairly trivial.
BR, Jarkko
> This patch depends on 9ec4175a30eb ("KEYS: trusted: Debugging as a
> feature"), which is in linux-tpmdd/master but not yet in mainline.
>
> Please apply on top of linux-tpmdd/master.
>
> base-commit: 67657fb65aa9f2d7dd46235246b1677792bd103e
>
> Changelog:
> v3:
> - Exclude the preparatory clean up patch as the problem has been addressed
> in commit 9ec4175a30eb ("KEYS: trusted: Debugging as a feature")
>
> v2:
> - Exclude the bug-fix patch as it has already been applied to 6.19-rc7
> - Rename instances of trusted_tpm_options from tpm_opts to private
> - Use pr_debug and KERN_DEBUG for logging debug messages (preparatory clean
> up patch)
> - Address other minor comments from Jarkko
>
> include/keys/trusted-type.h | 11 ---
> include/keys/trusted_tpm.h | 14 ++++
> security/keys/trusted-keys/trusted_tpm1.c | 95 ++++++++++++++---------
> security/keys/trusted-keys/trusted_tpm2.c | 51 ++++++------
> 4 files changed, 102 insertions(+), 69 deletions(-)
>
> diff --git a/include/keys/trusted-type.h b/include/keys/trusted-type.h
> index 9f9940482da4..3db61b57cf73 100644
> --- a/include/keys/trusted-type.h
> +++ b/include/keys/trusted-type.h
> @@ -39,17 +39,6 @@ struct trusted_key_payload {
>
> struct trusted_key_options {
> uint16_t keytype;
> - uint32_t keyhandle;
> - unsigned char keyauth[TPM_DIGEST_SIZE];
> - uint32_t blobauth_len;
> - unsigned char blobauth[TPM_DIGEST_SIZE];
> - uint32_t pcrinfo_len;
> - unsigned char pcrinfo[MAX_PCRINFO_SIZE];
> - int pcrlock;
> - uint32_t hash;
> - uint32_t policydigest_len;
> - unsigned char policydigest[MAX_DIGEST_SIZE];
> - uint32_t policyhandle;
> void *private;
> };
>
> diff --git a/include/keys/trusted_tpm.h b/include/keys/trusted_tpm.h
> index 0fadc6a4f166..355ebd36cbfd 100644
> --- a/include/keys/trusted_tpm.h
> +++ b/include/keys/trusted_tpm.h
> @@ -7,6 +7,20 @@
>
> extern struct trusted_key_ops trusted_key_tpm_ops;
>
> +struct trusted_tpm_options {
> + uint32_t keyhandle;
> + unsigned char keyauth[TPM_DIGEST_SIZE];
> + uint32_t blobauth_len;
> + unsigned char blobauth[TPM_DIGEST_SIZE];
> + uint32_t pcrinfo_len;
> + unsigned char pcrinfo[MAX_PCRINFO_SIZE];
> + int pcrlock;
> + uint32_t hash;
> + uint32_t policydigest_len;
> + unsigned char policydigest[MAX_DIGEST_SIZE];
> + uint32_t policyhandle;
> +};
> +
> int tpm2_seal_trusted(struct tpm_chip *chip,
> struct trusted_key_payload *payload,
> struct trusted_key_options *options);
> diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
> index 13513819991e..21360a41d290 100644
> --- a/security/keys/trusted-keys/trusted_tpm1.c
> +++ b/security/keys/trusted-keys/trusted_tpm1.c
> @@ -49,15 +49,17 @@ enum {
> #ifdef CONFIG_TRUSTED_KEYS_DEBUG
> static inline void dump_options(struct trusted_key_options *o)
> {
> + struct trusted_tpm_options *private = o->private;
> +
> if (!trusted_debug)
> return;
>
> pr_debug("sealing key type %d\n", o->keytype);
> - pr_debug("sealing key handle %0X\n", o->keyhandle);
> - pr_debug("pcrlock %d\n", o->pcrlock);
> - pr_debug("pcrinfo %d\n", o->pcrinfo_len);
> + pr_debug("sealing key handle %0X\n", private->keyhandle);
> + pr_debug("pcrlock %d\n", private->pcrlock);
> + pr_debug("pcrinfo %d\n", private->pcrinfo_len);
> print_hex_dump_debug("pcrinfo ", DUMP_PREFIX_NONE,
> - 16, 1, o->pcrinfo, o->pcrinfo_len, 0);
> + 16, 1, private->pcrinfo, private->pcrinfo_len, 0);
> }
>
> static inline void dump_sess(struct osapsess *s)
> @@ -631,6 +633,7 @@ static int tpm_unseal(struct tpm_buf *tb,
> static int key_seal(struct trusted_key_payload *p,
> struct trusted_key_options *o)
> {
> + struct trusted_tpm_options *private = o->private;
> struct tpm_buf tb;
> int ret;
>
> @@ -641,9 +644,10 @@ static int key_seal(struct trusted_key_payload *p,
> /* include migratable flag at end of sealed key */
> p->key[p->key_len] = p->migratable;
>
> - ret = tpm_seal(&tb, o->keytype, o->keyhandle, o->keyauth,
> + ret = tpm_seal(&tb, o->keytype, private->keyhandle, private->keyauth,
> p->key, p->key_len + 1, p->blob, &p->blob_len,
> - o->blobauth, o->pcrinfo, o->pcrinfo_len);
> + private->blobauth, private->pcrinfo,
> + private->pcrinfo_len);
> if (ret < 0)
> pr_info("srkseal failed (%d)\n", ret);
>
> @@ -657,6 +661,7 @@ static int key_seal(struct trusted_key_payload *p,
> static int key_unseal(struct trusted_key_payload *p,
> struct trusted_key_options *o)
> {
> + struct trusted_tpm_options *private = o->private;
> struct tpm_buf tb;
> int ret;
>
> @@ -664,8 +669,8 @@ static int key_unseal(struct trusted_key_payload *p,
> if (ret)
> return ret;
>
> - ret = tpm_unseal(&tb, o->keyhandle, o->keyauth, p->blob, p->blob_len,
> - o->blobauth, p->key, &p->key_len);
> + ret = tpm_unseal(&tb, private->keyhandle, private->keyauth, p->blob,
> + p->blob_len, private->blobauth, p->key, &p->key_len);
> if (ret < 0)
> pr_info("srkunseal failed (%d)\n", ret);
> else
> @@ -702,6 +707,7 @@ static const match_table_t key_tokens = {
> static int getoptions(char *c, struct trusted_key_payload *pay,
> struct trusted_key_options *opt)
> {
> + struct trusted_tpm_options *private = opt->private;
> substring_t args[MAX_OPT_ARGS];
> char *p = c;
> int token;
> @@ -717,7 +723,7 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
> if (tpm2 < 0)
> return tpm2;
>
> - opt->hash = tpm2 ? HASH_ALGO_SHA256 : HASH_ALGO_SHA1;
> + private->hash = tpm2 ? HASH_ALGO_SHA256 : HASH_ALGO_SHA1;
>
> if (!c)
> return 0;
> @@ -731,11 +737,11 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
>
> switch (token) {
> case Opt_pcrinfo:
> - opt->pcrinfo_len = strlen(args[0].from) / 2;
> - if (opt->pcrinfo_len > MAX_PCRINFO_SIZE)
> + private->pcrinfo_len = strlen(args[0].from) / 2;
> + if (private->pcrinfo_len > MAX_PCRINFO_SIZE)
> return -EINVAL;
> - res = hex2bin(opt->pcrinfo, args[0].from,
> - opt->pcrinfo_len);
> + res = hex2bin(private->pcrinfo, args[0].from,
> + private->pcrinfo_len);
> if (res < 0)
> return -EINVAL;
> break;
> @@ -744,12 +750,12 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
> if (res < 0)
> return -EINVAL;
> opt->keytype = SEAL_keytype;
> - opt->keyhandle = handle;
> + private->keyhandle = handle;
> break;
> case Opt_keyauth:
> if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE)
> return -EINVAL;
> - res = hex2bin(opt->keyauth, args[0].from,
> + res = hex2bin(private->keyauth, args[0].from,
> SHA1_DIGEST_SIZE);
> if (res < 0)
> return -EINVAL;
> @@ -760,21 +766,23 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
> * hex strings. TPM 2.0 authorizations are simple
> * passwords (although it can take a hash as well)
> */
> - opt->blobauth_len = strlen(args[0].from);
> + private->blobauth_len = strlen(args[0].from);
>
> - if (opt->blobauth_len == 2 * TPM_DIGEST_SIZE) {
> - res = hex2bin(opt->blobauth, args[0].from,
> + if (private->blobauth_len == 2 * TPM_DIGEST_SIZE) {
> + res = hex2bin(private->blobauth, args[0].from,
> TPM_DIGEST_SIZE);
> if (res < 0)
> return -EINVAL;
>
> - opt->blobauth_len = TPM_DIGEST_SIZE;
> + private->blobauth_len = TPM_DIGEST_SIZE;
> break;
> }
>
> - if (tpm2 && opt->blobauth_len <= sizeof(opt->blobauth)) {
> - memcpy(opt->blobauth, args[0].from,
> - opt->blobauth_len);
> + if (tpm2 &&
> + private->blobauth_len <=
> + sizeof(private->blobauth)) {
> + memcpy(private->blobauth, args[0].from,
> + private->blobauth_len);
> break;
> }
>
> @@ -792,14 +800,14 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
> res = kstrtoul(args[0].from, 10, &lock);
> if (res < 0)
> return -EINVAL;
> - opt->pcrlock = lock;
> + private->pcrlock = lock;
> break;
> case Opt_hash:
> if (test_bit(Opt_policydigest, &token_mask))
> return -EINVAL;
> for (i = 0; i < HASH_ALGO__LAST; i++) {
> if (!strcmp(args[0].from, hash_algo_name[i])) {
> - opt->hash = i;
> + private->hash = i;
> break;
> }
> }
> @@ -811,14 +819,14 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
> }
> break;
> case Opt_policydigest:
> - digest_len = hash_digest_size[opt->hash];
> + digest_len = hash_digest_size[private->hash];
> if (!tpm2 || strlen(args[0].from) != (2 * digest_len))
> return -EINVAL;
> - res = hex2bin(opt->policydigest, args[0].from,
> + res = hex2bin(private->policydigest, args[0].from,
> digest_len);
> if (res < 0)
> return -EINVAL;
> - opt->policydigest_len = digest_len;
> + private->policydigest_len = digest_len;
> break;
> case Opt_policyhandle:
> if (!tpm2)
> @@ -826,7 +834,7 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
> res = kstrtoul(args[0].from, 16, &handle);
> if (res < 0)
> return -EINVAL;
> - opt->policyhandle = handle;
> + private->policyhandle = handle;
> break;
> default:
> return -EINVAL;
> @@ -837,6 +845,7 @@ static int getoptions(char *c, struct trusted_key_payload *pay,
>
> static struct trusted_key_options *trusted_options_alloc(void)
> {
> + struct trusted_tpm_options *private;
> struct trusted_key_options *options;
> int tpm2;
>
> @@ -849,14 +858,23 @@ static struct trusted_key_options *trusted_options_alloc(void)
> /* set any non-zero defaults */
> options->keytype = SRK_keytype;
>
> - if (!tpm2)
> - options->keyhandle = SRKHANDLE;
> + private = kzalloc_obj(*private);
> + if (!private) {
> + kfree_sensitive(options);
> + options = NULL;
> + } else {
> + if (!tpm2)
> + private->keyhandle = SRKHANDLE;
> +
> + options->private = private;
> + }
> }
> return options;
> }
>
> static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob)
> {
> + struct trusted_tpm_options *private = NULL;
> struct trusted_key_options *options = NULL;
> int ret = 0;
> int tpm2;
> @@ -874,7 +892,8 @@ static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob)
> goto out;
> dump_options(options);
>
> - if (!options->keyhandle && !tpm2) {
> + private = options->private;
> + if (!private->keyhandle && !tpm2) {
> ret = -EINVAL;
> goto out;
> }
> @@ -888,20 +907,22 @@ static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob)
> goto out;
> }
>
> - if (options->pcrlock) {
> - ret = pcrlock(options->pcrlock);
> + if (private->pcrlock) {
> + ret = pcrlock(private->pcrlock);
> if (ret < 0) {
> pr_info("pcrlock failed (%d)\n", ret);
> goto out;
> }
> }
> out:
> + kfree_sensitive(options->private);
> kfree_sensitive(options);
> return ret;
> }
>
> static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob)
> {
> + struct trusted_tpm_options *private = NULL;
> struct trusted_key_options *options = NULL;
> int ret = 0;
> int tpm2;
> @@ -919,7 +940,8 @@ static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob)
> goto out;
> dump_options(options);
>
> - if (!options->keyhandle && !tpm2) {
> + private = options->private;
> + if (!private->keyhandle && !tpm2) {
> ret = -EINVAL;
> goto out;
> }
> @@ -931,14 +953,15 @@ static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob)
> if (ret < 0)
> pr_info("key_unseal failed (%d)\n", ret);
>
> - if (options->pcrlock) {
> - ret = pcrlock(options->pcrlock);
> + if (private->pcrlock) {
> + ret = pcrlock(private->pcrlock);
> if (ret < 0) {
> pr_info("pcrlock failed (%d)\n", ret);
> goto out;
> }
> }
> out:
> + kfree_sensitive(options->private);
> kfree_sensitive(options);
> return ret;
> }
> diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
> index 6340823f8b53..94e01249b921 100644
> --- a/security/keys/trusted-keys/trusted_tpm2.c
> +++ b/security/keys/trusted-keys/trusted_tpm2.c
> @@ -24,6 +24,7 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
> struct trusted_key_options *options,
> u8 *src, u32 len)
> {
> + struct trusted_tpm_options *private = options->private;
> const int SCRATCH_SIZE = PAGE_SIZE;
> u8 *scratch = kmalloc(SCRATCH_SIZE, GFP_KERNEL);
> u8 *work = scratch, *work1;
> @@ -46,7 +47,7 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
> work = asn1_encode_oid(work, end_work, tpm2key_oid,
> asn1_oid_len(tpm2key_oid));
>
> - if (options->blobauth_len == 0) {
> + if (private->blobauth_len == 0) {
> unsigned char bool[3], *w = bool;
> /* tag 0 is emptyAuth */
> w = asn1_encode_boolean(w, w + sizeof(bool), true);
> @@ -69,7 +70,7 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
> goto err;
> }
>
> - work = asn1_encode_integer(work, end_work, options->keyhandle);
> + work = asn1_encode_integer(work, end_work, private->keyhandle);
> work = asn1_encode_octet_string(work, end_work, pub, pub_len);
> work = asn1_encode_octet_string(work, end_work, priv, priv_len);
>
> @@ -102,6 +103,7 @@ static int tpm2_key_decode(struct trusted_key_payload *payload,
> struct trusted_key_options *options,
> u8 **buf)
> {
> + struct trusted_tpm_options *private = options->private;
> int ret;
> struct tpm2_key_context ctx;
> u8 *blob;
> @@ -121,7 +123,7 @@ static int tpm2_key_decode(struct trusted_key_payload *payload,
> return -ENOMEM;
>
> *buf = blob;
> - options->keyhandle = ctx.parent;
> + private->keyhandle = ctx.parent;
>
> memcpy(blob, ctx.priv, ctx.priv_len);
> blob += ctx.priv_len;
> @@ -233,6 +235,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
> struct trusted_key_payload *payload,
> struct trusted_key_options *options)
> {
> + struct trusted_tpm_options *private = options->private;
> off_t offset = TPM_HEADER_SIZE;
> struct tpm_buf buf, sized;
> int blob_len = 0;
> @@ -240,11 +243,11 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
> u32 flags;
> int rc;
>
> - hash = tpm2_find_hash_alg(options->hash);
> + hash = tpm2_find_hash_alg(private->hash);
> if (hash < 0)
> return hash;
>
> - if (!options->keyhandle)
> + if (!private->keyhandle)
> return -EINVAL;
>
> rc = tpm_try_get_ops(chip);
> @@ -268,18 +271,19 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
> goto out_put;
> }
>
> - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
> + rc = tpm_buf_append_name(chip, &buf, private->keyhandle, NULL);
> if (rc)
> goto out;
>
> tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT,
> - options->keyauth, TPM_DIGEST_SIZE);
> + private->keyauth, TPM_DIGEST_SIZE);
>
> /* sensitive */
> - tpm_buf_append_u16(&sized, options->blobauth_len);
> + tpm_buf_append_u16(&sized, private->blobauth_len);
>
> - if (options->blobauth_len)
> - tpm_buf_append(&sized, options->blobauth, options->blobauth_len);
> + if (private->blobauth_len)
> + tpm_buf_append(&sized, private->blobauth,
> + private->blobauth_len);
>
> tpm_buf_append_u16(&sized, payload->key_len);
> tpm_buf_append(&sized, payload->key, payload->key_len);
> @@ -292,14 +296,15 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
>
> /* key properties */
> flags = 0;
> - flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH;
> + flags |= private->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH;
> flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT);
> tpm_buf_append_u32(&sized, flags);
>
> /* policy */
> - tpm_buf_append_u16(&sized, options->policydigest_len);
> - if (options->policydigest_len)
> - tpm_buf_append(&sized, options->policydigest, options->policydigest_len);
> + tpm_buf_append_u16(&sized, private->policydigest_len);
> + if (private->policydigest_len)
> + tpm_buf_append(&sized, private->policydigest,
> + private->policydigest_len);
>
> /* public parameters */
> tpm_buf_append_u16(&sized, TPM_ALG_NULL);
> @@ -373,6 +378,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
> u32 *blob_handle)
> {
> u8 *blob_ref __free(kfree) = NULL;
> + struct trusted_tpm_options *private = options->private;
> struct tpm_buf buf;
> unsigned int private_len;
> unsigned int public_len;
> @@ -392,7 +398,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
> }
>
> /* new format carries keyhandle but old format doesn't */
> - if (!options->keyhandle)
> + if (!private->keyhandle)
> return -EINVAL;
>
> /* must be big enough for at least the two be16 size counts */
> @@ -433,11 +439,11 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
> return rc;
> }
>
> - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
> + rc = tpm_buf_append_name(chip, &buf, private->keyhandle, NULL);
> if (rc)
> goto out;
>
> - tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth,
> + tpm_buf_append_hmac_session(chip, &buf, 0, private->keyauth,
> TPM_DIGEST_SIZE);
>
> tpm_buf_append(&buf, blob, blob_len);
> @@ -481,6 +487,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
> struct trusted_key_options *options,
> u32 blob_handle)
> {
> + struct trusted_tpm_options *private = options->private;
> struct tpm_header *head;
> struct tpm_buf buf;
> u16 data_len;
> @@ -502,10 +509,10 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
> if (rc)
> goto out;
>
> - if (!options->policyhandle) {
> + if (!private->policyhandle) {
> tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT,
> - options->blobauth,
> - options->blobauth_len);
> + private->blobauth,
> + private->blobauth_len);
> } else {
> /*
> * FIXME: The policy session was generated outside the
> @@ -518,9 +525,9 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
> * could repeat our actions with the exfiltrated
> * password.
> */
> - tpm2_buf_append_auth(&buf, options->policyhandle,
> + tpm2_buf_append_auth(&buf, private->policyhandle,
> NULL /* nonce */, 0, 0,
> - options->blobauth, options->blobauth_len);
> + private->blobauth, private->blobauth_len);
> if (tpm2_chip_auth(chip)) {
> tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, NULL, 0);
> } else {
>
> --
> 2.51.0
>
^ permalink raw reply
* Re: [RFC PATCH v4 01/19] landlock: Support socket access-control
From: Günther Noack @ 2026-05-22 15:42 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Mikhail Ivanov, gnoack, willemdebruijn.kernel, matthieu,
linux-security-module, netdev, netfilter-devel, yusongping,
artem.kuzin, konstantin.meskhidze
In-Reply-To: <20260508.aeJoht7aepho@digikod.net>
On Fri, May 08, 2026 at 03:29:21PM +0200, Mickaël Salaün wrote:
> On Sat, Apr 18, 2026 at 02:29:04PM +0300, Mikhail Ivanov wrote:
> > On 11/22/2025 2:13 PM, Mikhail Ivanov wrote:
> > > On 11/22/2025 1:49 PM, Günther Noack wrote:
> > > > On Tue, Nov 18, 2025 at 09:46:21PM +0800, Mikhail Ivanov wrote:
> > > > > +/**
> > > > > + * struct landlock_socket_attr - Socket protocol definition
> > > > > + *
> > > > > + * Argument of sys_landlock_add_rule().
> > > > > + */
> > > > > +struct landlock_socket_attr {
> > > > > + /**
> > > > > + * @allowed_access: Bitmask of allowed access for a socket protocol
> > > > > + * (cf. `Socket flags`_).
> > > > > + */
> > > > > + __u64 allowed_access;
> > > > > + /**
> > > > > + * @family: Protocol family used for communication
> > > > > + * (cf. include/linux/socket.h).
> > > > > + */
> > > > > + __s32 family;
> > > > > + /**
> > > > > + * @type: Socket type (cf. include/linux/net.h)
> > > > > + */
> > > > > + __s32 type;
> > > > > + /**
> > > > > + * @protocol: Communication protocol specific to protocol
> > > > > family set in
> > > > > + * @family field.
> > > >
> > > > This is specific to both the @family and the @type, not just the @family.
> > > >
> > > > > From socket(2):
> > > >
> > > > Normally only a single protocol exists to support a particular
> > > > socket type within a given protocol family.
> > > >
> > > > For instance, in your commit message above the protocol in the example
> > > > is IPPROTO_TCP, which would imply the type SOCK_STREAM, but not work
> > > > with SOCK_DGRAM.
> > >
> > > You're right.
> > >
> >
> > I revised the socket(2) semantics and this part is about that kernel
> > maps (family, type, 0) to the default protocol of given family and type.
> > Eg. (AF_INET, SOCK_STREAM, 0) is mapped to (AF_INET, SOCK_STREAM,
> > IPPROTO_TCP). I would like to clarify that such mapping is taking place
> > in landlock_socket_attr.protocol field doc.
> >
> > There should be list of protocols defined per protocol family. From
> > socket(2):
> > The domain argument specifies a communication domain.
> > ...
> > The protocol number to use is specific to the “communication
> > domain” in which communication is to take place.
> >
> > Such mapping allows to define strange socket rules if setting @type=-1.
> > For example:
> > struct landlock_socket_attr attr = {
> > .family = AF_INET,
> > .type = -1,
> > .protocol = 0,
> > };
>
> Looking again at this API, I think we should not have a special handling
> of the "-1" values but instead change the struct landlock_socket_attr to
> start with a "wildcards" field to properly identify which socket
> property should be "any" value (according to a dedicated flag):
>
> struct landlock_socket_attr {
> __u64 allowed_perm; /* see the ns/cap patch series */
> __u32 wildcards; /* LANDLOCK_SOCKET_ANY_PROTOCOL */
> __u32 family;
> __u32 type;
> __u32 protocol;
> };
>
> In fact, I though a lot about the two potential wildcards (type and
> family we previously discussed), and my conclusion is that we should
> only handle "any protocol" (instead of any type too). This makes the
> UAPI simpler and less dangerous, especially wrt families that have very
> specific and sometime privileged types (e.g. SOCK_RAW).
I'm on board with that, API-wise. This seems reasonable.
> Another
> important point is that it would allow to do only one rbtree lookup
> (tweaking a bit the rbtree walk) instead of four lookup like with the
> current implementation. The idea is to generate an rbtree key with:
>
> family | type | !any-protocol-boolean | protocol
>
> This key format allows a one-descent walk lookup. We'll have to replace
> the use of landlock_find_rule() with a custom walk that first look for
> the any-protocol-boolean (which should probably be represented by 0 for
> "any protocol" and by 1 for "specific protocol"), and if no "any
> protocol" key is found, to continue the walk to match the full protocol
> value.
Seems reasonable as well.
> > This definition corresponds to (AF_INET, SOCK_STREAM, 0->IPPROTO_TCP)
> > and to (AF_INET, SOCK_DGRAM, 0->IPPROTO_UDP).
> >
> > I don't see this as a bad thing as far as there is proper documentation
> > for landlock_socket_attr.
>
> Thinking more about the asymmetry between UAPI and kernel state, I think
> the best approach is to canonicalize the rules' values to make them
> match the kernel equivalent. This behavior would be much less
> surprising to users (this is mostly an UX improvement, but also a way to
> deduplicate some rules). Indeed, users would be able to use the default
> value (e.g. protocol 0 for INET/STREAM) *and* the canonicalized value
> (e.g. protocol IPPROTO_TCP). Here is a patch to implement this approach
> and (most importantly) with the related kernel tests to make sure the
> canonicalizations are correct:
Impressive reverse engineering of that mapping. o_O
The approach makes sense to me in general, but I left some more
specific comments below.
> [PATCH] landlock: Canonicalize socket rules and add drift detection
>
> The kernel socket stack performs family-specific rewrites between the
> (family, type, protocol) triple passed to socket(2) and the resulting
> socket object. Rewrites currently mirrored:
>
> - __sock_create rewrites AF_INET + SOCK_PACKET to AF_PACKET for pre-2.2
> compatibility.
> - unix_create rewrites AF_UNIX + SOCK_RAW to AF_UNIX + SOCK_DGRAM as a
> BSD leftover, and ignores the user protocol (sk_protocol stays 0 for
> every AF_UNIX socket).
> - inet_create and inet6_create resolve protocol=0 to a type-specific
> default (IPPROTO_TCP for SOCK_STREAM, IPPROTO_UDP for SOCK_DGRAM,
> IPPROTO_SCTP for SOCK_SEQPACKET) via the inetsw walk.
> - ax25_create rewrites protocol=0 and protocol=PF_AX25 to AX25_P_TEXT
> for SOCK_DGRAM and SOCK_SEQPACKET.
> - pn_socket_create rewrites protocol=0 to PN_PROTO_PHONET (SOCK_DGRAM)
> or PN_PROTO_PIPE (SOCK_SEQPACKET).
> - vsock_create accepts protocol=0 or PF_VSOCK and stores sk_protocol=0
> for both, so PF_VSOCK aliases protocol=0.
> - Several families (AF_PACKET, AF_KEY, AF_APPLETALK, AF_ATMPVC,
> AF_ATMSVC, AF_LLC, AF_CAN raw/bcm, AF_RXRPC, AF_IEEE802154,
> AF_QIPCRTR) accept the user protocol but never write sk_protocol, so
> the kernel stores 0 regardless of input.
> - AF_CAN + SOCK_DGRAM is asymmetric: bcm and raw leave sk_protocol=0,
> but j1939_sk_init writes sk_protocol = CAN_J1939.
Relevant remark on the side:
With the socket options SO_DOMAIN, SO_TYPE and SO_PROTOCOL, the
canonicalized sockets have become exposed to userspace. So these are
not purely kernel-private mappings, but this canonicalization is
exposed to userspace (which should presumably make the mappings
stable).
I hope that the assumption is correct that the mappings for the
existing (f, t, p) combinations *are* stable? If these mappings were
permitted to change for existing (f, t, p) combinations, I suspect it
might be possible to construct a scenario where a landlocked program
that used to work stops working after a kernel update. That would be
a worse backwards compatibility issue than when getsockopt() returns a
new value for one of SO_{DOMAIN,TYPE,PROTOCOL}.
> Without Landlock-side canonicalization, a rule inserted with the user-
> facing form (e.g., AF_INET + SOCK_STREAM + 0) silently misses sockets
> the user wants to match: a socket created with protocol=0 reaches
> sk_protocol=IPPROTO_TCP, so a rule keyed on 0 does not apply. Each
> socket call form would require the user to write a separate rule.
>
> Store rules in the canonical form at insertion. The new
> landlock_canon_map[AF_MAX][SOCK_MAX] table with flag-driven entries
> (_LANDLOCK_CANON_REWRITE_FAMILY, _LANDLOCK_CANON_REWRITE_TYPE,
> _LANDLOCK_CANON_PROTOCOL_ZERO, _LANDLOCK_CANON_PROTOCOL_FAMILY_ID,
> _LANDLOCK_CANON_PROTOCOL_ALWAYS, _LANDLOCK_CANON_PROTOCOL_PRESERVE)
> encodes the rewrites. landlock_canonicalize_socket_key applies them
> idempotently; wildcards (TYPE_ALL, PROTOCOL_ALL) are preserved so
> wildcard rules remain first-class. Per-protocol overrides (the
> preserve_protocol field) handle asymmetries like AF_CAN + SOCK_DGRAM
> where one sub-protocol writes sk_protocol differently from the others.
> Lookup remains O(1): the override is part of the same cell the array
> index returns.
>
> Keep enforcement at security_socket_create (pre-create) so Landlock
> denies unauthorized triples before the kernel loads any family-specific
> module or allocates a socket. This preserves the EACCES error path for
> triples the kernel itself would reject (AF_UNSPEC, invalid family and
> type pairs) rather than leaking EAFNOSUPPORT, ESOCKTNOSUPPORT, or
> EPROTONOSUPPORT as a sandbox bypass signal. The pre-create hook
> canonicalizes the caller input through the same map before the rule
> lookup so that a rule keyed on the user form matches at the hook.
>
> Add security_socket_post_create purely for runtime drift detection. At
> post_create the family .create() has completed, so sk_family,
> sock->type, and sk_protocol are authoritative. Per-field WARN_ONCE
> fires when landlock_canonicalize_socket_key disagrees with these values,
> identifying which axis (family, type, or protocol) drifted and the
> user-supplied triple. This hook only runs for tasks sandboxed by a
> domain that handles LANDLOCK_ACCESS_SOCKET_CREATE, so non-sandboxed
> tasks pay no overhead.
Shouldn't this only be enabled under a suitable debug build config?
This is all supposed to be captured by the KUnit test already, right?
Or do you expect that there are differences that the KUnit test
doesn't cover?
>
> Four layers guard against landlock_canon_map drift:
>
> - static_assert(AF_MAX == N) and static_assert(SOCK_MAX == N) anchor the
> dimensions at the current kernel ABI; a new AF_* or SOCK_* value
> breaks the build and forces a map audit.
> - A new KUnit suite (landlock_socket) iterates every (family, type,
> protocol) triple over a probe range that covers all family IDs
> (0..NPROTO) plus IPPROTO_SCTP, 255, and 0xFFFF. For triples the
> kernel accepts, sock_create_kern is the oracle; canonicalization is
> validated against the resulting sk_family, sock->type, and
> sk_protocol. Families known to be unsupported by the default
> configuration are marked, and -EAFNOSUPPORT on those families is
> ignored so additional sub-protocol configs can extend coverage without
> per-arch .kunitconfig fragmentation. A separate map-entry test covers
> cells unreachable through sock_create_kern (currently AF_VSOCK +
> SOCK_DGRAM, which requires CONFIG_VIRTIO_VSOCKETS). A
> wildcard-preservation test pins the TYPE_ALL and PROTOCOL_ALL
> invariants.
> - The per-field runtime WARN_ONCE described above.
> - security/landlock/.kunitconfig enumerates the CONFIG entries required
> to exercise every audited family so a family disabled by kernel config
> causes a build-visible coverage loss rather than a silent KUnit skip.
>
> The four-way wildcard grid lookup in hook_socket_create gates every
> pack_socket_key call on a successful return. This incidentally fixes a
> latent bug in the original hook where the first pack failure was
> detected with == -EACCES (the helper only ever returns 0 or -EINVAL), so
> an out-of-range triple fed three subsequent check_socket_access calls
> with an uninitialized key.
>
> The tcp_protocol.variant2 selftest previously asserted that a rule for
> IPPROTO_TCP did NOT match socket(AF_INET, SOCK_STREAM, 0). Under
> canonicalization both forms alias by design, so the test is renamed to
> alias_equivalence and its body is updated to assert the aliasing (both
> call forms match irrespective of which form the rule was inserted with).
> Unrelated families remain restricted.
>
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> ---
> security/landlock/.kunitconfig | 40 +
> security/landlock/socket.c | 871 +++++++++++++++++-
> .../testing/selftests/landlock/socket_test.c | 38 +-
> 3 files changed, 910 insertions(+), 39 deletions(-)
>
> diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig
> index f9423f01ac5b..5aafd56e8ebd 100644
> --- a/security/landlock/.kunitconfig
> +++ b/security/landlock/.kunitconfig
> @@ -1,6 +1,46 @@
> +CONFIG_AF_RXRPC=y
> +CONFIG_ATALK=y
> +CONFIG_ATM=y
> CONFIG_AUDIT=y
> +CONFIG_AX25=y
> +CONFIG_BT=y
> +CONFIG_CAIF=y
> +CONFIG_CAN=y
> +CONFIG_CAN_BCM=y
> +CONFIG_CRYPTO=y
> +CONFIG_CRYPTO_USER_API_AEAD=y
> +CONFIG_HAMRADIO=y
> +CONFIG_IEEE802154=y
> +CONFIG_IEEE802154_SOCKET=y
> +CONFIG_INET=y
> +CONFIG_INFINIBAND=y
> +CONFIG_IP_SCTP=y
> +CONFIG_IPV6=y
> +CONFIG_ISDN=y
> CONFIG_KUNIT=y
> +CONFIG_LLC=y
> +CONFIG_LLC2=y
> +CONFIG_MCTP=y
> +CONFIG_MISDN=y
> +CONFIG_MPTCP=y
> +CONFIG_MPTCP_IPV6=y
> CONFIG_NET=y
> +CONFIG_NET_KEY=y
> +CONFIG_NETDEVICES=y
> +CONFIG_NETROM=y
> +CONFIG_NFC=y
> +CONFIG_PACKET=y
> +CONFIG_PHONET=y
> +CONFIG_PPP=y
> +CONFIG_PPPOE=y
> +CONFIG_QRTR=y
> +CONFIG_RDS=y
> +CONFIG_ROSE=y
> CONFIG_SECURITY=y
> CONFIG_SECURITY_LANDLOCK=y
> CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y
> +CONFIG_SMC=y
> +CONFIG_TIPC=y
> +CONFIG_UNIX=y
> +CONFIG_VSOCKETS=y
> +CONFIG_X25=y
Are there possible kernel configurations where the mapping changes in
incompatible ways?
Specifically, are there kernel configurations where there is a family,
type and protocol such that:
(a) the call to socket(f, t, p) yields a valid socket FD
(b) and the socket's mapping is still different to what Landlock says
It seems that for the CONFIG_* options which are enabling address
families, this should stay compatible because without these, step (a)
presumably yields an error. Are there other CONFIG_* options which
are not about address families where this is not the case?
> diff --git a/security/landlock/socket.c b/security/landlock/socket.c
> index 6afd5a0ac6d7..ef48949fa7d3 100644
> --- a/security/landlock/socket.c
> +++ b/security/landlock/socket.c
> @@ -5,20 +5,385 @@
> * Copyright © 2025 Huawei Tech. Co., Ltd.
> */
>
> +#include <linux/in.h>
> #include <linux/net.h>
> #include <linux/socket.h>
> #include <linux/stddef.h>
> #include <net/ipv6.h>
> +#include <net/sock.h>
>
> #include "audit.h"
> +#include "cred.h"
> #include "limits.h"
> #include "ruleset.h"
> #include "socket.h"
> -#include "cred.h"
>
> #define TYPE_ALL (-1)
> #define PROTOCOL_ALL (-1)
>
> +/*
> + * Compensation for kernel-internal socket rewrites.
> + *
> + * The kernel maps the user-visible (family, type, protocol) triple into
> + * (sk->sk_family, sock->type, sk->sk_protocol) following per-family rules.
> + * Landlock mirrors those rules at rule insertion and at hook time so that rules
> + * inserted with a user-facing form match the canonical triple seen after
> + * socket(2) completes. The known patterns are:
> + *
> + * - __sock_create rewrites AF_INET + SOCK_PACKET to AF_PACKET for
> + * pre-2.2 compatibility (net/socket.c).
> + * - unix_create rewrites AF_UNIX + SOCK_RAW to AF_UNIX + SOCK_DGRAM and
> + * ignores the user protocol (sk_protocol stays 0).
> + * - inet_create and inet6_create resolve protocol=0 to a type-specific
> + * default (IPPROTO_TCP for SOCK_STREAM, IPPROTO_UDP for SOCK_DGRAM,
> + * IPPROTO_SCTP for SOCK_SEQPACKET) via the inetsw walk.
> + * - ax25_create rewrites protocol=0 or PF_AX25 to AX25_P_TEXT for
> + * SOCK_DGRAM and SOCK_SEQPACKET.
> + * - pn_socket_create rewrites protocol=0 to PN_PROTO_PHONET for
> + * SOCK_DGRAM and PN_PROTO_PIPE for SOCK_SEQPACKET.
> + * - vsock_create accepts either 0 or PF_VSOCK and stores sk_protocol=0
> + * for both; Landlock canonicalizes the rule to protocol=0.
> + *
> + * Enforcement happens at security_socket_create (pre-create) so Landlock denies
> + * unauthorized triples before any family-specific module is loaded or socket
> + * allocated, preserving the EACCES error path for triples the kernel itself
> + * would reject.
> + *
> + * Drift between landlock_canon_map and kernel behavior is detected in four
> + * layers: static_asserts on AF_MAX and SOCK_MAX (new AF or SOCK values break
> + * the build), the exhaustive KUnit suite in this file using sock_create_kern as
> + * the oracle, a runtime WARN_ON_ONCE in the post_create hook, and an explicit
> + * CONFIG list in .kunitconfig so a missing family fails the build instead of
> + * silently skipping.
> + */
> +
> +#define _LANDLOCK_CANON_REWRITE_FAMILY BIT(0)
> +#define _LANDLOCK_CANON_REWRITE_TYPE BIT(1)
> +#define _LANDLOCK_CANON_PROTOCOL_ZERO BIT(2)
> +#define _LANDLOCK_CANON_PROTOCOL_FAMILY_ID BIT(3)
> +#define _LANDLOCK_CANON_PROTOCOL_ALWAYS BIT(4)
> +#define _LANDLOCK_CANON_PROTOCOL_PRESERVE BIT(5)
Naming nits:
* _LANDLOCK_CANON_PROTOCOL_ALWAYS could maybe be
_LANDLOCK_CANON_REWRITE_PROTOCOL, for symmetry with *REWRITE_FAMILY
and *REWRITE_TYPE.
* similarly, _LANDLOCK_CANON_PROTOCOL_ZERO =>
_LANDLOCK_CANON_REWRITE_PROTOCOL_IF_ZERO?
At the higher abstraction level in this implementation:
These enum values, the struct landlock_canon_entry and its
interpretation in landlock_canonicalize_socket_key() feels a bit heavy
handed for the mapping, and when reading it, I had to jump between the
table, the struct, the enums and the canonicalization function.
I assume that you have attempted to express that table as a big switch
statement with normal `if` conditions where these preconditions are
expressed more directly? Was that code worse?
> +
> +/*
> + * All fields fit in u8 today: ops uses 6 bits; SOCK_MAX-1 and AF_MAX-1 are both
> + * small; the largest canonicalization target is AX25_P_TEXT (0xF0), well within
> + * u8. -Woverflow catches a too-wide initializer at build time if a future
> + * entry exceeds the field width.
> + */
> +struct landlock_canon_entry {
> + u8 ops;
> + u8 new_type;
> + u8 new_family;
> + u8 new_protocol;
> + /*
> + * When PROTOCOL_PRESERVE is set and the user protocol matches this
> + * value, the protocol field is left unchanged regardless of the other
> + * PROTOCOL_* flags. Captures sub-protocols whose .create() writes
> + * sk_protocol explicitly (e.g. CAN_J1939) when other sub-protocols of
> + * the same (family, type) do not. Lookup stays O(1) because the
> + * override is part of the same cell.
> + */
> + u8 preserve_protocol;
> +};
> +
> +/*
> + * Shared initializer for Case 4 families (AF_UNIX, AF_PACKET, AF_VSOCK, AF_KEY,
> + * ...) whose .create() ignores the user protocol and leaves sk_protocol at 0.
> + * The canonical form is any type, protocol=0.
> + */
> +#define _LANDLOCK_CANON_ALWAYS_ZERO \
> + { \
> + .ops = _LANDLOCK_CANON_PROTOCOL_ALWAYS, .new_protocol = 0, \
> + }
> +
> +/*
> + * A change to AF_MAX or SOCK_MAX implies a new protocol family or socket type
> + * reached upstream. The assertion fires a build error that forces an audit:
> + * does the new family or type introduce a kernel-internal rewrite that Landlock
> + * must mirror? If not, bump the expected value below.
> + */
> +static_assert(AF_MAX == 46,
> + "AF_MAX changed; audit landlock_canon_map for new families.");
> +static_assert(SOCK_MAX == 11,
> + "SOCK_MAX changed; audit landlock_canon_map for new types.");
> +
> +static const struct landlock_canon_entry
> + landlock_canon_map[AF_MAX][SOCK_MAX] = {
> + /*
> + * unix_create rewrites SOCK_RAW to SOCK_DGRAM and ignores the
> + * user protocol; normalize every AF_UNIX socket to protocol=0.
> + */
> + [AF_UNIX] = {
> + [SOCK_STREAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_SEQPACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_RAW] = {
> + .ops = _LANDLOCK_CANON_REWRITE_TYPE |
> + _LANDLOCK_CANON_PROTOCOL_ALWAYS,
> + .new_type = SOCK_DGRAM,
> + .new_protocol = 0,
> + },
> + },
> + [AF_INET] = {
> + [SOCK_STREAM] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
> + .new_protocol = IPPROTO_TCP,
> + },
> + [SOCK_DGRAM] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
> + .new_protocol = IPPROTO_UDP,
> + },
> + [SOCK_SEQPACKET] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
> + .new_protocol = IPPROTO_SCTP,
> + },
> + /*
> + * __sock_create rewrites AF_INET + SOCK_PACKET to
> + * AF_PACKET; packet_create then stores sk_protocol=0
> + * regardless of the user protocol (see AF_PACKET rows
> + * below), so the canonical triple is (AF_PACKET,
> + * SOCK_PACKET, 0).
> + */
> + [SOCK_PACKET] = {
> + .ops = _LANDLOCK_CANON_REWRITE_FAMILY |
> + _LANDLOCK_CANON_PROTOCOL_ALWAYS,
> + .new_family = AF_PACKET,
> + .new_protocol = 0,
> + },
> + },
> + /*
> + * packet_create stores the user-provided protocol in po->num
> + * (for filtering) but never writes sk_protocol, so sk_protocol
> + * stays at 0 for every AF_PACKET socket. Canonicalize rules to
> + * protocol=0 to match.
> + */
> + [AF_PACKET] = {
> + [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_PACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + /*
> + * AX.25 rewrites protocol=0 AND PF_AX25 to AX25_P_TEXT (0xF0)
> + * for SOCK_DGRAM and SOCK_SEQPACKET; see ax25_create in
> + * net/ax25/af_ax25.c. SOCK_RAW preserves user input.
> + */
> + [AF_AX25] = {
> + [SOCK_DGRAM] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ZERO |
> + _LANDLOCK_CANON_PROTOCOL_FAMILY_ID,
> + .new_protocol = 0xF0, /* AX25_P_TEXT */
> + },
> + [SOCK_SEQPACKET] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ZERO |
> + _LANDLOCK_CANON_PROTOCOL_FAMILY_ID,
> + .new_protocol = 0xF0, /* AX25_P_TEXT */
> + },
> + },
> + [AF_INET6] = {
> + [SOCK_STREAM] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
> + .new_protocol = IPPROTO_TCP,
> + },
> + [SOCK_DGRAM] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
> + .new_protocol = IPPROTO_UDP,
> + },
> + [SOCK_SEQPACKET] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
> + .new_protocol = IPPROTO_SCTP,
> + },
> + },
> + /*
> + * Phonet rewrites protocol=0 to PN_PROTO_PHONET for SOCK_DGRAM
> + * and PN_PROTO_PIPE for SOCK_SEQPACKET; see pn_socket_create in
> + * net/phonet/af_phonet.c.
> + */
> + [AF_PHONET] = {
> + [SOCK_DGRAM] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
> + .new_protocol = 1, /* PN_PROTO_PHONET */
> + },
> + [SOCK_SEQPACKET] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ZERO,
> + .new_protocol = 2, /* PN_PROTO_PIPE */
> + },
> + },
> + /*
> + * atalk_create accepts SOCK_DGRAM and SOCK_RAW but never writes
> + * sk_protocol.
> + */
> + [AF_APPLETALK] = {
> + [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + /*
> + * ATM PVC and SVC go through vcc_create, which rejects
> + * SOCK_STREAM and accepts every other type in the
> + * 0..SOCK_PACKET range without writing sk_protocol.
> + */
> + [AF_ATMPVC] = {
> + [0] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
> + [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_RDM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_SEQPACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_DCCP] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [7] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
> + [8] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
> + [9] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
> + [SOCK_PACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + [AF_ATMSVC] = {
> + [0] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
> + [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_RDM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_SEQPACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_DCCP] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [7] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
> + [8] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
> + [9] = _LANDLOCK_CANON_ALWAYS_ZERO, /* reserved hole */
> + [SOCK_PACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + /*
> + * LLC, CAN, RxRPC, IEEE 802.15.4, and QRTR all rely on
> + * sock_init_data to zero sk_protocol and never override it, so
> + * any reachable triple is canonicalized to protocol=0.
> + */
> + [AF_LLC] = {
> + [SOCK_STREAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + /*
> + * AF_CAN dispatches by protocol: can_raw and can_bcm leave
> + * sk_protocol=0, but j1939_sk_init writes sk_protocol =
> + * CAN_J1939. Use PROTOCOL_PRESERVE on SOCK_DGRAM so the J1939
> + * value survives the canonicalization while all other
> + * sub-protocols get normalized to 0. SOCK_SEQPACKET has no
> + * registered CAN sub-protocol so no entry is needed (the cell
> + * is unreachable at the kernel).
> + */
> + [AF_CAN] = {
> + [SOCK_DGRAM] = {
> + .ops = _LANDLOCK_CANON_PROTOCOL_ALWAYS |
> + _LANDLOCK_CANON_PROTOCOL_PRESERVE,
> + .new_protocol = 0,
> + .preserve_protocol =
> + 7 /* CAN_J1939 */,
> + },
> + [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + [AF_RXRPC] = {
> + [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + [AF_IEEE802154] = {
> + [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + [AF_QIPCRTR] = {
> + [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + /*
> + * pfkey_create accepts only PF_KEY_V2 for SOCK_RAW but never
> + * writes sk_protocol, so the kernel stores sk_protocol=0.
> + */
> + [AF_KEY] = {
> + [SOCK_RAW] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + /*
> + * vsock accepts protocol=0 or PF_VSOCK and leaves sk_protocol
> + * at 0 in either case, for every transport (loopback, VMCI,
> + * virtio, hyperv) because vsock_create never writes
> + * sk_protocol. Canonicalize every AF_VSOCK rule to protocol=0
> + * so both call forms match the same rule.
> + *
> + * The vsock loopback transport (the only one enabled by the
> + * default .kunitconfig) does not advertise DGRAM capability, so
> + * the SOCK_DGRAM cell is not reachable through sock_create_kern
> + * at KUnit time; DGRAM reachability requires
> + * CONFIG_VIRTIO_VSOCKETS=y. The canonicalization is
> + * nonetheless correct; a dedicated map-entry test case
> + * validates it independently of any kernel configuration.
> + */
> + [AF_VSOCK] = {
> + [SOCK_STREAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_DGRAM] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + [SOCK_SEQPACKET] = _LANDLOCK_CANON_ALWAYS_ZERO,
> + },
> + };
Some impressive code archeology here! :)
> +
> +/*
> + * landlock_canonicalize_socket_key - Apply kernel-equivalent rewrites
> + *
> + * @family: in/out protocol family.
> + * @type: in/out socket type.
> + * @protocol: in/out protocol.
> + *
> + * Transforms a (@family, @type, @protocol) triple into the form the kernel
> + * stores after the family .create() completes. Wildcards (TYPE_ALL,
> + * PROTOCOL_ALL) are preserved unchanged since they do not pin a specific
> + * triple. Out-of-range values are left for the caller (pack_socket_key) to
> + * reject.
> + */
> +static void landlock_canonicalize_socket_key(s32 *family, s32 *type,
> + s32 *protocol)
> +{
> + const struct landlock_canon_entry *entry;
> + s32 input_family;
> +
> + /*
> + * Type is the map second index, so it must be concrete. Family is the
> + * map first index; checked below via bounds.
> + */
> + if (*type == TYPE_ALL)
> + return;
> +
> + if (*family < 0 || *family >= AF_MAX || *type < 0 || *type >= SOCK_MAX)
> + return;
> +
> + entry = &landlock_canon_map[*family][*type];
> + input_family = *family;
> +
> + /*
> + * Family and type rewrites apply regardless of protocol value; a
> + * PROTOCOL_ALL rule still benefits from an alias family or type
> + * rewrite.
> + */
If I understand correctly, this remark is crucial for the correctness;
if the API allows PROTOCOL_ALL, then the rewrite results must be
independent of the protocol, so that we can still rewrite the family
and type correctly in these cases.
But if I understand correctly, if the API allows TYPE_ALL, a rule with
TYPE_ALL can *not* be canonicalized correctly in all cases, and that
would be another reason why the "type" wildcard is problematic?
It's an obscure example, but a rule could then allow (AF_INET,
TYPE_ALL, PROTOCOL_ALL). When a process now invokes socket(AF_INET,
SOCK_PACKET, 0), that invocation *looks like it should match the
rule*, but the existing socket(2) logic rewrites that socket to a
(AF_PACKET, SOCK_PACKET, 0), which does not match the rule.
Generalized to the KUnit test, I think a very general test that would
catch this would need to look a bit like the
test_family_canonicalization test, but it would also have to try the
canonicalization with the permitted wildcard combinations and see that
the resulting rule still *matches* the socket that was created.
> + if (entry->ops & _LANDLOCK_CANON_REWRITE_FAMILY)
> + *family = entry->new_family;
> + if (entry->ops & _LANDLOCK_CANON_REWRITE_TYPE)
> + *type = entry->new_type;
> +
> + /*
> + * Protocol rewrites are skipped for wildcard protocols so that a
> + * PROTOCOL_ALL rule stays wildcard even for families the kernel
> + * canonicalizes.
> + */
> + if (*protocol == PROTOCOL_ALL)
> + return;
> +
> + /*
> + * Per-protocol preservation: when the map declares that one specific
> + * user protocol survives unchanged in sk_protocol (e.g., AF_CAN +
> + * SOCK_DGRAM + CAN_J1939: j1939_sk_init writes sk_protocol = CAN_J1939,
> + * while raw_init and bcm_init leave sk_protocol = 0), skip all protocol
> + * rewrites when the user input matches that value. Keeps the runtime
> + * drift WARN silent for the exception while PROTOCOL_ALWAYS still
> + * normalizes the rest of the cell to new_protocol.
> + */
> + if ((entry->ops & _LANDLOCK_CANON_PROTOCOL_PRESERVE) &&
> + *protocol == entry->preserve_protocol)
> + return;
> +
> + if (entry->ops & _LANDLOCK_CANON_PROTOCOL_ALWAYS)
> + *protocol = entry->new_protocol;
> + else if (((entry->ops & _LANDLOCK_CANON_PROTOCOL_ZERO) &&
> + *protocol == 0) ||
> + ((entry->ops & _LANDLOCK_CANON_PROTOCOL_FAMILY_ID) &&
> + *protocol == input_family))
> + *protocol = entry->new_protocol;
> +}
> +
> static int pack_socket_key(const s32 family, const s32 type, const s32 protocol,
> uintptr_t *val)
> {
> @@ -78,12 +443,12 @@ int landlock_append_socket_rule(struct landlock_ruleset *const ruleset,
> {
> int err;
> uintptr_t key;
> +
> /*
> - * (AF_INET, SOCK_PACKET) is an alias for (AF_PACKET, SOCK_PACKET)
> - * (cf. __sock_create).
> + * Apply the kernel rewrites so that this rule matches the triple seen
> + * at hook time. See landlock_canon_map.
> */
> - if (family == AF_INET && type == SOCK_PACKET)
> - family = AF_PACKET;
> + landlock_canonicalize_socket_key(&family, &type, &protocol);
>
> err = pack_socket_key(family, type, protocol, &key);
> if (err)
> @@ -123,6 +488,12 @@ static int check_socket_access(const struct landlock_ruleset *dom,
> return -EACCES;
> }
>
> +/*
> + * Enforcement happens at security_socket_create (pre-create) so Landlock denies
> + * unauthorized triples before the kernel loads any family-specific module or
> + * allocates a socket. This preserves the EACCES error path even for triples
> + * the kernel itself would reject (AF_UNSPEC, invalid family and type pairs).
> + */
> static int hook_socket_create(int family, int type, int protocol, int kern)
> {
> layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_SOCKET] = {};
> @@ -133,45 +504,52 @@ static int hook_socket_create(int family, int type, int protocol, int kern)
> const struct landlock_cred_security *const subject =
> landlock_get_applicable_subject(current_cred(), masks, NULL);
> uintptr_t key;
> + s32 canon_family = family, canon_type = type, canon_protocol = protocol;
> struct lsm_socket_audit audit_socket = {
> .family = family,
> .type = type,
> .protocol = protocol,
> };
>
> - if (!subject)
> - return 0;
> - /* Checks only user space sockets. */
> + /* Kernel-internal sockets bypass (accept, sock_create_lite, ...). */
> if (kern)
> return 0;
> + if (!subject)
> + return 0;
> +
> + /* Canonicalize the user-facing triple to match the rule storage. */
> + landlock_canonicalize_socket_key(&canon_family, &canon_type,
> + &canon_protocol);
>
> handled_access = landlock_init_layer_masks(
> subject->domain, LANDLOCK_ACCESS_SOCKET_CREATE, &layer_masks,
> LANDLOCK_KEY_SOCKET);
> +
> /*
> - * Error could happen due to parameters are outside of the allowed range,
> - * so this combination couldn't be added in ruleset previously.
> - * Therefore, it's not permitted.
> + * Four-way wildcard grid lookup. Each pack_socket_key call is gated on
> + * its return value so a rejected triple does not feed an uninitialized
> + * key into check_socket_access.
> */
> - if (pack_socket_key(family, type, protocol, &key) == -EACCES)
> - return -EACCES;
> - if (check_socket_access(subject->domain, key, &layer_masks,
> + if (pack_socket_key(canon_family, canon_type, canon_protocol, &key) ==
> + 0 &&
> + check_socket_access(subject->domain, key, &layer_masks,
> handled_access) == 0)
> return 0;
>
> - /* Ranges were already checked. */
> - (void)pack_socket_key(family, TYPE_ALL, protocol, &key);
> - if (check_socket_access(subject->domain, key, &layer_masks,
> + if (pack_socket_key(canon_family, TYPE_ALL, canon_protocol, &key) ==
> + 0 &&
> + check_socket_access(subject->domain, key, &layer_masks,
> handled_access) == 0)
> return 0;
>
> - (void)pack_socket_key(family, type, PROTOCOL_ALL, &key);
> - if (check_socket_access(subject->domain, key, &layer_masks,
> + if (pack_socket_key(canon_family, canon_type, PROTOCOL_ALL, &key) ==
> + 0 &&
> + check_socket_access(subject->domain, key, &layer_masks,
> handled_access) == 0)
> return 0;
>
> - (void)pack_socket_key(family, TYPE_ALL, PROTOCOL_ALL, &key);
> - if (check_socket_access(subject->domain, key, &layer_masks,
> + if (pack_socket_key(canon_family, TYPE_ALL, PROTOCOL_ALL, &key) == 0 &&
> + check_socket_access(subject->domain, key, &layer_masks,
> handled_access) == 0)
> return 0;
>
> @@ -187,8 +565,64 @@ static int hook_socket_create(int family, int type, int protocol, int kern)
> return -EACCES;
> }
>
> +/*
> + * Runtime drift detection: at post_create the family .create() has completed,
> + * so sk_family, sock->type and sk_protocol are authoritative. Compare them
> + * against what landlock_canonicalize_socket_key produces from the user input; a
> + * mismatch means a kernel canonicalization is not mirrored in
> + * landlock_canon_map. Enforcement already happened at socket_create, so this
> + * hook always returns 0.
> + *
> + * This hook only runs for tasks sandboxed by a Landlock domain that handles
> + * LANDLOCK_ACCESS_SOCKET_CREATE, so non-sandboxed tasks pay no overhead.
> + * Sandboxed tasks still cover enough of the reachable (family, type, protocol)
> + * space over the lifetime of a typical workload to surface drift in the field.
> + */
> +static int hook_socket_post_create(struct socket *sock, int family, int type,
> + int protocol, int kern)
> +{
> + const struct access_masks masks = {
> + .socket = LANDLOCK_ACCESS_SOCKET_CREATE,
> + };
> + s32 canon_family = family, canon_type = type, canon_protocol = protocol;
> +
> + if (kern)
> + return 0;
> + if (!landlock_get_applicable_subject(current_cred(), masks, NULL))
> + return 0;
> +
> + /*
> + * Kernel-resolved values are authoritative; no fallback to the user
> + * protocol argument. Families that ignore the user protocol (e.g.,
> + * AF_UNIX, AF_VSOCK) are represented in landlock_canon_map with
> + * PROTOCOL_ALWAYS so the canonicalization below yields the same value
> + * for both rule insertion and this comparison.
> + *
> + * Drift is reported per field so a reviewer knows which axis disagrees
> + * with the kernel without having to diff three values. WARN_ONCE
> + * rather than WARN_ON_ONCE keeps the stack trace but swallows
> + * subsequent identical drifts in the same boot.
> + */
> + landlock_canonicalize_socket_key(&canon_family, &canon_type,
> + &canon_protocol);
> + WARN_ONCE(
> + canon_family != sock->sk->sk_family,
> + "Landlock canon family drift: canon=%d kernel=%d (user family=%d type=%d protocol=%d)\n",
> + canon_family, sock->sk->sk_family, family, type, protocol);
> + WARN_ONCE(
> + canon_type != sock->type,
> + "Landlock canon type drift: canon=%d kernel=%d (user family=%d type=%d protocol=%d)\n",
> + canon_type, sock->type, family, type, protocol);
> + WARN_ONCE(
> + canon_protocol != sock->sk->sk_protocol,
> + "Landlock canon protocol drift: canon=%d kernel=%d (user family=%d type=%d protocol=%d)\n",
> + canon_protocol, sock->sk->sk_protocol, family, type, protocol);
> + return 0;
> +}
> +
> static struct security_hook_list landlock_hooks[] __ro_after_init = {
> LSM_HOOK_INIT(socket_create, hook_socket_create),
> + LSM_HOOK_INIT(socket_post_create, hook_socket_post_create),
> };
>
> __init void landlock_add_socket_hooks(void)
> @@ -196,3 +630,398 @@ __init void landlock_add_socket_hooks(void)
> security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
> &landlock_lsmid);
> }
> +
> +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
> +
> +#include <kunit/test.h>
> +#include <net/net_namespace.h>
> +
> +/*
> + * Per-family parameterized coverage test. One subtest per AF_*; each subtest
> + * iterates every (type, protocol) probe through sock_create_kern and checks
> + * either that landlock_canonicalize_socket_key matches the kernel-resolved
> + * triple (for reachable families) or that the call fails with the expected
> + * errno (for unsupported families). No subtest is skipped: every family
> + * produces PASS or FAIL, so an unsupported entry that quietly becomes stale is
> + * caught immediately.
> + *
> + * Every unsupported family entry expects sock_create_kern to fail with
> + * -EAFNOSUPPORT because no handler is registered for that family in the KUnit
> + * build environment (either the family was never implemented, was removed from
> + * Linux, is a reserved pseudo family, or requires an arch or CONFIG not
> + * satisfied by .kunitconfig). The family name in the TAP subtest header,
> + * combined with this errno, identifies the case unambiguously.
> + */
> +struct landlock_canon_family_case {
> + int af;
> + const char *name;
> + /*
> + * True means the family must be supported in the KUnit environment and
> + * canonicalization is exercised against the kernel. False means every
> + * probe must fail with -EAFNOSUPPORT.
> + */
> + bool supported;
> +};
> +
> +#define _LANDLOCK_AF_SUPPORTED(f) \
> + { \
> + .af = (f), .name = #f, .supported = true, \
> + }
> +
> +#define _LANDLOCK_AF_UNSUPPORTED(f) \
> + { \
> + .af = (f), .name = #f, .supported = false, \
> + }
> +
> +static const struct landlock_canon_family_case landlock_canon_families[] = {
> + /* kernel rejects socket(AF_UNSPEC) */
> + _LANDLOCK_AF_UNSUPPORTED(AF_UNSPEC),
> + _LANDLOCK_AF_SUPPORTED(AF_UNIX),
> + _LANDLOCK_AF_SUPPORTED(AF_INET),
> + _LANDLOCK_AF_SUPPORTED(AF_AX25),
> + /* AF_IPX removed from Linux */
> + _LANDLOCK_AF_UNSUPPORTED(AF_IPX),
> + _LANDLOCK_AF_SUPPORTED(AF_APPLETALK),
> + _LANDLOCK_AF_SUPPORTED(AF_NETROM),
> + /* AF_BRIDGE cannot be used to create sockets */
> + _LANDLOCK_AF_UNSUPPORTED(AF_BRIDGE),
> + _LANDLOCK_AF_SUPPORTED(AF_ATMPVC),
> + _LANDLOCK_AF_SUPPORTED(AF_X25),
> + _LANDLOCK_AF_SUPPORTED(AF_INET6),
> + _LANDLOCK_AF_SUPPORTED(AF_ROSE),
> + /* AF_DECnet removed from Linux */
> + _LANDLOCK_AF_UNSUPPORTED(AF_DECnet),
> + /* AF_NETBEUI not implemented */
> + _LANDLOCK_AF_UNSUPPORTED(AF_NETBEUI),
> + /* AF_SECURITY is a pseudo family */
> + _LANDLOCK_AF_UNSUPPORTED(AF_SECURITY),
> + _LANDLOCK_AF_SUPPORTED(AF_KEY),
> + _LANDLOCK_AF_SUPPORTED(AF_NETLINK),
> + _LANDLOCK_AF_SUPPORTED(AF_PACKET),
> + /* AF_ASH not implemented */
> + _LANDLOCK_AF_UNSUPPORTED(AF_ASH),
> + /* AF_ECONET removed from Linux */
> + _LANDLOCK_AF_UNSUPPORTED(AF_ECONET),
> + _LANDLOCK_AF_SUPPORTED(AF_ATMSVC),
> + _LANDLOCK_AF_SUPPORTED(AF_RDS),
> + /* AF_SNA not implemented */
> + _LANDLOCK_AF_UNSUPPORTED(AF_SNA),
> + /* AF_IRDA removed from Linux */
> + _LANDLOCK_AF_UNSUPPORTED(AF_IRDA),
> + _LANDLOCK_AF_SUPPORTED(AF_PPPOX),
> + /* AF_WANPIPE not implemented */
> + _LANDLOCK_AF_UNSUPPORTED(AF_WANPIPE),
> + _LANDLOCK_AF_SUPPORTED(AF_LLC),
> + /* AF_IB reserved by infiniband */
> + _LANDLOCK_AF_UNSUPPORTED(AF_IB),
> + /* AF_MPLS cannot be used to create sockets */
> + _LANDLOCK_AF_UNSUPPORTED(AF_MPLS),
> + _LANDLOCK_AF_SUPPORTED(AF_CAN),
> + _LANDLOCK_AF_SUPPORTED(AF_TIPC),
> + _LANDLOCK_AF_SUPPORTED(AF_BLUETOOTH),
> + /* AF_IUCV only on s390 */
> + _LANDLOCK_AF_UNSUPPORTED(AF_IUCV),
> + _LANDLOCK_AF_SUPPORTED(AF_RXRPC),
> + _LANDLOCK_AF_SUPPORTED(AF_ISDN),
> + _LANDLOCK_AF_SUPPORTED(AF_PHONET),
> + _LANDLOCK_AF_SUPPORTED(AF_IEEE802154),
> + _LANDLOCK_AF_SUPPORTED(AF_CAIF),
> + _LANDLOCK_AF_SUPPORTED(AF_ALG),
> + _LANDLOCK_AF_SUPPORTED(AF_NFC),
> + _LANDLOCK_AF_SUPPORTED(AF_VSOCK),
> + /* AF_KCM requires a kcm multiplexer setup */
> + _LANDLOCK_AF_UNSUPPORTED(AF_KCM),
> + _LANDLOCK_AF_SUPPORTED(AF_QIPCRTR),
> + _LANDLOCK_AF_SUPPORTED(AF_SMC),
> + /* AF_XDP requires a netdev with XDP support */
> + _LANDLOCK_AF_UNSUPPORTED(AF_XDP),
> + _LANDLOCK_AF_SUPPORTED(AF_MCTP),
> +};
> +
> +static_assert(ARRAY_SIZE(landlock_canon_families) == AF_MAX,
> + "landlock_canon_families size must track AF_MAX.");
> +
> +static void
> +landlock_canon_family_to_desc(const struct landlock_canon_family_case *c,
> + char *desc)
> +{
> + strscpy(desc, c->name, KUNIT_PARAM_DESC_SIZE);
> +}
> +
> +KUNIT_ARRAY_PARAM(landlock_canon_family, landlock_canon_families,
> + landlock_canon_family_to_desc);
> +
> +/*
> + * Edge protocol sentinels beyond the 0..NPROTO range exercised by the main
> + * sweep. IPPROTO_SCTP is the only value in landlock_canon_map above NPROTO
> + * today; 255 = IPPROTO_RAW covers the top of the IP protocol byte; 0xFFFF = u16
> + * upper bound covers the ethertype-wide range accepted by AF_PACKET and
> + * saturates pack_socket_key's u16 field.
> + */
> +static const int landlock_edge_protocols[] = {
> + IPPROTO_SCTP,
> + 255 /* IPPROTO_RAW */,
> + 0xFFFF /* u16 max */,
> +};
> +
> +/*
> + * Return the protocol value for probe index @p: indices 0..NPROTO cover every
> + * family-ID range (and NPROTO itself, one past the last valid PF_*, exercises
> + * an out-of-range probe); subsequent indices draw from landlock_edge_protocols.
> + * Returns -1 when the iteration is exhausted.
> + */
> +static int landlock_probe_protocol(size_t p)
> +{
> + if (p <= NPROTO)
> + return (int)p;
> + if (p - (NPROTO + 1) < ARRAY_SIZE(landlock_edge_protocols))
> + return landlock_edge_protocols[p - (NPROTO + 1)];
> + return -1;
> +}
> +
> +static void test_family_canonicalization(struct kunit *const test)
> +{
> + const struct landlock_canon_family_case *const c = test->param_value;
> + unsigned int tested = 0, canonicalized = 0, confirmed_unsupported = 0;
> + int type;
> + size_t probe;
> +
> + for (type = 0; type < SOCK_MAX; type++) {
> + for (probe = 0;; probe++) {
> + const int protocol = landlock_probe_protocol(probe);
> + struct socket *sock = NULL;
> + int ret, kernel_family, kernel_type, kernel_protocol;
> + s32 landlock_family, landlock_type, landlock_protocol;
> +
> + if (protocol < 0)
> + break;
> +
> + ret = sock_create_kern(&init_net, c->af, type, protocol,
> + &sock);
> +
> + if (ret == 0) {
> + /*
> + * Reached the kernel: fully verify Landlock
> + * canonicalization against the resolved
> + * sk_family / sock->type / sk_protocol triple.
> + * This path runs whether or not the family is
> + * listed as unsupported: if an extra CONFIG_*
> + * turns an unsupported family entry reachable,
> + * the test picks it up naturally.
> + */
> + kernel_family = sock->sk->sk_family;
> + kernel_type = sock->type;
> + kernel_protocol = sock->sk->sk_protocol;
> + sock_release(sock);
> +
> + landlock_family = c->af;
> + landlock_type = type;
> + landlock_protocol = protocol;
> + landlock_canonicalize_socket_key(
> + &landlock_family, &landlock_type,
> + &landlock_protocol);
> +
> + /* Drift axis identified by the failing macro
> + * line; KUNIT_EXPECT_EQ_MSG already prints
> + * expected vs actual values. The format
> + * "(F,T,P)" carries the user input that
> + * triggered the mismatch.
> + */
> + KUNIT_EXPECT_EQ_MSG(test, landlock_family,
> + kernel_family,
> + "family (%d,%d,%d)", c->af,
> + type, protocol);
> + KUNIT_EXPECT_EQ_MSG(test, landlock_type,
> + kernel_type,
> + "type (%d,%d,%d)", c->af,
> + type, protocol);
> + KUNIT_EXPECT_EQ_MSG(test, landlock_protocol,
> + kernel_protocol,
> + "protocol (%d,%d,%d)",
> + c->af, type, protocol);
> +
> + tested++;
> + if (landlock_family != c->af ||
> + landlock_type != type ||
> + landlock_protocol != protocol)
> + canonicalized++;
> + } else if (ret == -EAFNOSUPPORT && !c->supported) {
> + /*
> + * Ignore this specific combination when the
> + * kernel says the family is not registered AND
> + * the family is on the unsupported list.
> + * Rationale: the unsupported flag declares "we
> + * expect no kernel handler for this family in
> + * the KUnit build". If extra CONFIG_* entries
> + * are added later (e.g. via --kconfig_add or a
> + * per-arch config layer), an unsupported family
> + * entry may start returning ret == 0 for some
> + * probes; those fall into the branch above and
> + * are fully tested. No per-arch .kunitconfig
> + * fragmentation is required.
> + *
> + * Other negative errno values (e.g.,
> + * -EPROTONOSUPPORT, -ESOCKTNOSUPPORT,
> + * -EPROTOTYPE) indicate that this specific
> + * (type, protocol) triple is not supported by
> + * an otherwise-loaded family; silently skip the
> + * probe in that case too.
> + */
> + confirmed_unsupported++;
> + }
> + /*
> + * All other non-zero returns are silently skipped: the
> + * kernel rejected the specific triple; nothing to
> + * canonicalize.
> + */
> + }
> + }
> +
> + if (!c->supported) {
> + kunit_info(test,
> + "%u probes confirmed %s unsupported (errno %d)",
> + confirmed_unsupported, c->name, -EAFNOSUPPORT);
> + return;
> + }
> + if (tested == 0) {
> + /*
> + * Resolve by either (a) adding a sub-protocol CONFIG to
> + * .kunitconfig so sock_create_kern can reach the family, or (b)
> + * flipping the family entry's .supported field to false to
> + * declare it expected-unreachable.
> + */
> + KUNIT_FAIL(test, "%s: no reachable triple", c->name);
> + return;
> + }
> + kunit_info(test, "%u reachable triples (%u canonicalized)", tested,
> + canonicalized);
> +}
> +
> +/*
> + * Wildcards bypass canonicalization: the input triple is returned unchanged for
> + * TYPE_ALL, and protocol rewrites are skipped for PROTOCOL_ALL while family and
> + * type rewrites still apply.
> + */
> +static void test_canonicalization_preserves_wildcards(struct kunit *const test)
> +{
> + s32 family, type, protocol;
> +
> + /* Type wildcard: no map lookup. */
> + family = AF_INET;
> + type = TYPE_ALL;
> + protocol = 0;
> + landlock_canonicalize_socket_key(&family, &type, &protocol);
> + KUNIT_EXPECT_EQ(test, family, AF_INET);
> + KUNIT_EXPECT_EQ(test, type, TYPE_ALL);
> + KUNIT_EXPECT_EQ(test, protocol, 0);
This is btw the same example as the one I mentioned above; When you
call socket(AF_INET, SOCK_PACKET, 0), the resulting socket turns into
a (AF_PACKET, SOCK_PACKET, 0) and it does not match the canonicalized
(AF_INET, TYPE_ALL, 0) rule any more.
> +
> + /* Protocol wildcard with REWRITE_FAMILY row: family still rewrites. */
> + family = AF_INET;
> + type = SOCK_PACKET;
> + protocol = PROTOCOL_ALL;
> + landlock_canonicalize_socket_key(&family, &type, &protocol);
> + KUNIT_EXPECT_EQ(test, family, AF_PACKET);
> + KUNIT_EXPECT_EQ(test, type, SOCK_PACKET);
> + KUNIT_EXPECT_EQ(test, protocol, PROTOCOL_ALL);
> +
> + /* Protocol wildcard with PROTOCOL_ZERO row: protocol stays wildcard. */
> + family = AF_INET;
> + type = SOCK_STREAM;
> + protocol = PROTOCOL_ALL;
> + landlock_canonicalize_socket_key(&family, &type, &protocol);
> + KUNIT_EXPECT_EQ(test, family, AF_INET);
> + KUNIT_EXPECT_EQ(test, type, SOCK_STREAM);
> + KUNIT_EXPECT_EQ(test, protocol, PROTOCOL_ALL);
> +
> + /*
> + * Protocol wildcard with PROTOCOL_ALWAYS row: protocol stays wildcard
> + * even though the entry would unconditionally rewrite concrete protocol
> + * values.
> + */
> + family = AF_UNIX;
> + type = SOCK_STREAM;
> + protocol = PROTOCOL_ALL;
> + landlock_canonicalize_socket_key(&family, &type, &protocol);
> + KUNIT_EXPECT_EQ(test, family, AF_UNIX);
> + KUNIT_EXPECT_EQ(test, type, SOCK_STREAM);
> + KUNIT_EXPECT_EQ(test, protocol, PROTOCOL_ALL);
> +
> + /*
> + * REWRITE_TYPE with PROTOCOL_ALL still folds the type while leaving the
> + * protocol wildcard intact.
> + */
> + family = AF_UNIX;
> + type = SOCK_RAW;
> + protocol = PROTOCOL_ALL;
> + landlock_canonicalize_socket_key(&family, &type, &protocol);
> + KUNIT_EXPECT_EQ(test, family, AF_UNIX);
> + KUNIT_EXPECT_EQ(test, type, SOCK_DGRAM);
> + KUNIT_EXPECT_EQ(test, protocol, PROTOCOL_ALL);
> +}
> +
> +/*
> + * Each map entry produces the expected rewrite for a concrete triple.
> + * Parameterized for per-entry pass/fail visibility; semantic descriptors name
> + * what each entry is supposed to do.
> + */
> +struct landlock_canon_map_case {
> + const char *name;
> + s32 in_family, in_type, in_protocol;
> + s32 out_family, out_type, out_protocol;
> +};
> +
> +/*
> + * test_family_canonicalization covers every (family, type, protocol) cell that
> + * sock_create_kern can reach. The cases listed here are the kernel-unreachable
> + * cells whose canonicalization still matters in production and therefore needs
> + * config-independent validation.
> + *
> + * AF_VSOCK SOCK_DGRAM is the only such cell today: its reachability requires
> + * CONFIG_VIRTIO_VSOCKETS=y, which the default .kunitconfig does not enable.
> + * The two entries below cover both user call forms (protocol=0 and
> + * protocol=PF_VSOCK) that vsock_create accepts and canonicalize to 0.
> + */
> +static const struct landlock_canon_map_case landlock_canon_map_cases[] = {
> + { "vsock_dgram_zero_stays_zero", AF_VSOCK, SOCK_DGRAM, 0, AF_VSOCK,
> + SOCK_DGRAM, 0 },
> + { "vsock_dgram_family_id_forced_to_zero", AF_VSOCK, SOCK_DGRAM,
> + AF_VSOCK, AF_VSOCK, SOCK_DGRAM, 0 },
> +};
> +
> +static void landlock_canon_map_to_desc(const struct landlock_canon_map_case *c,
> + char *desc)
> +{
> + strscpy(desc, c->name, KUNIT_PARAM_DESC_SIZE);
> +}
> +
> +KUNIT_ARRAY_PARAM(landlock_canon_map, landlock_canon_map_cases,
> + landlock_canon_map_to_desc);
> +
> +static void test_canonicalization_map_entry(struct kunit *const test)
> +{
> + const struct landlock_canon_map_case *const c = test->param_value;
> + s32 family = c->in_family, type = c->in_type, protocol = c->in_protocol;
> +
> + landlock_canonicalize_socket_key(&family, &type, &protocol);
> + KUNIT_EXPECT_EQ(test, family, c->out_family);
> + KUNIT_EXPECT_EQ(test, type, c->out_type);
> + KUNIT_EXPECT_EQ(test, protocol, c->out_protocol);
> +}
> +
> +static struct kunit_case test_cases[] = {
> + KUNIT_CASE_PARAM(test_family_canonicalization,
> + landlock_canon_family_gen_params),
> + KUNIT_CASE(test_canonicalization_preserves_wildcards),
> + KUNIT_CASE_PARAM(test_canonicalization_map_entry,
> + landlock_canon_map_gen_params),
> + {}
> +};
> +
> +static struct kunit_suite test_suite = {
> + .name = "landlock_socket",
> + .test_cases = test_cases,
> +};
> +
> +kunit_test_suite(test_suite);
> +
> +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
> diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c
> index a091b8a883c8..5c0959f50ba2 100644
> --- a/tools/testing/selftests/landlock/socket_test.c
> +++ b/tools/testing/selftests/landlock/socket_test.c
> @@ -7,15 +7,15 @@
>
> #define _GNU_SOURCE
>
> +#include <arpa/inet.h>
> +#include <linux/can.h>
> +#include <linux/kcm.h>
> #include <linux/landlock.h>
> -#include <sys/prctl.h>
> #include <linux/pfkeyv2.h>
> -#include <linux/kcm.h>
> -#include <linux/can.h>
> -#include <sys/socket.h>
> -#include <stdint.h>
> #include <linux/sctp.h>
> -#include <arpa/inet.h>
> +#include <stdint.h>
> +#include <sys/prctl.h>
> +#include <sys/socket.h>
>
> #include "audit.h"
> #include "common.h"
> @@ -829,11 +829,12 @@ FIXTURE_VARIANT_ADD(tcp_protocol, variant2) {
> };
>
> /*
> - * Landlock doesn't perform protocol mappings handled by network stack on
> - * protocol family level. Test verifies that if only one definition is
> - * allowed another becomes restricted.
> + * Landlock canonicalizes AF_INET, SOCK_STREAM, protocol=0 to IPPROTO_TCP at
> + * rule insertion so that a rule inserted with either the default-protocol form
> + * or the explicit IPPROTO_TCP form matches both call syntaxes. Test verifies
> + * this aliasing. Unrelated families (AF_PACKET here) remain restricted.
> */
> -TEST_F(tcp_protocol, alias_restriction)
> +TEST_F(tcp_protocol, alias_equivalence)
> {
> const struct landlock_ruleset_attr ruleset_attr = {
> .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE,
> @@ -861,14 +862,15 @@ TEST_F(tcp_protocol, alias_restriction)
> enforce_ruleset(_metadata, ruleset_fd);
> ASSERT_EQ(0, close(ruleset_fd));
>
> - if (protocol == 0) {
> - EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0));
> - EXPECT_EQ(EACCES,
> - test_socket(AF_PACKET, SOCK_STREAM, IPPROTO_TCP));
> - } else if (protocol == IPPROTO_TCP) {
> - EXPECT_EQ(EACCES, test_socket(AF_INET, SOCK_STREAM, 0));
> - EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
> - }
> + /*
> + * Irrespective of which call form was used to insert the rule, both
> + * call forms of socket(2) match.
> + */
> + EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0));
> + EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
> +
> + /* Other families remain restricted. */
> + EXPECT_EQ(EACCES, test_socket(AF_PACKET, SOCK_STREAM, IPPROTO_TCP));
> }
>
> static int test_socketpair(int family, int type, int protocol)
> --
> 2.53.0
>
–Günther
^ permalink raw reply
* Re: [PATCH 04/11] treewide: Convert struct kernel_param_ops initializers to DEFINE_KERNEL_PARAM_OPS
From: Rafael J. Wysocki @ 2026-05-22 17:01 UTC (permalink / raw)
To: Kees Cook
Cc: Luis Chamberlain, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Jani Nikula,
Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, David Airlie,
Simona Vetter, Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
Benjamin Berg, Ilpo Järvinen, David E. Box,
Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
Heiko Carstens, Vasily Gorbik, Sean Christopherson, Paolo Bonzini,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Andrew Morton, John Johansen, Paul Moore, James Morris,
Serge E. Hallyn, Andy Shevchenko, Georgia Garcia, kvm, dmaengine,
linux-modules, kasan-dev, linux-mm, apparmor,
linux-security-module, linux-um, linux-acpi, openipmi-developer,
qemu-devel, intel-gfx, dri-devel, linux-rdma, linux-media,
linux-pci, linux-scsi, linux-pm, linuxppc-dev, linux-serial,
linux-usb, usb-storage, virtualization, linux-kernel, linux-arch,
netdev, linux-fsdevel, linux-hardening
In-Reply-To: <20260521133326.2465264-4-kees@kernel.org>
On Thu, May 21, 2026 at 3:33 PM Kees Cook <kees@kernel.org> wrote:
>
> Using Coccinelle, rewrite every struct kernel_param_ops initializer that
> sets .get into a DEFINE_KERNEL_PARAM_OPS-family macro invocation,
> for example:
>
> @@
> declarer name DEFINE_KERNEL_PARAM_OPS;
> identifier OPS;
> expression SET, GET;
> @@
> - const struct kernel_param_ops OPS = {
> - .set = SET,
> - .get = GET,
> - };
> + DEFINE_KERNEL_PARAM_OPS(OPS, SET, GET);
>
> Using the macro for initialization means future changes can manipulate
> the struct layout and callback prototypes without having to change every
> initializer.
>
> Signed-off-by: Kees Cook <kees@kernel.org>
For ACPI and hibernation:
Acked-by: Rafael J. Wysocki (Intel) <rafael@kernel.org>
> ---
> mm/kfence/core.c | 7 +-
> arch/powerpc/kvm/book3s_hv.c | 5 +-
> arch/s390/kernel/perf_cpum_sf.c | 6 +-
> arch/um/drivers/vfio_kern.c | 6 +-
> arch/um/drivers/virtio_uml.c | 6 +-
> arch/x86/kernel/msr.c | 6 +-
> arch/x86/kvm/mmu/mmu.c | 12 +--
> arch/x86/kvm/svm/avic.c | 6 +-
> arch/x86/kvm/vmx/vmx.c | 6 +-
> arch/x86/platform/uv/uv_nmi.c | 12 +--
> block/disk-events.c | 6 +-
> drivers/acpi/sysfs.c | 25 +++----
> drivers/block/loop.c | 12 +--
> drivers/block/null_blk/main.c | 12 +--
> drivers/block/rnbd/rnbd-srv.c | 6 +-
> drivers/block/ublk_drv.c | 7 +-
> drivers/char/ipmi/ipmi_msghandler.c | 6 +-
> drivers/char/ipmi/ipmi_watchdog.c | 17 ++---
> drivers/crypto/hisilicon/hpre/hpre_main.c | 16 +---
> drivers/crypto/hisilicon/sec2/sec_main.c | 23 ++----
> drivers/crypto/hisilicon/zip/zip_crypto.c | 5 +-
> drivers/crypto/hisilicon/zip/zip_main.c | 21 ++----
> drivers/dma/dmatest.c | 20 ++---
> drivers/edac/i10nm_base.c | 6 +-
> drivers/firmware/efi/efi-pstore.c | 6 +-
> drivers/firmware/qcom/qcom_scm.c | 6 +-
> drivers/firmware/qemu_fw_cfg.c | 6 +-
> drivers/gpu/drm/drm_panic.c | 6 +-
> drivers/gpu/drm/i915/i915_mitigations.c | 5 +-
> drivers/gpu/drm/imagination/pvr_fw_trace.c | 6 +-
> drivers/hid/hid-cougar.c | 6 +-
> drivers/hid/hid-steam.c | 6 +-
> drivers/infiniband/hw/hfi1/driver.c | 5 +-
> drivers/infiniband/ulp/iser/iscsi_iser.c | 6 +-
> drivers/infiniband/ulp/isert/ib_isert.c | 6 +-
> drivers/infiniband/ulp/srp/ib_srp.c | 5 +-
> drivers/input/misc/ati_remote2.c | 13 ++--
> drivers/input/mouse/psmouse-base.c | 6 +-
> drivers/media/usb/uvc/uvc_driver.c | 6 +-
> drivers/misc/lis3lv02d/lis3lv02d.c | 5 +-
> drivers/net/wireless/ath/wil6210/main.c | 10 +--
> drivers/nvme/host/multipath.c | 12 +--
> drivers/nvme/host/pci.c | 18 ++---
> drivers/nvme/target/rdma.c | 5 +-
> drivers/nvme/target/tcp.c | 5 +-
> drivers/platform/x86/acerhdf.c | 5 +-
> drivers/power/supply/bq27xxx_battery.c | 6 +-
> drivers/power/supply/test_power.c | 75 ++++++++-----------
> drivers/scsi/sg.c | 6 +-
> drivers/target/target_core_user.c | 13 ++--
> .../processor_thermal_soc_slider.c | 12 +--
> drivers/thermal/intel/intel_powerclamp.c | 20 +----
> drivers/tty/hvc/hvc_iucv.c | 6 +-
> drivers/tty/sysrq.c | 6 +-
> drivers/ufs/core/ufs-fault-injection.c | 5 +-
> drivers/ufs/core/ufs-mcq.c | 18 ++---
> drivers/ufs/core/ufs-txeq.c | 5 +-
> drivers/ufs/core/ufshcd.c | 12 +--
> drivers/usb/core/quirks.c | 6 +-
> drivers/usb/gadget/legacy/serial.c | 5 +-
> drivers/usb/storage/usb.c | 5 +-
> drivers/vhost/scsi.c | 7 +-
> drivers/virt/nitro_enclaves/ne_misc_dev.c | 6 +-
> drivers/virtio/virtio_mmio.c | 6 +-
> fs/ceph/super.c | 10 +--
> fs/fuse/dir.c | 5 +-
> fs/nfs/namespace.c | 6 +-
> fs/nfs/super.c | 6 +-
> fs/ubifs/super.c | 6 +-
> kernel/locking/locktorture.c | 6 +-
> kernel/panic.c | 6 +-
> kernel/params.c | 44 +++--------
> kernel/power/hibernate.c | 6 +-
> kernel/rcu/tree.c | 18 ++---
> kernel/sched/ext.c | 11 +--
> kernel/workqueue.c | 12 +--
> lib/dynamic_debug.c | 6 +-
> lib/test_dynamic_debug.c | 6 +-
> mm/damon/lru_sort.c | 19 ++---
> mm/damon/reclaim.c | 19 ++---
> mm/damon/stat.c | 6 +-
> mm/memory_hotplug.c | 12 +--
> mm/page_reporting.c | 11 +--
> mm/shuffle.c | 6 +-
> mm/zswap.c | 14 ++--
> net/batman-adv/bat_algo.c | 6 +-
> net/ceph/ceph_common.c | 5 +-
> net/ipv4/tcp_dctcp.c | 6 +-
> net/sunrpc/auth.c | 6 +-
> net/sunrpc/xprtsock.c | 18 ++---
> samples/damon/mtier.c | 6 +-
> samples/damon/prcl.c | 6 +-
> samples/damon/wsse.c | 6 +-
> security/apparmor/lsm.c | 34 +++------
> sound/hda/controllers/intel.c | 5 +-
> sound/usb/card.c | 7 +-
> 96 files changed, 307 insertions(+), 660 deletions(-)
>
> diff --git a/mm/kfence/core.c b/mm/kfence/core.c
> index 655dc5ce3240..e14102c01520 100644
> --- a/mm/kfence/core.c
> +++ b/mm/kfence/core.c
> @@ -92,10 +92,9 @@ static int param_get_sample_interval(char *buffer, const struct kernel_param *kp
> return param_get_ulong(buffer, kp);
> }
>
> -static const struct kernel_param_ops sample_interval_param_ops = {
> - .set = param_set_sample_interval,
> - .get = param_get_sample_interval,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(sample_interval_param_ops,
> + param_set_sample_interval,
> + param_get_sample_interval);
> module_param_cb(sample_interval, &sample_interval_param_ops, &kfence_sample_interval, 0600);
>
> /* Pool usage% threshold when currently covered allocations are skipped. */
> diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c
> index 61dbeea317f3..0c15f0426671 100644
> --- a/arch/powerpc/kvm/book3s_hv.c
> +++ b/arch/powerpc/kvm/book3s_hv.c
> @@ -114,10 +114,7 @@ module_param(one_vm_per_core, bool, S_IRUGO | S_IWUSR);
> MODULE_PARM_DESC(one_vm_per_core, "Only run vCPUs from the same VM on a core (requires POWER8 or older)");
>
> #ifdef CONFIG_KVM_XICS
> -static const struct kernel_param_ops module_param_ops = {
> - .set = param_set_int,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(module_param_ops, param_set_int, param_get_int);
>
> module_param_cb(kvm_irq_bypass, &module_param_ops, &kvm_irq_bypass, 0644);
> MODULE_PARM_DESC(kvm_irq_bypass, "Bypass passthrough interrupt optimization");
> diff --git a/arch/s390/kernel/perf_cpum_sf.c b/arch/s390/kernel/perf_cpum_sf.c
> index 7bfeb5208177..76119542562b 100644
> --- a/arch/s390/kernel/perf_cpum_sf.c
> +++ b/arch/s390/kernel/perf_cpum_sf.c
> @@ -2029,10 +2029,8 @@ static int param_set_sfb_size(const char *val, const struct kernel_param *kp)
> }
>
> #define param_check_sfb_size(name, p) __param_check(name, p, void)
> -static const struct kernel_param_ops param_ops_sfb_size = {
> - .set = param_set_sfb_size,
> - .get = param_get_sfb_size,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_sfb_size, param_set_sfb_size,
> + param_get_sfb_size);
>
> enum {
> RS_INIT_FAILURE_BSDES = 2, /* Bad basic sampling size */
> diff --git a/arch/um/drivers/vfio_kern.c b/arch/um/drivers/vfio_kern.c
> index e6dab473cde4..fb7988dc5482 100644
> --- a/arch/um/drivers/vfio_kern.c
> +++ b/arch/um/drivers/vfio_kern.c
> @@ -628,10 +628,8 @@ static int uml_vfio_cmdline_get(char *buffer, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops uml_vfio_cmdline_param_ops = {
> - .set = uml_vfio_cmdline_set,
> - .get = uml_vfio_cmdline_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(uml_vfio_cmdline_param_ops, uml_vfio_cmdline_set,
> + uml_vfio_cmdline_get);
>
> device_param_cb(device, ¨_vfio_cmdline_param_ops, NULL, 0400);
> __uml_help(uml_vfio_cmdline_param_ops,
> diff --git a/arch/um/drivers/virtio_uml.c b/arch/um/drivers/virtio_uml.c
> index 7425a8548141..f9ae745f4586 100644
> --- a/arch/um/drivers/virtio_uml.c
> +++ b/arch/um/drivers/virtio_uml.c
> @@ -1398,10 +1398,8 @@ static int vu_cmdline_get(char *buffer, const struct kernel_param *kp)
> return strlen(buffer) + 1;
> }
>
> -static const struct kernel_param_ops vu_cmdline_param_ops = {
> - .set = vu_cmdline_set,
> - .get = vu_cmdline_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(vu_cmdline_param_ops, vu_cmdline_set,
> + vu_cmdline_get);
>
> device_param_cb(device, &vu_cmdline_param_ops, NULL, S_IRUSR);
> __uml_help(vu_cmdline_param_ops,
> diff --git a/arch/x86/kernel/msr.c b/arch/x86/kernel/msr.c
> index 4469c784eaa0..5f4e1814dc4d 100644
> --- a/arch/x86/kernel/msr.c
> +++ b/arch/x86/kernel/msr.c
> @@ -322,10 +322,8 @@ static int get_allow_writes(char *buf, const struct kernel_param *kp)
> return sprintf(buf, "%s\n", res);
> }
>
> -static const struct kernel_param_ops allow_writes_ops = {
> - .set = set_allow_writes,
> - .get = get_allow_writes
> -};
> +static DEFINE_KERNEL_PARAM_OPS(allow_writes_ops, set_allow_writes,
> + get_allow_writes);
>
> module_param_cb(allow_writes, &allow_writes_ops, NULL, 0600);
>
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index 24fbc9ea502a..996818ee9b09 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
> @@ -74,15 +74,11 @@ static int get_nx_huge_pages(char *buffer, const struct kernel_param *kp);
> static int set_nx_huge_pages(const char *val, const struct kernel_param *kp);
> static int set_nx_huge_pages_recovery_param(const char *val, const struct kernel_param *kp);
>
> -static const struct kernel_param_ops nx_huge_pages_ops = {
> - .set = set_nx_huge_pages,
> - .get = get_nx_huge_pages,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(nx_huge_pages_ops, set_nx_huge_pages,
> + get_nx_huge_pages);
>
> -static const struct kernel_param_ops nx_huge_pages_recovery_param_ops = {
> - .set = set_nx_huge_pages_recovery_param,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(nx_huge_pages_recovery_param_ops,
> + set_nx_huge_pages_recovery_param, param_get_uint);
>
> module_param_cb(nx_huge_pages, &nx_huge_pages_ops, &nx_huge_pages, 0644);
> __MODULE_PARM_TYPE(nx_huge_pages, "bool");
> diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c
> index adf211860949..7907f9addff9 100644
> --- a/arch/x86/kvm/svm/avic.c
> +++ b/arch/x86/kvm/svm/avic.c
> @@ -87,11 +87,7 @@ static int avic_param_get(char *buffer, const struct kernel_param *kp)
> return param_get_bool(buffer, kp);
> }
>
> -static const struct kernel_param_ops avic_ops = {
> - .flags = KERNEL_PARAM_OPS_FL_NOARG,
> - .set = avic_param_set,
> - .get = avic_param_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS_NOARG(avic_ops, avic_param_set, avic_param_get);
>
> /*
> * Enable / disable AVIC. In "auto" mode (default behavior), AVIC is enabled
> diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
> index a29896a9ef14..07f4c7209ac0 100644
> --- a/arch/x86/kvm/vmx/vmx.c
> +++ b/arch/x86/kvm/vmx/vmx.c
> @@ -465,10 +465,8 @@ static int vmentry_l1d_flush_get(char *s, const struct kernel_param *kp)
> }
> #endif
>
> -static const struct kernel_param_ops vmentry_l1d_flush_ops = {
> - .set = vmentry_l1d_flush_set,
> - .get = vmentry_l1d_flush_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(vmentry_l1d_flush_ops, vmentry_l1d_flush_set,
> + vmentry_l1d_flush_get);
> module_param_cb(vmentry_l1d_flush, &vmentry_l1d_flush_ops, NULL, 0644);
>
> static __always_inline void vmx_disable_fb_clear(struct vcpu_vmx *vmx)
> diff --git a/arch/x86/platform/uv/uv_nmi.c b/arch/x86/platform/uv/uv_nmi.c
> index 5c50e550ab63..a7ac80b5f8d9 100644
> --- a/arch/x86/platform/uv/uv_nmi.c
> +++ b/arch/x86/platform/uv/uv_nmi.c
> @@ -123,10 +123,8 @@ static int param_set_local64(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops param_ops_local64 = {
> - .get = param_get_local64,
> - .set = param_set_local64,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_local64, param_set_local64,
> + param_get_local64);
> #define param_check_local64(name, p) __param_check(name, p, local64_t)
>
> static local64_t uv_nmi_count;
> @@ -232,10 +230,8 @@ static int param_set_action(const char *val, const struct kernel_param *kp)
> return -EINVAL;
> }
>
> -static const struct kernel_param_ops param_ops_action = {
> - .get = param_get_action,
> - .set = param_set_action,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_action, param_set_action,
> + param_get_action);
> #define param_check_action(name, p) __param_check(name, p, enum action_t)
>
> module_param_named(action, uv_nmi_action, action, 0644);
> diff --git a/block/disk-events.c b/block/disk-events.c
> index 074731ecc3d2..f2d4b48294c6 100644
> --- a/block/disk-events.c
> +++ b/block/disk-events.c
> @@ -416,10 +416,8 @@ static int disk_events_set_dfl_poll_msecs(const char *val,
> return 0;
> }
>
> -static const struct kernel_param_ops disk_events_dfl_poll_msecs_param_ops = {
> - .set = disk_events_set_dfl_poll_msecs,
> - .get = param_get_ulong,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(disk_events_dfl_poll_msecs_param_ops,
> + disk_events_set_dfl_poll_msecs, param_get_ulong);
>
> #undef MODULE_PARAM_PREFIX
> #define MODULE_PARAM_PREFIX "block."
> diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c
> index a625de3c3c8b..3d32a5280432 100644
> --- a/drivers/acpi/sysfs.c
> +++ b/drivers/acpi/sysfs.c
> @@ -138,15 +138,11 @@ static int param_get_debug_level(char *buffer, const struct kernel_param *kp)
> return result;
> }
>
> -static const struct kernel_param_ops param_ops_debug_layer = {
> - .set = param_set_uint,
> - .get = param_get_debug_layer,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_debug_layer, param_set_uint,
> + param_get_debug_layer);
>
> -static const struct kernel_param_ops param_ops_debug_level = {
> - .set = param_set_uint,
> - .get = param_get_debug_level,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_debug_level, param_set_uint,
> + param_get_debug_level);
>
> module_param_cb(debug_layer, ¶m_ops_debug_layer, &acpi_dbg_layer, 0644);
> module_param_cb(debug_level, ¶m_ops_debug_level, &acpi_dbg_level, 0644);
> @@ -201,15 +197,12 @@ static int param_get_trace_method_name(char *buffer, const struct kernel_param *
> return sysfs_emit(buffer, "%s\n", acpi_gbl_trace_method_name);
> }
>
> -static const struct kernel_param_ops param_ops_trace_method = {
> - .set = param_set_trace_method_name,
> - .get = param_get_trace_method_name,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_trace_method,
> + param_set_trace_method_name,
> + param_get_trace_method_name);
>
> -static const struct kernel_param_ops param_ops_trace_attrib = {
> - .set = param_set_uint,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_trace_attrib, param_set_uint,
> + param_get_uint);
>
> module_param_cb(trace_method_name, ¶m_ops_trace_method, &trace_method_name, 0644);
> module_param_cb(trace_debug_layer, ¶m_ops_trace_attrib, &acpi_gbl_trace_dbg_layer, 0644);
> diff --git a/drivers/block/loop.c b/drivers/block/loop.c
> index 0000913f7efc..147ad561e584 100644
> --- a/drivers/block/loop.c
> +++ b/drivers/block/loop.c
> @@ -1806,10 +1806,8 @@ static int max_loop_param_set_int(const char *val,
> return 0;
> }
>
> -static const struct kernel_param_ops max_loop_param_ops = {
> - .set = max_loop_param_set_int,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(max_loop_param_ops, max_loop_param_set_int,
> + param_get_int);
>
> module_param_cb(max_loop, &max_loop_param_ops, &max_loop, 0444);
> MODULE_PARM_DESC(max_loop, "Maximum number of loop devices");
> @@ -1836,10 +1834,8 @@ static int loop_set_hw_queue_depth(const char *s, const struct kernel_param *p)
> return 0;
> }
>
> -static const struct kernel_param_ops loop_hw_qdepth_param_ops = {
> - .set = loop_set_hw_queue_depth,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(loop_hw_qdepth_param_ops,
> + loop_set_hw_queue_depth, param_get_int);
>
> device_param_cb(hw_queue_depth, &loop_hw_qdepth_param_ops, &hw_queue_depth, 0444);
> MODULE_PARM_DESC(hw_queue_depth, "Queue depth for each hardware queue. Default: " __stringify(LOOP_DEFAULT_HW_Q_DEPTH));
> diff --git a/drivers/block/null_blk/main.c b/drivers/block/null_blk/main.c
> index f8c0fd57e041..332ad6ac838a 100644
> --- a/drivers/block/null_blk/main.c
> +++ b/drivers/block/null_blk/main.c
> @@ -149,10 +149,8 @@ static int null_set_queue_mode(const char *str, const struct kernel_param *kp)
> return null_param_store_val(str, &g_queue_mode, NULL_Q_BIO, NULL_Q_MQ);
> }
>
> -static const struct kernel_param_ops null_queue_mode_param_ops = {
> - .set = null_set_queue_mode,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(null_queue_mode_param_ops, null_set_queue_mode,
> + param_get_int);
>
> device_param_cb(queue_mode, &null_queue_mode_param_ops, &g_queue_mode, 0444);
> MODULE_PARM_DESC(queue_mode, "Block interface to use (0=bio,1=rq,2=multiqueue)");
> @@ -193,10 +191,8 @@ static int null_set_irqmode(const char *str, const struct kernel_param *kp)
> NULL_IRQ_TIMER);
> }
>
> -static const struct kernel_param_ops null_irqmode_param_ops = {
> - .set = null_set_irqmode,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(null_irqmode_param_ops, null_set_irqmode,
> + param_get_int);
>
> device_param_cb(irqmode, &null_irqmode_param_ops, &g_irqmode, 0444);
> MODULE_PARM_DESC(irqmode, "IRQ completion handler. 0-none, 1-softirq, 2-timer");
> diff --git a/drivers/block/rnbd/rnbd-srv.c b/drivers/block/rnbd/rnbd-srv.c
> index 10e8c438bb43..81961f7c883a 100644
> --- a/drivers/block/rnbd/rnbd-srv.c
> +++ b/drivers/block/rnbd/rnbd-srv.c
> @@ -49,10 +49,8 @@ static struct kparam_string dev_search_path_kparam_str = {
> .string = dev_search_path
> };
>
> -static const struct kernel_param_ops dev_search_path_ops = {
> - .set = dev_search_path_set,
> - .get = param_get_string,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(dev_search_path_ops, dev_search_path_set,
> + param_get_string);
>
> module_param_cb(dev_search_path, &dev_search_path_ops,
> &dev_search_path_kparam_str, 0444);
> diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
> index 8e5f3738c203..f7bf7ea2d088 100644
> --- a/drivers/block/ublk_drv.c
> +++ b/drivers/block/ublk_drv.c
> @@ -5874,10 +5874,9 @@ static int ublk_get_max_unprivileged_ublks(char *buf,
> return sysfs_emit(buf, "%u\n", unprivileged_ublks_max);
> }
>
> -static const struct kernel_param_ops ublk_max_unprivileged_ublks_ops = {
> - .set = ublk_set_max_unprivileged_ublks,
> - .get = ublk_get_max_unprivileged_ublks,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(ublk_max_unprivileged_ublks_ops,
> + ublk_set_max_unprivileged_ublks,
> + ublk_get_max_unprivileged_ublks);
>
> module_param_cb(ublks_max, &ublk_max_unprivileged_ublks_ops,
> &unprivileged_ublks_max, 0644);
> diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c
> index 869ac87a4b6a..b5fed11707e8 100644
> --- a/drivers/char/ipmi/ipmi_msghandler.c
> +++ b/drivers/char/ipmi/ipmi_msghandler.c
> @@ -102,10 +102,8 @@ static int panic_op_read_handler(char *buffer, const struct kernel_param *kp)
> return sprintf(buffer, "%s\n", event_str);
> }
>
> -static const struct kernel_param_ops panic_op_ops = {
> - .set = panic_op_write_handler,
> - .get = panic_op_read_handler
> -};
> +static DEFINE_KERNEL_PARAM_OPS(panic_op_ops, panic_op_write_handler,
> + panic_op_read_handler);
> module_param_cb(panic_op, &panic_op_ops, NULL, 0600);
> MODULE_PARM_DESC(panic_op, "Sets if the IPMI driver will attempt to store panic information in the event log in the event of a panic. Set to 'none' for no, 'event' for a single event, or 'string' for a generic event and the panic string in IPMI OEM events.");
>
> diff --git a/drivers/char/ipmi/ipmi_watchdog.c b/drivers/char/ipmi/ipmi_watchdog.c
> index a013ddbf1466..91a99417d204 100644
> --- a/drivers/char/ipmi/ipmi_watchdog.c
> +++ b/drivers/char/ipmi/ipmi_watchdog.c
> @@ -193,10 +193,8 @@ static int set_param_timeout(const char *val, const struct kernel_param *kp)
> return rv;
> }
>
> -static const struct kernel_param_ops param_ops_timeout = {
> - .set = set_param_timeout,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_timeout, set_param_timeout,
> + param_get_int);
> #define param_check_timeout param_check_int
>
> typedef int (*action_fn)(const char *intval, char *outval);
> @@ -259,17 +257,12 @@ static int set_param_wdog_ifnum(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops param_ops_wdog_ifnum = {
> - .set = set_param_wdog_ifnum,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_wdog_ifnum, set_param_wdog_ifnum,
> + param_get_int);
>
> #define param_check_wdog_ifnum param_check_int
>
> -static const struct kernel_param_ops param_ops_str = {
> - .set = set_param_str,
> - .get = get_param_str,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_str, set_param_str, get_param_str);
>
> module_param(ifnum_to_use, wdog_ifnum, 0644);
> MODULE_PARM_DESC(ifnum_to_use, "The interface number to use for the watchdog "
> diff --git a/drivers/crypto/hisilicon/hpre/hpre_main.c b/drivers/crypto/hisilicon/hpre/hpre_main.c
> index 357ab5e5887e..1104184c2f63 100644
> --- a/drivers/crypto/hisilicon/hpre/hpre_main.c
> +++ b/drivers/crypto/hisilicon/hpre/hpre_main.c
> @@ -420,10 +420,8 @@ static int hpre_cluster_regs_show(struct seq_file *s, void *unused)
>
> DEFINE_SHOW_ATTRIBUTE(hpre_cluster_regs);
>
> -static const struct kernel_param_ops hpre_uacce_mode_ops = {
> - .set = uacce_mode_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(hpre_uacce_mode_ops, uacce_mode_set,
> + param_get_int);
>
> /*
> * uacce_mode = 0 means hpre only register to crypto,
> @@ -441,19 +439,13 @@ static int pf_q_num_set(const char *val, const struct kernel_param *kp)
> return hisi_qm_q_num_set(val, kp, PCI_DEVICE_ID_HUAWEI_HPRE_PF);
> }
>
> -static const struct kernel_param_ops hpre_pf_q_num_ops = {
> - .set = pf_q_num_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(hpre_pf_q_num_ops, pf_q_num_set, param_get_int);
>
> static u32 pf_q_num = HPRE_PF_DEF_Q_NUM;
> module_param_cb(pf_q_num, &hpre_pf_q_num_ops, &pf_q_num, 0444);
> MODULE_PARM_DESC(pf_q_num, "Number of queues in PF of CS(2-1024)");
>
> -static const struct kernel_param_ops vfs_num_ops = {
> - .set = vfs_num_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(vfs_num_ops, vfs_num_set, param_get_int);
>
> static u32 vfs_num;
> module_param_cb(vfs_num, &vfs_num_ops, &vfs_num, 0444);
> diff --git a/drivers/crypto/hisilicon/sec2/sec_main.c b/drivers/crypto/hisilicon/sec2/sec_main.c
> index 056bd8f4da5a..3d13f2faa3d0 100644
> --- a/drivers/crypto/hisilicon/sec2/sec_main.c
> +++ b/drivers/crypto/hisilicon/sec2/sec_main.c
> @@ -362,10 +362,8 @@ static int sec_pf_q_num_set(const char *val, const struct kernel_param *kp)
> return hisi_qm_q_num_set(val, kp, PCI_DEVICE_ID_HUAWEI_SEC_PF);
> }
>
> -static const struct kernel_param_ops sec_pf_q_num_ops = {
> - .set = sec_pf_q_num_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(sec_pf_q_num_ops, sec_pf_q_num_set,
> + param_get_int);
>
> static u32 pf_q_num = SEC_PF_DEF_Q_NUM;
> module_param_cb(pf_q_num, &sec_pf_q_num_ops, &pf_q_num, 0444);
> @@ -391,18 +389,13 @@ static int sec_ctx_q_num_set(const char *val, const struct kernel_param *kp)
> return param_set_int(val, kp);
> }
>
> -static const struct kernel_param_ops sec_ctx_q_num_ops = {
> - .set = sec_ctx_q_num_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(sec_ctx_q_num_ops, sec_ctx_q_num_set,
> + param_get_int);
> static u32 ctx_q_num = SEC_CTX_Q_NUM_DEF;
> module_param_cb(ctx_q_num, &sec_ctx_q_num_ops, &ctx_q_num, 0444);
> MODULE_PARM_DESC(ctx_q_num, "Queue num in ctx (2 default, 2, 4, ..., 32)");
>
> -static const struct kernel_param_ops vfs_num_ops = {
> - .set = vfs_num_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(vfs_num_ops, vfs_num_set, param_get_int);
>
> static u32 vfs_num;
> module_param_cb(vfs_num, &vfs_num_ops, &vfs_num, 0444);
> @@ -454,10 +447,8 @@ u64 sec_get_alg_bitmap(struct hisi_qm *qm, u32 high, u32 low)
> return ((u64)cap_val_h << SEC_ALG_BITMAP_SHIFT) | (u64)cap_val_l;
> }
>
> -static const struct kernel_param_ops sec_uacce_mode_ops = {
> - .set = uacce_mode_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(sec_uacce_mode_ops, uacce_mode_set,
> + param_get_int);
>
> /*
> * uacce_mode = 0 means sec only register to crypto,
> diff --git a/drivers/crypto/hisilicon/zip/zip_crypto.c b/drivers/crypto/hisilicon/zip/zip_crypto.c
> index 70adde049b53..8f8d57420fb4 100644
> --- a/drivers/crypto/hisilicon/zip/zip_crypto.c
> +++ b/drivers/crypto/hisilicon/zip/zip_crypto.c
> @@ -108,10 +108,7 @@ static int sgl_sge_nr_set(const char *val, const struct kernel_param *kp)
> return param_set_ushort(val, kp);
> }
>
> -static const struct kernel_param_ops sgl_sge_nr_ops = {
> - .set = sgl_sge_nr_set,
> - .get = param_get_ushort,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(sgl_sge_nr_ops, sgl_sge_nr_set, param_get_ushort);
>
> static u16 sgl_sge_nr = HZIP_SGL_SGE_NR;
> module_param_cb(sgl_sge_nr, &sgl_sge_nr_ops, &sgl_sge_nr, 0444);
> diff --git a/drivers/crypto/hisilicon/zip/zip_main.c b/drivers/crypto/hisilicon/zip/zip_main.c
> index 44df9c859bd8..e07d0cf8eca8 100644
> --- a/drivers/crypto/hisilicon/zip/zip_main.c
> +++ b/drivers/crypto/hisilicon/zip/zip_main.c
> @@ -394,10 +394,7 @@ static int perf_mode_set(const char *val, const struct kernel_param *kp)
> return param_set_int(val, kp);
> }
>
> -static const struct kernel_param_ops zip_com_perf_ops = {
> - .set = perf_mode_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(zip_com_perf_ops, perf_mode_set, param_get_int);
>
> /*
> * perf_mode = 0 means enable high compression rate mode,
> @@ -408,10 +405,8 @@ static u32 perf_mode = HZIP_HIGH_COMP_RATE;
> module_param_cb(perf_mode, &zip_com_perf_ops, &perf_mode, 0444);
> MODULE_PARM_DESC(perf_mode, "ZIP high perf mode 0(default), 1(enable)");
>
> -static const struct kernel_param_ops zip_uacce_mode_ops = {
> - .set = uacce_mode_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(zip_uacce_mode_ops, uacce_mode_set,
> + param_get_int);
>
> /*
> * uacce_mode = 0 means zip only register to crypto,
> @@ -429,19 +424,13 @@ static int pf_q_num_set(const char *val, const struct kernel_param *kp)
> return hisi_qm_q_num_set(val, kp, PCI_DEVICE_ID_HUAWEI_ZIP_PF);
> }
>
> -static const struct kernel_param_ops pf_q_num_ops = {
> - .set = pf_q_num_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(pf_q_num_ops, pf_q_num_set, param_get_int);
>
> static u32 pf_q_num = HZIP_PF_DEF_Q_NUM;
> module_param_cb(pf_q_num, &pf_q_num_ops, &pf_q_num, 0444);
> MODULE_PARM_DESC(pf_q_num, "Number of queues in PF(v1 2-4096, v2 2-1024)");
>
> -static const struct kernel_param_ops vfs_num_ops = {
> - .set = vfs_num_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(vfs_num_ops, vfs_num_set, param_get_int);
>
> static u32 vfs_num;
> module_param_cb(vfs_num, &vfs_num_ops, &vfs_num, 0444);
> diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c
> index df38681a1ff4..a7bddadcc52d 100644
> --- a/drivers/dma/dmatest.c
> +++ b/drivers/dma/dmatest.c
> @@ -154,20 +154,15 @@ static struct dmatest_info {
>
> static int dmatest_run_set(const char *val, const struct kernel_param *kp);
> static int dmatest_run_get(char *val, const struct kernel_param *kp);
> -static const struct kernel_param_ops run_ops = {
> - .set = dmatest_run_set,
> - .get = dmatest_run_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(run_ops, dmatest_run_set, dmatest_run_get);
> static bool dmatest_run;
> module_param_cb(run, &run_ops, &dmatest_run, 0644);
> MODULE_PARM_DESC(run, "Run the test (default: false)");
>
> static int dmatest_chan_set(const char *val, const struct kernel_param *kp);
> static int dmatest_chan_get(char *val, const struct kernel_param *kp);
> -static const struct kernel_param_ops multi_chan_ops = {
> - .set = dmatest_chan_set,
> - .get = dmatest_chan_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(multi_chan_ops, dmatest_chan_set,
> + dmatest_chan_get);
>
> static char test_channel[20];
> static struct kparam_string newchan_kps = {
> @@ -178,9 +173,7 @@ module_param_cb(channel, &multi_chan_ops, &newchan_kps, 0644);
> MODULE_PARM_DESC(channel, "Bus ID of the channel to test (default: any)");
>
> static int dmatest_test_list_get(char *val, const struct kernel_param *kp);
> -static const struct kernel_param_ops test_list_ops = {
> - .get = dmatest_test_list_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(test_list_ops, NULL, dmatest_test_list_get);
> module_param_cb(test_list, &test_list_ops, NULL, 0444);
> MODULE_PARM_DESC(test_list, "Print current test list");
>
> @@ -292,10 +285,7 @@ static int dmatest_wait_get(char *val, const struct kernel_param *kp)
> return param_get_bool(val, kp);
> }
>
> -static const struct kernel_param_ops wait_ops = {
> - .get = dmatest_wait_get,
> - .set = param_set_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(wait_ops, param_set_bool, dmatest_wait_get);
> module_param_cb(wait, &wait_ops, &wait, 0444);
> MODULE_PARM_DESC(wait, "Wait for tests to complete (default: false)");
>
> diff --git a/drivers/edac/i10nm_base.c b/drivers/edac/i10nm_base.c
> index 63df35444214..ab05e58faaa8 100644
> --- a/drivers/edac/i10nm_base.c
> +++ b/drivers/edac/i10nm_base.c
> @@ -1284,10 +1284,8 @@ static int set_decoding_via_mca(const char *buf, const struct kernel_param *kp)
> return ret;
> }
>
> -static const struct kernel_param_ops decoding_via_mca_param_ops = {
> - .set = set_decoding_via_mca,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(decoding_via_mca_param_ops, set_decoding_via_mca,
> + param_get_int);
>
> module_param_cb(decoding_via_mca, &decoding_via_mca_param_ops, &decoding_via_mca, 0644);
> MODULE_PARM_DESC(decoding_via_mca, "decoding_via_mca: 0=off(default), 1=enable");
> diff --git a/drivers/firmware/efi/efi-pstore.c b/drivers/firmware/efi/efi-pstore.c
> index a253b6144945..fd0f3579fd54 100644
> --- a/drivers/firmware/efi/efi-pstore.c
> +++ b/drivers/firmware/efi/efi-pstore.c
> @@ -43,10 +43,8 @@ static int efi_pstore_disable_set(const char *val, const struct kernel_param *kp
> return 0;
> }
>
> -static const struct kernel_param_ops pstore_disable_ops = {
> - .set = efi_pstore_disable_set,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(pstore_disable_ops, efi_pstore_disable_set,
> + param_get_bool);
>
> module_param_cb(pstore_disable, &pstore_disable_ops, &pstore_disable, 0644);
> __MODULE_PARM_TYPE(pstore_disable, "bool");
> diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
> index 9b06a69d3a6d..ef57df53e087 100644
> --- a/drivers/firmware/qcom/qcom_scm.c
> +++ b/drivers/firmware/qcom/qcom_scm.c
> @@ -2725,10 +2725,8 @@ static int set_download_mode(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops download_mode_param_ops = {
> - .get = get_download_mode,
> - .set = set_download_mode,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(download_mode_param_ops, set_download_mode,
> + get_download_mode);
>
> module_param_cb(download_mode, &download_mode_param_ops, NULL, 0644);
> MODULE_PARM_DESC(download_mode, "download mode: off/0/N for no dump mode, full/on/1/Y for full dump mode, mini for minidump mode and full,mini for both full and minidump mode together are acceptable values");
> diff --git a/drivers/firmware/qemu_fw_cfg.c b/drivers/firmware/qemu_fw_cfg.c
> index 87a5421bc7d5..c87a5449ba8c 100644
> --- a/drivers/firmware/qemu_fw_cfg.c
> +++ b/drivers/firmware/qemu_fw_cfg.c
> @@ -897,10 +897,8 @@ static int fw_cfg_cmdline_get(char *buf, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops fw_cfg_cmdline_param_ops = {
> - .set = fw_cfg_cmdline_set,
> - .get = fw_cfg_cmdline_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(fw_cfg_cmdline_param_ops, fw_cfg_cmdline_set,
> + fw_cfg_cmdline_get);
>
> device_param_cb(ioport, &fw_cfg_cmdline_param_ops, NULL, S_IRUSR);
> device_param_cb(mmio, &fw_cfg_cmdline_param_ops, NULL, S_IRUSR);
> diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c
> index d6d3b8d85dea..c35d1adf2ce3 100644
> --- a/drivers/gpu/drm/drm_panic.c
> +++ b/drivers/gpu/drm/drm_panic.c
> @@ -847,10 +847,8 @@ static int drm_panic_type_get(char *buffer, const struct kernel_param *kp)
> drm_panic_type_map[drm_panic_type]);
> }
>
> -static const struct kernel_param_ops drm_panic_ops = {
> - .set = drm_panic_type_set,
> - .get = drm_panic_type_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(drm_panic_ops, drm_panic_type_set,
> + drm_panic_type_get);
>
> module_param_cb(panic_screen, &drm_panic_ops, NULL, 0644);
> MODULE_PARM_DESC(panic_screen,
> diff --git a/drivers/gpu/drm/i915/i915_mitigations.c b/drivers/gpu/drm/i915/i915_mitigations.c
> index def7302ef7fe..6061eae84e9c 100644
> --- a/drivers/gpu/drm/i915/i915_mitigations.c
> +++ b/drivers/gpu/drm/i915/i915_mitigations.c
> @@ -124,10 +124,7 @@ static int mitigations_get(char *buffer, const struct kernel_param *kp)
> return count;
> }
>
> -static const struct kernel_param_ops ops = {
> - .set = mitigations_set,
> - .get = mitigations_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(ops, mitigations_set, mitigations_get);
>
> module_param_cb_unsafe(mitigations, &ops, NULL, 0600);
> MODULE_PARM_DESC(mitigations,
> diff --git a/drivers/gpu/drm/imagination/pvr_fw_trace.c b/drivers/gpu/drm/imagination/pvr_fw_trace.c
> index 6193811ef7be..2df7274e21a8 100644
> --- a/drivers/gpu/drm/imagination/pvr_fw_trace.c
> +++ b/drivers/gpu/drm/imagination/pvr_fw_trace.c
> @@ -71,10 +71,8 @@ pvr_fw_trace_init_mask_set(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -const struct kernel_param_ops pvr_fw_trace_init_mask_ops = {
> - .set = pvr_fw_trace_init_mask_set,
> - .get = param_get_hexint,
> -};
> +DEFINE_KERNEL_PARAM_OPS(pvr_fw_trace_init_mask_ops, pvr_fw_trace_init_mask_set,
> + param_get_hexint);
>
> param_check_hexint(init_fw_trace_mask, &pvr_fw_trace_init_mask);
> module_param_cb(init_fw_trace_mask, &pvr_fw_trace_init_mask_ops, &pvr_fw_trace_init_mask, 0600);
> diff --git a/drivers/hid/hid-cougar.c b/drivers/hid/hid-cougar.c
> index ad027c45f162..271048bdf7fc 100644
> --- a/drivers/hid/hid-cougar.c
> +++ b/drivers/hid/hid-cougar.c
> @@ -314,10 +314,8 @@ static int cougar_param_set_g6_is_space(const char *val,
> return 0;
> }
>
> -static const struct kernel_param_ops cougar_g6_is_space_ops = {
> - .set = cougar_param_set_g6_is_space,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(cougar_g6_is_space_ops,
> + cougar_param_set_g6_is_space, param_get_bool);
> module_param_cb(g6_is_space, &cougar_g6_is_space_ops, &g6_is_space, 0644);
>
> static const struct hid_device_id cougar_id_table[] = {
> diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
> index 197126d6e081..962ed60492ea 100644
> --- a/drivers/hid/hid-steam.c
> +++ b/drivers/hid/hid-steam.c
> @@ -1840,10 +1840,8 @@ static int steam_param_set_lizard_mode(const char *val,
> return 0;
> }
>
> -static const struct kernel_param_ops steam_lizard_mode_ops = {
> - .set = steam_param_set_lizard_mode,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(steam_lizard_mode_ops,
> + steam_param_set_lizard_mode, param_get_bool);
>
> module_param_cb(lizard_mode, &steam_lizard_mode_ops, &lizard_mode, 0644);
> MODULE_PARM_DESC(lizard_mode,
> diff --git a/drivers/infiniband/hw/hfi1/driver.c b/drivers/infiniband/hw/hfi1/driver.c
> index c7259cc39013..5b9b0b38b419 100644
> --- a/drivers/infiniband/hw/hfi1/driver.c
> +++ b/drivers/infiniband/hw/hfi1/driver.c
> @@ -42,10 +42,7 @@ MODULE_PARM_DESC(cu, "Credit return units");
> unsigned long hfi1_cap_mask = HFI1_CAP_MASK_DEFAULT;
> static int hfi1_caps_set(const char *val, const struct kernel_param *kp);
> static int hfi1_caps_get(char *buffer, const struct kernel_param *kp);
> -static const struct kernel_param_ops cap_ops = {
> - .set = hfi1_caps_set,
> - .get = hfi1_caps_get
> -};
> +static DEFINE_KERNEL_PARAM_OPS(cap_ops, hfi1_caps_set, hfi1_caps_get);
> module_param_cb(cap_mask, &cap_ops, &hfi1_cap_mask, S_IWUSR | S_IRUGO);
> MODULE_PARM_DESC(cap_mask, "Bit mask of enabled/disabled HW features");
>
> diff --git a/drivers/infiniband/ulp/iser/iscsi_iser.c b/drivers/infiniband/ulp/iser/iscsi_iser.c
> index 7df441685780..758e527ca7c4 100644
> --- a/drivers/infiniband/ulp/iser/iscsi_iser.c
> +++ b/drivers/infiniband/ulp/iser/iscsi_iser.c
> @@ -90,10 +90,8 @@ module_param_named(debug_level, iser_debug_level, int, S_IRUGO | S_IWUSR);
> MODULE_PARM_DESC(debug_level, "Enable debug tracing if > 0 (default:disabled)");
>
> static int iscsi_iser_set(const char *val, const struct kernel_param *kp);
> -static const struct kernel_param_ops iscsi_iser_size_ops = {
> - .set = iscsi_iser_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(iscsi_iser_size_ops, iscsi_iser_set,
> + param_get_uint);
>
> static unsigned int iscsi_max_lun = 512;
> module_param_cb(max_lun, &iscsi_iser_size_ops, &iscsi_max_lun, S_IRUGO);
> diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c
> index 348005e71891..bd91900a0ebf 100644
> --- a/drivers/infiniband/ulp/isert/ib_isert.c
> +++ b/drivers/infiniband/ulp/isert/ib_isert.c
> @@ -30,10 +30,8 @@ MODULE_PARM_DESC(debug_level, "Enable debug tracing if > 0 (default:0)");
>
> static int isert_sg_tablesize_set(const char *val,
> const struct kernel_param *kp);
> -static const struct kernel_param_ops sg_tablesize_ops = {
> - .set = isert_sg_tablesize_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(sg_tablesize_ops, isert_sg_tablesize_set,
> + param_get_int);
>
> static int isert_sg_tablesize = ISCSI_ISER_MIN_SG_TABLESIZE;
> module_param_cb(sg_tablesize, &sg_tablesize_ops, &isert_sg_tablesize, 0644);
> diff --git a/drivers/infiniband/ulp/srp/ib_srp.c b/drivers/infiniband/ulp/srp/ib_srp.c
> index b58868e1cf11..a81515f52a4f 100644
> --- a/drivers/infiniband/ulp/srp/ib_srp.c
> +++ b/drivers/infiniband/ulp/srp/ib_srp.c
> @@ -195,10 +195,7 @@ static int srp_tmo_set(const char *val, const struct kernel_param *kp)
> return res;
> }
>
> -static const struct kernel_param_ops srp_tmo_ops = {
> - .get = srp_tmo_get,
> - .set = srp_tmo_set,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(srp_tmo_ops, srp_tmo_set, srp_tmo_get);
>
> static inline struct srp_target_port *host_to_target(struct Scsi_Host *host)
> {
> diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c
> index 8db2dca84975..8b4ef7e163d3 100644
> --- a/drivers/input/misc/ati_remote2.c
> +++ b/drivers/input/misc/ati_remote2.c
> @@ -89,19 +89,16 @@ static int ati_remote2_get_mode_mask(char *buffer,
>
> static unsigned int channel_mask = ATI_REMOTE2_MAX_CHANNEL_MASK;
> #define param_check_channel_mask(name, p) __param_check(name, p, unsigned int)
> -static const struct kernel_param_ops param_ops_channel_mask = {
> - .set = ati_remote2_set_channel_mask,
> - .get = ati_remote2_get_channel_mask,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_channel_mask,
> + ati_remote2_set_channel_mask,
> + ati_remote2_get_channel_mask);
> module_param(channel_mask, channel_mask, 0644);
> MODULE_PARM_DESC(channel_mask, "Bitmask of channels to accept <15:Channel16>...<1:Channel2><0:Channel1>");
>
> static unsigned int mode_mask = ATI_REMOTE2_MAX_MODE_MASK;
> #define param_check_mode_mask(name, p) __param_check(name, p, unsigned int)
> -static const struct kernel_param_ops param_ops_mode_mask = {
> - .set = ati_remote2_set_mode_mask,
> - .get = ati_remote2_get_mode_mask,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_mode_mask, ati_remote2_set_mode_mask,
> + ati_remote2_get_mode_mask);
> module_param(mode_mask, mode_mask, 0644);
> MODULE_PARM_DESC(mode_mask, "Bitmask of modes to accept <4:PC><3:AUX4><2:AUX3><1:AUX2><0:AUX1>");
>
> diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
> index 6ab5f1d96eae..f9ebb1fd0b6f 100644
> --- a/drivers/input/mouse/psmouse-base.c
> +++ b/drivers/input/mouse/psmouse-base.c
> @@ -45,10 +45,8 @@ MODULE_LICENSE("GPL");
> static unsigned int psmouse_max_proto = PSMOUSE_AUTO;
> static int psmouse_set_maxproto(const char *val, const struct kernel_param *);
> static int psmouse_get_maxproto(char *buffer, const struct kernel_param *kp);
> -static const struct kernel_param_ops param_ops_proto_abbrev = {
> - .set = psmouse_set_maxproto,
> - .get = psmouse_get_maxproto,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_proto_abbrev, psmouse_set_maxproto,
> + psmouse_get_maxproto);
> #define param_check_proto_abbrev(name, p) __param_check(name, p, unsigned int)
> module_param_named(proto, psmouse_max_proto, proto_abbrev, 0644);
> MODULE_PARM_DESC(proto, "Highest protocol extension to probe (bare, imps, exps, any). Useful for KVM switches.");
> diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
> index 31b4ac3b48c1..2338cab7fef9 100644
> --- a/drivers/media/usb/uvc/uvc_driver.c
> +++ b/drivers/media/usb/uvc/uvc_driver.c
> @@ -2488,10 +2488,8 @@ static int param_set_nodrop(const char *val, const struct kernel_param *kp)
> return param_set_bool(val, kp);
> }
>
> -static const struct kernel_param_ops param_ops_nodrop = {
> - .set = param_set_nodrop,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_nodrop, param_set_nodrop,
> + param_get_uint);
>
> param_check_uint(nodrop, &uvc_no_drop_param);
> module_param_cb(nodrop, ¶m_ops_nodrop, &uvc_no_drop_param, 0644);
> diff --git a/drivers/misc/lis3lv02d/lis3lv02d.c b/drivers/misc/lis3lv02d/lis3lv02d.c
> index 21e8ad0a7444..6e40c14be51e 100644
> --- a/drivers/misc/lis3lv02d/lis3lv02d.c
> +++ b/drivers/misc/lis3lv02d/lis3lv02d.c
> @@ -103,10 +103,7 @@ static int param_set_axis(const char *val, const struct kernel_param *kp)
> return ret;
> }
>
> -static const struct kernel_param_ops param_ops_axis = {
> - .set = param_set_axis,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_axis, param_set_axis, param_get_int);
>
> #define param_check_axis(name, p) param_check_int(name, p)
>
> diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c
> index d5aec72ecdce..e566d94d5f5c 100644
> --- a/drivers/net/wireless/ath/wil6210/main.c
> +++ b/drivers/net/wireless/ath/wil6210/main.c
> @@ -62,10 +62,7 @@ static int mtu_max_set(const char *val, const struct kernel_param *kp)
> return ret;
> }
>
> -static const struct kernel_param_ops mtu_max_ops = {
> - .set = mtu_max_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(mtu_max_ops, mtu_max_set, param_get_uint);
>
> module_param_cb(mtu_max, &mtu_max_ops, &mtu_max, 0444);
> MODULE_PARM_DESC(mtu_max, " Max MTU value.");
> @@ -91,10 +88,7 @@ static int ring_order_set(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops ring_order_ops = {
> - .set = ring_order_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(ring_order_ops, ring_order_set, param_get_uint);
>
> module_param_cb(rx_ring_order, &ring_order_ops, &rx_ring_order, 0444);
> MODULE_PARM_DESC(rx_ring_order, " Rx ring order; size = 1 << order");
> diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c
> index 263161cb8ac0..f7362377e427 100644
> --- a/drivers/nvme/host/multipath.c
> +++ b/drivers/nvme/host/multipath.c
> @@ -30,10 +30,8 @@ static int multipath_param_set(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops multipath_param_ops = {
> - .set = multipath_param_set,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(multipath_param_ops, multipath_param_set,
> + param_get_bool);
>
> module_param_cb(multipath, &multipath_param_ops, &multipath, 0444);
> MODULE_PARM_DESC(multipath,
> @@ -55,10 +53,8 @@ static int multipath_always_on_set(const char *val,
> return 0;
> }
>
> -static const struct kernel_param_ops multipath_always_on_ops = {
> - .set = multipath_always_on_set,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(multipath_always_on_ops, multipath_always_on_set,
> + param_get_bool);
>
> module_param_cb(multipath_always_on, &multipath_always_on_ops,
> &multipath_always_on, 0444);
> diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c
> index 9fd04cd7c5cb..c77e9b848d03 100644
> --- a/drivers/nvme/host/pci.c
> +++ b/drivers/nvme/host/pci.c
> @@ -100,10 +100,8 @@ MODULE_PARM_DESC(sgl_threshold,
> #define NVME_PCI_MIN_QUEUE_SIZE 2
> #define NVME_PCI_MAX_QUEUE_SIZE 4095
> static int io_queue_depth_set(const char *val, const struct kernel_param *kp);
> -static const struct kernel_param_ops io_queue_depth_ops = {
> - .set = io_queue_depth_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(io_queue_depth_ops, io_queue_depth_set,
> + param_get_uint);
>
> static unsigned int io_queue_depth = 1024;
> module_param_cb(io_queue_depth, &io_queue_depth_ops, &io_queue_depth, 0644);
> @@ -232,10 +230,8 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp)
> }
>
> static char quirks_param[128];
> -static const struct kernel_param_ops quirks_param_ops = {
> - .set = quirks_param_set,
> - .get = param_get_string,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(quirks_param_ops, quirks_param_set,
> + param_get_string);
>
> static struct kparam_string quirks_param_string = {
> .maxlen = sizeof(quirks_param),
> @@ -257,10 +253,8 @@ static int io_queue_count_set(const char *val, const struct kernel_param *kp)
> return param_set_uint(val, kp);
> }
>
> -static const struct kernel_param_ops io_queue_count_ops = {
> - .set = io_queue_count_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(io_queue_count_ops, io_queue_count_set,
> + param_get_uint);
>
> static unsigned int write_queues;
> module_param_cb(write_queues, &io_queue_count_ops, &write_queues, 0644);
> diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c
> index e6e2c3f9afdf..dc544813300f 100644
> --- a/drivers/nvme/target/rdma.c
> +++ b/drivers/nvme/target/rdma.c
> @@ -147,10 +147,7 @@ module_param_named(use_srq, nvmet_rdma_use_srq, bool, 0444);
> MODULE_PARM_DESC(use_srq, "Use shared receive queue.");
>
> static int srq_size_set(const char *val, const struct kernel_param *kp);
> -static const struct kernel_param_ops srq_size_ops = {
> - .set = srq_size_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(srq_size_ops, srq_size_set, param_get_int);
>
> static int nvmet_rdma_srq_size = 1024;
> module_param_cb(srq_size, &srq_size_ops, &nvmet_rdma_srq_size, 0644);
> diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
> index 164a564ba3b4..2f336cd7e559 100644
> --- a/drivers/nvme/target/tcp.c
> +++ b/drivers/nvme/target/tcp.c
> @@ -46,10 +46,7 @@ static int set_params(const char *str, const struct kernel_param *kp)
> return param_store_val(str, kp->arg, 0, INT_MAX);
> }
>
> -static const struct kernel_param_ops set_param_ops = {
> - .set = set_params,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(set_param_ops, set_params, param_get_int);
>
> /* Define the socket priority to use for connections were it is desirable
> * that the NIC consider performing optimized packet processing or filtering.
> diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
> index 5ce5ad3efe69..bd59c3f3f2c5 100644
> --- a/drivers/platform/x86/acerhdf.c
> +++ b/drivers/platform/x86/acerhdf.c
> @@ -762,10 +762,7 @@ static int interval_set_uint(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops interval_ops = {
> - .set = interval_set_uint,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(interval_ops, interval_set_uint, param_get_uint);
>
> module_param_cb(interval, &interval_ops, &interval, 0000);
> MODULE_PARM_DESC(interval, "Polling interval of temperature check");
> diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
> index 45f0e39b8c2d..09829ee1a49d 100644
> --- a/drivers/power/supply/bq27xxx_battery.c
> +++ b/drivers/power/supply/bq27xxx_battery.c
> @@ -1133,10 +1133,8 @@ static int poll_interval_param_set(const char *val, const struct kernel_param *k
> return ret;
> }
>
> -static const struct kernel_param_ops param_ops_poll_interval = {
> - .get = param_get_uint,
> - .set = poll_interval_param_set,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_poll_interval, poll_interval_param_set,
> + param_get_uint);
>
> static unsigned int poll_interval = 360;
> module_param_cb(poll_interval, ¶m_ops_poll_interval, &poll_interval, 0644);
> diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c
> index 2c0e9ad820c0..0bf2bef3383a 100644
> --- a/drivers/power/supply/test_power.c
> +++ b/drivers/power/supply/test_power.c
> @@ -649,60 +649,47 @@ static int param_set_battery_extension(const char *key,
>
> #define param_get_battery_extension param_get_bool
>
> -static const struct kernel_param_ops param_ops_ac_online = {
> - .set = param_set_ac_online,
> - .get = param_get_ac_online,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_ac_online, param_set_ac_online,
> + param_get_ac_online);
>
> -static const struct kernel_param_ops param_ops_usb_online = {
> - .set = param_set_usb_online,
> - .get = param_get_usb_online,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_usb_online, param_set_usb_online,
> + param_get_usb_online);
>
> -static const struct kernel_param_ops param_ops_battery_status = {
> - .set = param_set_battery_status,
> - .get = param_get_battery_status,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_battery_status,
> + param_set_battery_status,
> + param_get_battery_status);
>
> -static const struct kernel_param_ops param_ops_battery_present = {
> - .set = param_set_battery_present,
> - .get = param_get_battery_present,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_battery_present,
> + param_set_battery_present,
> + param_get_battery_present);
>
> -static const struct kernel_param_ops param_ops_battery_technology = {
> - .set = param_set_battery_technology,
> - .get = param_get_battery_technology,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_battery_technology,
> + param_set_battery_technology,
> + param_get_battery_technology);
>
> -static const struct kernel_param_ops param_ops_battery_health = {
> - .set = param_set_battery_health,
> - .get = param_get_battery_health,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_battery_health,
> + param_set_battery_health,
> + param_get_battery_health);
>
> -static const struct kernel_param_ops param_ops_battery_capacity = {
> - .set = param_set_battery_capacity,
> - .get = param_get_battery_capacity,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_battery_capacity,
> + param_set_battery_capacity,
> + param_get_battery_capacity);
>
> -static const struct kernel_param_ops param_ops_battery_voltage = {
> - .set = param_set_battery_voltage,
> - .get = param_get_battery_voltage,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_battery_voltage,
> + param_set_battery_voltage,
> + param_get_battery_voltage);
>
> -static const struct kernel_param_ops param_ops_battery_charge_counter = {
> - .set = param_set_battery_charge_counter,
> - .get = param_get_battery_charge_counter,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_battery_charge_counter,
> + param_set_battery_charge_counter,
> + param_get_battery_charge_counter);
>
> -static const struct kernel_param_ops param_ops_battery_current = {
> - .set = param_set_battery_current,
> - .get = param_get_battery_current,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_battery_current,
> + param_set_battery_current,
> + param_get_battery_current);
>
> -static const struct kernel_param_ops param_ops_battery_extension = {
> - .set = param_set_battery_extension,
> - .get = param_get_battery_extension,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_battery_extension,
> + param_set_battery_extension,
> + param_get_battery_extension);
>
> #define param_check_ac_online(name, p) __param_check(name, p, void);
> #define param_check_usb_online(name, p) __param_check(name, p, void);
> diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c
> index 2b4b2a1a8e44..c075b567f84e 100644
> --- a/drivers/scsi/sg.c
> +++ b/drivers/scsi/sg.c
> @@ -1644,10 +1644,8 @@ static int def_reserved_size_set(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops def_reserved_size_ops = {
> - .set = def_reserved_size_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(def_reserved_size_ops, def_reserved_size_set,
> + param_get_int);
>
> module_param_cb(def_reserved_size, &def_reserved_size_ops, &def_reserved_size,
> S_IRUGO | S_IWUSR);
> diff --git a/drivers/target/target_core_user.c b/drivers/target/target_core_user.c
> index edc2afd5f4ee..676a12b44e88 100644
> --- a/drivers/target/target_core_user.c
> +++ b/drivers/target/target_core_user.c
> @@ -255,10 +255,9 @@ static int tcmu_get_global_max_data_area(char *buffer,
> return sprintf(buffer, "%d\n", TCMU_PAGES_TO_MBS(tcmu_global_max_pages));
> }
>
> -static const struct kernel_param_ops tcmu_global_max_data_area_op = {
> - .set = tcmu_set_global_max_data_area,
> - .get = tcmu_get_global_max_data_area,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(tcmu_global_max_data_area_op,
> + tcmu_set_global_max_data_area,
> + tcmu_get_global_max_data_area);
>
> module_param_cb(global_max_data_area_mb, &tcmu_global_max_data_area_op, NULL,
> S_IWUSR | S_IRUGO);
> @@ -292,10 +291,8 @@ static int tcmu_set_block_netlink(const char *str,
> return 0;
> }
>
> -static const struct kernel_param_ops tcmu_block_netlink_op = {
> - .set = tcmu_set_block_netlink,
> - .get = tcmu_get_block_netlink,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(tcmu_block_netlink_op, tcmu_set_block_netlink,
> + tcmu_get_block_netlink);
>
> module_param_cb(block_netlink, &tcmu_block_netlink_op, NULL, S_IWUSR | S_IRUGO);
> MODULE_PARM_DESC(block_netlink, "Block new netlink commands.");
> diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
> index 91f291627132..68275c3f2c9b 100644
> --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
> +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
> @@ -83,10 +83,8 @@ static int slider_def_balance_get(char *buf, const struct kernel_param *kp)
> return sysfs_emit(buf, "%02x\n", slider_values[SOC_POWER_SLIDER_BALANCE]);
> }
>
> -static const struct kernel_param_ops slider_def_balance_ops = {
> - .set = slider_def_balance_set,
> - .get = slider_def_balance_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(slider_def_balance_ops, slider_def_balance_set,
> + slider_def_balance_get);
>
> module_param_cb(slider_balance, &slider_def_balance_ops, NULL, 0644);
> MODULE_PARM_DESC(slider_balance, "Set slider default value for balance");
> @@ -117,10 +115,8 @@ static int slider_def_offset_get(char *buf, const struct kernel_param *kp)
> return sysfs_emit(buf, "%02x\n", slider_offset);
> }
>
> -static const struct kernel_param_ops slider_offset_ops = {
> - .set = slider_def_offset_set,
> - .get = slider_def_offset_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(slider_offset_ops, slider_def_offset_set,
> + slider_def_offset_get);
>
> /*
> * To enhance power efficiency dynamically, the firmware can optionally
> diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c
> index ccf380da12f2..98fbc6892714 100644
> --- a/drivers/thermal/intel/intel_powerclamp.c
> +++ b/drivers/thermal/intel/intel_powerclamp.c
> @@ -112,10 +112,7 @@ static int duration_get(char *buf, const struct kernel_param *kp)
> return ret;
> }
>
> -static const struct kernel_param_ops duration_ops = {
> - .set = duration_set,
> - .get = duration_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(duration_ops, duration_set, duration_get);
>
> module_param_cb(duration, &duration_ops, NULL, 0644);
> MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec.");
> @@ -203,10 +200,7 @@ static int cpumask_get(char *buf, const struct kernel_param *kp)
> return cpumap_print_to_pagebuf(false, buf, idle_injection_cpu_mask);
> }
>
> -static const struct kernel_param_ops cpumask_ops = {
> - .set = cpumask_set,
> - .get = cpumask_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(cpumask_ops, cpumask_set, cpumask_get);
>
> module_param_cb(cpumask, &cpumask_ops, NULL, 0644);
> MODULE_PARM_DESC(cpumask, "Mask of CPUs to use for idle injection.");
> @@ -252,10 +246,7 @@ static int max_idle_set(const char *arg, const struct kernel_param *kp)
> return ret;
> }
>
> -static const struct kernel_param_ops max_idle_ops = {
> - .set = max_idle_set,
> - .get = param_get_byte,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(max_idle_ops, max_idle_set, param_get_byte);
>
> module_param_cb(max_idle, &max_idle_ops, &max_idle, 0644);
> MODULE_PARM_DESC(max_idle, "maximum injected idle time to the total CPU time ratio in percent range:1-100");
> @@ -299,10 +290,7 @@ static int window_size_set(const char *arg, const struct kernel_param *kp)
> return ret;
> }
>
> -static const struct kernel_param_ops window_size_ops = {
> - .set = window_size_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(window_size_ops, window_size_set, param_get_int);
>
> module_param_cb(window_size, &window_size_ops, &window_size, 0644);
> MODULE_PARM_DESC(window_size, "sliding window in number of clamping cycles\n"
> diff --git a/drivers/tty/hvc/hvc_iucv.c b/drivers/tty/hvc/hvc_iucv.c
> index 37db8a3e5158..29612a4a32cb 100644
> --- a/drivers/tty/hvc/hvc_iucv.c
> +++ b/drivers/tty/hvc/hvc_iucv.c
> @@ -1290,10 +1290,8 @@ static int param_get_vmidfilter(char *buffer, const struct kernel_param *kp)
>
> #define param_check_vmidfilter(name, p) __param_check(name, p, void)
>
> -static const struct kernel_param_ops param_ops_vmidfilter = {
> - .set = param_set_vmidfilter,
> - .get = param_get_vmidfilter,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_vmidfilter, param_set_vmidfilter,
> + param_get_vmidfilter);
>
> /**
> * hvc_iucv_init() - z/VM IUCV HVC device driver initialization
> diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
> index c2e4b31b699a..c6279c496279 100644
> --- a/drivers/tty/sysrq.c
> +++ b/drivers/tty/sysrq.c
> @@ -1074,10 +1074,8 @@ static int sysrq_reset_seq_param_set(const char *buffer,
> return 0;
> }
>
> -static const struct kernel_param_ops param_ops_sysrq_reset_seq = {
> - .get = param_get_ushort,
> - .set = sysrq_reset_seq_param_set,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_sysrq_reset_seq,
> + sysrq_reset_seq_param_set, param_get_ushort);
>
> #define param_check_sysrq_reset_seq(name, p) \
> __param_check(name, p, unsigned short)
> diff --git a/drivers/ufs/core/ufs-fault-injection.c b/drivers/ufs/core/ufs-fault-injection.c
> index 55db38e75cc4..7d2873da7dc5 100644
> --- a/drivers/ufs/core/ufs-fault-injection.c
> +++ b/drivers/ufs/core/ufs-fault-injection.c
> @@ -11,10 +11,7 @@
> static int ufs_fault_get(char *buffer, const struct kernel_param *kp);
> static int ufs_fault_set(const char *val, const struct kernel_param *kp);
>
> -static const struct kernel_param_ops ufs_fault_ops = {
> - .get = ufs_fault_get,
> - .set = ufs_fault_set,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(ufs_fault_ops, ufs_fault_set, ufs_fault_get);
>
> enum { FAULT_INJ_STR_SIZE = 80 };
>
> diff --git a/drivers/ufs/core/ufs-mcq.c b/drivers/ufs/core/ufs-mcq.c
> index c1b1d67a1ddc..83100aad2a97 100644
> --- a/drivers/ufs/core/ufs-mcq.c
> +++ b/drivers/ufs/core/ufs-mcq.c
> @@ -43,10 +43,8 @@ static int rw_queue_count_set(const char *val, const struct kernel_param *kp)
> num_possible_cpus());
> }
>
> -static const struct kernel_param_ops rw_queue_count_ops = {
> - .set = rw_queue_count_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(rw_queue_count_ops, rw_queue_count_set,
> + param_get_uint);
>
> static unsigned int rw_queues;
> module_param_cb(rw_queues, &rw_queue_count_ops, &rw_queues, 0644);
> @@ -59,10 +57,8 @@ static int read_queue_count_set(const char *val, const struct kernel_param *kp)
> num_possible_cpus());
> }
>
> -static const struct kernel_param_ops read_queue_count_ops = {
> - .set = read_queue_count_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(read_queue_count_ops, read_queue_count_set,
> + param_get_uint);
>
> static unsigned int read_queues;
> module_param_cb(read_queues, &read_queue_count_ops, &read_queues, 0644);
> @@ -75,10 +71,8 @@ static int poll_queue_count_set(const char *val, const struct kernel_param *kp)
> num_possible_cpus());
> }
>
> -static const struct kernel_param_ops poll_queue_count_ops = {
> - .set = poll_queue_count_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(poll_queue_count_ops, poll_queue_count_set,
> + param_get_uint);
>
> static unsigned int poll_queues = 1;
> module_param_cb(poll_queues, &poll_queue_count_ops, &poll_queues, 0644);
> diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
> index b2dc89124353..3bdd87b434ad 100644
> --- a/drivers/ufs/core/ufs-txeq.c
> +++ b/drivers/ufs/core/ufs-txeq.c
> @@ -23,10 +23,7 @@ static int txeq_gear_set(const char *val, const struct kernel_param *kp)
> return param_set_uint_minmax(val, kp, UFS_HS_G1, UFS_HS_GEAR_MAX);
> }
>
> -static const struct kernel_param_ops txeq_gear_ops = {
> - .set = txeq_gear_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(txeq_gear_ops, txeq_gear_set, param_get_uint);
>
> static unsigned int adaptive_txeq_gear = UFS_HS_G6;
> module_param_cb(adaptive_txeq_gear, &txeq_gear_ops, &adaptive_txeq_gear, 0644);
> diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
> index 4805e40ed4d7..4e930710d9a6 100644
> --- a/drivers/ufs/core/ufshcd.c
> +++ b/drivers/ufs/core/ufshcd.c
> @@ -128,10 +128,8 @@ static int uic_cmd_timeout_set(const char *val, const struct kernel_param *kp)
> UIC_CMD_TIMEOUT_MAX);
> }
>
> -static const struct kernel_param_ops uic_cmd_timeout_ops = {
> - .set = uic_cmd_timeout_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(uic_cmd_timeout_ops, uic_cmd_timeout_set,
> + param_get_uint);
>
> module_param_cb(uic_cmd_timeout, &uic_cmd_timeout_ops, &uic_cmd_timeout, 0644);
> MODULE_PARM_DESC(uic_cmd_timeout,
> @@ -145,10 +143,8 @@ static int dev_cmd_timeout_set(const char *val, const struct kernel_param *kp)
> QUERY_REQ_TIMEOUT_MAX);
> }
>
> -static const struct kernel_param_ops dev_cmd_timeout_ops = {
> - .set = dev_cmd_timeout_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(dev_cmd_timeout_ops, dev_cmd_timeout_set,
> + param_get_uint);
>
> module_param_cb(dev_cmd_timeout, &dev_cmd_timeout_ops, &dev_cmd_timeout, 0644);
> MODULE_PARM_DESC(dev_cmd_timeout,
> diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
> index 0ffdaefba508..d52ecc886925 100644
> --- a/drivers/usb/core/quirks.c
> +++ b/drivers/usb/core/quirks.c
> @@ -160,10 +160,8 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops quirks_param_ops = {
> - .set = quirks_param_set,
> - .get = param_get_string,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(quirks_param_ops, quirks_param_set,
> + param_get_string);
>
> static struct kparam_string quirks_param_string = {
> .maxlen = sizeof(quirks_param),
> diff --git a/drivers/usb/gadget/legacy/serial.c b/drivers/usb/gadget/legacy/serial.c
> index 4974bee6049a..e34717e553da 100644
> --- a/drivers/usb/gadget/legacy/serial.c
> +++ b/drivers/usb/gadget/legacy/serial.c
> @@ -121,10 +121,7 @@ static int enable_set(const char *s, const struct kernel_param *kp)
> return ret;
> }
>
> -static const struct kernel_param_ops enable_ops = {
> - .set = enable_set,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(enable_ops, enable_set, param_get_bool);
>
> module_param_cb(enable, &enable_ops, &enable, 0644);
>
> diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c
> index fa83fe0defe2..71dd623b95c9 100644
> --- a/drivers/usb/storage/usb.c
> +++ b/drivers/usb/storage/usb.c
> @@ -158,10 +158,7 @@ static int delay_use_get(char *s, const struct kernel_param *kp)
> return format_delay_ms(delay_ms, 3, "ms", s, PAGE_SIZE);
> }
>
> -static const struct kernel_param_ops delay_use_ops = {
> - .set = delay_use_set,
> - .get = delay_use_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(delay_use_ops, delay_use_set, delay_use_get);
> module_param_cb(delay_use, &delay_use_ops, &delay_use, 0644);
> MODULE_PARM_DESC(delay_use, "time to delay before using a new device");
>
> diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c
> index 9a1253b9d8c5..fd52f2213e27 100644
> --- a/drivers/vhost/scsi.c
> +++ b/drivers/vhost/scsi.c
> @@ -87,10 +87,9 @@ static int vhost_scsi_get_inline_sg_cnt(char *buf,
> return sprintf(buf, "%u\n", vhost_scsi_inline_sg_cnt);
> }
>
> -static const struct kernel_param_ops vhost_scsi_inline_sg_cnt_op = {
> - .get = vhost_scsi_get_inline_sg_cnt,
> - .set = vhost_scsi_set_inline_sg_cnt,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(vhost_scsi_inline_sg_cnt_op,
> + vhost_scsi_set_inline_sg_cnt,
> + vhost_scsi_get_inline_sg_cnt);
>
> module_param_cb(inline_sg_cnt, &vhost_scsi_inline_sg_cnt_op, NULL, 0644);
> MODULE_PARM_DESC(inline_sg_cnt, "Set the number of scatterlist entries to pre-allocate. The default is 2048.");
> diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.c b/drivers/virt/nitro_enclaves/ne_misc_dev.c
> index c91300a73f50..218ddd93f960 100644
> --- a/drivers/virt/nitro_enclaves/ne_misc_dev.c
> +++ b/drivers/virt/nitro_enclaves/ne_misc_dev.c
> @@ -86,10 +86,8 @@ struct ne_devs ne_devs = {
> */
> static int ne_set_kernel_param(const char *val, const struct kernel_param *kp);
>
> -static const struct kernel_param_ops ne_cpu_pool_ops = {
> - .get = param_get_string,
> - .set = ne_set_kernel_param,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(ne_cpu_pool_ops, ne_set_kernel_param,
> + param_get_string);
>
> static char ne_cpus[NE_CPUS_SIZE];
> static struct kparam_string ne_cpus_arg = {
> diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c
> index 595c2274fbb5..f6df9c76ee81 100644
> --- a/drivers/virtio/virtio_mmio.c
> +++ b/drivers/virtio/virtio_mmio.c
> @@ -748,10 +748,8 @@ static int vm_cmdline_get(char *buffer, const struct kernel_param *kp)
> return strlen(buffer) + 1;
> }
>
> -static const struct kernel_param_ops vm_cmdline_param_ops = {
> - .set = vm_cmdline_set,
> - .get = vm_cmdline_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(vm_cmdline_param_ops, vm_cmdline_set,
> + vm_cmdline_get);
>
> device_param_cb(device, &vm_cmdline_param_ops, NULL, S_IRUSR);
>
> diff --git a/fs/ceph/super.c b/fs/ceph/super.c
> index c05fbd4237f8..dec8024b8ac7 100644
> --- a/fs/ceph/super.c
> +++ b/fs/ceph/super.c
> @@ -1684,10 +1684,8 @@ static int param_set_metrics(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static const struct kernel_param_ops param_ops_metrics = {
> - .set = param_set_metrics,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_metrics, param_set_metrics,
> + param_get_bool);
>
> bool disable_send_metrics = false;
> module_param_cb(disable_send_metrics, ¶m_ops_metrics, &disable_send_metrics, 0644);
> @@ -1695,9 +1693,7 @@ MODULE_PARM_DESC(disable_send_metrics, "Enable sending perf metrics to ceph clus
>
> /* for both v1 and v2 syntax */
> static bool mount_support = true;
> -static const struct kernel_param_ops param_ops_mount_syntax = {
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_mount_syntax, NULL, param_get_bool);
> module_param_cb(mount_syntax_v1, ¶m_ops_mount_syntax, &mount_support, 0444);
> module_param_cb(mount_syntax_v2, ¶m_ops_mount_syntax, &mount_support, 0444);
>
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index b658b6baf72f..cc955c9952d1 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -71,10 +71,7 @@ static int inval_wq_set(const char *val, const struct kernel_param *kp)
>
> return 0;
> }
> -static const struct kernel_param_ops inval_wq_ops = {
> - .set = inval_wq_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(inval_wq_ops, inval_wq_set, param_get_uint);
> module_param_cb(inval_wq, &inval_wq_ops, &inval_wq, 0644);
> __MODULE_PARM_TYPE(inval_wq, "uint");
> MODULE_PARM_DESC(inval_wq,
> diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c
> index af9be0c5f516..f2fba60dc5ed 100644
> --- a/fs/nfs/namespace.c
> +++ b/fs/nfs/namespace.c
> @@ -372,10 +372,8 @@ static int param_get_nfs_timeout(char *buffer, const struct kernel_param *kp)
> return sysfs_emit(buffer, "%li\n", num);
> }
>
> -static const struct kernel_param_ops param_ops_nfs_timeout = {
> - .set = param_set_nfs_timeout,
> - .get = param_get_nfs_timeout,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_nfs_timeout, param_set_nfs_timeout,
> + param_get_nfs_timeout);
> #define param_check_nfs_timeout(name, p) __param_check(name, p, int)
>
> module_param(nfs_mountpoint_expiry_timeout, nfs_timeout, 0644);
> diff --git a/fs/nfs/super.c b/fs/nfs/super.c
> index 4cd420b14ce3..59d89a18aba6 100644
> --- a/fs/nfs/super.c
> +++ b/fs/nfs/super.c
> @@ -1419,10 +1419,8 @@ static int param_set_portnr(const char *val, const struct kernel_param *kp)
> *((unsigned int *)kp->arg) = num;
> return 0;
> }
> -static const struct kernel_param_ops param_ops_portnr = {
> - .set = param_set_portnr,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_portnr, param_set_portnr,
> + param_get_uint);
> #define param_check_portnr(name, p) __param_check(name, p, unsigned int)
>
> module_param_named(callback_tcpport, nfs_callback_set_tcpport, portnr, 0644);
> diff --git a/fs/ubifs/super.c b/fs/ubifs/super.c
> index 9a77d8b64ffa..e44f8e18f756 100644
> --- a/fs/ubifs/super.c
> +++ b/fs/ubifs/super.c
> @@ -36,10 +36,8 @@ static int ubifs_default_version_set(const char *val, const struct kernel_param
> return param_set_int(val, kp);
> }
>
> -static const struct kernel_param_ops ubifs_default_version_ops = {
> - .set = ubifs_default_version_set,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(ubifs_default_version_ops,
> + ubifs_default_version_set, param_get_int);
>
> int ubifs_default_version = UBIFS_FORMAT_VERSION;
> module_param_cb(default_version, &ubifs_default_version_ops, &ubifs_default_version, 0600);
> diff --git a/kernel/locking/locktorture.c b/kernel/locking/locktorture.c
> index e618bcf75e2d..38ae3b596ef2 100644
> --- a/kernel/locking/locktorture.c
> +++ b/kernel/locking/locktorture.c
> @@ -98,10 +98,8 @@ static bool cpumask_nonempty(cpumask_var_t mask)
> return cpumask_available(mask) && !cpumask_empty(mask);
> }
>
> -static const struct kernel_param_ops lt_bind_ops = {
> - .set = param_set_cpumask,
> - .get = param_get_cpumask,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(lt_bind_ops, param_set_cpumask,
> + param_get_cpumask);
>
> module_param_cb(bind_readers, <_bind_ops, &bind_readers, 0444);
> module_param_cb(bind_writers, <_bind_ops, &bind_writers, 0444);
> diff --git a/kernel/panic.c b/kernel/panic.c
> index 42e5ebde4585..8698374b0d21 100644
> --- a/kernel/panic.c
> +++ b/kernel/panic.c
> @@ -1214,10 +1214,8 @@ static int panic_print_set(const char *val, const struct kernel_param *kp)
> return param_set_ulong(val, kp);
> }
>
> -static const struct kernel_param_ops panic_print_ops = {
> - .set = panic_print_set,
> - .get = param_get_ulong,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(panic_print_ops, panic_print_set,
> + param_get_ulong);
> __core_param_cb(panic_print, &panic_print_ops, &panic_print, 0644);
>
> static int __init oops_setup(char *s)
> diff --git a/kernel/params.c b/kernel/params.c
> index 2cbad1f4dd06..e19fff2926bc 100644
> --- a/kernel/params.c
> +++ b/kernel/params.c
> @@ -297,11 +297,8 @@ void param_free_charp(void *arg)
> }
> EXPORT_SYMBOL(param_free_charp);
>
> -const struct kernel_param_ops param_ops_charp = {
> - .set = param_set_charp,
> - .get = param_get_charp,
> - .free = param_free_charp,
> -};
> +DEFINE_KERNEL_PARAM_OPS_FREE(param_ops_charp, param_set_charp, param_get_charp,
> + param_free_charp);
> EXPORT_SYMBOL(param_ops_charp);
>
> /* Actually could be a bool or an int, for historical reasons. */
> @@ -322,11 +319,7 @@ int param_get_bool(char *buffer, const struct kernel_param *kp)
> }
> EXPORT_SYMBOL(param_get_bool);
>
> -const struct kernel_param_ops param_ops_bool = {
> - .flags = KERNEL_PARAM_OPS_FL_NOARG,
> - .set = param_set_bool,
> - .get = param_get_bool,
> -};
> +DEFINE_KERNEL_PARAM_OPS_NOARG(param_ops_bool, param_set_bool, param_get_bool);
> EXPORT_SYMBOL(param_ops_bool);
>
> int param_set_bool_enable_only(const char *val, const struct kernel_param *kp)
> @@ -353,11 +346,8 @@ int param_set_bool_enable_only(const char *val, const struct kernel_param *kp)
> }
> EXPORT_SYMBOL_GPL(param_set_bool_enable_only);
>
> -const struct kernel_param_ops param_ops_bool_enable_only = {
> - .flags = KERNEL_PARAM_OPS_FL_NOARG,
> - .set = param_set_bool_enable_only,
> - .get = param_get_bool,
> -};
> +DEFINE_KERNEL_PARAM_OPS_NOARG(param_ops_bool_enable_only,
> + param_set_bool_enable_only, param_get_bool);
> EXPORT_SYMBOL_GPL(param_ops_bool_enable_only);
>
> /* This one must be bool. */
> @@ -381,10 +371,7 @@ int param_get_invbool(char *buffer, const struct kernel_param *kp)
> }
> EXPORT_SYMBOL(param_get_invbool);
>
> -const struct kernel_param_ops param_ops_invbool = {
> - .set = param_set_invbool,
> - .get = param_get_invbool,
> -};
> +DEFINE_KERNEL_PARAM_OPS(param_ops_invbool, param_set_invbool, param_get_invbool);
> EXPORT_SYMBOL(param_ops_invbool);
>
> int param_set_bint(const char *val, const struct kernel_param *kp)
> @@ -403,11 +390,7 @@ int param_set_bint(const char *val, const struct kernel_param *kp)
> }
> EXPORT_SYMBOL(param_set_bint);
>
> -const struct kernel_param_ops param_ops_bint = {
> - .flags = KERNEL_PARAM_OPS_FL_NOARG,
> - .set = param_set_bint,
> - .get = param_get_int,
> -};
> +DEFINE_KERNEL_PARAM_OPS_NOARG(param_ops_bint, param_set_bint, param_get_int);
> EXPORT_SYMBOL(param_ops_bint);
>
> /* We break the rule and mangle the string. */
> @@ -515,11 +498,8 @@ static void param_array_free(void *arg)
> arr->ops->free(arr->elem + arr->elemsize * i);
> }
>
> -const struct kernel_param_ops param_array_ops = {
> - .set = param_array_set,
> - .get = param_array_get,
> - .free = param_array_free,
> -};
> +DEFINE_KERNEL_PARAM_OPS_FREE(param_array_ops, param_array_set, param_array_get,
> + param_array_free);
> EXPORT_SYMBOL(param_array_ops);
>
> int param_set_copystring(const char *val, const struct kernel_param *kp)
> @@ -544,10 +524,8 @@ int param_get_string(char *buffer, const struct kernel_param *kp)
> }
> EXPORT_SYMBOL(param_get_string);
>
> -const struct kernel_param_ops param_ops_string = {
> - .set = param_set_copystring,
> - .get = param_get_string,
> -};
> +DEFINE_KERNEL_PARAM_OPS(param_ops_string, param_set_copystring,
> + param_get_string);
> EXPORT_SYMBOL(param_ops_string);
>
> /* sysfs output in /sys/modules/XYZ/parameters/ */
> diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
> index af8d07bafe02..aba1e4489447 100644
> --- a/kernel/power/hibernate.c
> +++ b/kernel/power/hibernate.c
> @@ -1528,10 +1528,8 @@ static int hibernate_compressor_param_set(const char *compressor,
> return ret;
> }
>
> -static const struct kernel_param_ops hibernate_compressor_param_ops = {
> - .set = hibernate_compressor_param_set,
> - .get = param_get_string,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(hibernate_compressor_param_ops,
> + hibernate_compressor_param_set, param_get_string);
>
> static struct kparam_string hibernate_compressor_param_string = {
> .maxlen = sizeof(hibernate_compressor),
> diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
> index 55df6d37145e..e675d7f1b4ee 100644
> --- a/kernel/rcu/tree.c
> +++ b/kernel/rcu/tree.c
> @@ -498,15 +498,11 @@ static int param_set_next_fqs_jiffies(const char *val, const struct kernel_param
> return ret;
> }
>
> -static const struct kernel_param_ops first_fqs_jiffies_ops = {
> - .set = param_set_first_fqs_jiffies,
> - .get = param_get_ulong,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(first_fqs_jiffies_ops,
> + param_set_first_fqs_jiffies, param_get_ulong);
>
> -static const struct kernel_param_ops next_fqs_jiffies_ops = {
> - .set = param_set_next_fqs_jiffies,
> - .get = param_get_ulong,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(next_fqs_jiffies_ops, param_set_next_fqs_jiffies,
> + param_get_ulong);
>
> module_param_cb(jiffies_till_first_fqs, &first_fqs_jiffies_ops, &jiffies_till_first_fqs, 0644);
> module_param_cb(jiffies_till_next_fqs, &next_fqs_jiffies_ops, &jiffies_till_next_fqs, 0644);
> @@ -3979,10 +3975,8 @@ static int param_get_do_rcu_barrier(char *buffer, const struct kernel_param *kp)
> return sprintf(buffer, "%d\n", atomic_read((atomic_t *)kp->arg));
> }
>
> -static const struct kernel_param_ops do_rcu_barrier_ops = {
> - .set = param_set_do_rcu_barrier,
> - .get = param_get_do_rcu_barrier,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(do_rcu_barrier_ops, param_set_do_rcu_barrier,
> + param_get_do_rcu_barrier);
> static atomic_t do_rcu_barrier;
> module_param_cb(do_rcu_barrier, &do_rcu_barrier_ops, &do_rcu_barrier, 0644);
>
> diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c
> index 345aa11b84b2..fcf31e3e4965 100644
> --- a/kernel/sched/ext.c
> +++ b/kernel/sched/ext.c
> @@ -166,20 +166,15 @@ static int set_slice_us(const char *val, const struct kernel_param *kp)
> return param_set_uint_minmax(val, kp, 100, 100 * USEC_PER_MSEC);
> }
>
> -static const struct kernel_param_ops slice_us_param_ops = {
> - .set = set_slice_us,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(slice_us_param_ops, set_slice_us, param_get_uint);
>
> static int set_bypass_lb_intv_us(const char *val, const struct kernel_param *kp)
> {
> return param_set_uint_minmax(val, kp, 0, 10 * USEC_PER_SEC);
> }
>
> -static const struct kernel_param_ops bypass_lb_intv_us_param_ops = {
> - .set = set_bypass_lb_intv_us,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(bypass_lb_intv_us_param_ops,
> + set_bypass_lb_intv_us, param_get_uint);
>
> #undef MODULE_PARAM_PREFIX
> #define MODULE_PARAM_PREFIX "sched_ext."
> diff --git a/kernel/workqueue.c b/kernel/workqueue.c
> index 5f747f241a5f..42562b811d94 100644
> --- a/kernel/workqueue.c
> +++ b/kernel/workqueue.c
> @@ -7162,10 +7162,8 @@ static int wq_affn_dfl_get(char *buffer, const struct kernel_param *kp)
> return scnprintf(buffer, PAGE_SIZE, "%s\n", wq_affn_names[wq_affn_dfl]);
> }
>
> -static const struct kernel_param_ops wq_affn_dfl_ops = {
> - .set = wq_affn_dfl_set,
> - .get = wq_affn_dfl_get,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(wq_affn_dfl_ops, wq_affn_dfl_set,
> + wq_affn_dfl_get);
>
> module_param_cb(default_affinity_scope, &wq_affn_dfl_ops, NULL, 0644);
>
> @@ -7861,10 +7859,8 @@ static int wq_watchdog_param_set_thresh(const char *val,
> return 0;
> }
>
> -static const struct kernel_param_ops wq_watchdog_thresh_ops = {
> - .set = wq_watchdog_param_set_thresh,
> - .get = param_get_ulong,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(wq_watchdog_thresh_ops,
> + wq_watchdog_param_set_thresh, param_get_ulong);
>
> module_param_cb(watchdog_thresh, &wq_watchdog_thresh_ops, &wq_watchdog_thresh,
> 0644);
> diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
> index 18a71a9108d3..cf0405ba0dbd 100644
> --- a/lib/dynamic_debug.c
> +++ b/lib/dynamic_debug.c
> @@ -807,10 +807,8 @@ int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp)
> }
> EXPORT_SYMBOL(param_get_dyndbg_classes);
>
> -const struct kernel_param_ops param_ops_dyndbg_classes = {
> - .set = param_set_dyndbg_classes,
> - .get = param_get_dyndbg_classes,
> -};
> +DEFINE_KERNEL_PARAM_OPS(param_ops_dyndbg_classes, param_set_dyndbg_classes,
> + param_get_dyndbg_classes);
> EXPORT_SYMBOL(param_ops_dyndbg_classes);
>
> #define PREFIX_SIZE 128
> diff --git a/lib/test_dynamic_debug.c b/lib/test_dynamic_debug.c
> index 77c2a669b6af..30880b6c726a 100644
> --- a/lib/test_dynamic_debug.c
> +++ b/lib/test_dynamic_debug.c
> @@ -23,10 +23,8 @@ static int param_get_do_prints(char *buffer, const struct kernel_param *kp)
> do_prints();
> return scnprintf(buffer, PAGE_SIZE, "did do_prints\n");
> }
> -static const struct kernel_param_ops param_ops_do_prints = {
> - .set = param_set_do_prints,
> - .get = param_get_do_prints,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_do_prints, param_set_do_prints,
> + param_get_do_prints);
> module_param_cb(do_prints, ¶m_ops_do_prints, NULL, 0600);
>
> /*
> diff --git a/mm/damon/lru_sort.c b/mm/damon/lru_sort.c
> index 8494040b1ee4..5feb93c5262e 100644
> --- a/mm/damon/lru_sort.c
> +++ b/mm/damon/lru_sort.c
> @@ -405,10 +405,8 @@ static int damon_lru_sort_addr_unit_store(const char *val,
> return 0;
> }
>
> -static const struct kernel_param_ops addr_unit_param_ops = {
> - .set = damon_lru_sort_addr_unit_store,
> - .get = param_get_ulong,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(addr_unit_param_ops,
> + damon_lru_sort_addr_unit_store, param_get_ulong);
>
> module_param_cb(addr_unit, &addr_unit_param_ops, &addr_unit, 0600);
> MODULE_PARM_DESC(addr_unit,
> @@ -446,10 +444,8 @@ static int damon_lru_sort_enabled_load(char *buffer,
> return sprintf(buffer, "%c\n", damon_lru_sort_enabled() ? 'Y' : 'N');
> }
>
> -static const struct kernel_param_ops enabled_param_ops = {
> - .set = damon_lru_sort_enabled_store,
> - .get = damon_lru_sort_enabled_load,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops, damon_lru_sort_enabled_store,
> + damon_lru_sort_enabled_load);
>
> module_param_cb(enabled, &enabled_param_ops, &enabled, 0600);
> MODULE_PARM_DESC(enabled,
> @@ -478,10 +474,9 @@ static int damon_lru_sort_kdamond_pid_load(char *buffer,
> return sprintf(buffer, "%d\n", kdamond_pid);
> }
>
> -static const struct kernel_param_ops kdamond_pid_param_ops = {
> - .set = damon_lru_sort_kdamond_pid_store,
> - .get = damon_lru_sort_kdamond_pid_load,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(kdamond_pid_param_ops,
> + damon_lru_sort_kdamond_pid_store,
> + damon_lru_sort_kdamond_pid_load);
>
> /*
> * PID of the DAMON thread
> diff --git a/mm/damon/reclaim.c b/mm/damon/reclaim.c
> index fe7fce26cf6c..27e772b095fa 100644
> --- a/mm/damon/reclaim.c
> +++ b/mm/damon/reclaim.c
> @@ -307,10 +307,8 @@ static int damon_reclaim_addr_unit_store(const char *val,
> return 0;
> }
>
> -static const struct kernel_param_ops addr_unit_param_ops = {
> - .set = damon_reclaim_addr_unit_store,
> - .get = param_get_ulong,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(addr_unit_param_ops,
> + damon_reclaim_addr_unit_store, param_get_ulong);
>
> module_param_cb(addr_unit, &addr_unit_param_ops, &addr_unit, 0600);
> MODULE_PARM_DESC(addr_unit,
> @@ -348,10 +346,8 @@ static int damon_reclaim_enabled_load(char *buffer,
> return sprintf(buffer, "%c\n", damon_reclaim_enabled() ? 'Y' : 'N');
> }
>
> -static const struct kernel_param_ops enabled_param_ops = {
> - .set = damon_reclaim_enabled_store,
> - .get = damon_reclaim_enabled_load,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops, damon_reclaim_enabled_store,
> + damon_reclaim_enabled_load);
>
> module_param_cb(enabled, &enabled_param_ops, &enabled, 0600);
> MODULE_PARM_DESC(enabled,
> @@ -380,10 +376,9 @@ static int damon_reclaim_kdamond_pid_load(char *buffer,
> return sprintf(buffer, "%d\n", kdamond_pid);
> }
>
> -static const struct kernel_param_ops kdamond_pid_param_ops = {
> - .set = damon_reclaim_kdamond_pid_store,
> - .get = damon_reclaim_kdamond_pid_load,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(kdamond_pid_param_ops,
> + damon_reclaim_kdamond_pid_store,
> + damon_reclaim_kdamond_pid_load);
>
> /*
> * PID of the DAMON thread
> diff --git a/mm/damon/stat.c b/mm/damon/stat.c
> index 3951b762cbdd..6eb548793802 100644
> --- a/mm/damon/stat.c
> +++ b/mm/damon/stat.c
> @@ -22,10 +22,8 @@ static int damon_stat_enabled_store(
> static int damon_stat_enabled_load(char *buffer,
> const struct kernel_param *kp);
>
> -static const struct kernel_param_ops enabled_param_ops = {
> - .set = damon_stat_enabled_store,
> - .get = damon_stat_enabled_load,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops, damon_stat_enabled_store,
> + damon_stat_enabled_load);
>
> static bool enabled __read_mostly = IS_ENABLED(
> CONFIG_DAMON_STAT_ENABLED_DEFAULT);
> diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
> index 2a943ec57c85..42e0cf313281 100644
> --- a/mm/memory_hotplug.c
> +++ b/mm/memory_hotplug.c
> @@ -109,10 +109,8 @@ static int get_memmap_mode(char *buffer, const struct kernel_param *kp)
> return sprintf(buffer, "%c\n", mode ? 'Y' : 'N');
> }
>
> -static const struct kernel_param_ops memmap_mode_ops = {
> - .set = set_memmap_mode,
> - .get = get_memmap_mode,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(memmap_mode_ops, set_memmap_mode,
> + get_memmap_mode);
> module_param_cb(memmap_on_memory, &memmap_mode_ops, &memmap_mode, 0444);
> MODULE_PARM_DESC(memmap_on_memory, "Enable memmap on memory for memory hotplug\n"
> "With value \"force\" it could result in memory wastage due "
> @@ -163,10 +161,8 @@ static int get_online_policy(char *buffer, const struct kernel_param *kp)
> * (auto_movable_ratio, auto_movable_numa_aware) allows for it
> */
> static int online_policy __read_mostly = ONLINE_POLICY_CONTIG_ZONES;
> -static const struct kernel_param_ops online_policy_ops = {
> - .set = set_online_policy,
> - .get = get_online_policy,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(online_policy_ops, set_online_policy,
> + get_online_policy);
> module_param_cb(online_policy, &online_policy_ops, &online_policy, 0644);
> MODULE_PARM_DESC(online_policy,
> "Set the online policy (\"contig-zones\", \"auto-movable\") "
> diff --git a/mm/page_reporting.c b/mm/page_reporting.c
> index 7418f2e500bb..61351e12b4d9 100644
> --- a/mm/page_reporting.c
> +++ b/mm/page_reporting.c
> @@ -23,15 +23,8 @@ static int page_order_update_notify(const char *val, const struct kernel_param *
> return param_set_uint_minmax(val, kp, 0, MAX_PAGE_ORDER);
> }
>
> -static const struct kernel_param_ops page_reporting_param_ops = {
> - .set = &page_order_update_notify,
> - /*
> - * For the get op, use param_get_int instead of param_get_uint.
> - * This is to make sure that when unset the initialized value of
> - * -1 is shown correctly
> - */
> - .get = ¶m_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(page_reporting_param_ops,
> + &page_order_update_notify, ¶m_get_int);
>
> module_param_cb(page_reporting_order, &page_reporting_param_ops,
> &page_reporting_order, 0644);
> diff --git a/mm/shuffle.c b/mm/shuffle.c
> index fb1393b8b3a9..114fe7467516 100644
> --- a/mm/shuffle.c
> +++ b/mm/shuffle.c
> @@ -23,10 +23,8 @@ static __meminit int shuffle_param_set(const char *val,
> return 0;
> }
>
> -static const struct kernel_param_ops shuffle_param_ops = {
> - .set = shuffle_param_set,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(shuffle_param_ops, shuffle_param_set,
> + param_get_bool);
> module_param_cb(shuffle, &shuffle_param_ops, &shuffle_param, 0400);
>
> /*
> diff --git a/mm/zswap.c b/mm/zswap.c
> index 4b5149173b0e..ed3aa07c2f1d 100644
> --- a/mm/zswap.c
> +++ b/mm/zswap.c
> @@ -90,21 +90,17 @@ static DEFINE_STATIC_KEY_MAYBE(CONFIG_ZSWAP_DEFAULT_ON, zswap_ever_enabled);
> static bool zswap_enabled = IS_ENABLED(CONFIG_ZSWAP_DEFAULT_ON);
> static int zswap_enabled_param_set(const char *,
> const struct kernel_param *);
> -static const struct kernel_param_ops zswap_enabled_param_ops = {
> - .set = zswap_enabled_param_set,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(zswap_enabled_param_ops, zswap_enabled_param_set,
> + param_get_bool);
> module_param_cb(enabled, &zswap_enabled_param_ops, &zswap_enabled, 0644);
>
> /* Crypto compressor to use */
> static char *zswap_compressor = CONFIG_ZSWAP_COMPRESSOR_DEFAULT;
> static int zswap_compressor_param_set(const char *,
> const struct kernel_param *);
> -static const struct kernel_param_ops zswap_compressor_param_ops = {
> - .set = zswap_compressor_param_set,
> - .get = param_get_charp,
> - .free = param_free_charp,
> -};
> +static DEFINE_KERNEL_PARAM_OPS_FREE(zswap_compressor_param_ops,
> + zswap_compressor_param_set, param_get_charp,
> + param_free_charp);
> module_param_cb(compressor, &zswap_compressor_param_ops,
> &zswap_compressor, 0644);
>
> diff --git a/net/batman-adv/bat_algo.c b/net/batman-adv/bat_algo.c
> index 49e5861b58ec..54d66a948298 100644
> --- a/net/batman-adv/bat_algo.c
> +++ b/net/batman-adv/bat_algo.c
> @@ -134,10 +134,8 @@ static int batadv_param_set_ra(const char *val, const struct kernel_param *kp)
> return param_set_copystring(algo_name, kp);
> }
>
> -static const struct kernel_param_ops batadv_param_ops_ra = {
> - .set = batadv_param_set_ra,
> - .get = param_get_string,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(batadv_param_ops_ra, batadv_param_set_ra,
> + param_get_string);
>
> static struct kparam_string batadv_param_string_ra = {
> .maxlen = sizeof(batadv_routing_algo),
> diff --git a/net/ceph/ceph_common.c b/net/ceph/ceph_common.c
> index 952121849180..633202a99e4a 100644
> --- a/net/ceph/ceph_common.c
> +++ b/net/ceph/ceph_common.c
> @@ -52,9 +52,8 @@ static int param_get_supported_features(char *buffer,
> {
> return sprintf(buffer, "0x%llx", CEPH_FEATURES_SUPPORTED_DEFAULT);
> }
> -static const struct kernel_param_ops param_ops_supported_features = {
> - .get = param_get_supported_features,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_supported_features, NULL,
> + param_get_supported_features);
> module_param_cb(supported_features, ¶m_ops_supported_features, NULL,
> 0444);
>
> diff --git a/net/ipv4/tcp_dctcp.c b/net/ipv4/tcp_dctcp.c
> index 274e628e7cf8..2c9c3d0f3c3d 100644
> --- a/net/ipv4/tcp_dctcp.c
> +++ b/net/ipv4/tcp_dctcp.c
> @@ -64,10 +64,8 @@ static int dctcp_shift_g_set(const char *val, const struct kernel_param *kp)
> return param_set_uint_minmax(val, kp, 0, 10);
> }
>
> -static const struct kernel_param_ops dctcp_shift_g_ops = {
> - .set = dctcp_shift_g_set,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(dctcp_shift_g_ops, dctcp_shift_g_set,
> + param_get_uint);
>
> module_param_cb(dctcp_shift_g, &dctcp_shift_g_ops, &dctcp_shift_g, 0644);
> MODULE_PARM_DESC(dctcp_shift_g, "parameter g for updating dctcp_alpha");
> diff --git a/net/sunrpc/auth.c b/net/sunrpc/auth.c
> index 68c0595ea2fd..64a3e894fd4c 100644
> --- a/net/sunrpc/auth.c
> +++ b/net/sunrpc/auth.c
> @@ -83,10 +83,8 @@ static int param_get_hashtbl_sz(char *buffer, const struct kernel_param *kp)
>
> #define param_check_hashtbl_sz(name, p) __param_check(name, p, unsigned int);
>
> -static const struct kernel_param_ops param_ops_hashtbl_sz = {
> - .set = param_set_hashtbl_sz,
> - .get = param_get_hashtbl_sz,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_hashtbl_sz, param_set_hashtbl_sz,
> + param_get_hashtbl_sz);
>
> module_param_named(auth_hashtable_size, auth_hashbits, hashtbl_sz, 0644);
> MODULE_PARM_DESC(auth_hashtable_size, "RPC credential cache hashtable size");
> diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c
> index 2e1fe6013361..e8d087798994 100644
> --- a/net/sunrpc/xprtsock.c
> +++ b/net/sunrpc/xprtsock.c
> @@ -3710,10 +3710,8 @@ static int param_set_portnr(const char *val, const struct kernel_param *kp)
> RPC_MAX_RESVPORT);
> }
>
> -static const struct kernel_param_ops param_ops_portnr = {
> - .set = param_set_portnr,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_portnr, param_set_portnr,
> + param_get_uint);
>
> #define param_check_portnr(name, p) \
> __param_check(name, p, unsigned int);
> @@ -3729,10 +3727,8 @@ static int param_set_slot_table_size(const char *val,
> RPC_MAX_SLOT_TABLE);
> }
>
> -static const struct kernel_param_ops param_ops_slot_table_size = {
> - .set = param_set_slot_table_size,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_slot_table_size,
> + param_set_slot_table_size, param_get_uint);
>
> #define param_check_slot_table_size(name, p) \
> __param_check(name, p, unsigned int);
> @@ -3745,10 +3741,8 @@ static int param_set_max_slot_table_size(const char *val,
> RPC_MAX_SLOT_TABLE_LIMIT);
> }
>
> -static const struct kernel_param_ops param_ops_max_slot_table_size = {
> - .set = param_set_max_slot_table_size,
> - .get = param_get_uint,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_max_slot_table_size,
> + param_set_max_slot_table_size, param_get_uint);
>
> #define param_check_max_slot_table_size(name, p) \
> __param_check(name, p, unsigned int);
> diff --git a/samples/damon/mtier.c b/samples/damon/mtier.c
> index 775838a23d93..c8018a7ea891 100644
> --- a/samples/damon/mtier.c
> +++ b/samples/damon/mtier.c
> @@ -38,10 +38,8 @@ module_param(node0_mem_free_bp, ulong, 0600);
> static int damon_sample_mtier_enable_store(
> const char *val, const struct kernel_param *kp);
>
> -static const struct kernel_param_ops enabled_param_ops = {
> - .set = damon_sample_mtier_enable_store,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops,
> + damon_sample_mtier_enable_store, param_get_bool);
>
> static bool enabled __read_mostly;
> module_param_cb(enabled, &enabled_param_ops, &enabled, 0600);
> diff --git a/samples/damon/prcl.c b/samples/damon/prcl.c
> index b7c50f2656ce..7cab9bd0f7bd 100644
> --- a/samples/damon/prcl.c
> +++ b/samples/damon/prcl.c
> @@ -22,10 +22,8 @@ module_param(target_pid, int, 0600);
> static int damon_sample_prcl_enable_store(
> const char *val, const struct kernel_param *kp);
>
> -static const struct kernel_param_ops enabled_param_ops = {
> - .set = damon_sample_prcl_enable_store,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops,
> + damon_sample_prcl_enable_store, param_get_bool);
>
> static bool enabled __read_mostly;
> module_param_cb(enabled, &enabled_param_ops, &enabled, 0600);
> diff --git a/samples/damon/wsse.c b/samples/damon/wsse.c
> index 799ad4443943..56634853bd0b 100644
> --- a/samples/damon/wsse.c
> +++ b/samples/damon/wsse.c
> @@ -23,10 +23,8 @@ module_param(target_pid, int, 0600);
> static int damon_sample_wsse_enable_store(
> const char *val, const struct kernel_param *kp);
>
> -static const struct kernel_param_ops enabled_param_ops = {
> - .set = damon_sample_wsse_enable_store,
> - .get = param_get_bool,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops,
> + damon_sample_wsse_enable_store, param_get_bool);
>
> static bool enabled __read_mostly;
> module_param_cb(enabled, &enabled_param_ops, &enabled, 0600);
> diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
> index 3491e9f60194..8a253c743363 100644
> --- a/security/apparmor/lsm.c
> +++ b/security/apparmor/lsm.c
> @@ -1767,38 +1767,30 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = {
> static int param_set_aabool(const char *val, const struct kernel_param *kp);
> static int param_get_aabool(char *buffer, const struct kernel_param *kp);
> #define param_check_aabool param_check_bool
> -static const struct kernel_param_ops param_ops_aabool = {
> - .flags = KERNEL_PARAM_OPS_FL_NOARG,
> - .set = param_set_aabool,
> - .get = param_get_aabool
> -};
> +static DEFINE_KERNEL_PARAM_OPS_NOARG(param_ops_aabool, param_set_aabool,
> + param_get_aabool);
>
> static int param_set_aauint(const char *val, const struct kernel_param *kp);
> static int param_get_aauint(char *buffer, const struct kernel_param *kp);
> #define param_check_aauint param_check_uint
> -static const struct kernel_param_ops param_ops_aauint = {
> - .set = param_set_aauint,
> - .get = param_get_aauint
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_aauint, param_set_aauint,
> + param_get_aauint);
>
> static int param_set_aacompressionlevel(const char *val,
> const struct kernel_param *kp);
> static int param_get_aacompressionlevel(char *buffer,
> const struct kernel_param *kp);
> #define param_check_aacompressionlevel param_check_int
> -static const struct kernel_param_ops param_ops_aacompressionlevel = {
> - .set = param_set_aacompressionlevel,
> - .get = param_get_aacompressionlevel
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_aacompressionlevel,
> + param_set_aacompressionlevel,
> + param_get_aacompressionlevel);
>
> static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp);
> static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp);
> #define param_check_aalockpolicy param_check_bool
> -static const struct kernel_param_ops param_ops_aalockpolicy = {
> - .flags = KERNEL_PARAM_OPS_FL_NOARG,
> - .set = param_set_aalockpolicy,
> - .get = param_get_aalockpolicy
> -};
> +static DEFINE_KERNEL_PARAM_OPS_NOARG(param_ops_aalockpolicy,
> + param_set_aalockpolicy,
> + param_get_aalockpolicy);
>
> static int param_set_debug(const char *val, const struct kernel_param *kp);
> static int param_get_debug(char *buffer, const struct kernel_param *kp);
> @@ -1879,10 +1871,8 @@ module_param_named(paranoid_load, aa_g_paranoid_load, aabool, S_IRUGO);
> static int param_get_aaintbool(char *buffer, const struct kernel_param *kp);
> static int param_set_aaintbool(const char *val, const struct kernel_param *kp);
> #define param_check_aaintbool param_check_int
> -static const struct kernel_param_ops param_ops_aaintbool = {
> - .set = param_set_aaintbool,
> - .get = param_get_aaintbool
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_aaintbool, param_set_aaintbool,
> + param_get_aaintbool);
> /* Boot time disable flag */
> static int apparmor_enabled __ro_after_init = 1;
> module_param_named(enabled, apparmor_enabled, aaintbool, 0444);
> diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c
> index c87d75dbd8aa..02bd61e67902 100644
> --- a/sound/hda/controllers/intel.c
> +++ b/sound/hda/controllers/intel.c
> @@ -164,10 +164,7 @@ MODULE_PARM_DESC(ctl_dev_id, "Use control device identifier (based on codec addr
>
> #ifdef CONFIG_PM
> static int param_set_xint(const char *val, const struct kernel_param *kp);
> -static const struct kernel_param_ops param_ops_xint = {
> - .set = param_set_xint,
> - .get = param_get_int,
> -};
> +static DEFINE_KERNEL_PARAM_OPS(param_ops_xint, param_set_xint, param_get_int);
> #define param_check_xint param_check_int
>
> static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
> diff --git a/sound/usb/card.c b/sound/usb/card.c
> index f42d72cd0378..34cbb9d72315 100644
> --- a/sound/usb/card.c
> +++ b/sound/usb/card.c
> @@ -118,11 +118,8 @@ static int param_set_quirkp(const char *val,
> return param_set_charp(val, kp);
> }
>
> -static const struct kernel_param_ops param_ops_quirkp = {
> - .set = param_set_quirkp,
> - .get = param_get_charp,
> - .free = param_free_charp,
> -};
> +static DEFINE_KERNEL_PARAM_OPS_FREE(param_ops_quirkp, param_set_quirkp,
> + param_get_charp, param_free_charp);
>
> #define param_check_quirkp param_check_charp
>
> --
> 2.34.1
>
^ permalink raw reply
* Re: [PATCH 09/11] treewide: Convert custom kernel_param_ops .get callbacks to seq_buf via cocci
From: Rafael J. Wysocki @ 2026-05-22 17:03 UTC (permalink / raw)
To: Kees Cook
Cc: Luis Chamberlain, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Jani Nikula,
Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, David Airlie,
Simona Vetter, Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
Benjamin Berg, Ilpo Järvinen, David E. Box,
Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
Heiko Carstens, Vasily Gorbik, Sean Christopherson, Paolo Bonzini,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Andrew Morton, John Johansen, Paul Moore, James Morris,
Serge E. Hallyn, Andy Shevchenko, Georgia Garcia, kvm, dmaengine,
linux-modules, kasan-dev, linux-mm, apparmor,
linux-security-module, linux-um, linux-acpi, openipmi-developer,
qemu-devel, intel-gfx, dri-devel, linux-rdma, linux-media,
linux-pci, linux-scsi, linux-pm, linuxppc-dev, linux-serial,
linux-usb, usb-storage, virtualization, linux-kernel, linux-arch,
netdev, linux-fsdevel, linux-hardening
In-Reply-To: <20260521133326.2465264-9-kees@kernel.org>
On Thu, May 21, 2026 at 3:33 PM Kees Cook <kees@kernel.org> wrote:
>
> Using the following Coccinelle script, convert struct kernel_param_ops
> .get callbacks from "char *" to "struct seq_buf *" when the only write
> to the buffer is via a final call of scnprintf(), snprintf(), sprintf(),
> or sysfs_emit().
>
> Since seq_buf_printf() will return -1 on overflow, and struct
> kernel_param_ops .get callbacks are expected to truncate without error,
> we must ignore the return value from seq_buf_print() and always return 0
> (as the length is calculated in the common dispatcher code).
>
> @@
> identifier FN, BUF, KP;
> expression FMT;
> expression list ARGS;
> @@
> int FN(
> - char *BUF
> + struct seq_buf *BUF
> , const struct kernel_param *KP)
> {
> ... when any
> (
> - return scnprintf(BUF, PAGE_SIZE, FMT, ARGS);
> |
> - return snprintf(BUF, PAGE_SIZE, FMT, ARGS);
> |
> - return sprintf(BUF, FMT, ARGS);
> |
> - return sysfs_emit(BUF, FMT, ARGS);
> )
> + seq_buf_printf(BUF, FMT, ARGS);
> + return 0;
> }
>
> No struct kernel_param_ops initializations need changing since
> DEFINE_KERNEL_PARAM_OPS already routes the pointer to .get or .get_str
> via _Generic based on the function signature, so converted callbacks
> are automatically moved from the .get_str to the .get callback.
>
> Signed-off-by: Kees Cook <kees@kernel.org>
For ACPI:
Acked-by: Rafael J. Wysocki (Intel) <rafael@kernel.org>
> ---
> arch/s390/kernel/perf_cpum_sf.c | 6 ++-
> arch/x86/kernel/msr.c | 5 +-
> arch/x86/kvm/vmx/vmx.c | 18 ++++---
> arch/x86/platform/uv/uv_nmi.c | 12 +++--
> drivers/acpi/ec.c | 14 ++++--
> drivers/acpi/sysfs.c | 6 ++-
> drivers/block/ublk_drv.c | 5 +-
> drivers/char/ipmi/ipmi_msghandler.c | 6 ++-
> drivers/firmware/qcom/qcom_scm.c | 12 +++--
> drivers/gpu/drm/drm_panic.c | 7 +--
> drivers/infiniband/hw/hfi1/driver.c | 7 +--
> drivers/infiniband/ulp/srpt/ib_srpt.c | 5 +-
> drivers/input/misc/ati_remote2.c | 10 ++--
> drivers/input/mouse/psmouse-base.c | 9 ++--
> drivers/md/md.c | 5 +-
> drivers/media/pci/tw686x/tw686x-core.c | 6 ++-
> drivers/nvme/host/multipath.c | 5 +-
> drivers/power/supply/test_power.c | 47 +++++++++++--------
> drivers/target/target_core_user.c | 12 +++--
> .../processor_thermal_soc_slider.c | 12 +++--
> drivers/ufs/core/ufs-fault-injection.c | 7 +--
> drivers/vhost/scsi.c | 5 +-
> fs/nfs/namespace.c | 6 ++-
> fs/ocfs2/dlmfs/dlmfs.c | 5 +-
> fs/overlayfs/copy_up.c | 5 +-
> kernel/locking/locktorture.c | 6 ++-
> kernel/rcu/tree.c | 6 ++-
> kernel/workqueue.c | 6 ++-
> lib/test_dynamic_debug.c | 6 ++-
> mm/damon/lru_sort.c | 14 +++---
> mm/damon/reclaim.c | 14 +++---
> mm/damon/stat.c | 10 ++--
> mm/memory_hotplug.c | 18 ++++---
> net/ceph/ceph_common.c | 5 +-
> net/sunrpc/auth.c | 6 ++-
> net/sunrpc/svc.c | 5 +-
> security/apparmor/lsm.c | 16 ++++---
> 37 files changed, 218 insertions(+), 131 deletions(-)
>
> diff --git a/arch/s390/kernel/perf_cpum_sf.c b/arch/s390/kernel/perf_cpum_sf.c
> index 76119542562b..75b0d441d238 100644
> --- a/arch/s390/kernel/perf_cpum_sf.c
> +++ b/arch/s390/kernel/perf_cpum_sf.c
> @@ -1991,11 +1991,13 @@ static int s390_pmu_sf_offline_cpu(unsigned int cpu)
> return cpusf_pmu_setup(cpu, PMC_RELEASE);
> }
>
> -static int param_get_sfb_size(char *buffer, const struct kernel_param *kp)
> +static int param_get_sfb_size(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> if (!cpum_sf_avail())
> return -ENODEV;
> - return sprintf(buffer, "%lu,%lu", CPUM_SF_MIN_SDB, CPUM_SF_MAX_SDB);
> + seq_buf_printf(buffer, "%lu,%lu", CPUM_SF_MIN_SDB, CPUM_SF_MAX_SDB);
> + return 0;
> }
>
> static int param_set_sfb_size(const char *val, const struct kernel_param *kp)
> diff --git a/arch/x86/kernel/msr.c b/arch/x86/kernel/msr.c
> index 5f4e1814dc4d..9f07f66c3cfc 100644
> --- a/arch/x86/kernel/msr.c
> +++ b/arch/x86/kernel/msr.c
> @@ -309,7 +309,7 @@ static int set_allow_writes(const char *val, const struct kernel_param *cp)
> return 0;
> }
>
> -static int get_allow_writes(char *buf, const struct kernel_param *kp)
> +static int get_allow_writes(struct seq_buf *buf, const struct kernel_param *kp)
> {
> const char *res;
>
> @@ -319,7 +319,8 @@ static int get_allow_writes(char *buf, const struct kernel_param *kp)
> default: res = "default"; break;
> }
>
> - return sprintf(buf, "%s\n", res);
> + seq_buf_printf(buf, "%s\n", res);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(allow_writes_ops, set_allow_writes,
> diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
> index 07f4c7209ac0..00317774a90b 100644
> --- a/arch/x86/kvm/vmx/vmx.c
> +++ b/arch/x86/kvm/vmx/vmx.c
> @@ -368,12 +368,16 @@ static int vmentry_l1d_flush_set(const char *s, const struct kernel_param *kp)
> return ret;
> }
>
> -static int vmentry_l1d_flush_get(char *s, const struct kernel_param *kp)
> +static int vmentry_l1d_flush_get(struct seq_buf *s,
> + const struct kernel_param *kp)
> {
> - if (WARN_ON_ONCE(l1tf_vmx_mitigation >= ARRAY_SIZE(vmentry_l1d_param)))
> - return sysfs_emit(s, "???\n");
> + if (WARN_ON_ONCE(l1tf_vmx_mitigation >= ARRAY_SIZE(vmentry_l1d_param))) {
> + seq_buf_printf(s, "???\n");
> + return 0;
> + }
>
> - return sysfs_emit(s, "%s\n", vmentry_l1d_param[l1tf_vmx_mitigation].option);
> + seq_buf_printf(s, "%s\n", vmentry_l1d_param[l1tf_vmx_mitigation].option);
> + return 0;
> }
>
> /*
> @@ -459,9 +463,11 @@ static int vmentry_l1d_flush_set(const char *s, const struct kernel_param *kp)
> pr_warn_once("Kernel compiled without mitigations, ignoring vmentry_l1d_flush\n");
> return 0;
> }
> -static int vmentry_l1d_flush_get(char *s, const struct kernel_param *kp)
> +static int vmentry_l1d_flush_get(struct seq_buf *s,
> + const struct kernel_param *kp)
> {
> - return sysfs_emit(s, "never\n");
> + seq_buf_printf(s, "never\n");
> + return 0;
> }
> #endif
>
> diff --git a/arch/x86/platform/uv/uv_nmi.c b/arch/x86/platform/uv/uv_nmi.c
> index a7ac80b5f8d9..c401369efe22 100644
> --- a/arch/x86/platform/uv/uv_nmi.c
> +++ b/arch/x86/platform/uv/uv_nmi.c
> @@ -111,9 +111,11 @@ module_param_named(dump_loglevel, uv_nmi_loglevel, int, 0644);
> * The following values show statistics on how perf events are affecting
> * this system.
> */
> -static int param_get_local64(char *buffer, const struct kernel_param *kp)
> +static int param_get_local64(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%lu\n", local64_read((local64_t *)kp->arg));
> + seq_buf_printf(buffer, "%lu\n", local64_read((local64_t *)kp->arg));
> + return 0;
> }
>
> static int param_set_local64(const char *val, const struct kernel_param *kp)
> @@ -207,9 +209,11 @@ static const char * const actions_desc[nmi_act_max] = {
>
> static enum action_t uv_nmi_action = nmi_act_dump;
>
> -static int param_get_action(char *buffer, const struct kernel_param *kp)
> +static int param_get_action(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%s\n", actions[uv_nmi_action]);
> + seq_buf_printf(buffer, "%s\n", actions[uv_nmi_action]);
> + return 0;
> }
>
> static int param_set_action(const char *val, const struct kernel_param *kp)
> diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c
> index 45204538ed87..6478e5290faf 100644
> --- a/drivers/acpi/ec.c
> +++ b/drivers/acpi/ec.c
> @@ -2236,18 +2236,22 @@ static int param_set_event_clearing(const char *val,
> return result;
> }
>
> -static int param_get_event_clearing(char *buffer,
> +static int param_get_event_clearing(struct seq_buf *buffer,
> const struct kernel_param *kp)
> {
> switch (ec_event_clearing) {
> case ACPI_EC_EVT_TIMING_STATUS:
> - return sprintf(buffer, "status\n");
> + seq_buf_printf(buffer, "status\n");
> + return 0;
> case ACPI_EC_EVT_TIMING_QUERY:
> - return sprintf(buffer, "query\n");
> + seq_buf_printf(buffer, "query\n");
> + return 0;
> case ACPI_EC_EVT_TIMING_EVENT:
> - return sprintf(buffer, "event\n");
> + seq_buf_printf(buffer, "event\n");
> + return 0;
> default:
> - return sprintf(buffer, "invalid\n");
> + seq_buf_printf(buffer, "invalid\n");
> + return 0;
> }
> return 0;
> }
> diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c
> index 3d32a5280432..5247ed7e05cc 100644
> --- a/drivers/acpi/sysfs.c
> +++ b/drivers/acpi/sysfs.c
> @@ -192,9 +192,11 @@ static int param_set_trace_method_name(const char *val,
> return 0;
> }
>
> -static int param_get_trace_method_name(char *buffer, const struct kernel_param *kp)
> +static int param_get_trace_method_name(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sysfs_emit(buffer, "%s\n", acpi_gbl_trace_method_name);
> + seq_buf_printf(buffer, "%s\n", acpi_gbl_trace_method_name);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(param_ops_trace_method,
> diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
> index f7bf7ea2d088..ea35662381bf 100644
> --- a/drivers/block/ublk_drv.c
> +++ b/drivers/block/ublk_drv.c
> @@ -5868,10 +5868,11 @@ static int ublk_set_max_unprivileged_ublks(const char *buf,
> return param_set_uint_minmax(buf, kp, 0, UBLK_MAX_UBLKS);
> }
>
> -static int ublk_get_max_unprivileged_ublks(char *buf,
> +static int ublk_get_max_unprivileged_ublks(struct seq_buf *buf,
> const struct kernel_param *kp)
> {
> - return sysfs_emit(buf, "%u\n", unprivileged_ublks_max);
> + seq_buf_printf(buf, "%u\n", unprivileged_ublks_max);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(ublk_max_unprivileged_ublks_ops,
> diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c
> index b5fed11707e8..45941605b88f 100644
> --- a/drivers/char/ipmi/ipmi_msghandler.c
> +++ b/drivers/char/ipmi/ipmi_msghandler.c
> @@ -90,7 +90,8 @@ static int panic_op_write_handler(const char *val,
> return 0;
> }
>
> -static int panic_op_read_handler(char *buffer, const struct kernel_param *kp)
> +static int panic_op_read_handler(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> const char *event_str;
>
> @@ -99,7 +100,8 @@ static int panic_op_read_handler(char *buffer, const struct kernel_param *kp)
> else
> event_str = ipmi_panic_event_str[ipmi_send_panic_event];
>
> - return sprintf(buffer, "%s\n", event_str);
> + seq_buf_printf(buffer, "%s\n", event_str);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(panic_op_ops, panic_op_write_handler,
> diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
> index ef57df53e087..1bdb497e354e 100644
> --- a/drivers/firmware/qcom/qcom_scm.c
> +++ b/drivers/firmware/qcom/qcom_scm.c
> @@ -2694,12 +2694,16 @@ static irqreturn_t qcom_scm_irq_handler(int irq, void *data)
> return IRQ_HANDLED;
> }
>
> -static int get_download_mode(char *buffer, const struct kernel_param *kp)
> +static int get_download_mode(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - if (download_mode >= ARRAY_SIZE(download_mode_name))
> - return sysfs_emit(buffer, "unknown mode\n");
> + if (download_mode >= ARRAY_SIZE(download_mode_name)) {
> + seq_buf_printf(buffer, "unknown mode\n");
> + return 0;
> + }
>
> - return sysfs_emit(buffer, "%s\n", download_mode_name[download_mode]);
> + seq_buf_printf(buffer, "%s\n", download_mode_name[download_mode]);
> + return 0;
> }
>
> static int set_download_mode(const char *val, const struct kernel_param *kp)
> diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c
> index c35d1adf2ce3..8b3b749284f0 100644
> --- a/drivers/gpu/drm/drm_panic.c
> +++ b/drivers/gpu/drm/drm_panic.c
> @@ -841,10 +841,11 @@ static int drm_panic_type_set(const char *val, const struct kernel_param *kp)
> return -EINVAL;
> }
>
> -static int drm_panic_type_get(char *buffer, const struct kernel_param *kp)
> +static int drm_panic_type_get(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return scnprintf(buffer, PAGE_SIZE, "%s\n",
> - drm_panic_type_map[drm_panic_type]);
> + seq_buf_printf(buffer, "%s\n", drm_panic_type_map[drm_panic_type]);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(drm_panic_ops, drm_panic_type_set,
> diff --git a/drivers/infiniband/hw/hfi1/driver.c b/drivers/infiniband/hw/hfi1/driver.c
> index 5b9b0b38b419..3c3f8d4db99d 100644
> --- a/drivers/infiniband/hw/hfi1/driver.c
> +++ b/drivers/infiniband/hw/hfi1/driver.c
> @@ -41,7 +41,7 @@ MODULE_PARM_DESC(cu, "Credit return units");
>
> unsigned long hfi1_cap_mask = HFI1_CAP_MASK_DEFAULT;
> static int hfi1_caps_set(const char *val, const struct kernel_param *kp);
> -static int hfi1_caps_get(char *buffer, const struct kernel_param *kp);
> +static int hfi1_caps_get(struct seq_buf *buffer, const struct kernel_param *kp);
> static DEFINE_KERNEL_PARAM_OPS(cap_ops, hfi1_caps_set, hfi1_caps_get);
> module_param_cb(cap_mask, &cap_ops, &hfi1_cap_mask, S_IWUSR | S_IRUGO);
> MODULE_PARM_DESC(cap_mask, "Bit mask of enabled/disabled HW features");
> @@ -101,14 +101,15 @@ static int hfi1_caps_set(const char *val, const struct kernel_param *kp)
> return ret;
> }
>
> -static int hfi1_caps_get(char *buffer, const struct kernel_param *kp)
> +static int hfi1_caps_get(struct seq_buf *buffer, const struct kernel_param *kp)
> {
> unsigned long cap_mask = *(unsigned long *)kp->arg;
>
> cap_mask &= ~HFI1_CAP_LOCKED_SMASK;
> cap_mask |= ((cap_mask & HFI1_CAP_K2U) << HFI1_CAP_USER_SHIFT);
>
> - return sysfs_emit(buffer, "0x%lx\n", cap_mask);
> + seq_buf_printf(buffer, "0x%lx\n", cap_mask);
> + return 0;
> }
>
> struct pci_dev *get_pci_dev(struct rvt_dev_info *rdi)
> diff --git a/drivers/infiniband/ulp/srpt/ib_srpt.c b/drivers/infiniband/ulp/srpt/ib_srpt.c
> index 9aec5d80117f..97c77d52a86a 100644
> --- a/drivers/infiniband/ulp/srpt/ib_srpt.c
> +++ b/drivers/infiniband/ulp/srpt/ib_srpt.c
> @@ -86,9 +86,10 @@ static int srpt_set_u64_x(const char *buffer, const struct kernel_param *kp)
> {
> return kstrtou64(buffer, 16, (u64 *)kp->arg);
> }
> -static int srpt_get_u64_x(char *buffer, const struct kernel_param *kp)
> +static int srpt_get_u64_x(struct seq_buf *buffer, const struct kernel_param *kp)
> {
> - return sprintf(buffer, "0x%016llx\n", *(u64 *)kp->arg);
> + seq_buf_printf(buffer, "0x%016llx\n", *(u64 *)kp->arg);
> + return 0;
> }
> module_param_call(srpt_service_guid, srpt_set_u64_x, srpt_get_u64_x,
> &srpt_service_guid, 0444);
> diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c
> index 8b4ef7e163d3..d101fe1c2c4c 100644
> --- a/drivers/input/misc/ati_remote2.c
> +++ b/drivers/input/misc/ati_remote2.c
> @@ -63,12 +63,13 @@ static int ati_remote2_set_channel_mask(const char *val,
> return ati_remote2_set_mask(val, kp, ATI_REMOTE2_MAX_CHANNEL_MASK);
> }
>
> -static int ati_remote2_get_channel_mask(char *buffer,
> +static int ati_remote2_get_channel_mask(struct seq_buf *buffer,
> const struct kernel_param *kp)
> {
> pr_debug("%s()\n", __func__);
>
> - return sprintf(buffer, "0x%04x\n", *(unsigned int *)kp->arg);
> + seq_buf_printf(buffer, "0x%04x\n", *(unsigned int *)kp->arg);
> + return 0;
> }
>
> static int ati_remote2_set_mode_mask(const char *val,
> @@ -79,12 +80,13 @@ static int ati_remote2_set_mode_mask(const char *val,
> return ati_remote2_set_mask(val, kp, ATI_REMOTE2_MAX_MODE_MASK);
> }
>
> -static int ati_remote2_get_mode_mask(char *buffer,
> +static int ati_remote2_get_mode_mask(struct seq_buf *buffer,
> const struct kernel_param *kp)
> {
> pr_debug("%s()\n", __func__);
>
> - return sprintf(buffer, "0x%02x\n", *(unsigned int *)kp->arg);
> + seq_buf_printf(buffer, "0x%02x\n", *(unsigned int *)kp->arg);
> + return 0;
> }
>
> static unsigned int channel_mask = ATI_REMOTE2_MAX_CHANNEL_MASK;
> diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
> index f9ebb1fd0b6f..39a9b87e69d1 100644
> --- a/drivers/input/mouse/psmouse-base.c
> +++ b/drivers/input/mouse/psmouse-base.c
> @@ -44,7 +44,8 @@ MODULE_LICENSE("GPL");
>
> static unsigned int psmouse_max_proto = PSMOUSE_AUTO;
> static int psmouse_set_maxproto(const char *val, const struct kernel_param *);
> -static int psmouse_get_maxproto(char *buffer, const struct kernel_param *kp);
> +static int psmouse_get_maxproto(struct seq_buf *buffer,
> + const struct kernel_param *kp);
> static DEFINE_KERNEL_PARAM_OPS(param_ops_proto_abbrev, psmouse_set_maxproto,
> psmouse_get_maxproto);
> #define param_check_proto_abbrev(name, p) __param_check(name, p, unsigned int)
> @@ -1994,11 +1995,13 @@ static int psmouse_set_maxproto(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static int psmouse_get_maxproto(char *buffer, const struct kernel_param *kp)
> +static int psmouse_get_maxproto(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> int type = *((unsigned int *)kp->arg);
>
> - return sprintf(buffer, "%s\n", psmouse_protocol_by_type(type)->name);
> + seq_buf_printf(buffer, "%s\n", psmouse_protocol_by_type(type)->name);
> + return 0;
> }
>
> static int __init psmouse_init(void)
> diff --git a/drivers/md/md.c b/drivers/md/md.c
> index 8b568eee8743..ce3eb1396ad0 100644
> --- a/drivers/md/md.c
> +++ b/drivers/md/md.c
> @@ -10989,9 +10989,10 @@ static __exit void md_exit(void)
> subsys_initcall(md_init);
> module_exit(md_exit)
>
> -static int get_ro(char *buffer, const struct kernel_param *kp)
> +static int get_ro(struct seq_buf *buffer, const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%d\n", start_readonly);
> + seq_buf_printf(buffer, "%d\n", start_readonly);
> + return 0;
> }
> static int set_ro(const char *val, const struct kernel_param *kp)
> {
> diff --git a/drivers/media/pci/tw686x/tw686x-core.c b/drivers/media/pci/tw686x/tw686x-core.c
> index a10e38221817..35a6ff8d77fc 100644
> --- a/drivers/media/pci/tw686x/tw686x-core.c
> +++ b/drivers/media/pci/tw686x/tw686x-core.c
> @@ -69,9 +69,11 @@ static const char *dma_mode_name(unsigned int mode)
> }
> }
>
> -static int tw686x_dma_mode_get(char *buffer, const struct kernel_param *kp)
> +static int tw686x_dma_mode_get(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%s", dma_mode_name(dma_mode));
> + seq_buf_printf(buffer, "%s", dma_mode_name(dma_mode));
> + return 0;
> }
>
> static int tw686x_dma_mode_set(const char *val, const struct kernel_param *kp)
> diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c
> index f7362377e427..e0c87447074d 100644
> --- a/drivers/nvme/host/multipath.c
> +++ b/drivers/nvme/host/multipath.c
> @@ -85,9 +85,10 @@ static int nvme_set_iopolicy(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static int nvme_get_iopolicy(char *buf, const struct kernel_param *kp)
> +static int nvme_get_iopolicy(struct seq_buf *buf, const struct kernel_param *kp)
> {
> - return sprintf(buf, "%s\n", nvme_iopolicy_names[iopolicy]);
> + seq_buf_printf(buf, "%s\n", nvme_iopolicy_names[iopolicy]);
> + return 0;
> }
>
> module_param_call(iopolicy, nvme_set_iopolicy, nvme_get_iopolicy,
> diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c
> index 0bf2bef3383a..9dcd588ab5c9 100644
> --- a/drivers/power/supply/test_power.c
> +++ b/drivers/power/supply/test_power.c
> @@ -490,10 +490,12 @@ static int param_set_ac_online(const char *key, const struct kernel_param *kp)
> return 0;
> }
>
> -static int param_get_ac_online(char *buffer, const struct kernel_param *kp)
> +static int param_get_ac_online(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%s\n",
> - map_get_key(map_ac_online, ac_online, "unknown"));
> + seq_buf_printf(buffer, "%s\n",
> + map_get_key(map_ac_online, ac_online, "unknown"));
> + return 0;
> }
>
> static int param_set_usb_online(const char *key, const struct kernel_param *kp)
> @@ -503,10 +505,12 @@ static int param_set_usb_online(const char *key, const struct kernel_param *kp)
> return 0;
> }
>
> -static int param_get_usb_online(char *buffer, const struct kernel_param *kp)
> +static int param_get_usb_online(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%s\n",
> - map_get_key(map_ac_online, usb_online, "unknown"));
> + seq_buf_printf(buffer, "%s\n",
> + map_get_key(map_ac_online, usb_online, "unknown"));
> + return 0;
> }
>
> static int param_set_battery_status(const char *key,
> @@ -517,10 +521,12 @@ static int param_set_battery_status(const char *key,
> return 0;
> }
>
> -static int param_get_battery_status(char *buffer, const struct kernel_param *kp)
> +static int param_get_battery_status(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%s\n",
> - map_get_key(map_ac_online, battery_status, "unknown"));
> + seq_buf_printf(buffer, "%s\n",
> + map_get_key(map_ac_online, battery_status, "unknown"));
> + return 0;
> }
>
> static int param_set_battery_health(const char *key,
> @@ -531,10 +537,12 @@ static int param_set_battery_health(const char *key,
> return 0;
> }
>
> -static int param_get_battery_health(char *buffer, const struct kernel_param *kp)
> +static int param_get_battery_health(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%s\n",
> - map_get_key(map_ac_online, battery_health, "unknown"));
> + seq_buf_printf(buffer, "%s\n",
> + map_get_key(map_ac_online, battery_health, "unknown"));
> + return 0;
> }
>
> static int param_set_battery_present(const char *key,
> @@ -545,11 +553,12 @@ static int param_set_battery_present(const char *key,
> return 0;
> }
>
> -static int param_get_battery_present(char *buffer,
> +static int param_get_battery_present(struct seq_buf *buffer,
> const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%s\n",
> - map_get_key(map_ac_online, battery_present, "unknown"));
> + seq_buf_printf(buffer, "%s\n",
> + map_get_key(map_ac_online, battery_present, "unknown"));
> + return 0;
> }
>
> static int param_set_battery_technology(const char *key,
> @@ -561,12 +570,12 @@ static int param_set_battery_technology(const char *key,
> return 0;
> }
>
> -static int param_get_battery_technology(char *buffer,
> +static int param_get_battery_technology(struct seq_buf *buffer,
> const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%s\n",
> - map_get_key(map_ac_online, battery_technology,
> - "unknown"));
> + seq_buf_printf(buffer, "%s\n",
> + map_get_key(map_ac_online, battery_technology, "unknown"));
> + return 0;
> }
>
> static int param_set_battery_capacity(const char *key,
> diff --git a/drivers/target/target_core_user.c b/drivers/target/target_core_user.c
> index 676a12b44e88..5e8817a63726 100644
> --- a/drivers/target/target_core_user.c
> +++ b/drivers/target/target_core_user.c
> @@ -249,10 +249,11 @@ static int tcmu_set_global_max_data_area(const char *str,
> return 0;
> }
>
> -static int tcmu_get_global_max_data_area(char *buffer,
> +static int tcmu_get_global_max_data_area(struct seq_buf *buffer,
> const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%d\n", TCMU_PAGES_TO_MBS(tcmu_global_max_pages));
> + seq_buf_printf(buffer, "%d\n", TCMU_PAGES_TO_MBS(tcmu_global_max_pages));
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(tcmu_global_max_data_area_op,
> @@ -265,11 +266,12 @@ MODULE_PARM_DESC(global_max_data_area_mb,
> "Max MBs allowed to be allocated to all the tcmu device's "
> "data areas.");
>
> -static int tcmu_get_block_netlink(char *buffer,
> +static int tcmu_get_block_netlink(struct seq_buf *buffer,
> const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%s\n", tcmu_netlink_blocked ?
> - "blocked" : "unblocked");
> + seq_buf_printf(buffer, "%s\n",
> + tcmu_netlink_blocked ? "blocked" : "unblocked");
> + return 0;
> }
>
> static int tcmu_set_block_netlink(const char *str,
> diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
> index 68275c3f2c9b..1a68721748d9 100644
> --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
> +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
> @@ -77,10 +77,12 @@ static int slider_def_balance_set(const char *arg, const struct kernel_param *kp
> return ret;
> }
>
> -static int slider_def_balance_get(char *buf, const struct kernel_param *kp)
> +static int slider_def_balance_get(struct seq_buf *buf,
> + const struct kernel_param *kp)
> {
> guard(mutex)(&slider_param_lock);
> - return sysfs_emit(buf, "%02x\n", slider_values[SOC_POWER_SLIDER_BALANCE]);
> + seq_buf_printf(buf, "%02x\n", slider_values[SOC_POWER_SLIDER_BALANCE]);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(slider_def_balance_ops, slider_def_balance_set,
> @@ -109,10 +111,12 @@ static int slider_def_offset_set(const char *arg, const struct kernel_param *kp)
> return ret;
> }
>
> -static int slider_def_offset_get(char *buf, const struct kernel_param *kp)
> +static int slider_def_offset_get(struct seq_buf *buf,
> + const struct kernel_param *kp)
> {
> guard(mutex)(&slider_param_lock);
> - return sysfs_emit(buf, "%02x\n", slider_offset);
> + seq_buf_printf(buf, "%02x\n", slider_offset);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(slider_offset_ops, slider_def_offset_set,
> diff --git a/drivers/ufs/core/ufs-fault-injection.c b/drivers/ufs/core/ufs-fault-injection.c
> index 7d2873da7dc5..88f348b41614 100644
> --- a/drivers/ufs/core/ufs-fault-injection.c
> +++ b/drivers/ufs/core/ufs-fault-injection.c
> @@ -8,7 +8,7 @@
> #include <ufs/ufshcd.h>
> #include "ufs-fault-injection.h"
>
> -static int ufs_fault_get(char *buffer, const struct kernel_param *kp);
> +static int ufs_fault_get(struct seq_buf *buffer, const struct kernel_param *kp);
> static int ufs_fault_set(const char *val, const struct kernel_param *kp);
>
> static DEFINE_KERNEL_PARAM_OPS(ufs_fault_ops, ufs_fault_set, ufs_fault_get);
> @@ -31,11 +31,12 @@ MODULE_PARM_DESC(timeout,
> "Fault injection. timeout=<interval>,<probability>,<space>,<times>");
> static DECLARE_FAULT_ATTR(ufs_timeout_attr);
>
> -static int ufs_fault_get(char *buffer, const struct kernel_param *kp)
> +static int ufs_fault_get(struct seq_buf *buffer, const struct kernel_param *kp)
> {
> const char *fault_str = kp->arg;
>
> - return sysfs_emit(buffer, "%s\n", fault_str);
> + seq_buf_printf(buffer, "%s\n", fault_str);
> + return 0;
> }
>
> static int ufs_fault_set(const char *val, const struct kernel_param *kp)
> diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c
> index fd52f2213e27..23ca63ebf3d2 100644
> --- a/drivers/vhost/scsi.c
> +++ b/drivers/vhost/scsi.c
> @@ -81,10 +81,11 @@ static int vhost_scsi_set_inline_sg_cnt(const char *buf,
> }
> #endif
>
> -static int vhost_scsi_get_inline_sg_cnt(char *buf,
> +static int vhost_scsi_get_inline_sg_cnt(struct seq_buf *buf,
> const struct kernel_param *kp)
> {
> - return sprintf(buf, "%u\n", vhost_scsi_inline_sg_cnt);
> + seq_buf_printf(buf, "%u\n", vhost_scsi_inline_sg_cnt);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(vhost_scsi_inline_sg_cnt_op,
> diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c
> index f2fba60dc5ed..5b7debe5274b 100644
> --- a/fs/nfs/namespace.c
> +++ b/fs/nfs/namespace.c
> @@ -358,7 +358,8 @@ static int param_set_nfs_timeout(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static int param_get_nfs_timeout(char *buffer, const struct kernel_param *kp)
> +static int param_get_nfs_timeout(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> long num = *((int *)kp->arg);
>
> @@ -369,7 +370,8 @@ static int param_get_nfs_timeout(char *buffer, const struct kernel_param *kp)
> num = (num + (HZ - 1)) / HZ;
> } else
> num = -1;
> - return sysfs_emit(buffer, "%li\n", num);
> + seq_buf_printf(buffer, "%li\n", num);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(param_ops_nfs_timeout, param_set_nfs_timeout,
> diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c
> index 5821e33df78f..8fd759d31ff9 100644
> --- a/fs/ocfs2/dlmfs/dlmfs.c
> +++ b/fs/ocfs2/dlmfs/dlmfs.c
> @@ -78,10 +78,11 @@ static int param_set_dlmfs_capabilities(const char *val,
> printk(KERN_ERR "%s: readonly parameter\n", kp->name);
> return -EINVAL;
> }
> -static int param_get_dlmfs_capabilities(char *buffer,
> +static int param_get_dlmfs_capabilities(struct seq_buf *buffer,
> const struct kernel_param *kp)
> {
> - return sysfs_emit(buffer, DLMFS_CAPABILITIES);
> + seq_buf_printf(buffer, DLMFS_CAPABILITIES);
> + return 0;
> }
> module_param_call(capabilities, param_set_dlmfs_capabilities,
> param_get_dlmfs_capabilities, NULL, 0444);
> diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
> index 13cb60b52bd6..d9a21b813b4f 100644
> --- a/fs/overlayfs/copy_up.c
> +++ b/fs/overlayfs/copy_up.c
> @@ -28,9 +28,10 @@ static int ovl_ccup_set(const char *buf, const struct kernel_param *param)
> return 0;
> }
>
> -static int ovl_ccup_get(char *buf, const struct kernel_param *param)
> +static int ovl_ccup_get(struct seq_buf *buf, const struct kernel_param *param)
> {
> - return sprintf(buf, "N\n");
> + seq_buf_printf(buf, "N\n");
> + return 0;
> }
>
> module_param_call(check_copy_up, ovl_ccup_set, ovl_ccup_get, NULL, 0644);
> diff --git a/kernel/locking/locktorture.c b/kernel/locking/locktorture.c
> index 38ae3b596ef2..9c9b6dc25888 100644
> --- a/kernel/locking/locktorture.c
> +++ b/kernel/locking/locktorture.c
> @@ -86,11 +86,13 @@ static int param_set_cpumask(const char *val, const struct kernel_param *kp)
> }
>
> // Output a cpumask kernel parameter.
> -static int param_get_cpumask(char *buffer, const struct kernel_param *kp)
> +static int param_get_cpumask(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> cpumask_var_t *cm_bind = kp->arg;
>
> - return sprintf(buffer, "%*pbl", cpumask_pr_args(*cm_bind));
> + seq_buf_printf(buffer, "%*pbl", cpumask_pr_args(*cm_bind));
> + return 0;
> }
>
> static bool cpumask_nonempty(cpumask_var_t mask)
> diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
> index e675d7f1b4ee..ffbbb7d4ff2a 100644
> --- a/kernel/rcu/tree.c
> +++ b/kernel/rcu/tree.c
> @@ -3970,9 +3970,11 @@ static int param_set_do_rcu_barrier(const char *val, const struct kernel_param *
> /*
> * Output the number of outstanding rcutree.do_rcu_barrier requests.
> */
> -static int param_get_do_rcu_barrier(char *buffer, const struct kernel_param *kp)
> +static int param_get_do_rcu_barrier(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%d\n", atomic_read((atomic_t *)kp->arg));
> + seq_buf_printf(buffer, "%d\n", atomic_read((atomic_t *)kp->arg));
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(do_rcu_barrier_ops, param_set_do_rcu_barrier,
> diff --git a/kernel/workqueue.c b/kernel/workqueue.c
> index 42562b811d94..3fe338d2ca64 100644
> --- a/kernel/workqueue.c
> +++ b/kernel/workqueue.c
> @@ -7157,9 +7157,11 @@ static int wq_affn_dfl_set(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static int wq_affn_dfl_get(char *buffer, const struct kernel_param *kp)
> +static int wq_affn_dfl_get(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return scnprintf(buffer, PAGE_SIZE, "%s\n", wq_affn_names[wq_affn_dfl]);
> + seq_buf_printf(buffer, "%s\n", wq_affn_names[wq_affn_dfl]);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(wq_affn_dfl_ops, wq_affn_dfl_set,
> diff --git a/lib/test_dynamic_debug.c b/lib/test_dynamic_debug.c
> index 30880b6c726a..70faf8ede76d 100644
> --- a/lib/test_dynamic_debug.c
> +++ b/lib/test_dynamic_debug.c
> @@ -18,10 +18,12 @@ static int param_set_do_prints(const char *instr, const struct kernel_param *kp)
> do_prints();
> return 0;
> }
> -static int param_get_do_prints(char *buffer, const struct kernel_param *kp)
> +static int param_get_do_prints(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> do_prints();
> - return scnprintf(buffer, PAGE_SIZE, "did do_prints\n");
> + seq_buf_printf(buffer, "did do_prints\n");
> + return 0;
> }
> static DEFINE_KERNEL_PARAM_OPS(param_ops_do_prints, param_set_do_prints,
> param_get_do_prints);
> diff --git a/mm/damon/lru_sort.c b/mm/damon/lru_sort.c
> index 5feb93c5262e..84e607f76126 100644
> --- a/mm/damon/lru_sort.c
> +++ b/mm/damon/lru_sort.c
> @@ -438,10 +438,11 @@ static int damon_lru_sort_enabled_store(const char *val,
> return damon_lru_sort_turn(enabled);
> }
>
> -static int damon_lru_sort_enabled_load(char *buffer,
> - const struct kernel_param *kp)
> +static int damon_lru_sort_enabled_load(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%c\n", damon_lru_sort_enabled() ? 'Y' : 'N');
> + seq_buf_printf(buffer, "%c\n", damon_lru_sort_enabled() ? 'Y' : 'N');
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops, damon_lru_sort_enabled_store,
> @@ -461,8 +462,8 @@ static int damon_lru_sort_kdamond_pid_store(const char *val,
> return 0;
> }
>
> -static int damon_lru_sort_kdamond_pid_load(char *buffer,
> - const struct kernel_param *kp)
> +static int damon_lru_sort_kdamond_pid_load(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> int kdamond_pid = -1;
>
> @@ -471,7 +472,8 @@ static int damon_lru_sort_kdamond_pid_load(char *buffer,
> if (kdamond_pid < 0)
> kdamond_pid = -1;
> }
> - return sprintf(buffer, "%d\n", kdamond_pid);
> + seq_buf_printf(buffer, "%d\n", kdamond_pid);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(kdamond_pid_param_ops,
> diff --git a/mm/damon/reclaim.c b/mm/damon/reclaim.c
> index 27e772b095fa..546bdf356a40 100644
> --- a/mm/damon/reclaim.c
> +++ b/mm/damon/reclaim.c
> @@ -340,10 +340,11 @@ static int damon_reclaim_enabled_store(const char *val,
> return damon_reclaim_turn(enabled);
> }
>
> -static int damon_reclaim_enabled_load(char *buffer,
> - const struct kernel_param *kp)
> +static int damon_reclaim_enabled_load(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%c\n", damon_reclaim_enabled() ? 'Y' : 'N');
> + seq_buf_printf(buffer, "%c\n", damon_reclaim_enabled() ? 'Y' : 'N');
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops, damon_reclaim_enabled_store,
> @@ -363,8 +364,8 @@ static int damon_reclaim_kdamond_pid_store(const char *val,
> return 0;
> }
>
> -static int damon_reclaim_kdamond_pid_load(char *buffer,
> - const struct kernel_param *kp)
> +static int damon_reclaim_kdamond_pid_load(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> int kdamond_pid = -1;
>
> @@ -373,7 +374,8 @@ static int damon_reclaim_kdamond_pid_load(char *buffer,
> if (kdamond_pid < 0)
> kdamond_pid = -1;
> }
> - return sprintf(buffer, "%d\n", kdamond_pid);
> + seq_buf_printf(buffer, "%d\n", kdamond_pid);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(kdamond_pid_param_ops,
> diff --git a/mm/damon/stat.c b/mm/damon/stat.c
> index 6eb548793802..70d6b477fc0b 100644
> --- a/mm/damon/stat.c
> +++ b/mm/damon/stat.c
> @@ -19,8 +19,8 @@
> static int damon_stat_enabled_store(
> const char *val, const struct kernel_param *kp);
>
> -static int damon_stat_enabled_load(char *buffer,
> - const struct kernel_param *kp);
> +static int damon_stat_enabled_load(struct seq_buf *buffer,
> + const struct kernel_param *kp);
>
> static DEFINE_KERNEL_PARAM_OPS(enabled_param_ops, damon_stat_enabled_store,
> damon_stat_enabled_load);
> @@ -306,9 +306,11 @@ static int damon_stat_enabled_store(
> return 0;
> }
>
> -static int damon_stat_enabled_load(char *buffer, const struct kernel_param *kp)
> +static int damon_stat_enabled_load(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%c\n", damon_stat_enabled() ? 'Y' : 'N');
> + seq_buf_printf(buffer, "%c\n", damon_stat_enabled() ? 'Y' : 'N');
> + return 0;
> }
>
> static int __init damon_stat_init(void)
> diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
> index 42e0cf313281..887c18a193ac 100644
> --- a/mm/memory_hotplug.c
> +++ b/mm/memory_hotplug.c
> @@ -100,13 +100,17 @@ static int set_memmap_mode(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static int get_memmap_mode(char *buffer, const struct kernel_param *kp)
> +static int get_memmap_mode(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> int mode = *((int *)kp->arg);
>
> - if (mode == MEMMAP_ON_MEMORY_FORCE)
> - return sprintf(buffer, "force\n");
> - return sprintf(buffer, "%c\n", mode ? 'Y' : 'N');
> + if (mode == MEMMAP_ON_MEMORY_FORCE) {
> + seq_buf_printf(buffer, "force\n");
> + return 0;
> + }
> + seq_buf_printf(buffer, "%c\n", mode ? 'Y' : 'N');
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(memmap_mode_ops, set_memmap_mode,
> @@ -147,9 +151,11 @@ static int set_online_policy(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static int get_online_policy(char *buffer, const struct kernel_param *kp)
> +static int get_online_policy(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - return sprintf(buffer, "%s\n", online_policy_to_str[*((int *)kp->arg)]);
> + seq_buf_printf(buffer, "%s\n", online_policy_to_str[*((int *)kp->arg)]);
> + return 0;
> }
>
> /*
> diff --git a/net/ceph/ceph_common.c b/net/ceph/ceph_common.c
> index 633202a99e4a..583b11a2489c 100644
> --- a/net/ceph/ceph_common.c
> +++ b/net/ceph/ceph_common.c
> @@ -47,10 +47,11 @@ bool libceph_compatible(void *data)
> }
> EXPORT_SYMBOL(libceph_compatible);
>
> -static int param_get_supported_features(char *buffer,
> +static int param_get_supported_features(struct seq_buf *buffer,
> const struct kernel_param *kp)
> {
> - return sprintf(buffer, "0x%llx", CEPH_FEATURES_SUPPORTED_DEFAULT);
> + seq_buf_printf(buffer, "0x%llx", CEPH_FEATURES_SUPPORTED_DEFAULT);
> + return 0;
> }
> static DEFINE_KERNEL_PARAM_OPS(param_ops_supported_features, NULL,
> param_get_supported_features);
> diff --git a/net/sunrpc/auth.c b/net/sunrpc/auth.c
> index 64a3e894fd4c..5a2b64dcf9e5 100644
> --- a/net/sunrpc/auth.c
> +++ b/net/sunrpc/auth.c
> @@ -73,12 +73,14 @@ static int param_set_hashtbl_sz(const char *val, const struct kernel_param *kp)
> return -EINVAL;
> }
>
> -static int param_get_hashtbl_sz(char *buffer, const struct kernel_param *kp)
> +static int param_get_hashtbl_sz(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> unsigned int nbits;
>
> nbits = *(unsigned int *)kp->arg;
> - return sprintf(buffer, "%u\n", 1U << nbits);
> + seq_buf_printf(buffer, "%u\n", 1U << nbits);
> + return 0;
> }
>
> #define param_check_hashtbl_sz(name, p) __param_check(name, p, unsigned int);
> diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
> index 576fa42e7abf..26b85077ecc8 100644
> --- a/net/sunrpc/svc.c
> +++ b/net/sunrpc/svc.c
> @@ -148,7 +148,7 @@ sunrpc_get_pool_mode(char *buf, size_t size)
> EXPORT_SYMBOL(sunrpc_get_pool_mode);
>
> static int
> -param_get_pool_mode(char *buf, const struct kernel_param *kp)
> +param_get_pool_mode(struct seq_buf *buf, const struct kernel_param *kp)
> {
> char str[16];
> int len;
> @@ -162,7 +162,8 @@ param_get_pool_mode(char *buf, const struct kernel_param *kp)
> str[len] = '\n';
> str[len + 1] = '\0';
>
> - return sysfs_emit(buf, "%s", str);
> + seq_buf_printf(buf, "%s", str);
> + return 0;
> }
>
> module_param_call(pool_mode, param_set_pool_mode, param_get_pool_mode,
> diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
> index a6815b4bd0da..748d08c57f60 100644
> --- a/security/apparmor/lsm.c
> +++ b/security/apparmor/lsm.c
> @@ -1797,10 +1797,11 @@ static int param_set_debug(const char *val, const struct kernel_param *kp);
> static int param_get_debug(struct seq_buf *buffer, const struct kernel_param *kp);
>
> static int param_set_audit(const char *val, const struct kernel_param *kp);
> -static int param_get_audit(char *buffer, const struct kernel_param *kp);
> +static int param_get_audit(struct seq_buf *buffer,
> + const struct kernel_param *kp);
>
> static int param_set_mode(const char *val, const struct kernel_param *kp);
> -static int param_get_mode(char *buffer, const struct kernel_param *kp);
> +static int param_get_mode(struct seq_buf *buffer, const struct kernel_param *kp);
>
> /* Flag values, also controllable via /sys/module/apparmor/parameters
> * We define special types as we want to do additional mediation.
> @@ -2050,13 +2051,15 @@ static int param_set_debug(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static int param_get_audit(char *buffer, const struct kernel_param *kp)
> +static int param_get_audit(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> if (!apparmor_enabled)
> return -EINVAL;
> if (apparmor_initialized && !aa_current_policy_view_capable(NULL))
> return -EPERM;
> - return sysfs_emit(buffer, "%s\n", audit_mode_names[aa_g_audit]);
> + seq_buf_printf(buffer, "%s\n", audit_mode_names[aa_g_audit]);
> + return 0;
> }
>
> static int param_set_audit(const char *val, const struct kernel_param *kp)
> @@ -2078,13 +2081,14 @@ static int param_set_audit(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static int param_get_mode(char *buffer, const struct kernel_param *kp)
> +static int param_get_mode(struct seq_buf *buffer, const struct kernel_param *kp)
> {
> if (!apparmor_enabled)
> return -EINVAL;
> if (apparmor_initialized && !aa_current_policy_view_capable(NULL))
> return -EPERM;
> - return sysfs_emit(buffer, "%s\n", aa_profile_mode_names[aa_g_profile_mode]);
> + seq_buf_printf(buffer, "%s\n", aa_profile_mode_names[aa_g_profile_mode]);
> + return 0;
> }
>
> static int param_set_mode(const char *val, const struct kernel_param *kp)
> --
> 2.34.1
>
^ permalink raw reply
* Re: [PATCH 10/11] treewide: Manually convert custom kernel_param_ops .get callbacks
From: Rafael J. Wysocki @ 2026-05-22 17:05 UTC (permalink / raw)
To: Kees Cook
Cc: Luis Chamberlain, Pengpeng Hou, Petr Pavlu, Richard Weinberger,
Anton Ivanov, Johannes Berg, Rafael J. Wysocki, Len Brown,
Corey Minyard, Gabriel Somlo, Michael S. Tsirkin, Jani Nikula,
Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, David Airlie,
Simona Vetter, Bart Van Assche, Jason Gunthorpe, Leon Romanovsky,
Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
Bjorn Helgaas, Hannes Reinecke, James E.J. Bottomley,
Martin K. Petersen, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Greg Kroah-Hartman, Jiri Slaby, Alan Stern, Jason Wang, Xuan Zhuo,
Eugenio Pérez, Jason Baron, Jim Cromie, Tiwei Bie,
Benjamin Berg, Ilpo Järvinen, David E. Box,
Maciej W. Rozycki, Srinivas Pandruvada, Peter Zijlstra,
Heiko Carstens, Vasily Gorbik, Sean Christopherson, Paolo Bonzini,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Vinod Koul, Frank Li, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Andrew Morton, John Johansen, Paul Moore, James Morris,
Serge E. Hallyn, Andy Shevchenko, Georgia Garcia, kvm, dmaengine,
linux-modules, kasan-dev, linux-mm, apparmor,
linux-security-module, linux-um, linux-acpi, openipmi-developer,
qemu-devel, intel-gfx, dri-devel, linux-rdma, linux-media,
linux-pci, linux-scsi, linux-pm, linuxppc-dev, linux-serial,
linux-usb, usb-storage, virtualization, linux-kernel, linux-arch,
netdev, linux-fsdevel, linux-hardening
In-Reply-To: <20260521133326.2465264-10-kees@kernel.org>
On Thu, May 21, 2026 at 3:33 PM Kees Cook <kees@kernel.org> wrote:
>
> Convert struct kernel_param_ops .get callbacks from legacy "char *" to
> "struct seq_buf *".
>
> Since seq_buf_printf() will return -1 on overflow, and struct
> kernel_param_ops .get callbacks are expected to truncate without error,
> we must ignore the return value from seq_buf_print() and always return 0
> (as the length is calculated in the common dispatcher code).
>
> No struct kernel_param_ops initializations need changing since
> DEFINE_KERNEL_PARAM_OPS already routes the pointer to .get or .get_str
> via _Generic based on the function signature, so converted callbacks
> are automatically moved from the .get_str to the .get callback.
>
> Signed-off-by: Kees Cook <kees@kernel.org>
For ACPI:
Acked-by: Rafael J. Wysocki (Intel) <rafael@kernel.org>
> ---
> include/linux/dynamic_debug.h | 8 ++-
> arch/um/drivers/vfio_kern.c | 3 +-
> arch/um/drivers/virtio_uml.c | 12 ++--
> drivers/acpi/button.c | 19 ++++--
> drivers/acpi/sysfs.c | 83 +++++++++++-------------
> drivers/char/ipmi/ipmi_watchdog.c | 33 ++++------
> drivers/firmware/qemu_fw_cfg.c | 34 +++++-----
> drivers/gpu/drm/i915/i915_mitigations.c | 26 ++++----
> drivers/infiniband/ulp/srp/ib_srp.c | 7 +-
> drivers/media/usb/uvc/uvc_driver.c | 8 ++-
> drivers/pci/pcie/aspm.c | 17 +++--
> drivers/scsi/fcoe/fcoe_transport.c | 22 +++----
> drivers/thermal/intel/intel_powerclamp.c | 14 ++--
> drivers/tty/hvc/hvc_iucv.c | 18 ++---
> drivers/usb/storage/usb.c | 20 +++---
> drivers/virtio/virtio_mmio.c | 21 +++---
> lib/dynamic_debug.c | 10 ++-
> 17 files changed, 178 insertions(+), 177 deletions(-)
>
> diff --git a/include/linux/dynamic_debug.h b/include/linux/dynamic_debug.h
> index 05743900a116..999a25671b6a 100644
> --- a/include/linux/dynamic_debug.h
> +++ b/include/linux/dynamic_debug.h
> @@ -334,8 +334,10 @@ void __dynamic_ibdev_dbg(struct _ddebug *descriptor,
> extern int ddebug_dyndbg_module_param_cb(char *param, char *val,
> const char *modname);
> struct kernel_param;
> +struct seq_buf;
> int param_set_dyndbg_classes(const char *instr, const struct kernel_param *kp);
> -int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp);
> +int param_get_dyndbg_classes(struct seq_buf *buffer,
> + const struct kernel_param *kp);
>
> #else
>
> @@ -352,9 +354,11 @@ static inline int ddebug_dyndbg_module_param_cb(char *param, char *val,
> }
>
> struct kernel_param;
> +struct seq_buf;
> static inline int param_set_dyndbg_classes(const char *instr, const struct kernel_param *kp)
> { return 0; }
> -static inline int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp)
> +static inline int param_get_dyndbg_classes(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> { return 0; }
>
> #endif
> diff --git a/arch/um/drivers/vfio_kern.c b/arch/um/drivers/vfio_kern.c
> index fb7988dc5482..7c1119d0d9c1 100644
> --- a/arch/um/drivers/vfio_kern.c
> +++ b/arch/um/drivers/vfio_kern.c
> @@ -623,7 +623,8 @@ static int uml_vfio_cmdline_set(const char *device, const struct kernel_param *k
> return 0;
> }
>
> -static int uml_vfio_cmdline_get(char *buffer, const struct kernel_param *kp)
> +static int uml_vfio_cmdline_get(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> return 0;
> }
> diff --git a/arch/um/drivers/virtio_uml.c b/arch/um/drivers/virtio_uml.c
> index f9ae745f4586..cea806540625 100644
> --- a/arch/um/drivers/virtio_uml.c
> +++ b/arch/um/drivers/virtio_uml.c
> @@ -1379,23 +1379,21 @@ static int vu_cmdline_get_device(struct device *dev, void *data)
> {
> struct platform_device *pdev = to_platform_device(dev);
> struct virtio_uml_platform_data *pdata = pdev->dev.platform_data;
> - char *buffer = data;
> - unsigned int len = strlen(buffer);
> + struct seq_buf *s = data;
>
> - snprintf(buffer + len, PAGE_SIZE - len, "%s:%d:%d\n",
> - pdata->socket_path, pdata->virtio_device_id, pdev->id);
> + seq_buf_printf(s, "%s:%d:%d\n",
> + pdata->socket_path, pdata->virtio_device_id, pdev->id);
> return 0;
> }
>
> -static int vu_cmdline_get(char *buffer, const struct kernel_param *kp)
> +static int vu_cmdline_get(struct seq_buf *buffer, const struct kernel_param *kp)
> {
> guard(mutex)(&vu_cmdline_lock);
>
> - buffer[0] = '\0';
> if (vu_cmdline_parent_registered)
> device_for_each_child(&vu_cmdline_parent, buffer,
> vu_cmdline_get_device);
> - return strlen(buffer) + 1;
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(vu_cmdline_param_ops, vu_cmdline_set,
> diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c
> index dc064a388c23..31c624bebc65 100644
> --- a/drivers/acpi/button.c
> +++ b/drivers/acpi/button.c
> @@ -715,19 +715,24 @@ static int param_set_lid_init_state(const char *val,
> return 0;
> }
>
> -static int param_get_lid_init_state(char *buf, const struct kernel_param *kp)
> +static int param_get_lid_init_state(struct seq_buf *buf,
> + const struct kernel_param *kp)
> {
> - int i, c = 0;
> + int i;
>
> - for (i = 0; i < ARRAY_SIZE(lid_init_state_str); i++)
> + for (i = 0; i < ARRAY_SIZE(lid_init_state_str); i++) {
> if (i == lid_init_state)
> - c += sprintf(buf + c, "[%s] ", lid_init_state_str[i]);
> + seq_buf_printf(buf, "[%s] ", lid_init_state_str[i]);
> else
> - c += sprintf(buf + c, "%s ", lid_init_state_str[i]);
> + seq_buf_printf(buf, "%s ", lid_init_state_str[i]);
> + }
>
> - buf[c - 1] = '\n'; /* Replace the final space with a newline */
> + /* Replace the final space with a newline. */
> + if (!seq_buf_has_overflowed(buf) && buf->len > 0 &&
> + buf->buffer[buf->len - 1] == ' ')
> + buf->buffer[buf->len - 1] = '\n';
>
> - return c;
> + return 0;
> }
>
> module_param_call(lid_init_state,
> diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c
> index 5247ed7e05cc..dff7cc7da8bf 100644
> --- a/drivers/acpi/sysfs.c
> +++ b/drivers/acpi/sysfs.c
> @@ -89,53 +89,49 @@ static const struct acpi_dlevel acpi_debug_levels[] = {
> ACPI_DEBUG_INIT(ACPI_LV_EVENTS),
> };
>
> -static int param_get_debug_layer(char *buffer, const struct kernel_param *kp)
> +static int param_get_debug_layer(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - int result = 0;
> int i;
>
> - result = sprintf(buffer, "%-25s\tHex SET\n", "Description");
> + seq_buf_printf(buffer, "%-25s\tHex SET\n", "Description");
>
> for (i = 0; i < ARRAY_SIZE(acpi_debug_layers); i++) {
> - result += sprintf(buffer + result, "%-25s\t0x%08lX [%c]\n",
> - acpi_debug_layers[i].name,
> - acpi_debug_layers[i].value,
> - (acpi_dbg_layer & acpi_debug_layers[i].value)
> - ? '*' : ' ');
> + seq_buf_printf(buffer, "%-25s\t0x%08lX [%c]\n",
> + acpi_debug_layers[i].name,
> + acpi_debug_layers[i].value,
> + (acpi_dbg_layer & acpi_debug_layers[i].value)
> + ? '*' : ' ');
> }
> - result +=
> - sprintf(buffer + result, "%-25s\t0x%08X [%c]\n", "ACPI_ALL_DRIVERS",
> - ACPI_ALL_DRIVERS,
> - (acpi_dbg_layer & ACPI_ALL_DRIVERS) ==
> - ACPI_ALL_DRIVERS ? '*' : (acpi_dbg_layer & ACPI_ALL_DRIVERS)
> - == 0 ? ' ' : '-');
> - result +=
> - sprintf(buffer + result,
> - "--\ndebug_layer = 0x%08X ( * = enabled)\n",
> - acpi_dbg_layer);
> + seq_buf_printf(buffer, "%-25s\t0x%08X [%c]\n", "ACPI_ALL_DRIVERS",
> + ACPI_ALL_DRIVERS,
> + (acpi_dbg_layer & ACPI_ALL_DRIVERS) == ACPI_ALL_DRIVERS
> + ? '*' : (acpi_dbg_layer & ACPI_ALL_DRIVERS) == 0
> + ? ' ' : '-');
> + seq_buf_printf(buffer, "--\ndebug_layer = 0x%08X ( * = enabled)\n",
> + acpi_dbg_layer);
>
> - return result;
> + return 0;
> }
>
> -static int param_get_debug_level(char *buffer, const struct kernel_param *kp)
> +static int param_get_debug_level(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - int result = 0;
> int i;
>
> - result = sprintf(buffer, "%-25s\tHex SET\n", "Description");
> + seq_buf_printf(buffer, "%-25s\tHex SET\n", "Description");
>
> for (i = 0; i < ARRAY_SIZE(acpi_debug_levels); i++) {
> - result += sprintf(buffer + result, "%-25s\t0x%08lX [%c]\n",
> - acpi_debug_levels[i].name,
> - acpi_debug_levels[i].value,
> - (acpi_dbg_level & acpi_debug_levels[i].value)
> - ? '*' : ' ');
> + seq_buf_printf(buffer, "%-25s\t0x%08lX [%c]\n",
> + acpi_debug_levels[i].name,
> + acpi_debug_levels[i].value,
> + (acpi_dbg_level & acpi_debug_levels[i].value)
> + ? '*' : ' ');
> }
> - result +=
> - sprintf(buffer + result, "--\ndebug_level = 0x%08X (* = enabled)\n",
> - acpi_dbg_level);
> + seq_buf_printf(buffer, "--\ndebug_level = 0x%08X (* = enabled)\n",
> + acpi_dbg_level);
>
> - return result;
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(param_ops_debug_layer, param_set_uint,
> @@ -247,16 +243,18 @@ static int param_set_trace_state(const char *val,
> return 0;
> }
>
> -static int param_get_trace_state(char *buffer, const struct kernel_param *kp)
> +static int param_get_trace_state(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> if (!(acpi_gbl_trace_flags & ACPI_TRACE_ENABLED))
> - return sprintf(buffer, "disable\n");
> - if (!acpi_gbl_trace_method_name)
> - return sprintf(buffer, "enable\n");
> - if (acpi_gbl_trace_flags & ACPI_TRACE_ONESHOT)
> - return sprintf(buffer, "method-once\n");
> + seq_buf_printf(buffer, "disable\n");
> + else if (!acpi_gbl_trace_method_name)
> + seq_buf_printf(buffer, "enable\n");
> + else if (acpi_gbl_trace_flags & ACPI_TRACE_ONESHOT)
> + seq_buf_printf(buffer, "method-once\n");
> else
> - return sprintf(buffer, "method\n");
> + seq_buf_printf(buffer, "method\n");
> + return 0;
> }
>
> module_param_call(trace_state, param_set_trace_state, param_get_trace_state,
> @@ -272,14 +270,11 @@ MODULE_PARM_DESC(aml_debug_output,
> "To enable/disable the ACPI Debug Object output.");
>
> /* /sys/module/acpi/parameters/acpica_version */
> -static int param_get_acpica_version(char *buffer,
> +static int param_get_acpica_version(struct seq_buf *buffer,
> const struct kernel_param *kp)
> {
> - int result;
> -
> - result = sprintf(buffer, "%x\n", ACPI_CA_VERSION);
> -
> - return result;
> + seq_buf_printf(buffer, "%x\n", ACPI_CA_VERSION);
> + return 0;
> }
>
> module_param_call(acpica_version, NULL, param_get_acpica_version, NULL, 0444);
> diff --git a/drivers/char/ipmi/ipmi_watchdog.c b/drivers/char/ipmi/ipmi_watchdog.c
> index 91a99417d204..2bfec85ef331 100644
> --- a/drivers/char/ipmi/ipmi_watchdog.c
> +++ b/drivers/char/ipmi/ipmi_watchdog.c
> @@ -197,11 +197,11 @@ static DEFINE_KERNEL_PARAM_OPS(param_ops_timeout, set_param_timeout,
> param_get_int);
> #define param_check_timeout param_check_int
>
> -typedef int (*action_fn)(const char *intval, char *outval);
> +typedef int (*action_fn)(const char *intval, struct seq_buf *outval);
>
> -static int action_op(const char *inval, char *outval);
> -static int preaction_op(const char *inval, char *outval);
> -static int preop_op(const char *inval, char *outval);
> +static int action_op(const char *inval, struct seq_buf *outval);
> +static int preaction_op(const char *inval, struct seq_buf *outval);
> +static int preop_op(const char *inval, struct seq_buf *outval);
> static void check_parms(void);
>
> static int set_param_str(const char *val, const struct kernel_param *kp)
> @@ -227,20 +227,11 @@ static int set_param_str(const char *val, const struct kernel_param *kp)
> return rv;
> }
>
> -static int get_param_str(char *buffer, const struct kernel_param *kp)
> +static int get_param_str(struct seq_buf *buffer, const struct kernel_param *kp)
> {
> action_fn fn = (action_fn) kp->arg;
> - int rv, len;
>
> - rv = fn(NULL, buffer);
> - if (rv)
> - return rv;
> -
> - len = strlen(buffer);
> - buffer[len++] = '\n';
> - buffer[len] = 0;
> -
> - return len;
> + return fn(NULL, buffer);
> }
>
>
> @@ -1154,12 +1145,12 @@ static int action_op_set_val(const char *inval)
> return 0;
> }
>
> -static int action_op(const char *inval, char *outval)
> +static int action_op(const char *inval, struct seq_buf *outval)
> {
> int rv;
>
> if (outval)
> - strcpy(outval, action);
> + seq_buf_printf(outval, "%s\n", action);
>
> if (!inval)
> return 0;
> @@ -1186,12 +1177,12 @@ static int preaction_op_set_val(const char *inval)
> return 0;
> }
>
> -static int preaction_op(const char *inval, char *outval)
> +static int preaction_op(const char *inval, struct seq_buf *outval)
> {
> int rv;
>
> if (outval)
> - strcpy(outval, preaction);
> + seq_buf_printf(outval, "%s\n", preaction);
>
> if (!inval)
> return 0;
> @@ -1214,12 +1205,12 @@ static int preop_op_set_val(const char *inval)
> return 0;
> }
>
> -static int preop_op(const char *inval, char *outval)
> +static int preop_op(const char *inval, struct seq_buf *outval)
> {
> int rv;
>
> if (outval)
> - strcpy(outval, preop);
> + seq_buf_printf(outval, "%s\n", preop);
>
> if (!inval)
> return 0;
> diff --git a/drivers/firmware/qemu_fw_cfg.c b/drivers/firmware/qemu_fw_cfg.c
> index c87a5449ba8c..4ebc1e327849 100644
> --- a/drivers/firmware/qemu_fw_cfg.c
> +++ b/drivers/firmware/qemu_fw_cfg.c
> @@ -860,7 +860,8 @@ static int fw_cfg_cmdline_set(const char *arg, const struct kernel_param *kp)
> return PTR_ERR_OR_ZERO(fw_cfg_cmdline_dev);
> }
>
> -static int fw_cfg_cmdline_get(char *buf, const struct kernel_param *kp)
> +static int fw_cfg_cmdline_get(struct seq_buf *buf,
> + const struct kernel_param *kp)
> {
> /* stay silent if device was not configured via the command
> * line, or if the parameter name (ioport/mmio) doesn't match
> @@ -873,22 +874,25 @@ static int fw_cfg_cmdline_get(char *buf, const struct kernel_param *kp)
>
> switch (fw_cfg_cmdline_dev->num_resources) {
> case 1:
> - return snprintf(buf, PAGE_SIZE, PH_ADDR_PR_1_FMT,
> - resource_size(&fw_cfg_cmdline_dev->resource[0]),
> - fw_cfg_cmdline_dev->resource[0].start);
> + seq_buf_printf(buf, PH_ADDR_PR_1_FMT,
> + resource_size(&fw_cfg_cmdline_dev->resource[0]),
> + fw_cfg_cmdline_dev->resource[0].start);
> + return 0;
> case 3:
> - return snprintf(buf, PAGE_SIZE, PH_ADDR_PR_3_FMT,
> - resource_size(&fw_cfg_cmdline_dev->resource[0]),
> - fw_cfg_cmdline_dev->resource[0].start,
> - fw_cfg_cmdline_dev->resource[1].start,
> - fw_cfg_cmdline_dev->resource[2].start);
> + seq_buf_printf(buf, PH_ADDR_PR_3_FMT,
> + resource_size(&fw_cfg_cmdline_dev->resource[0]),
> + fw_cfg_cmdline_dev->resource[0].start,
> + fw_cfg_cmdline_dev->resource[1].start,
> + fw_cfg_cmdline_dev->resource[2].start);
> + return 0;
> case 4:
> - return snprintf(buf, PAGE_SIZE, PH_ADDR_PR_4_FMT,
> - resource_size(&fw_cfg_cmdline_dev->resource[0]),
> - fw_cfg_cmdline_dev->resource[0].start,
> - fw_cfg_cmdline_dev->resource[1].start,
> - fw_cfg_cmdline_dev->resource[2].start,
> - fw_cfg_cmdline_dev->resource[3].start);
> + seq_buf_printf(buf, PH_ADDR_PR_4_FMT,
> + resource_size(&fw_cfg_cmdline_dev->resource[0]),
> + fw_cfg_cmdline_dev->resource[0].start,
> + fw_cfg_cmdline_dev->resource[1].start,
> + fw_cfg_cmdline_dev->resource[2].start,
> + fw_cfg_cmdline_dev->resource[3].start);
> + return 0;
> }
>
> /* Should never get here */
> diff --git a/drivers/gpu/drm/i915/i915_mitigations.c b/drivers/gpu/drm/i915/i915_mitigations.c
> index 6061eae84e9c..99cb38f355b6 100644
> --- a/drivers/gpu/drm/i915/i915_mitigations.c
> +++ b/drivers/gpu/drm/i915/i915_mitigations.c
> @@ -95,33 +95,37 @@ static int mitigations_set(const char *val, const struct kernel_param *kp)
> return 0;
> }
>
> -static int mitigations_get(char *buffer, const struct kernel_param *kp)
> +static int mitigations_get(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> unsigned long local = READ_ONCE(mitigations);
> - int count, i;
> bool enable;
> + int i;
>
> - if (!local)
> - return scnprintf(buffer, PAGE_SIZE, "%s\n", "off");
> + if (!local) {
> + seq_buf_printf(buffer, "%s\n", "off");
> + return 0;
> + }
>
> if (local & BIT(BITS_PER_LONG - 1)) {
> - count = scnprintf(buffer, PAGE_SIZE, "%s,", "auto");
> + seq_buf_printf(buffer, "%s,", "auto");
> enable = false;
> } else {
> enable = true;
> - count = 0;
> }
>
> for (i = 0; i < ARRAY_SIZE(names); i++) {
> if ((local & BIT(i)) != enable)
> continue;
> -
> - count += scnprintf(buffer + count, PAGE_SIZE - count,
> - "%s%s,", enable ? "" : "!", names[i]);
> + seq_buf_printf(buffer, "%s%s,", enable ? "" : "!", names[i]);
> }
>
> - buffer[count - 1] = '\n';
> - return count;
> + /* Replace the trailing comma with a newline. */
> + if (!seq_buf_has_overflowed(buffer) && buffer->len > 0 &&
> + buffer->buffer[buffer->len - 1] == ',')
> + buffer->buffer[buffer->len - 1] = '\n';
> +
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(ops, mitigations_set, mitigations_get);
> diff --git a/drivers/infiniband/ulp/srp/ib_srp.c b/drivers/infiniband/ulp/srp/ib_srp.c
> index a81515f52a4f..4f53e939eec1 100644
> --- a/drivers/infiniband/ulp/srp/ib_srp.c
> +++ b/drivers/infiniband/ulp/srp/ib_srp.c
> @@ -161,14 +161,15 @@ static struct ib_client srp_client = {
>
> static struct ib_sa_client srp_sa_client;
>
> -static int srp_tmo_get(char *buffer, const struct kernel_param *kp)
> +static int srp_tmo_get(struct seq_buf *buffer, const struct kernel_param *kp)
> {
> int tmo = *(int *)kp->arg;
>
> if (tmo >= 0)
> - return sysfs_emit(buffer, "%d\n", tmo);
> + seq_buf_printf(buffer, "%d\n", tmo);
> else
> - return sysfs_emit(buffer, "off\n");
> + seq_buf_printf(buffer, "off\n");
> + return 0;
> }
>
> static int srp_tmo_set(const char *val, const struct kernel_param *kp)
> diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
> index 2338cab7fef9..1c5c40ce852d 100644
> --- a/drivers/media/usb/uvc/uvc_driver.c
> +++ b/drivers/media/usb/uvc/uvc_driver.c
> @@ -2451,12 +2451,14 @@ static int uvc_reset_resume(struct usb_interface *intf)
> * Module parameters
> */
>
> -static int uvc_clock_param_get(char *buffer, const struct kernel_param *kp)
> +static int uvc_clock_param_get(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> if (uvc_clock_param == CLOCK_MONOTONIC)
> - return sprintf(buffer, "CLOCK_MONOTONIC");
> + seq_buf_printf(buffer, "CLOCK_MONOTONIC");
> else
> - return sprintf(buffer, "CLOCK_REALTIME");
> + seq_buf_printf(buffer, "CLOCK_REALTIME");
> + return 0;
> }
>
> static int uvc_clock_param_set(const char *val, const struct kernel_param *kp)
> diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
> index 925373b98dff..af2dd668fe4d 100644
> --- a/drivers/pci/pcie/aspm.c
> +++ b/drivers/pci/pcie/aspm.c
> @@ -1572,16 +1572,19 @@ static int pcie_aspm_set_policy(const char *val,
> return 0;
> }
>
> -static int pcie_aspm_get_policy(char *buffer, const struct kernel_param *kp)
> +static int pcie_aspm_get_policy(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - int i, cnt = 0;
> - for (i = 0; i < ARRAY_SIZE(policy_str); i++)
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(policy_str); i++) {
> if (i == aspm_policy)
> - cnt += sprintf(buffer + cnt, "[%s] ", policy_str[i]);
> + seq_buf_printf(buffer, "[%s] ", policy_str[i]);
> else
> - cnt += sprintf(buffer + cnt, "%s ", policy_str[i]);
> - cnt += sprintf(buffer + cnt, "\n");
> - return cnt;
> + seq_buf_printf(buffer, "%s ", policy_str[i]);
> + }
> + seq_buf_putc(buffer, '\n');
> + return 0;
> }
>
> module_param_call(policy, pcie_aspm_set_policy, pcie_aspm_get_policy,
> diff --git a/drivers/scsi/fcoe/fcoe_transport.c b/drivers/scsi/fcoe/fcoe_transport.c
> index 88d85fc9a52a..aa10514ec46e 100644
> --- a/drivers/scsi/fcoe/fcoe_transport.c
> +++ b/drivers/scsi/fcoe/fcoe_transport.c
> @@ -23,7 +23,8 @@ MODULE_LICENSE("GPL v2");
>
> static int fcoe_transport_create(const char *, const struct kernel_param *);
> static int fcoe_transport_destroy(const char *, const struct kernel_param *);
> -static int fcoe_transport_show(char *buffer, const struct kernel_param *kp);
> +static int fcoe_transport_show(struct seq_buf *buffer,
> + const struct kernel_param *kp);
> static struct fcoe_transport *fcoe_transport_lookup(struct net_device *device);
> static struct fcoe_transport *fcoe_netdev_map_lookup(struct net_device *device);
> static int fcoe_transport_enable(const char *, const struct kernel_param *);
> @@ -595,22 +596,21 @@ int fcoe_transport_detach(struct fcoe_transport *ft)
> }
> EXPORT_SYMBOL(fcoe_transport_detach);
>
> -static int fcoe_transport_show(char *buffer, const struct kernel_param *kp)
> +static int fcoe_transport_show(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - int i, j;
> struct fcoe_transport *ft = NULL;
>
> - i = j = sprintf(buffer, "Attached FCoE transports:");
> + seq_buf_printf(buffer, "Attached FCoE transports:");
> mutex_lock(&ft_mutex);
> - list_for_each_entry(ft, &fcoe_transports, list) {
> - if (i >= PAGE_SIZE - IFNAMSIZ)
> - break;
> - i += snprintf(&buffer[i], IFNAMSIZ, "%s ", ft->name);
> + if (list_empty(&fcoe_transports)) {
> + seq_buf_printf(buffer, "none");
> + } else {
> + list_for_each_entry(ft, &fcoe_transports, list)
> + seq_buf_printf(buffer, "%s ", ft->name);
> }
> mutex_unlock(&ft_mutex);
> - if (i == j)
> - i += snprintf(&buffer[i], IFNAMSIZ, "none");
> - return i;
> + return 0;
> }
>
> static int __init fcoe_transport_init(void)
> diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c
> index 98fbc6892714..50ec1a0ff1ab 100644
> --- a/drivers/thermal/intel/intel_powerclamp.c
> +++ b/drivers/thermal/intel/intel_powerclamp.c
> @@ -101,15 +101,13 @@ static int duration_set(const char *arg, const struct kernel_param *kp)
> return ret;
> }
>
> -static int duration_get(char *buf, const struct kernel_param *kp)
> +static int duration_get(struct seq_buf *buf, const struct kernel_param *kp)
> {
> - int ret;
> -
> mutex_lock(&powerclamp_lock);
> - ret = sysfs_emit(buf, "%d\n", duration / 1000);
> + seq_buf_printf(buf, "%d\n", duration / 1000);
> mutex_unlock(&powerclamp_lock);
>
> - return ret;
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(duration_ops, duration_set, duration_get);
> @@ -192,12 +190,14 @@ static int cpumask_set(const char *arg, const struct kernel_param *kp)
> return ret;
> }
>
> -static int cpumask_get(char *buf, const struct kernel_param *kp)
> +static int cpumask_get(struct seq_buf *buf, const struct kernel_param *kp)
> {
> if (!cpumask_available(idle_injection_cpu_mask))
> return -ENODEV;
>
> - return cpumap_print_to_pagebuf(false, buf, idle_injection_cpu_mask);
> + seq_buf_printf(buf, "%*pb\n", nr_cpu_ids,
> + cpumask_bits(idle_injection_cpu_mask));
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(cpumask_ops, cpumask_set, cpumask_get);
> diff --git a/drivers/tty/hvc/hvc_iucv.c b/drivers/tty/hvc/hvc_iucv.c
> index 29612a4a32cb..b27c1dfbd249 100644
> --- a/drivers/tty/hvc/hvc_iucv.c
> +++ b/drivers/tty/hvc/hvc_iucv.c
> @@ -1256,36 +1256,32 @@ static int param_set_vmidfilter(const char *val, const struct kernel_param *kp)
>
> /**
> * param_get_vmidfilter() - Get z/VM user ID filter
> - * @buffer: Buffer to store z/VM user ID filter,
> - * (buffer size assumption PAGE_SIZE)
> + * @buffer: seq_buf to store z/VM user ID filter
> * @kp: Kernel parameter pointing to the hvc_iucv_filter array
> *
> * The function stores the filter as a comma-separated list of z/VM user IDs
> * in @buffer. Typically, sysfs routines call this function for attr show.
> */
> -static int param_get_vmidfilter(char *buffer, const struct kernel_param *kp)
> +static int param_get_vmidfilter(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> - int rc;
> size_t index, len;
> void *start, *end;
>
> if (!machine_is_vm() || !hvc_iucv_devices)
> return -ENODEV;
>
> - rc = 0;
> read_lock_bh(&hvc_iucv_filter_lock);
> for (index = 0; index < hvc_iucv_filter_size; index++) {
> start = hvc_iucv_filter + (8 * index);
> end = memchr(start, ' ', 8);
> len = (end) ? end - start : 8;
> - memcpy(buffer + rc, start, len);
> - rc += len;
> - buffer[rc++] = ',';
> + if (index)
> + seq_buf_putc(buffer, ',');
> + seq_buf_printf(buffer, "%.*s", (int)len, (char *)start);
> }
> read_unlock_bh(&hvc_iucv_filter_lock);
> - if (rc)
> - buffer[--rc] = '\0'; /* replace last comma and update rc */
> - return rc;
> + return 0;
> }
>
> #define param_check_vmidfilter(name, p) __param_check(name, p, void)
> diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c
> index 71dd623b95c9..637e1b8f622f 100644
> --- a/drivers/usb/storage/usb.c
> +++ b/drivers/usb/storage/usb.c
> @@ -115,27 +115,22 @@ static int parse_delay_str(const char *str, int ndecimals, const char *suffix,
> * @val: The integer value to format, scaled by 10^(@ndecimals).
> * @ndecimals: Number of decimal to scale down.
> * @suffix: Suffix string to format.
> - * @str: Where to store the formatted string.
> - * @size: The size of buffer for @str.
> + * @s: Where to store the formatted string.
> *
> * Format an integer value in @val scale down by 10^(@ndecimals) without @suffix
> * if @val is divisible by 10^(@ndecimals).
> * Otherwise format a value in @val just as it is with @suffix
> - *
> - * Returns the number of characters written into @str.
> */
> -static int format_delay_ms(unsigned int val, int ndecimals, const char *suffix,
> - char *str, int size)
> +static void format_delay_ms(unsigned int val, int ndecimals, const char *suffix,
> + struct seq_buf *s)
> {
> u64 delay_ms = val;
> unsigned int rem = do_div(delay_ms, int_pow(10, ndecimals));
> - int ret;
>
> if (rem)
> - ret = scnprintf(str, size, "%u%s\n", val, suffix);
> + seq_buf_printf(s, "%u%s\n", val, suffix);
> else
> - ret = scnprintf(str, size, "%u\n", (unsigned int)delay_ms);
> - return ret;
> + seq_buf_printf(s, "%u\n", (unsigned int)delay_ms);
> }
>
> static int delay_use_set(const char *s, const struct kernel_param *kp)
> @@ -151,11 +146,12 @@ static int delay_use_set(const char *s, const struct kernel_param *kp)
> return 0;
> }
>
> -static int delay_use_get(char *s, const struct kernel_param *kp)
> +static int delay_use_get(struct seq_buf *s, const struct kernel_param *kp)
> {
> unsigned int delay_ms = *((unsigned int *)kp->arg);
>
> - return format_delay_ms(delay_ms, 3, "ms", s, PAGE_SIZE);
> + format_delay_ms(delay_ms, 3, "ms", s);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(delay_use_ops, delay_use_set, delay_use_get);
> diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c
> index f6df9c76ee81..81a7455e4643 100644
> --- a/drivers/virtio/virtio_mmio.c
> +++ b/drivers/virtio/virtio_mmio.c
> @@ -728,24 +728,21 @@ static int vm_cmdline_set(const char *device,
>
> static int vm_cmdline_get_device(struct device *dev, void *data)
> {
> - char *buffer = data;
> - unsigned int len = strlen(buffer);
> + struct seq_buf *s = data;
> struct platform_device *pdev = to_platform_device(dev);
>
> - snprintf(buffer + len, PAGE_SIZE - len, "0x%llx@0x%llx:%llu:%d\n",
> - pdev->resource[0].end - pdev->resource[0].start + 1ULL,
> - (unsigned long long)pdev->resource[0].start,
> - (unsigned long long)pdev->resource[1].start,
> - pdev->id);
> + seq_buf_printf(s, "0x%llx@0x%llx:%llu:%d\n",
> + pdev->resource[0].end - pdev->resource[0].start + 1ULL,
> + (unsigned long long)pdev->resource[0].start,
> + (unsigned long long)pdev->resource[1].start,
> + pdev->id);
> return 0;
> }
>
> -static int vm_cmdline_get(char *buffer, const struct kernel_param *kp)
> +static int vm_cmdline_get(struct seq_buf *s, const struct kernel_param *kp)
> {
> - buffer[0] = '\0';
> - device_for_each_child(&vm_cmdline_parent, buffer,
> - vm_cmdline_get_device);
> - return strlen(buffer) + 1;
> + device_for_each_child(&vm_cmdline_parent, s, vm_cmdline_get_device);
> + return 0;
> }
>
> static DEFINE_KERNEL_PARAM_OPS(vm_cmdline_param_ops, vm_cmdline_set,
> diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
> index cf0405ba0dbd..123f061c2fb2 100644
> --- a/lib/dynamic_debug.c
> +++ b/lib/dynamic_debug.c
> @@ -17,6 +17,7 @@
> #include <linux/module.h>
> #include <linux/moduleparam.h>
> #include <linux/kallsyms.h>
> +#include <linux/seq_buf.h>
> #include <linux/types.h>
> #include <linux/mutex.h>
> #include <linux/proc_fs.h>
> @@ -787,7 +788,8 @@ EXPORT_SYMBOL(param_set_dyndbg_classes);
> * altered by direct >control. Displays 0x for DISJOINT, 0-N for
> * LEVEL Returns: #chars written or <0 on error
> */
> -int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp)
> +int param_get_dyndbg_classes(struct seq_buf *buffer,
> + const struct kernel_param *kp)
> {
> const struct ddebug_class_param *dcp = kp->arg;
> const struct ddebug_class_map *map = dcp->map;
> @@ -796,11 +798,13 @@ int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp)
>
> case DD_CLASS_TYPE_DISJOINT_NAMES:
> case DD_CLASS_TYPE_DISJOINT_BITS:
> - return scnprintf(buffer, PAGE_SIZE, "0x%lx\n", *dcp->bits);
> + seq_buf_printf(buffer, "0x%lx\n", *dcp->bits);
> + return 0;
>
> case DD_CLASS_TYPE_LEVEL_NAMES:
> case DD_CLASS_TYPE_LEVEL_NUM:
> - return scnprintf(buffer, PAGE_SIZE, "%d\n", *dcp->lvl);
> + seq_buf_printf(buffer, "%d\n", *dcp->lvl);
> + return 0;
> default:
> return -1;
> }
> --
> 2.34.1
>
^ permalink raw reply
* Re: [PATCH bpf-next 00/13] Signed BPF + IPE Policies
From: Paul Moore @ 2026-05-22 18:56 UTC (permalink / raw)
To: KP Singh; +Cc: linux-security-module, bpf, ast, daniel, memxor, James.Bottomley
In-Reply-To: <20260522023234.3778588-1-kpsingh@kernel.org>
On Thu, May 21, 2026 at 10:32 PM KP Singh <kpsingh@kernel.org> wrote:
>
> This series continues the "Signed BPF programs" work and adds
> the missing pieces needed for an LSM to do policy enforcement
> and addresses the concerns raised by the developers of Hornet.
>
> One signing scheme, please.
This is why we tried working with you and Alexei for quite some time,
providing requirements, patches, reviews, and code suggestions.
Unfortunately, our input was not reflected in the signing scheme that
was merged into Linus' tree, so we have had to get creative to meet
our our needs and those of others.
The good news is that the Hornet signatures are compatible with the
existing BPF signing scheme; a Hornet signed BPF program can be
successfully verified by your signing scheme, in current kernels, as
well as the Hornet LSM. Which, while were on the topic of
compatibility, it looks like your proposed changes in this patchset
would break compatibility with existing signed BPF programs, am I
reading the patchset right in this regard?
> BPF does not need a second signing scheme. It needs a policy
> framework that consumes the verdict the existing signing pipeline
> produces. Two parallel signing stacks is harmful UX for Cilium,
> bpftrace, systemd, distros, and everyone shipping signed lskels.
Once again, if one signs BPF programs using the Hornet signing tools
provided in Blaise's patches, the resulting signed BPF can be loaded
and verified using both the existing BPF signature verification code
and Hornet. It is also easy to disable Hornet and/or IPE at kernel
boot using the "lsm=" command line parameter, and of course one can
always tailor the IPE policy to meet specific needs. Although you
surely must be aware of that as your own patchset borrows heavily from
Blaise's original code, albeit without any attribution.
> Hornet has been NACK'd repeatedly by the BPF maintainers [1][2]
> on layering and TOCTOU grounds.
Blaise has recorded Alexei's NACK on the Hornet patchset and addressed
the TOCTOU that Alexei identified. On a related note, Blaise and I
were working with Eric Biggers today - thank you again Eric! - on
identifying some additional issues and I expect Blaise will have
another patch early next week which leverages security_bpf_prog() to
simplify the Hornet code and address some additional issues.
> What this series adds
>
> - prog->aux->sig (verdict + keyring) and prog->aux->is_kernel,
> populated by the syscall path before security_bpf_prog_load
> fires.
> - bpf_loader_verify_metadata kfunc -- the metadata check is now
> kernel C code, not BPF bytecode. The verifier injects the
> calling prog->aux as an implicit argument via KF_IMPLICIT_ARGS.
I think there may have been a misunderstanding regarding the "kernel
C" comments. Our desire was to not have to trust the lskel loader to
do any of the BPF signature verification; perfoming the verification
of both the lskel loader and the original BPF program map in the
native kernel's C code flows, without relying on triggering by the
loader. This greatly simplifies things from a security perspective.
> - Loader-side prog BTF with BPF_PSEUDO_KFUNC_CALL_PROG_BTF so
> the kfunc CALL is reproducible across build hosts and resolved
> at load time.
> - security_bpf_prog_load_post_integrity LSM hook, fired by the
> kfunc on a successful metadata check.
Does a kfunc calling a LSM hook open the door to a potential recursion
issue involving a BPF LSM?
> - IPE properties (bpf_signature, bpf_keyring, bpf_kernel) and
> two ops (BPF_PROG_LOAD, BPF_PROG_LOAD_POST_INTEGRITY).
>
> This series address concerns raised by the Hornet developers:
>
> * The metadata hash check should be in kernel C, not BPF
> bytecode -- Blaise Boscaccy [3]:
>
> The bpf_loader_verify_metadata kfunc moves the hash check from
> inline BPF instructions into kernel C code.
Please see my comments above. While the kfunc is written in C, it is
still reliant on the loader calling that kfunc.
Of course there is still also the usbility issue of not being able to
return an error status to userspace at load time regarding the
verification success of both the loader and the original BPF program
map.
> * LSMs cannot observe the verification result at hook time --
> Paul Moore [4]:
>
> prog->aux->sig.verdict and sig.keyring are populated before any
> LSM hook runs. Furthermore, security_bpf_prog_load_post_integrity
> hook fires after the in-kernel hash check for consumers that want
> to observe or gate the post-integrity transition.
>
> [1] Alexei Starovoitov, NACK on Hornet (TOCTOU + layering),
> https://lore.kernel.org/all/CAADnVQJ1CRvTXBU771KaYzrx-vRaWF+k164DcFOqOsCxmuL+ig@mail.gmail.com/
> [2] Daniel Borkmann, NACK on Hornet v3,
> https://lore.kernel.org/all/798dba24-b5a7-4584-a1f6-793883fe9b5e@iogearbox.net/
> [3] Blaise Boscaccy, Hornet v6 (C-side hash verification rationale),
> https://lore.kernel.org/all/20260429191431.2345448-1-bboscaccy@linux.microsoft.com/
> [4] Paul Moore, push for post-verifier observability,
> https://lore.kernel.org/all/CACYkzJ4+=3owK+ELD9Nw7Rrm-UajxXEw8kVtOTJJ+SNAXpsOpw@mail.gmail.com/
>
>
> KP Singh (13):
> bpf: expose signature verdict to LSMs via bpf_prog_aux
> bpf: include prog BTF in the signed loader signature scope
> bpf, libbpf: load prog BTF in the skel_internal loader
> bpf: add bpf_loader_verify_metadata kfunc
> bpf: compute prog->digest at BPF_PROG_LOAD entry
> bpf: resolve loader-style kfunc CALLs against prog BTF
> libbpf: generate prog BTF for loader programs
> bpftool gen: embed loader prog BTF in the lskel header
> lsm: add bpf_prog_load_post_integrity hook
> bpf: invoke security_bpf_prog_load_post_integrity from the metadata
> kfunc
> ipe: add BPF program signature properties
> ipe: gate post-integrity BPF program loads
> selftests/bpf: add IPE BPF policy integration tests
>
> include/linux/bpf.h | 19 +++
> include/linux/bpf_verifier.h | 6 +
> include/linux/btf.h | 1 +
> include/linux/lsm_hook_defs.h | 1 +
> include/linux/security.h | 6 +
> include/uapi/linux/bpf.h | 5 +
> kernel/bpf/btf.c | 8 +
> kernel/bpf/check_btf.c | 18 +-
> kernel/bpf/helpers.c | 65 ++++++++
> kernel/bpf/syscall.c | 76 ++++++++-
> kernel/bpf/verifier.c | 58 ++++++-
> security/ipe/Kconfig | 14 ++
> security/ipe/audit.c | 13 ++
> security/ipe/eval.c | 57 +++++++
> security/ipe/eval.h | 5 +
> security/ipe/hooks.c | 42 +++++
> security/ipe/hooks.h | 9 +
> security/ipe/ipe.c | 4 +
> security/ipe/policy.h | 11 ++
> security/ipe/policy_parser.c | 20 +++
> security/security.c | 17 ++
> tools/bpf/bpftool/gen.c | 21 +++
> tools/bpf/bpftool/sign.c | 17 +-
> tools/include/uapi/linux/bpf.h | 5 +
> tools/lib/bpf/bpf_gen_internal.h | 2 +
> tools/lib/bpf/gen_loader.c | 127 +++++++++++---
> tools/lib/bpf/libbpf.h | 4 +-
> tools/lib/bpf/skel_internal.h | 67 +++++---
> .../selftests/bpf/test_signed_bpf_ipe.sh | 156 ++++++++++++++++++
> tools/testing/selftests/bpf/vmtest.sh | 4 +-
> 30 files changed, 775 insertions(+), 83 deletions(-)
> create mode 100755 tools/testing/selftests/bpf/test_signed_bpf_ipe.sh
>
> --
> 2.53.0
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH bpf-next 00/13] Signed BPF + IPE Policies
From: KP Singh @ 2026-05-22 20:46 UTC (permalink / raw)
To: Paul Moore
Cc: linux-security-module, bpf, ast, daniel, memxor, James.Bottomley
In-Reply-To: <CAHC9VhTk3qovnQQjK0C_FWU605j6Yi+-poMVVMS8WycRFg9xhQ@mail.gmail.com>
On Fri, May 22, 2026 at 8:56 PM Paul Moore <paul@paul-moore.com> wrote:
>
> On Thu, May 21, 2026 at 10:32 PM KP Singh <kpsingh@kernel.org> wrote:
> >
> > This series continues the "Signed BPF programs" work and adds
> > the missing pieces needed for an LSM to do policy enforcement
> > and addresses the concerns raised by the developers of Hornet.
> >
> > One signing scheme, please.
>
> This is why we tried working with you and Alexei for quite some time,
> providing requirements, patches, reviews, and code suggestions.
> Unfortunately, our input was not reflected in the signing scheme that
> was merged into Linus' tree, so we have had to get creative to meet
> our our needs and those of others.
Is your idea of creativity to circumvent working with the BPF
maintainers and community?
>
> The good news is that the Hornet signatures are compatible with the
> existing BPF signing scheme; a Hornet signed BPF program can be
> successfully verified by your signing scheme, in current kernels, as
> well as the Hornet LSM. Which, while were on the topic of
> compatibility, it looks like your proposed changes in this patchset
> would break compatibility with existing signed BPF programs, am I
> reading the patchset right in this regard?
No, they won't.
>
> > BPF does not need a second signing scheme. It needs a policy
> > framework that consumes the verdict the existing signing pipeline
> > produces. Two parallel signing stacks is harmful UX for Cilium,
> > bpftrace, systemd, distros, and everyone shipping signed lskels.
>
> Once again, if one signs BPF programs using the Hornet signing tools
> provided in Blaise's patches, the resulting signed BPF can be loaded
> and verified using both the existing BPF signature verification code
> and Hornet. It is also easy to disable Hornet and/or IPE at kernel
> boot using the "lsm=" command line parameter, and of course one can
> always tailor the IPE policy to meet specific needs. Although you
> surely must be aware of that as your own patchset borrows heavily from
> Blaise's original code, albeit without any attribution.
>
> > Hornet has been NACK'd repeatedly by the BPF maintainers [1][2]
> > on layering and TOCTOU grounds.
>
> Blaise has recorded Alexei's NACK on the Hornet patchset and addressed
Please record the NACK from Daniel and me.
> the TOCTOU that Alexei identified. On a related note, Blaise and I
> were working with Eric Biggers today - thank you again Eric! - on
> identifying some additional issues and I expect Blaise will have
> another patch early next week which leverages security_bpf_prog() to
> simplify the Hornet code and address some additional issues.
>
> > What this series adds
> >
> > - prog->aux->sig (verdict + keyring) and prog->aux->is_kernel,
> > populated by the syscall path before security_bpf_prog_load
> > fires.
> > - bpf_loader_verify_metadata kfunc -- the metadata check is now
> > kernel C code, not BPF bytecode. The verifier injects the
> > calling prog->aux as an implicit argument via KF_IMPLICIT_ARGS.
>
> I think there may have been a misunderstanding regarding the "kernel
> C" comments. Our desire was to not have to trust the lskel loader to
> do any of the BPF signature verification; perfoming the verification
> of both the lskel loader and the original BPF program map in the
> native kernel's C code flows, without relying on triggering by the
> loader. This greatly simplifies things from a security perspective.
>
> > - Loader-side prog BTF with BPF_PSEUDO_KFUNC_CALL_PROG_BTF so
> > the kfunc CALL is reproducible across build hosts and resolved
> > at load time.
> > - security_bpf_prog_load_post_integrity LSM hook, fired by the
> > kfunc on a successful metadata check.
>
> Does a kfunc calling a LSM hook open the door to a potential recursion
> issue involving a BPF LSM?
What's the concern here? LSM hooks can fire from BPF kfuncs and
helpers for a while now.
>
> > - IPE properties (bpf_signature, bpf_keyring, bpf_kernel) and
> > two ops (BPF_PROG_LOAD, BPF_PROG_LOAD_POST_INTEGRITY).
> >
> > This series address concerns raised by the Hornet developers:
> >
> > * The metadata hash check should be in kernel C, not BPF
> > bytecode -- Blaise Boscaccy [3]:
> >
> > The bpf_loader_verify_metadata kfunc moves the hash check from
> > inline BPF instructions into kernel C code.
>
> Please see my comments above. While the kfunc is written in C, it is
> still reliant on the loader calling that kfunc.
Yes, that's the idea of transitive trust here. Ultimately you signed
the loader with your key. The mechanism proposed here allows
verification of any dynamic metadata, it does not need to be hermetic
to the loader, provided the digest of the metadata is incorporated
into the loader's signed payload.
- KP
>
> Of course there is still also the usbility issue of not being able to
> return an error status to userspace at load time regarding the
> verification success of both the loader and the original BPF program
> map.
>
> > * LSMs cannot observe the verification result at hook time --
> > Paul Moore [4]:
> >
> > prog->aux->sig.verdict and sig.keyring are populated before any
> > LSM hook runs. Furthermore, security_bpf_prog_load_post_integrity
> > hook fires after the in-kernel hash check for consumers that want
> > to observe or gate the post-integrity transition.
> >
> > [1] Alexei Starovoitov, NACK on Hornet (TOCTOU + layering),
> > https://lore.kernel.org/all/CAADnVQJ1CRvTXBU771KaYzrx-vRaWF+k164DcFOqOsCxmuL+ig@mail.gmail.com/
> > [2] Daniel Borkmann, NACK on Hornet v3,
> > https://lore.kernel.org/all/798dba24-b5a7-4584-a1f6-793883fe9b5e@iogearbox.net/
> > [3] Blaise Boscaccy, Hornet v6 (C-side hash verification rationale),
> > https://lore.kernel.org/all/20260429191431.2345448-1-bboscaccy@linux.microsoft.com/
> > [4] Paul Moore, push for post-verifier observability,
> > https://lore.kernel.org/all/CACYkzJ4+=3owK+ELD9Nw7Rrm-UajxXEw8kVtOTJJ+SNAXpsOpw@mail.gmail.com/
> >
> >
> > KP Singh (13):
> > bpf: expose signature verdict to LSMs via bpf_prog_aux
> > bpf: include prog BTF in the signed loader signature scope
> > bpf, libbpf: load prog BTF in the skel_internal loader
> > bpf: add bpf_loader_verify_metadata kfunc
> > bpf: compute prog->digest at BPF_PROG_LOAD entry
> > bpf: resolve loader-style kfunc CALLs against prog BTF
> > libbpf: generate prog BTF for loader programs
> > bpftool gen: embed loader prog BTF in the lskel header
> > lsm: add bpf_prog_load_post_integrity hook
> > bpf: invoke security_bpf_prog_load_post_integrity from the metadata
> > kfunc
> > ipe: add BPF program signature properties
> > ipe: gate post-integrity BPF program loads
> > selftests/bpf: add IPE BPF policy integration tests
> >
> > include/linux/bpf.h | 19 +++
> > include/linux/bpf_verifier.h | 6 +
> > include/linux/btf.h | 1 +
> > include/linux/lsm_hook_defs.h | 1 +
> > include/linux/security.h | 6 +
> > include/uapi/linux/bpf.h | 5 +
> > kernel/bpf/btf.c | 8 +
> > kernel/bpf/check_btf.c | 18 +-
> > kernel/bpf/helpers.c | 65 ++++++++
> > kernel/bpf/syscall.c | 76 ++++++++-
> > kernel/bpf/verifier.c | 58 ++++++-
> > security/ipe/Kconfig | 14 ++
> > security/ipe/audit.c | 13 ++
> > security/ipe/eval.c | 57 +++++++
> > security/ipe/eval.h | 5 +
> > security/ipe/hooks.c | 42 +++++
> > security/ipe/hooks.h | 9 +
> > security/ipe/ipe.c | 4 +
> > security/ipe/policy.h | 11 ++
> > security/ipe/policy_parser.c | 20 +++
> > security/security.c | 17 ++
> > tools/bpf/bpftool/gen.c | 21 +++
> > tools/bpf/bpftool/sign.c | 17 +-
> > tools/include/uapi/linux/bpf.h | 5 +
> > tools/lib/bpf/bpf_gen_internal.h | 2 +
> > tools/lib/bpf/gen_loader.c | 127 +++++++++++---
> > tools/lib/bpf/libbpf.h | 4 +-
> > tools/lib/bpf/skel_internal.h | 67 +++++---
> > .../selftests/bpf/test_signed_bpf_ipe.sh | 156 ++++++++++++++++++
> > tools/testing/selftests/bpf/vmtest.sh | 4 +-
> > 30 files changed, 775 insertions(+), 83 deletions(-)
> > create mode 100755 tools/testing/selftests/bpf/test_signed_bpf_ipe.sh
> >
> > --
> > 2.53.0
>
> --
> paul-moore.com
^ permalink raw reply
* Re: [PATCH v4 3/7] landlock: Add UDP send access control
From: Mickaël Salaün @ 2026-05-22 21:10 UTC (permalink / raw)
To: Matthieu Buffet
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, Tingmao Wang, netdev, Paul Moore
In-Reply-To: <20260502124306.3975990-4-matthieu@buffet.re>
On Sat, May 02, 2026 at 02:43:02PM +0200, Matthieu Buffet wrote:
> Add the second half of LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP: control the
> ability to specify an explicit destination when sending a datagram, to
> override any remote peer set on a UDP socket (in sendto(), sendmsg(), and
> sendmmsg()). It will make the right useful for clients which want to
> send datagrams while specifying a destination address each time.
>
> Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
> ---
> include/uapi/linux/landlock.h | 4 ++
> security/landlock/net.c | 70 ++++++++++++++++++++++++++++++++---
> 2 files changed, 68 insertions(+), 6 deletions(-)
>
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index 22c8cc63f30e..b147223efc97 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -396,6 +396,10 @@ struct landlock_net_port_attr {
> * - or grant %LANDLOCK_ACCESS_NET_BIND_UDP on a specific port, and
> * call :manpage:`bind(2)` on that port before trying to
> * :manpage:`connect(2)` or send datagrams.
> + *
> + * .. note:: Sending datagrams to an ``AF_UNSPEC`` destination address
> + * family is not supported for IPv6 UDP sockets: you will need to use a
> + * ``NULL`` address instead.
> */
> /* clang-format off */
> #define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
> diff --git a/security/landlock/net.c b/security/landlock/net.c
> index 045881f81295..8a53aebdb8c6 100644
> --- a/security/landlock/net.c
> +++ b/security/landlock/net.c
> @@ -44,7 +44,8 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
> static int current_check_access_socket(struct socket *const sock,
> struct sockaddr *const address,
> const int addrlen,
> - access_mask_t access_request)
> + access_mask_t access_request,
> + bool connecting)
> {
> __be16 port;
> struct layer_access_masks layer_masks = {};
> @@ -69,7 +70,8 @@ static int current_check_access_socket(struct socket *const sock,
> switch (address->sa_family) {
> case AF_UNSPEC:
> if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
> - access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
> + (access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP &&
> + connecting)) {
> /*
> * Connecting to an address with AF_UNSPEC dissolves
> * the remote association while retaining the socket
> @@ -82,6 +84,35 @@ static int current_check_access_socket(struct socket *const sock,
> * inconsistencies and return -EINVAL if needed.
> */
> return 0;
> + } else if (access_request ==
> + LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
> + if (sock->sk->__sk_common.skc_family == AF_INET6) {
> + /*
> + * We cannot allow sending UDP datagrams to an
> + * explicit AF_UNSPEC address on IPv6 sockets,
> + * even if AF_UNSPEC is treated as "no address"
> + * on such sockets (so it should always be allowed).
> + * That's because the socket's family can change under
> + * our feet (if another thread calls setsockopt(IPV6_ADDRFORM))
> + * to IPv4, which would then treat AF_UNSPEC as
> + * AF_INET.
> + */
> + audit_net.family = AF_UNSPEC;
I sent this patch and I just merged it in my tree:
https://lore.kernel.org/all/20260406143717.1815792-11-mic@digikod.net/
Günther, could you please take a look at this patch too?
For consistency, we need to add `audit_net.sk = sock->sk;` here.
> + landlock_init_layer_masks(
> + subject->domain, access_request,
> + &layer_masks, LANDLOCK_KEY_NET_PORT);
> + landlock_log_denial(
> + subject,
> + &(struct landlock_request){
> + .type = LANDLOCK_REQUEST_NET_ACCESS,
> + .audit.type =
> + LSM_AUDIT_DATA_NET,
> + .audit.u.net = &audit_net,
> + .access = access_request,
> + .layer_masks = &layer_masks,
> + });
> + return -EACCES;
> + }
> } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
> access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
> /*
> @@ -124,7 +155,10 @@ static int current_check_access_socket(struct socket *const sock,
> } else {
> WARN_ON_ONCE(1);
> }
> - /* Only for bind(AF_UNSPEC+INADDR_ANY) on IPv4 socket. */
> + /*
> + * For bind(AF_UNSPEC+INADDR_ANY) on IPv4 socket and
> + * for sending to AF_UNSPEC addresses on IPv4 socket.
> + */
> fallthrough;
> case AF_INET: {
> const struct sockaddr_in *addr4;
> @@ -257,7 +291,7 @@ static int current_check_autobind_udp_socket(struct socket *const sock)
>
> return current_check_access_socket(sock, (struct sockaddr *)&port0,
> sizeof(port0),
> - LANDLOCK_ACCESS_NET_BIND_UDP);
> + LANDLOCK_ACCESS_NET_BIND_UDP, false);
> }
>
> static int hook_socket_bind(struct socket *const sock,
> @@ -273,7 +307,7 @@ static int hook_socket_bind(struct socket *const sock,
> return 0;
>
> return current_check_access_socket(sock, address, addrlen,
> - access_request);
> + access_request, false);
> }
>
> static int hook_socket_connect(struct socket *const sock,
> @@ -291,7 +325,7 @@ static int hook_socket_connect(struct socket *const sock,
> return 0;
>
> ret = current_check_access_socket(sock, address, addrlen,
> - access_request);
> + access_request, true);
>
> if (ret == 0 && sk_is_udp(sock->sk))
> ret = current_check_autobind_udp_socket(sock);
> @@ -299,9 +333,33 @@ static int hook_socket_connect(struct socket *const sock,
> return ret;
> }
>
> +static int hook_socket_sendmsg(struct socket *const sock,
> + struct msghdr *const msg, const int size)
> +{
> + struct sockaddr *const address = msg->msg_name;
> + const int addrlen = msg->msg_namelen;
> + access_mask_t access_request;
> + int ret = 0;
> +
> + if (sk_is_udp(sock->sk))
> + access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
> + else
> + return 0;
> +
> + if (address != NULL)
> + ret = current_check_access_socket(sock, address, addrlen,
> + access_request, false);
> +
> + if (ret == 0)
> + ret = current_check_autobind_udp_socket(sock);
> +
> + return ret;
> +}
> +
> static struct security_hook_list landlock_hooks[] __ro_after_init = {
> LSM_HOOK_INIT(socket_bind, hook_socket_bind),
> LSM_HOOK_INIT(socket_connect, hook_socket_connect),
> + LSM_HOOK_INIT(socket_sendmsg, hook_socket_sendmsg),
> };
>
> __init void landlock_add_net_hooks(void)
> --
> 2.39.5
>
^ permalink raw reply
* Re: [PATCH v4 7/7] landlock: Add documentation for UDP support
From: Mickaël Salaün @ 2026-05-22 21:11 UTC (permalink / raw)
To: Matthieu Buffet
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, Tingmao Wang, netdev
In-Reply-To: <20260502124306.3975990-8-matthieu@buffet.re>
On Sat, May 02, 2026 at 02:43:06PM +0200, Matthieu Buffet wrote:
> Add example of UDP usage, without detailing the two access right.
> Slightly change the example used in code blocks: build a ruleset for a
> DNS client, so that it uses both TCP and UDP.
>
> Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
> ---
> Documentation/userspace-api/landlock.rst | 89 ++++++++++++++++++------
> 1 file changed, 68 insertions(+), 21 deletions(-)
>
> diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
> index fd8b78c31f2f..9d5da9896628 100644
> --- a/Documentation/userspace-api/landlock.rst
> +++ b/Documentation/userspace-api/landlock.rst
> @@ -40,8 +40,8 @@ Filesystem rules
> and the related filesystem actions are defined with
> `filesystem access rights`.
>
> -Network rules (since ABI v4)
> - For these rules, the object is a TCP port,
> +Network rules (since ABI v4 for TCP and v10 for UDP)
> + For these rules, the object is a TCP or UDP port,
> and the related actions are defined with `network access rights`.
>
> Defining and enforcing a security policy
> @@ -49,11 +49,11 @@ Defining and enforcing a security policy
>
> We first need to define the ruleset that will contain our rules.
>
> -For this example, the ruleset will contain rules that only allow filesystem
> -read actions and establish a specific TCP connection. Filesystem write
> -actions and other TCP actions will be denied.
> +For this example, the ruleset will contain rules that only allow some
> +filesystem read actions and some specific UDP and TCP actions. Filesystem
> +write actions and other TCP/UDP actions will be denied.
>
> -The ruleset then needs to handle both these kinds of actions. This is
> +The ruleset then needs to handle all these kinds of actions. This is
> required for backward and forward compatibility (i.e. the kernel and user
> space may not know each other's supported restrictions), hence the need
> to be explicit about the denied-by-default access rights.
> @@ -81,7 +81,9 @@ to be explicit about the denied-by-default access rights.
> LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
> .handled_access_net =
> LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + LANDLOCK_ACCESS_NET_CONNECT_TCP |
> + LANDLOCK_ACCESS_NET_BIND_UDP |
> + LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
> .scoped =
> LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
> LANDLOCK_SCOPE_SIGNAL,
> @@ -132,6 +134,12 @@ version, and only use the available subset of access rights:
> case 6 ... 8:
> /* Removes LANDLOCK_ACCESS_FS_RESOLVE_UNIX for ABI < 9 */
> ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_RESOLVE_UNIX;
> + __attribute__((fallthrough));
> + case 9:
> + /* Removes LANDLOCK_ACCESS_*_UDP for ABI < 10 */
> + ruleset_attr.handled_access_net &=
> + ~(LANDLOCK_ACCESS_NET_BIND_UDP |
> + LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
> }
>
> This enables the creation of an inclusive ruleset that will contain our rules.
> @@ -180,21 +188,50 @@ this file descriptor.
>
> It may also be required to create rules following the same logic as explained
> for the ruleset creation, by filtering access rights according to the Landlock
> -ABI version. In this example, this is not required because all of the requested
> -``allowed_access`` rights are already available in ABI 1.
> +ABI version. So far, this was not required because all of the requested
> +``allowed_access`` rights have always been available, from ABI 1.
>
> -For network access-control, we can add a set of rules that allow to use a port
> -number for a specific action: HTTPS connections.
> +For network access-control, we will add a set of rules to allow DNS
> +queries, which requires both UDP and TCP. For TCP, we need to allow
> +outbound connections to port 53, which can be handled and granted starting
> +with ABI 4:
>
> .. code-block:: c
>
> - struct landlock_net_port_attr net_port = {
> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> - .port = 443,
> - };
> + if (ruleset_attr.handled_access_net & LANDLOCK_ACCESS_NET_CONNECT_TCP) {
> + struct landlock_net_port_attr net_port = {
> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + .port = 53,
> + };
>
> - err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &net_port, 0);
> + err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> + &net_port, 0);
> +
> +We also need to be able to send UDP datagrams to port 53, which requires
> +granting ``LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP``. Since our DNS client will
> +emit datagrams without explicitly binding to a specific source port, its UDP
> +socket will automatically bind an ephemeral port. To allow this behaviour,
> +we also need to grant ``LANDLOCK_ACCESS_NET_BIND_UDP`` on port 0, as if
> +the program explicitly called :manpage:`bind(2)` on port 0.
Please follow the pattern in this new patch (already in my next branch):
https://lore.kernel.org/all/20260513151856.148423-1-mic@digikod.net/
> +
> +.. code-block:: c
> +
> + if (ruleset_attr.handled_access_net & LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
> + const struct landlock_net_port_attr send_dst_port = {
> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
> + .port = 53,
> + };
> + err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> + &send_dst_port, 0);
> + [...]
> +
> + if (ruleset_attr.handled_access_net & LANDLOCK_ACCESS_NET_BIND_UDP) {
> + const struct landlock_net_port_attr bind_src_port = {
> + .allowed_access = LANDLOCK_ACCESS_NET_BIND_UDP,
> + .port = 0,
> + };
> + err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> + &bind_src_port, 0);
>
> When passing a non-zero ``flags`` argument to ``landlock_restrict_self()``, a
> similar backwards compatibility check is needed for the restrict flags
> @@ -228,7 +265,7 @@ similar backwards compatibility check is needed for the restrict flags
> The next step is to restrict the current thread from gaining more privileges
> (e.g. through a SUID binary). We now have a ruleset with the first rule
> allowing read and execute access to ``/usr`` while denying all other handled
> -accesses for the filesystem, and a second rule allowing HTTPS connections.
> +accesses for the filesystem, and two more rules allowing DNS queries.
>
> .. code-block:: c
>
> @@ -716,6 +753,16 @@ Starting with the Landlock ABI version 9, it is possible to restrict
> connections to pathname UNIX domain sockets (:manpage:`unix(7)`) using
> the new ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` right.
>
> +UDP bind, connect, sendto, sendmsg and sendmmsg (ABI < 10)
nit: Make it a bit shorter because it is also use in link (#). Maybe
something like this:
UDP bind, connect and send* (ABI < 10)
...and specifying each syscall in the text below.
> +----------------------------------------------------------
> +
> +Starting with the Landlock ABI version 10, it is possible to restrict
> +setting the local port of UDP sockets with the
> +``LANDLOCK_ACCESS_NET_BIND_UDP`` right.
> +The ``LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP`` right controls setting the
> +remote port of UDP sockets, and sending datagrams to an explicit remote
> +port (ignoring any destination set on UDP sockets).
> +
> .. _kernel_support:
>
> Kernel support
> @@ -778,10 +825,10 @@ the boot loader.
> Network support
> ---------------
>
> -To be able to explicitly allow TCP operations (e.g., adding a network rule with
> -``LANDLOCK_ACCESS_NET_BIND_TCP``), the kernel must support TCP
> +To be able to explicitly allow TCP or UDP operations (e.g., adding a network rule with
> +``LANDLOCK_ACCESS_NET_BIND_TCP``), the kernel must support the TCP/IP protocol suite
> (``CONFIG_INET=y``). Otherwise, sys_landlock_add_rule() returns an
> -``EAFNOSUPPORT`` error, which can safely be ignored because this kind of TCP
> +``EAFNOSUPPORT`` error, which can safely be ignored because this kind of TCP or UDP
> operation is already not possible.
>
> Questions and answers
> --
> 2.39.5
>
>
^ permalink raw reply
* Re: [PATCH v4 2/7] landlock: Add UDP connect() access control
From: Mickaël Salaün @ 2026-05-22 21:10 UTC (permalink / raw)
To: Matthieu Buffet
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, Tingmao Wang, netdev
In-Reply-To: <20260502124306.3975990-3-matthieu@buffet.re>
On Sat, May 02, 2026 at 02:43:01PM +0200, Matthieu Buffet wrote:
> Add support for a second fine-grained UDP access right.
> This first half of LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP controls the
> ability to set the remote port of a socket (via connect()). It will be
> useful for applications that send datagrams, and for some servers too
> (those creating per-client sockets, which want to receive traffic only
> from a specific address).
>
> Similarly as for bind(), this access control is performed when
> configuring sockets, not in hot code paths.
>
> Include detection of when autobind is about to be required, and check if
> the process would be allowed to call bind(0) explicitly. Autobind can
> only be performed when sending a first datagram, when connect()ing, and
> in some splice() EOF edge case which, afaiu, can only happen after a
> remote peer has been set (which is already covered).
>
> Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
> ---
> include/uapi/linux/landlock.h | 19 +++++
> security/landlock/audit.c | 2 +
> security/landlock/limits.h | 2 +-
> security/landlock/net.c | 79 +++++++++++++++++----
> tools/testing/selftests/landlock/net_test.c | 5 +-
> 5 files changed, 92 insertions(+), 15 deletions(-)
>
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index 045b251ff1b4..22c8cc63f30e 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -378,11 +378,30 @@ struct landlock_net_port_attr {
> *
> * - %LANDLOCK_ACCESS_NET_BIND_UDP: Bind UDP sockets to the given local
> * port. Support added in Landlock ABI version 10.
> + * - %LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP: Set the remote port of UDP
> + * sockets to the given port, or send datagrams to the given remote port
> + * ignoring any destination pre-set on a socket. Support added in
> + * Landlock ABI version 10.
> + *
> + * .. note:: Setting a remote address or sending a first datagram
> + * auto-binds UDP sockets to an ephemeral local source port if not
> + * already bound. To allow this if both %LANDLOCK_ACCESS_NET_BIND_UDP
> + * and %LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP are handled, you need to
> + * either:
> + *
> + * - use a socket already bound to a port before the ruleset started
> + * being enforced;
> + * - or grant %LANDLOCK_ACCESS_NET_BIND_UDP on port 0, meaning "any
> + * port in the ephemeral port range";
> + * - or grant %LANDLOCK_ACCESS_NET_BIND_UDP on a specific port, and
> + * call :manpage:`bind(2)` on that port before trying to
> + * :manpage:`connect(2)` or send datagrams.
> */
> /* clang-format off */
> #define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
> #define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
> #define LANDLOCK_ACCESS_NET_BIND_UDP (1ULL << 2)
> +#define LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP (1ULL << 3)
> /* clang-format on */
>
> /**
> diff --git a/security/landlock/audit.c b/security/landlock/audit.c
> index e676ebffeebe..851647197a01 100644
> --- a/security/landlock/audit.c
> +++ b/security/landlock/audit.c
> @@ -46,6 +46,8 @@ static const char *const net_access_strings[] = {
> [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp",
> [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp",
> [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_UDP)] = "net.bind_udp",
> + [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)] =
> + "net.connect_send_udp",
> };
>
> static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);
> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> index c0f30a4591b8..a4d908b240a2 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -23,7 +23,7 @@
> #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
> #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>
> -#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_BIND_UDP
> +#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP
> #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
> #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
>
> diff --git a/security/landlock/net.c b/security/landlock/net.c
> index f9ccb52e7d45..045881f81295 100644
> --- a/security/landlock/net.c
> +++ b/security/landlock/net.c
> @@ -68,16 +68,17 @@ static int current_check_access_socket(struct socket *const sock,
>
> switch (address->sa_family) {
> case AF_UNSPEC:
> - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
> + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
> + access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
> /*
> * Connecting to an address with AF_UNSPEC dissolves
> - * the TCP association, which have the same effect as
> - * closing the connection while retaining the socket
> - * object (i.e., the file descriptor). As for dropping
> - * privileges, closing connections is always allowed.
> - *
> - * For a TCP access control system, this request is
> - * legitimate. Let the network stack handle potential
> + * the remote association while retaining the socket
> + * object (i.e., the file descriptor). For TCP, it has
> + * the same effect as closing the connection. For UDP,
> + * it removes any preset remote address. As for
> + * dropping privileges, these actions are always
> + * allowed.
> + * Let the network stack handle potential
> * inconsistencies and return -EINVAL if needed.
> */
> return 0;
> @@ -134,7 +135,8 @@ static int current_check_access_socket(struct socket *const sock,
> addr4 = (struct sockaddr_in *)address;
> port = addr4->sin_port;
>
> - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
> + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
> + access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
> audit_net.dport = port;
> audit_net.v4info.daddr = addr4->sin_addr.s_addr;
> } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
> @@ -157,7 +159,8 @@ static int current_check_access_socket(struct socket *const sock,
> addr6 = (struct sockaddr_in6 *)address;
> port = addr6->sin6_port;
>
> - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
> + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
> + access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
> audit_net.dport = port;
> audit_net.v6info.daddr = addr6->sin6_addr;
> } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
> @@ -213,6 +216,50 @@ static int current_check_access_socket(struct socket *const sock,
> return -EACCES;
> }
>
> +static int current_check_autobind_udp_socket(struct socket *const sock)
> +{
> + struct sockaddr_storage port0 = { 0 };
unsigned short num;
bool slow;
> +
> + /*
> + * On UDP sockets, if a local port has not already been bound,
> + * calling connect() or sending a first datagram has the side
> + * effect of autobinding an ephemeral port: we also have to check
> + * that the process would have had the right to bind(0) explicitly.
> + * Note: socket is not locked, so another thread could do an
> + * explicit bind(!=0) on this socket, changing inet_num to non-zero
> + * after we read it, but this would only have us enforce an
> + * additional bind(0) access check and would not bypass policy.
> + */
> + if (inet_sk(sock->sk)->inet_num != 0)
There is still a race condition, this should fix it:
*
* Hold the socket lock around the inet_num read to exclude
* udp_lib_get_port()'s transient inet_num = snum write that is reverted
* to 0 on a failing reuseport bind.
*/
if (inet_sk(sock->sk)->inet_num != 0)
slow = lock_sock_fast(sock->sk);
num = inet_sk(sock->sk)->inet_num;
unlock_sock_fast(sock->sk, slow);
if (num != 0)
return 0;
> + return 0;
> +
> + /*
> + * Construct a struct sockaddr* with port 0 to pretend the
> + * process tried to bind() on that address.
> + */
> + port0.ss_family = sock->sk->__sk_common.skc_family;
Looking at net/, __sk_common.skc_family (and other __sk_common.* fields)
should be replaced by sk_family (see #define in include/net/sock.h).
Same for all other __sk_common.
In fact, it should be READ_ONCE(sock->sk->sk_family) for consistency
with the net/ code and because the socket are not locked when the LSM
hooks are called. I realized that the existing Landlock code have the
same issue... Could you please add a new patch (to be backported) to
always use this pattern?
> + switch (port0.ss_family) {
> + case AF_INET: {
> + ((struct sockaddr_in *)&port0)->sin_port = 0;
> + break;
> + }
> +
> +#if IS_ENABLED(CONFIG_IPV6)
> + case AF_INET6: {
> + ((struct sockaddr_in6 *)&port0)->sin6_port = 0;
> + break;
> + }
> +#endif /* IS_ENABLED(CONFIG_IPV6) */
> +
> + default:
> + return 0;
> + }
> +
> + return current_check_access_socket(sock, (struct sockaddr *)&port0,
> + sizeof(port0),
> + LANDLOCK_ACCESS_NET_BIND_UDP);
> +}
> +
> static int hook_socket_bind(struct socket *const sock,
> struct sockaddr *const address, const int addrlen)
> {
> @@ -234,14 +281,22 @@ static int hook_socket_connect(struct socket *const sock,
> const int addrlen)
> {
> access_mask_t access_request;
> + int ret = 0;
>
> if (sk_is_tcp(sock->sk))
> access_request = LANDLOCK_ACCESS_NET_CONNECT_TCP;
> + else if (sk_is_udp(sock->sk))
> + access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
> else
> return 0;
>
> - return current_check_access_socket(sock, address, addrlen,
> - access_request);
> + ret = current_check_access_socket(sock, address, addrlen,
> + access_request);
> +
> + if (ret == 0 && sk_is_udp(sock->sk))
> + ret = current_check_autobind_udp_socket(sock);
> +
> + return ret;
> }
>
> static struct security_hook_list landlock_hooks[] __ro_after_init = {
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> index ec392d971ea3..016c7277e370 100644
> --- a/tools/testing/selftests/landlock/net_test.c
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -1326,12 +1326,13 @@ FIXTURE_TEARDOWN(mini)
>
> /* clang-format off */
>
> -#define ACCESS_LAST LANDLOCK_ACCESS_NET_BIND_UDP
> +#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP
>
> #define ACCESS_ALL ( \
> LANDLOCK_ACCESS_NET_BIND_TCP | \
> LANDLOCK_ACCESS_NET_CONNECT_TCP | \
> - LANDLOCK_ACCESS_NET_BIND_UDP)
> + LANDLOCK_ACCESS_NET_BIND_UDP | \
> + LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)
>
> /* clang-format on */
>
> --
> 2.39.5
>
^ permalink raw reply
* Re: [PATCH v4 0/7] landlock: Add UDP access control support
From: Mickaël Salaün @ 2026-05-22 21:08 UTC (permalink / raw)
To: Matthieu Buffet
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, Tingmao Wang, netdev
In-Reply-To: <20260502124306.3975990-1-matthieu@buffet.re>
Thanks again, I only spotted some minor issues.
On Sat, May 02, 2026 at 02:42:59PM +0200, Matthieu Buffet wrote:
> Hi,
>
> This is V4 of UDP access control in Landlock. Thanks to the round of
> review of v3, access rights have changed to something that seems easier
> to use and understand. It adds only two access rights, to restrict
> configuring local and remote addresses on UDP sockets. The one that
> restricts setting a remote address also controls sending datagrams to
> explicit remote addresses -ignoring any remote address preset on the
> socket-. The one that restricts binding to a local port also applies
> when the kernel auto-binds an ephemeral port.
> v1:
> Link: https://lore.kernel.org/all/20240916122230.114800-1-matthieu@buffet.re/
> v2:
> Link: https://lore.kernel.org/all/20241214184540.3835222-1-matthieu@buffet.re/
> v3:
> Link: https://lore.kernel.org/all/20251212163704.142301-1-matthieu@buffet.re/
>
> The limitation around allowing a process to send but not receive is
> still there, and could warrant another patch if there is a real user
> need.
> I'm just not super happy about the clarity of logs generated for denied
> autobinds ("domain=xxxxxx blockers=net.bind_udp"), due to the fact that
> addresses and ports are currently only logged if they are non-0. A later
> (coordinated LSM-wide) patch could improve readability by replacing != 0
> checks with new booleans in struct lsm_network_audit.
Do you plan to send such patch after this series? I guess we could add
has_{port,addr} fields to lsm_network_audit and handle AF_UNSPEC too?
> I'm also not
> exactly happy with the integration in existing TCP selftests, but
> refactoring them has already been discussed earlier.
Can you remind us what was your concern and the potential fix?
>
> Changes v1->v2
> ==============
> - recvmsg hook is gone and sendmsg hook doesn't apply when sending to a
> remote address pre-set on socket, to improve performance
> - don't add a get_addr_port() helper function, which required a weird
> "am I in IPv4 or IPv6 context"
> - reorder hook prologue for consistency: check domain, then type and
> family
>
> Changes v2->v3
> ==============
> - removed support for sending datagrams with explicit destination
> address of family AF_UNSPEC, which allowed to bypass restrictions with
> a race condition
> - rebased on linux-mic/next => add support for auditing
> - fixed mistake in selftests when using unspec_srv variables, which were
> implicitly of type SOCK_STREAM and did not actually test UDP code
> - add tests for IPPROTO_IP
> - improved docs, split off TCP-related refactoring
>
> Changes v3->v4
> ==============
> - merge LANDLOCK_ACCESS_NET_CONNECT_UDP and
> LANDLOCK_ACCESS_NET_SENDTO_UDP into
> LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP (everything that might set the
> destination of a datagram)
> - make LANDLOCK_ACCESS_NET_BIND_UDP apply when kernel is about to
> auto-bind an ephemeral port for the caller. Block it if policy would
> not allow an explicit call to bind(0)
> - only deny sending AF_UNSPEC datagrams on IPv6 sockets, where there is
> a risk of the address family changing midway
>
> Patch is based on https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git
> 3457a5ccacd3 ("landlock: Document fallocate(2) as another truncation corner case")
> All lines added are covered with selftests, except the "default: return
> 0" in current_check_autobind_udp_socket() which is not currently
> reachable (net.c goes from 92.9%->94.6% line coverage).
>
> Let me know what you think!
>
> Closes: https://github.com/landlock-lsm/linux/issues/10
>
> Matthieu Buffet (7):
> landlock: Add UDP bind() access control
> landlock: Add UDP connect() access control
> landlock: Add UDP send access control
> selftests/landlock: Add UDP bind/connect tests
> selftests/landlock: Add tests for sendmsg()
> samples/landlock: Add sandboxer UDP access control
> landlock: Add documentation for UDP support
>
> Documentation/userspace-api/landlock.rst | 89 +-
> include/uapi/linux/landlock.h | 35 +-
> samples/landlock/sandboxer.c | 40 +-
> security/landlock/audit.c | 3 +
> security/landlock/limits.h | 2 +-
> security/landlock/net.c | 161 ++-
> security/landlock/syscalls.c | 2 +-
> tools/testing/selftests/landlock/base_test.c | 4 +-
> tools/testing/selftests/landlock/net_test.c | 1146 ++++++++++++++++--
> 9 files changed, 1341 insertions(+), 141 deletions(-)
>
>
> base-commit: 3457a5ccacd34fdd5ebd3a4745e721b5a1239690
> --
> 2.39.5
>
>
^ permalink raw reply
* Re: [PATCH v4 2/7] landlock: Add UDP connect() access control
From: Mickaël Salaün @ 2026-05-22 21:18 UTC (permalink / raw)
To: Matthieu Buffet
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, Tingmao Wang, netdev
In-Reply-To: <20260502124306.3975990-3-matthieu@buffet.re>
On Sat, May 02, 2026 at 02:43:01PM +0200, Matthieu Buffet wrote:
> Add support for a second fine-grained UDP access right.
> This first half of LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP controls the
> ability to set the remote port of a socket (via connect()). It will be
> useful for applications that send datagrams, and for some servers too
> (those creating per-client sockets, which want to receive traffic only
> from a specific address).
>
> Similarly as for bind(), this access control is performed when
> configuring sockets, not in hot code paths.
>
> Include detection of when autobind is about to be required, and check if
> the process would be allowed to call bind(0) explicitly. Autobind can
> only be performed when sending a first datagram, when connect()ing, and
> in some splice() EOF edge case which, afaiu, can only happen after a
> remote peer has been set (which is already covered).
>
> Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
> ---
> include/uapi/linux/landlock.h | 19 +++++
> security/landlock/audit.c | 2 +
> security/landlock/limits.h | 2 +-
> security/landlock/net.c | 79 +++++++++++++++++----
> tools/testing/selftests/landlock/net_test.c | 5 +-
> 5 files changed, 92 insertions(+), 15 deletions(-)
> diff --git a/security/landlock/net.c b/security/landlock/net.c
> index f9ccb52e7d45..045881f81295 100644
> --- a/security/landlock/net.c
> +++ b/security/landlock/net.c
> @@ -68,16 +68,17 @@ static int current_check_access_socket(struct socket *const sock,
>
> switch (address->sa_family) {
> case AF_UNSPEC:
> - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
> + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
> + access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
> /*
> * Connecting to an address with AF_UNSPEC dissolves
> - * the TCP association, which have the same effect as
> - * closing the connection while retaining the socket
> - * object (i.e., the file descriptor). As for dropping
> - * privileges, closing connections is always allowed.
> - *
> - * For a TCP access control system, this request is
> - * legitimate. Let the network stack handle potential
> + * the remote association while retaining the socket
> + * object (i.e., the file descriptor). For TCP, it has
> + * the same effect as closing the connection. For UDP,
> + * it removes any preset remote address. As for
> + * dropping privileges, these actions are always
> + * allowed.
> + * Let the network stack handle potential
> * inconsistencies and return -EINVAL if needed.
> */
> return 0;
> @@ -134,7 +135,8 @@ static int current_check_access_socket(struct socket *const sock,
> addr4 = (struct sockaddr_in *)address;
> port = addr4->sin_port;
>
> - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
> + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
> + access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
> audit_net.dport = port;
> audit_net.v4info.daddr = addr4->sin_addr.s_addr;
> } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
> @@ -157,7 +159,8 @@ static int current_check_access_socket(struct socket *const sock,
> addr6 = (struct sockaddr_in6 *)address;
> port = addr6->sin6_port;
>
> - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
> + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
> + access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) {
> audit_net.dport = port;
> audit_net.v6info.daddr = addr6->sin6_addr;
> } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
> @@ -213,6 +216,50 @@ static int current_check_access_socket(struct socket *const sock,
> return -EACCES;
> }
>
> +static int current_check_autobind_udp_socket(struct socket *const sock)
> +{
> + struct sockaddr_storage port0 = { 0 };
struct sockaddr_storage port0 = {};
> +
> + /*
> + * On UDP sockets, if a local port has not already been bound,
> + * calling connect() or sending a first datagram has the side
> + * effect of autobinding an ephemeral port: we also have to check
> + * that the process would have had the right to bind(0) explicitly.
> + * Note: socket is not locked, so another thread could do an
> + * explicit bind(!=0) on this socket, changing inet_num to non-zero
> + * after we read it, but this would only have us enforce an
> + * additional bind(0) access check and would not bypass policy.
> + */
> + if (inet_sk(sock->sk)->inet_num != 0)
> + return 0;
> +
> + /*
> + * Construct a struct sockaddr* with port 0 to pretend the
> + * process tried to bind() on that address.
> + */
> + port0.ss_family = sock->sk->__sk_common.skc_family;
> + switch (port0.ss_family) {
> + case AF_INET: {
> + ((struct sockaddr_in *)&port0)->sin_port = 0;
Why is this useful? The struct is already initialized to 0.
> + break;
> + }
> +
> +#if IS_ENABLED(CONFIG_IPV6)
> + case AF_INET6: {
> + ((struct sockaddr_in6 *)&port0)->sin6_port = 0;
Same question.
> + break;
> + }
> +#endif /* IS_ENABLED(CONFIG_IPV6) */
> +
> + default:
> + return 0;
> + }
> +
> + return current_check_access_socket(sock, (struct sockaddr *)&port0,
> + sizeof(port0),
> + LANDLOCK_ACCESS_NET_BIND_UDP);
> +}
^ permalink raw reply
* Re: [PATCH v2 1/2] libbpf: fix off-by-one in emit_signature_match jump offset
From: Daniel Borkmann @ 2026-05-22 21:21 UTC (permalink / raw)
To: KP Singh, bpf, linux-security-module; +Cc: ast, memxor
In-Reply-To: <20260521152217.2345235-2-kpsingh@kernel.org>
On 5/21/26 5:22 PM, KP Singh wrote:
> The offset for the cleanup-label jump is computed before the MOV R7
> instruction is emitted, but the JMP lands after it. Account for the
> extra insn in the offset calculation (-2 instead of -1). Drop the
> redundant self-loop in the else branch; gen->error = -ERANGE already
> marks the generation as failed.
>
> Fixes: fb2b0e290147 ("libbpf: Update light skeleton for signing")
Fixes: ea923080c145 ("libbpf: Embed and verify the metadata hash in the loader")
The fb2b0e290147 does not touch emit_signature_match as pointed out.
> Signed-off-by: KP Singh <kpsingh@kernel.org>
Other than that, lgtm:
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
> ---
> tools/lib/bpf/gen_loader.c | 3 +--
> 1 file changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
> index cd5c2543f54d..9478b8f78f26 100644
> --- a/tools/lib/bpf/gen_loader.c
> +++ b/tools/lib/bpf/gen_loader.c
> @@ -592,13 +592,12 @@ static void emit_signature_match(struct bpf_gen *gen)
> gen->hash_insn_offset[i] = gen->insn_cur - gen->insn_start;
> emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_3, 0, 0, 0, 0, 0));
>
> - off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 1;
> + off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
> if (is_simm16(off)) {
> emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
> emit(gen, BPF_JMP_REG(BPF_JNE, BPF_REG_2, BPF_REG_3, off));
> } else {
> gen->error = -ERANGE;
> - emit(gen, BPF_JMP_IMM(BPF_JA, 0, 0, -1));
It would make sense to move this into a separate commit, and then also clean
up the same in emit_check_err() in one go?
> }
> }
> }
^ permalink raw reply
* Re: [PATCH v2 2/2] bpf, libbpf: reject non-exclusive metadata maps in the signed loader
From: Daniel Borkmann @ 2026-05-22 21:44 UTC (permalink / raw)
To: KP Singh, bpf, linux-security-module; +Cc: ast, memxor
In-Reply-To: <20260521152217.2345235-3-kpsingh@kernel.org>
On 5/21/26 5:22 PM, KP Singh wrote:
> The loader verifies map->sha against the metadata hash in its
> instructions. map->sha is calculated when BPF_OBJ_GET_INFO_BY_FD is called
> on the frozen map.
>
> While the map is frozen, the loader must also ensure the map is
> exclusive, as, without exclusivity, another BPF program with map access
> can mutate the contents afterwards, so the check passes on stale data.
>
> Place excl_prog_sha right after sha[] in struct bpf_map and have
> gen_loader bail with -EINVAL when it is NULL, via BPF_PSEUDO_MAP_IDX at
> fixed offset 32. Declare excl_prog_sha with __bpf_md_ptr so the
> 8-byte BPF_LDX_MEM read works on 32-bit kernels.
>
> Fixes: fb2b0e290147 ("libbpf: Update light skeleton for signing")
> Signed-off-by: KP Singh <kpsingh@kernel.org>
> ---
> include/linux/bpf.h | 2 +-
> tools/lib/bpf/gen_loader.c | 16 ++++++++++++++++
> .../selftests/bpf/progs/verifier_map_ptr.c | 10 ++++++----
> 3 files changed, 23 insertions(+), 5 deletions(-)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index cd191c5fdb0a..ea9bd24f82c0 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -295,6 +295,7 @@ struct bpf_map_owner {
>
> struct bpf_map {
> u8 sha[SHA256_DIGEST_SIZE];
> + __bpf_md_ptr(char *, excl_prog_sha);
> const struct bpf_map_ops *ops;
> struct bpf_map *inner_map_meta;
> #ifdef CONFIG_SECURITY
> @@ -335,7 +336,6 @@ struct bpf_map {
> atomic64_t sleepable_refcnt;
> s64 __percpu *elem_count;
> u64 cookie; /* write-once */
> - char *excl_prog_sha;
> };
>
> static inline const char *btf_field_type_name(enum btf_field_type type)
> diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
> index 9478b8f78f26..fee35c26deb8 100644
> --- a/tools/lib/bpf/gen_loader.c
> +++ b/tools/lib/bpf/gen_loader.c
> @@ -600,6 +600,22 @@ static void emit_signature_match(struct bpf_gen *gen)
> gen->error = -ERANGE;
> }
> }
> +
> + /* Reject if the metadata map is not exclusive. Without exclusivity
> + * the cached map->sha[] verified above can be stale: another BPF
> + * program with map access could have mutated the contents between
> + * BPF_OBJ_GET_INFO_BY_FD and loader execution.
> + */
We could probably add sth like this somewhere in the bpf kernel side :
/* See: emit_signature_match() in tools/lib/bpf/gen_loader.c */
BUILD_BUG_ON(offsetof(struct bpf_map, sha) != 0);
BUILD_BUG_ON(offsetof(struct bpf_map, excl_prog_sha) != SHA256_DIGEST_SIZE);
BUILD_BUG_ON(sizeof_field(struct bpf_map, excl_prog_sha) != sizeof(__u64));
> + emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
> + 0, 0, 0, 0));
> + emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, SHA256_DWORD_SIZE * sizeof(__u64)));
nit: SHA256_DWORD_SIZE is SHA256_DIGEST_LENGTH / sizeof(__u64), can we just use
SHA256_DIGEST_LENGTH ?
> + off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
> + if (is_simm16(off)) {
> + emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
> + emit(gen, BPF_JMP_IMM(BPF_JEQ, BPF_REG_2, 0, off));
> + } else {
> + gen->error = -ERANGE;
> + }
> }
>
> void bpf_gen__record_attach_target(struct bpf_gen *gen, const char *attach_name,
^ permalink raw reply
* [PATCH bpf v3 0/2] gen_loader fixes
From: KP Singh @ 2026-05-22 21:53 UTC (permalink / raw)
To: bpf, linux-security-module; +Cc: ast, daniel, memxor
# v2 -> v3
defer fixing 32 bit.
Fix off-by-one in the signed loader's cleanup jump and reject
non-exclusive metadata maps.
Patch 1 fixes a pre-existing bug: the cleanup-label offset in
emit_signature_match() does not account for the MOV insn emitted
before the JMP.
Patch 2 adds an exclusivity check so the loader bails if the
metadata map lacks excl_prog_hash, preventing stale-hash attacks
from concurrent map writers.
KP Singh (2):
libbpf: fix off-by-one in emit_signature_match jump offset
bpf, libbpf: reject non-exclusive metadata maps in the signed loader
include/linux/bpf.h | 2 +-
tools/lib/bpf/gen_loader.c | 20 +++++++++++++++++--
.../selftests/bpf/progs/verifier_map_ptr.c | 8 +++++---
3 files changed, 24 insertions(+), 6 deletions(-)
--
2.53.0
^ permalink raw reply
* [PATCH bpf v3 1/2] libbpf: fix off-by-one in emit_signature_match jump offset
From: KP Singh @ 2026-05-22 21:53 UTC (permalink / raw)
To: bpf, linux-security-module; +Cc: ast, daniel, memxor, KP Singh
In-Reply-To: <20260522215337.662271-1-kpsingh@kernel.org>
The offset for the cleanup-label jump is computed before the MOV R7
instruction is emitted, but the JMP lands after it. Account for the
extra insn in the offset calculation (-2 instead of -1). Drop the
redundant self-loop in the else branch; gen->error = -ERANGE already
marks the generation as failed.
Fixes: fb2b0e290147 ("libbpf: Update light skeleton for signing")
Signed-off-by: KP Singh <kpsingh@kernel.org>
---
tools/lib/bpf/gen_loader.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
index cd5c2543f54d..9478b8f78f26 100644
--- a/tools/lib/bpf/gen_loader.c
+++ b/tools/lib/bpf/gen_loader.c
@@ -592,13 +592,12 @@ static void emit_signature_match(struct bpf_gen *gen)
gen->hash_insn_offset[i] = gen->insn_cur - gen->insn_start;
emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_3, 0, 0, 0, 0, 0));
- off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 1;
+ off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
if (is_simm16(off)) {
emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
emit(gen, BPF_JMP_REG(BPF_JNE, BPF_REG_2, BPF_REG_3, off));
} else {
gen->error = -ERANGE;
- emit(gen, BPF_JMP_IMM(BPF_JA, 0, 0, -1));
}
}
}
--
2.53.0
^ permalink raw reply related
* [PATCH bpf v3 2/2] bpf, libbpf: reject non-exclusive metadata maps in the signed loader
From: KP Singh @ 2026-05-22 21:53 UTC (permalink / raw)
To: bpf, linux-security-module; +Cc: ast, daniel, memxor, KP Singh
In-Reply-To: <20260522215337.662271-1-kpsingh@kernel.org>
The loader verifies map->sha against the metadata hash in its
instructions. map->sha is calculated when BPF_OBJ_GET_INFO_BY_FD is called
on the frozen map.
While the map is frozen, the loader must also ensure the map is
exclusive, as, without exclusivity, another BPF program with map access
can mutate the contents afterwards, so the check passes on stale data.
Place excl_prog_sha right after sha[] in struct bpf_map and have
gen_loader bail with -EINVAL when it is NULL, via BPF_PSEUDO_MAP_IDX at
fixed offset 32. The 8-byte read of the pointer field limits this to
64-bit kernels; gen_loader needs target pointer size tracking to emit
the right sized read on 32-bit (follow-up).
Fixes: fb2b0e290147 ("libbpf: Update light skeleton for signing")
Signed-off-by: KP Singh <kpsingh@kernel.org>
---
include/linux/bpf.h | 2 +-
tools/lib/bpf/gen_loader.c | 17 +++++++++++++++++
.../selftests/bpf/progs/verifier_map_ptr.c | 8 +++++---
3 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index cd191c5fdb0a..11bec73db199 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -295,6 +295,7 @@ struct bpf_map_owner {
struct bpf_map {
u8 sha[SHA256_DIGEST_SIZE];
+ char *excl_prog_sha;
const struct bpf_map_ops *ops;
struct bpf_map *inner_map_meta;
#ifdef CONFIG_SECURITY
@@ -335,7 +336,6 @@ struct bpf_map {
atomic64_t sleepable_refcnt;
s64 __percpu *elem_count;
u64 cookie; /* write-once */
- char *excl_prog_sha;
};
static inline const char *btf_field_type_name(enum btf_field_type type)
diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
index 9478b8f78f26..5077472b9fd2 100644
--- a/tools/lib/bpf/gen_loader.c
+++ b/tools/lib/bpf/gen_loader.c
@@ -600,6 +600,23 @@ static void emit_signature_match(struct bpf_gen *gen)
gen->error = -ERANGE;
}
}
+
+ /*
+ * Reject if the metadata map is not exclusive. Without exclusivity
+ * the cached map->sha[] verified above can be stale: another BPF
+ * program with map access could have mutated the contents between
+ * BPF_OBJ_GET_INFO_BY_FD and loader execution.
+ */
+ emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
+ 0, 0, 0, 0));
+ emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, SHA256_DWORD_SIZE * sizeof(__u64)));
+ off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
+ if (is_simm16(off)) {
+ emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
+ emit(gen, BPF_JMP_IMM(BPF_JEQ, BPF_REG_2, 0, off));
+ } else {
+ gen->error = -ERANGE;
+ }
}
void bpf_gen__record_attach_target(struct bpf_gen *gen, const char *attach_name,
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
index e2767d27d8aa..a89d25ce4f91 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
@@ -70,13 +70,15 @@ __naked void bpf_map_ptr_write_rejected(void)
: __clobber_all);
}
-/* The first element of struct bpf_map is a SHA256 hash of 32 bytes, accessing
- * into this array is valid. The opts field is now at offset 33.
+/*
+ * The first 32 bytes of struct bpf_map are the SHA256 hash; accessing into
+ * this array is valid. excl_prog_sha follows at offset 32, reading at
+ * offset 33 crosses into the pointer and must be rejected.
*/
SEC("socket")
__description("bpf_map_ptr: read non-existent field rejected")
__failure
-__msg("cannot access ptr member ops with moff 32 in struct bpf_map with off 33 size 4")
+__msg("cannot access ptr member excl_prog_sha with moff 32 in struct bpf_map with off 33 size 4")
__failure_unpriv
__msg_unpriv("access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
__flag(BPF_F_ANY_ALIGNMENT)
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2 2/2] bpf, libbpf: reject non-exclusive metadata maps in the signed loader
From: Daniel Borkmann @ 2026-05-22 22:05 UTC (permalink / raw)
To: KP Singh, bpf, linux-security-module; +Cc: ast, memxor
In-Reply-To: <dcc02e2d-58dd-46eb-9b72-54cd8465fb64@iogearbox.net>
On 5/22/26 11:44 PM, Daniel Borkmann wrote:
> On 5/21/26 5:22 PM, KP Singh wrote:
>> The loader verifies map->sha against the metadata hash in its
>> instructions. map->sha is calculated when BPF_OBJ_GET_INFO_BY_FD is called
>> on the frozen map.
>>
>> While the map is frozen, the loader must also ensure the map is
>> exclusive, as, without exclusivity, another BPF program with map access
>> can mutate the contents afterwards, so the check passes on stale data.
>>
>> Place excl_prog_sha right after sha[] in struct bpf_map and have
>> gen_loader bail with -EINVAL when it is NULL, via BPF_PSEUDO_MAP_IDX at
>> fixed offset 32. Declare excl_prog_sha with __bpf_md_ptr so the
>> 8-byte BPF_LDX_MEM read works on 32-bit kernels.
>>
>> Fixes: fb2b0e290147 ("libbpf: Update light skeleton for signing")
>> Signed-off-by: KP Singh <kpsingh@kernel.org>
>> ---
>> include/linux/bpf.h | 2 +-
>> tools/lib/bpf/gen_loader.c | 16 ++++++++++++++++
>> .../selftests/bpf/progs/verifier_map_ptr.c | 10 ++++++----
>> 3 files changed, 23 insertions(+), 5 deletions(-)
>>
>> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
>> index cd191c5fdb0a..ea9bd24f82c0 100644
>> --- a/include/linux/bpf.h
>> +++ b/include/linux/bpf.h
>> @@ -295,6 +295,7 @@ struct bpf_map_owner {
>> struct bpf_map {
>> u8 sha[SHA256_DIGEST_SIZE];
>> + __bpf_md_ptr(char *, excl_prog_sha);
>> const struct bpf_map_ops *ops;
>> struct bpf_map *inner_map_meta;
>> #ifdef CONFIG_SECURITY
>> @@ -335,7 +336,6 @@ struct bpf_map {
>> atomic64_t sleepable_refcnt;
>> s64 __percpu *elem_count;
>> u64 cookie; /* write-once */
>> - char *excl_prog_sha;
>> };
>> static inline const char *btf_field_type_name(enum btf_field_type type)
>> diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
>> index 9478b8f78f26..fee35c26deb8 100644
>> --- a/tools/lib/bpf/gen_loader.c
>> +++ b/tools/lib/bpf/gen_loader.c
>> @@ -600,6 +600,22 @@ static void emit_signature_match(struct bpf_gen *gen)
>> gen->error = -ERANGE;
>> }
>> }
>> +
>> + /* Reject if the metadata map is not exclusive. Without exclusivity
>> + * the cached map->sha[] verified above can be stale: another BPF
>> + * program with map access could have mutated the contents between
>> + * BPF_OBJ_GET_INFO_BY_FD and loader execution.
>> + */
(One other small nit: it probably makes sense to first check the exclusivity
before we compare the map->sha[], so this could be reordered inside the
emit_signature_match.)
> We could probably add sth like this somewhere in the bpf kernel side :
>
> /* See: emit_signature_match() in tools/lib/bpf/gen_loader.c */
> BUILD_BUG_ON(offsetof(struct bpf_map, sha) != 0);
> BUILD_BUG_ON(offsetof(struct bpf_map, excl_prog_sha) != SHA256_DIGEST_SIZE);
> BUILD_BUG_ON(sizeof_field(struct bpf_map, excl_prog_sha) != sizeof(__u64));
>
>> + emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
>> + 0, 0, 0, 0));
>> + emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, SHA256_DWORD_SIZE * sizeof(__u64)));
>
> nit: SHA256_DWORD_SIZE is SHA256_DIGEST_LENGTH / sizeof(__u64), can we just use
> SHA256_DIGEST_LENGTH ?
>
>> + off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
>> + if (is_simm16(off)) {
>> + emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
>> + emit(gen, BPF_JMP_IMM(BPF_JEQ, BPF_REG_2, 0, off));
>> + } else {
>> + gen->error = -ERANGE;
>> + }
>> }
>> void bpf_gen__record_attach_target(struct bpf_gen *gen, const char *attach_name,
>
^ permalink raw reply
* Re: [PATCH bpf v3 1/2] libbpf: fix off-by-one in emit_signature_match jump offset
From: bot+bpf-ci @ 2026-05-22 22:41 UTC (permalink / raw)
To: kpsingh, bpf, linux-security-module
Cc: ast, daniel, memxor, kpsingh, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260522215337.662271-2-kpsingh@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 1918 bytes --]
> Fixes: fb2b0e290147 ("libbpf: Update light skeleton for signing")
Daniel Borkmann reviewed v2 and pointed out that the Fixes tag
references the wrong commit. The commit fb2b0e290147 only modified
tools/lib/bpf/skel_internal.h and did not touch emit_signature_match.
He suggested using ea923080c145 ("libbpf: Embed and verify the
metadata hash in the loader") instead.
Reference: https://lore.kernel.org/bpf/a9345efb-b691-4fc1-9bad-d571bd324c25@iogearbox.net/
The search for the correct Fixes tag confirmed that fb2b0e290147 did
not introduce the emit_signature_match function or the buggy jump
offset calculation in tools/lib/bpf/gen_loader.c. The true origin
commit could not be determined in this repository's history.
Should the Fixes tag be updated to point to the commit that actually
introduced the emit_signature_match function with the buggy offset
calculation?
> diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
> index cd5c2543f54d..9478b8f78f26 100644
> --- a/tools/lib/bpf/gen_loader.c
> +++ b/tools/lib/bpf/gen_loader.c
> @@ -592,13 +592,12 @@ static void emit_signature_match(struct bpf_gen *gen)
> gen->hash_insn_offset[i] = gen->insn_cur - gen->insn_start;
> emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_3, 0, 0, 0, 0, 0));
>
> - off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 1;
> + off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
> if (is_simm16(off)) {
> emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
> emit(gen, BPF_JMP_REG(BPF_JNE, BPF_REG_2, BPF_REG_3, off));
> } else {
> gen->error = -ERANGE;
> - emit(gen, BPF_JMP_IMM(BPF_JA, 0, 0, -1));
> }
> }
> }
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26314489328
^ permalink raw reply
* Re: [PATCH bpf v3 2/2] bpf, libbpf: reject non-exclusive metadata maps in the signed loader
From: bot+bpf-ci @ 2026-05-22 22:41 UTC (permalink / raw)
To: kpsingh, bpf, linux-security-module
Cc: ast, daniel, memxor, kpsingh, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
In-Reply-To: <20260522215337.662271-3-kpsingh@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 3151 bytes --]
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index cd191c5fdb0a..11bec73db199 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -295,6 +295,7 @@ struct bpf_map_owner {
>
> struct bpf_map {
> u8 sha[SHA256_DIGEST_SIZE];
> + char *excl_prog_sha;
Daniel Borkmann suggested adding BUILD_BUG_ON checks to validate the
struct layout assumptions that gen_loader relies on. Specifically:
BUILD_BUG_ON(offsetof(struct bpf_map, sha) != 0);
BUILD_BUG_ON(offsetof(struct bpf_map, excl_prog_sha) != SHA256_DIGEST_SIZE);
BUILD_BUG_ON(sizeof_field(struct bpf_map, excl_prog_sha) != sizeof(__u64));
Would this make the code more robust against future struct layout changes?
> const struct bpf_map_ops *ops;
> struct bpf_map *inner_map_meta;
> #ifdef CONFIG_SECURITY
[ ... ]
> diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
> index 9478b8f78f26..5077472b9fd2 100644
> --- a/tools/lib/bpf/gen_loader.c
> +++ b/tools/lib/bpf/gen_loader.c
> @@ -600,6 +600,23 @@ static void emit_signature_match(struct bpf_gen *gen)
> gen->error = -ERANGE;
> }
> }
> +
> + /*
> + * Reject if the metadata map is not exclusive. Without exclusivity
> + * the cached map->sha[] verified above can be stale: another BPF
> + * program with map access could have mutated the contents between
> + * BPF_OBJ_GET_INFO_BY_FD and loader execution.
> + */
> + emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX,
> + 0, 0, 0, 0));
> + emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, SHA256_DWORD_SIZE * sizeof(__u64)));
Will this BPF_DW (8-byte) read pass the BPF verifier on 32-bit
architectures?
Sashiko AI flagged this in v2, and you confirmed through testing on a
32-bit kernel that the verifier rejects this with 'cannot access ptr
member excl_prog_sha with moff 0 in struct (anon) with off 0 size 8'.
The issue is that __bpf_md_ptr doesn't emit padding in BTF; the kernel's
BTF represents the pointer member with a size of 4 bytes on 32-bit
systems, while btf_struct_walk() enforces exact size matching.
The commit message acknowledges this: 'The 8-byte read of the pointer
field limits this to 64-bit kernels; gen_loader needs target pointer
size tracking to emit the right sized read on 32-bit (follow-up).'
This means the security check will be silently bypassed on 32-bit
systems until the follow-up is implemented.
Could SHA256_DIGEST_SIZE be used here instead of 'SHA256_DWORD_SIZE *
sizeof(__u64)'? Daniel Borkmann pointed out that SHA256_DWORD_SIZE is
already SHA256_DIGEST_SIZE / sizeof(__u64), so the multiplication
essentially reconstructs SHA256_DIGEST_SIZE.
> + off = -(gen->insn_cur - gen->insn_start - gen->cleanup_label) / 8 - 2;
> + if (is_simm16(off)) {
> + emit(gen, BPF_MOV64_IMM(BPF_REG_7, -EINVAL));
> + emit(gen, BPF_JMP_IMM(BPF_JEQ, BPF_REG_2, 0, off));
> + } else {
> + gen->error = -ERANGE;
> + }
> }
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26314489328
^ permalink raw reply
* Re: [PATCH v4 1/7] lsm: Add granular mount hooks to replace security_sb_mount
From: Song Liu @ 2026-05-23 0:09 UTC (permalink / raw)
To: Christian Brauner
Cc: linux-security-module, linux-fsdevel, selinux, apparmor, paul,
jmorris, serge, viro, jack, john.johansen, stephen.smalley.work,
omosnace, mic, gnoack, takedakn, penguin-kernel, herton,
kernel-team
In-Reply-To: <177944135249.2871055.11361892114499566606.b4-review@b4>
On Fri, May 22, 2026 at 2:16 AM Christian Brauner <brauner@kernel.org> wrote:
>
> On Fri, 15 May 2026 13:01:52 -0700, Song Liu <song@kernel.org> wrote:
> > [...]
> >
> > Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> > Tested-by: Stephen Smalley <stephen.smalley.work@gmail.com> # for selinux only
> > Signed-off-by: Song Liu <song@kernel.org>
> > Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
^^^^^ I assume you didn't mean to add this SoB yet.
>
> Please cleanly separate the preparatory work for introducing the new
> hooks from any changes to fs/namespace.c
>
> Once you have all of the new machinery in place, switch fs/namespace.c
> over to the new hooks.
Could you please be more specific on how to arrange these changes?
Currently, we have
1/7 adds new hooks:
lsm: Add granular mount hooks to replace security_sb_mount
2/7 through 6/7 migrate LSMs from old hooks to new hooks:
apparmor: Remove redundant MS_MGC_MSK stripping in apparmor_sb_mount
apparmor: Convert from sb_mount to granular mount hooks
selinux: Convert from sb_mount to granular mount hooks
landlock: Convert from sb_mount to granular mount hooks
tomoyo: Convert from sb_mount to granular mount hooks
7/7 removes old hooks:
lsm: Remove security_sb_mount and security_move_mount
Some ideas to change this:
Idea #1:
Do you mean to split 1/7 and 7/7 into two patches each: one
for changes in fs/namespace.c, the other for everything else?
IOW, the set of 9 patches will look like:
1/9 adds new hooks in security/ core code
2/9 adds calls to new hooks to fs/namespace.c
3/9 through 7/9 migrate LSMs from old hooks to new hooks
8/9 removes calls to old hooks from s/namespace.c
9/9 removes old hooks from security/ core code
Idea #2:
Or do you mean all the changes to fs/namespace.c have to
stay in one commit? I am not sure how we can make the
whole patchset clean with this approach. We will probably
need 2+ commits for each of the 4 LSMs we update. IOW,
the set will look like:
Add new hooks in security core code
Add new hooks in selinux
Add new hooks in apparmor
Add new hooks in landlock
Add new hooks in tamoyo
Replace old hooks with new hooks in fs/namespace.c
Remove old hooks from selinux
Remove old hooks from apparmor
Remove old hooks from landlock
Remove old hooks from tamoyo
Remove old hooks in security core code
This will require at least 11 patches, and it is not
really clean.
Idea #3:
Did I miss any other solutions?
> This will make it way easier to review and easier to distribute the
This sentence appears to be incomplete. Do you mean to distribute
different patches in the patchset to different trees and merge them
separately?
I actually think the patchset is clean as-is.
The overall change in fs/namespace.c is really minimal:
fs/namespace.c | 41 +++++++---
We can clearly see what it changed in fs/namespace.c with
"git diff HEAD~7 -- fs/" (attached below).
Therefore, I don't see a way to make the whole patchset more
clean. If I missed anything, please let me know.
Thanks,
Song
$ git diff HEAD~7 -- fs/
diff --git c/fs/namespace.c w/fs/namespace.c
index fe919abd2f01..43f22c5e2bf4 100644
--- c/fs/namespace.c
+++ w/fs/namespace.c
@@ -2888,6 +2888,10 @@ static int do_change_type(const struct path
*path, int ms_flags)
if (!type)
return -EINVAL;
+ err = security_mount_change_type(path, ms_flags);
+ if (err)
+ return err;
+
guard(namespace_excl)();
err = may_change_propagation(mnt);
@@ -3006,6 +3010,10 @@ static int do_loopback(const struct path *path,
const char *old_name,
if (err)
return err;
+ err = security_mount_bind(&old_path, path, recurse);
+ if (err)
+ return err;
+
if (mnt_ns_loop(old_path.dentry))
return -EINVAL;
@@ -3328,7 +3336,8 @@ static void mnt_warn_timestamp_expiry(const
struct path *mountpoint,
* superblock it refers to. This is triggered by specifying MS_REMOUNT|MS_BIND
* to mount(2).
*/
-static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
+static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags,
+ unsigned long flags)
{
struct super_block *sb = path->mnt->mnt_sb;
struct mount *mnt = real_mount(path->mnt);
@@ -3343,6 +3352,10 @@ static int do_reconfigure_mnt(const struct path
*path, unsigned int mnt_flags)
if (!can_change_locked_flags(mnt, mnt_flags))
return -EPERM;
+ ret = security_mount_reconfigure(path, mnt_flags, flags);
+ if (ret)
+ return ret;
+
/*
* We're only checking whether the superblock is read-only not
* changing it, so only take down_read(&sb->s_umount).
@@ -3366,7 +3379,7 @@ static int do_reconfigure_mnt(const struct path
*path, unsigned int mnt_flags)
* on it - tough luck.
*/
static int do_remount(const struct path *path, int sb_flags,
- int mnt_flags, void *data)
+ int mnt_flags, void *data, unsigned long flags)
{
int err;
struct super_block *sb = path->mnt->mnt_sb;
@@ -3393,6 +3406,9 @@ static int do_remount(const struct path *path,
int sb_flags,
fc->oldapi = true;
err = parse_monolithic_mount_data(fc, data);
+ if (!err)
+ err = security_mount_remount(fc, path, mnt_flags, flags,
+ data);
if (!err) {
down_write(&sb->s_umount);
err = -EPERM;
@@ -3708,6 +3724,10 @@ static int do_move_mount_old(const struct path
*path, const char *old_name)
if (err)
return err;
+ err = security_mount_move(&old_path, path);
+ if (err)
+ return err;
+
return do_move_mount(&old_path, path, 0);
}
@@ -3786,7 +3806,7 @@ static int do_new_mount_fc(struct fs_context
*fc, const struct path *mountpoint,
*/
static int do_new_mount(const struct path *path, const char *fstype,
int sb_flags, int mnt_flags,
- const char *name, void *data)
+ const char *name, void *data, unsigned long flags)
{
struct file_system_type *type;
struct fs_context *fc;
@@ -3830,6 +3850,9 @@ static int do_new_mount(const struct path *path,
const char *fstype,
err = parse_monolithic_mount_data(fc, data);
if (!err && !mount_capable(fc))
err = -EPERM;
+
+ if (!err)
+ err = security_mount_new(fc, path, mnt_flags, flags, data);
if (!err)
err = do_new_mount_fc(fc, path, mnt_flags);
@@ -4080,7 +4103,6 @@ int path_mount(const char *dev_name, const
struct path *path,
const char *type_page, unsigned long flags, void *data_page)
{
unsigned int mnt_flags = 0, sb_flags;
- int ret;
/* Discard magic */
if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
@@ -4093,9 +4115,6 @@ int path_mount(const char *dev_name, const
struct path *path,
if (flags & MS_NOUSER)
return -EINVAL;
- ret = security_sb_mount(dev_name, path, type_page, flags, data_page);
- if (ret)
- return ret;
if (!may_mount())
return -EPERM;
if (flags & SB_MANDLOCK)
@@ -4141,9 +4160,9 @@ int path_mount(const char *dev_name, const
struct path *path,
SB_I_VERSION);
if ((flags & (MS_REMOUNT | MS_BIND)) == (MS_REMOUNT | MS_BIND))
- return do_reconfigure_mnt(path, mnt_flags);
+ return do_reconfigure_mnt(path, mnt_flags, flags);
if (flags & MS_REMOUNT)
- return do_remount(path, sb_flags, mnt_flags, data_page);
+ return do_remount(path, sb_flags, mnt_flags, data_page, flags);
if (flags & MS_BIND)
return do_loopback(path, dev_name, flags & MS_REC);
if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
@@ -4152,7 +4171,7 @@ int path_mount(const char *dev_name, const
struct path *path,
return do_move_mount_old(path, dev_name);
return do_new_mount(path, type_page, sb_flags, mnt_flags, dev_name,
- data_page);
+ data_page, flags);
}
int do_mount(const char *dev_name, const char __user *dir_name,
@@ -4545,7 +4564,7 @@ static inline int vfs_move_mount(const struct
path *from_path,
{
int ret;
- ret = security_move_mount(from_path, to_path);
+ ret = security_mount_move(from_path, to_path);
if (ret)
return ret;
^ permalink raw reply related
* Re: [PATCH 2/4] firmware: arm_ffa: Register core as a platform driver
From: Nathan Chancellor @ 2026-05-23 0:11 UTC (permalink / raw)
To: Sudeep Holla
Cc: linux-security-module, linux-kernel, linux-integrity,
linux-arm-kernel, kvmarm, Yeoreum Yun
In-Reply-To: <20260508-b4-ffa_plat_dev-v1-2-c5a30f8cf7b8@kernel.org>
Hi Sudeep,
On Fri, May 08, 2026 at 06:54:16PM +0100, Sudeep Holla wrote:
> Move the FF-A core bring-up and teardown paths into platform driver
> probe and remove callbacks, and register a synthetic arm-ffa platform
> device to bind the driver.
>
> This makes the FF-A core lifetime follow the driver model while keeping
> the device creation internal to the FF-A core. Use normal platform driver
> registration so the probe path has standard driver-core semantics.
>
> The synthetic platform device is a temporary bridge until ACPI and
> devicetree describe the FF-A core device or object. Once those firmware
> description paths are defined, the internal platform device creation can
> be dropped and the driver can bind to the firmware-described device
> directly.
>
> Since the transport selection now happens from the platform probe path,
> drop the __init annotation from ffa_transport_init().
>
> Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
I am seeing
arm-ffa arm-ffa: probe with driver arm-ffa failed with error -95
on my two arm64 test machines after this change landed in -next as
commit e659fc8e537c ("firmware: arm_ffa: Register core as a platform
driver"), is this expected? If so, perhaps it should be silenced?
Cheers,
Nathan
# bad: [c1ecb239fa3456529a32255359fc78b69eb9d847] Add linux-next specific files for 20260522
# good: [6779b50faa562e6cca1aa6a4649a4d764c6c7e28] Merge tag 'pci-v7.1-fixes-2' of git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci
git bisect start 'c1ecb239fa3456529a32255359fc78b69eb9d847' '6779b50faa562e6cca1aa6a4649a4d764c6c7e28'
# bad: [c09bb0d7c56d56daed51e374409472c3fece9931] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git
git bisect bad c09bb0d7c56d56daed51e374409472c3fece9931
# bad: [42b0664d6f7b3d04f961e0709811280e52d4d39c] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/geert/linux-m68k.git
git bisect bad 42b0664d6f7b3d04f961e0709811280e52d4d39c
# good: [63775c4b77cf7ac5863ae0e76f8626b80d217562] Merge branch 'mm-nonmm-unstable' of https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
git bisect good 63775c4b77cf7ac5863ae0e76f8626b80d217562
# good: [f437936af5c74fddfdfcd2388a5f1eec2f9a105b] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-omap.git
git bisect good f437936af5c74fddfdfcd2388a5f1eec2f9a105b
# good: [01bf3f256f1ed15fa3884e4893e5a9d78fdfb2d6] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git
git bisect good 01bf3f256f1ed15fa3884e4893e5a9d78fdfb2d6
# bad: [bee649097c8de82d9dccd3660c41163a6507e3d4] Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee.git
git bisect bad bee649097c8de82d9dccd3660c41163a6507e3d4
# bad: [82ccfdd8dd1e29229a3ed7c95a76578706fc4a1d] Merge branch 'for-next' of https://github.com/sophgo/linux.git
git bisect bad 82ccfdd8dd1e29229a3ed7c95a76578706fc4a1d
# good: [32bc5496b48174dbca1f187f710955ee4d9527a1] firmware: arm_scmi: Validate SENSOR_UPDATE payload size
git bisect good 32bc5496b48174dbca1f187f710955ee4d9527a1
# bad: [64251369d33884b431a52b89c60fabf466f3913c] Merge branches 'for-next/scmi/updates' and 'for-next/ffa/updates' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux into for-linux-next
git bisect bad 64251369d33884b431a52b89c60fabf466f3913c
# bad: [70492cfce2a4d41e87bf46989028a90f4bc6b38f] firmware: smccc: Fix Arm SMCCC SOC_ID name call
git bisect bad 70492cfce2a4d41e87bf46989028a90f4bc6b38f
# bad: [7fe2ec9fb8e9a78dad8d6b1e551cb4d126e36f1e] firmware: arm_ffa: Set the core device as FF-A device parent
git bisect bad 7fe2ec9fb8e9a78dad8d6b1e551cb4d126e36f1e
# bad: [e659fc8e537c7a21d5d693d6f30d8852f2fa8d91] firmware: arm_ffa: Register core as a platform driver
git bisect bad e659fc8e537c7a21d5d693d6f30d8852f2fa8d91
# good: [cc7e8f21b9f0c229d68cf19a837cba82b5ac2d87] Revert "firmware: arm_ffa: Change initcall level of ffa_init() to rootfs_initcall"
git bisect good cc7e8f21b9f0c229d68cf19a837cba82b5ac2d87
# first bad commit: [e659fc8e537c7a21d5d693d6f30d8852f2fa8d91] firmware: arm_ffa: Register core as a platform driver
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox