From nobody Fri Sep 25 21:02:15 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 044564825DB; Mon, 21 Sep 2026 10:55:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988146; cv=none; b=BCHSN5rOjve1fFH9UlljQp2XzUfhhVy8Er9c3+o/LU8UgBueB37VNdsZgWH3D+4CVxDiebNy/y6W//dpcYA6Tpotpbd4c2Xg2sJUH6dU7num/QGuKGnrDqybAajuAKw/oBEV81yk/eaFaztheDzij9YuOW+fw6j1nUA+Nue5GfU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988146; c=relaxed/simple; bh=LdcQiQl81BRUFzpQHKHOia0MgssJ6qnirzJpzHwoU/k=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rgukyMyXKkbQjInT6icLkvsodgBDAXYu5LHlUN+AsOFCQq3jkEzNY66gBmSGN532aRQgJlj1+z1dXXprsS7RkIdfvlPdZVCnOm7Z3X5FwqG8NUyGHtKrQoxd4N2VDgSyZIzLGqKjfghbVN3lXoJE05VpiG/w/IPEcCK6CVEWqfs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=nKXktkLF; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="nKXktkLF" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7AC0A1F00893; Mon, 21 Sep 2026 10:55:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789988143; bh=5KAq1qQJkKfrEAH9W82Sgm7oMlioE3hwh5IBDMHyP9o=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=nKXktkLFSOw6dnBlr+gZII6UxyRZkLcFiZYq7P8fWjmeIcAD2oLXYn/uEn+bNv2/J kEkNR8kHrgT0G+r3+XlHvHiRe+gzT48TlsGaQirIDp2fMzf5FwKikuAMa8PCLC6otD ZlerfdKoNsHNRA5MpBSBOxH7efJkMCw1yP6Tk0ndRQBgDDmuglxus8NyaK6cgHlJtq tHVuS2rbCVbXMSJ8YploNqo/Q6xSHvQKe6qSq9I3f+7EwSU7lUrYbDcQ2o7Dse9w+e Wc4yzPgSTbp6xcJHGCJ7DLVxp2ZvRBFKvZIzycqnQxVtAFcd4SCtlekdsg9JLEYDMO Zl3gtSY9Y0R5Q== From: Alexey Gladkov To: Joel Granados Cc: =?UTF-8?q?Ondrej=20Mosn=C3=A1=C4=8Dek?= , Andrew Morton , Kees Cook , Ryan Roberts , Serge Hallyn , "Eric W . Biederman" , LKML , linux-fsdevel@vger.kernel.org Subject: [PATCH v2 1/6] proc: sysctl: address table entries by index Date: Mon, 21 Sep 2026 12:54:48 +0200 Message-ID: <083fea8fc2eecad8c33e922231f8f9886bcb9be9.1789987960.git.legion@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" proc_sysctl keeps pointers to ctl_table entries in several lookup and inode paths. That makes it hard to support alternative descriptor formats because the tree logic assumes that every registered entry already is a ctl_table object. Store the entry index in proc inodes and pass header/index pairs through the lookup, iteration and permission paths instead. The existing ctl_table entries are still used for all handler, permission and poll operations, but the core no longer needs to carry a direct ctl_table pointer through every internal path. Signed-off-by: Alexey Gladkov --- fs/proc/inode.c | 2 +- fs/proc/internal.h | 2 +- fs/proc/proc_sysctl.c | 330 ++++++++++++++++++++++++------------------ 3 files changed, 194 insertions(+), 140 deletions(-) diff --git a/fs/proc/inode.c b/fs/proc/inode.c index b7634f975d98..c735338ecc8d 100644 --- a/fs/proc/inode.c +++ b/fs/proc/inode.c @@ -62,7 +62,7 @@ static struct inode *proc_alloc_inode(struct super_block = *sb) ei->op.proc_get_link =3D NULL; ei->pde =3D NULL; ei->sysctl =3D NULL; - ei->sysctl_entry =3D NULL; + ei->sysctl_entry_idx =3D 0; INIT_HLIST_NODE(&ei->sibling_inodes); ei->ns_ops =3D NULL; return &ei->vfs_inode; diff --git a/fs/proc/internal.h b/fs/proc/internal.h index 04bd6c9e65a7..7fc730b0d7ab 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -123,7 +123,7 @@ struct proc_inode { union proc_op op; struct proc_dir_entry *pde; struct ctl_table_header *sysctl; - const struct ctl_table *sysctl_entry; + size_t sysctl_entry_idx; struct hlist_node sibling_inodes; const struct proc_ns_operations *ns_ops; struct inode vfs_inode; diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index 04a382178c65..b5cb219bcdbc 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -20,9 +20,8 @@ #include #include "internal.h" =20 -#define list_for_each_table_entry(entry, header) \ - entry =3D header->ctl_table; \ - for (size_t i =3D 0 ; i < header->ctl_table_size; ++i, entry++) +#define list_for_each_table_entry(index, header) \ + for (size_t index =3D 0; index < (header)->ctl_table_size; index++) =20 static const struct dentry_operations proc_sys_dentry_operations; static const struct file_operations proc_sys_file_operations; @@ -88,16 +87,39 @@ static struct ctl_table_root sysctl_table_root =3D { static DEFINE_SPINLOCK(sysctl_lock); =20 static void drop_sysctl_table(struct ctl_table_header *header); -static int sysctl_follow_link(struct ctl_table_header **phead, - const struct ctl_table **pentry); +static int sysctl_follow_link(struct ctl_table_header **phead, size_t *pin= dex); static int insert_links(struct ctl_table_header *head); static void put_links(struct ctl_table_header *header); =20 +static const char *sysctl_entry_procname(struct ctl_table_header *head, + size_t index) +{ + return head->ctl_table[index].procname; +} + +static umode_t sysctl_entry_mode(struct ctl_table_header *head, size_t ind= ex) +{ + return head->ctl_table[index].mode; +} + +static struct ctl_table_poll *sysctl_entry_poll(struct ctl_table_header *h= ead, + size_t index) +{ + return head->ctl_table[index].poll; +} + +static const struct ctl_table * +sysctl_entry_table(struct ctl_table_header *head, size_t index, + struct ctl_table *table) +{ + return &head->ctl_table[index]; +} + static void sysctl_print_dir(struct ctl_dir *dir) { if (dir->header.parent) sysctl_print_dir(dir->header.parent); - pr_cont("%s/", dir->header.ctl_table[0].procname); + pr_cont("%s/", sysctl_entry_procname(&dir->header, 0)); } =20 static int namecmp(const char *name1, int len1, const char *name2, int len= 2) @@ -110,11 +132,10 @@ static int namecmp(const char *name1, int len1, const= char *name2, int len2) return cmp; } =20 -static const struct ctl_table *find_entry(struct ctl_table_header **phead, - struct ctl_dir *dir, const char *name, int namelen) +static bool find_entry(struct ctl_table_header **phead, size_t *pindex, + struct ctl_dir *dir, const char *name, int namelen) { struct ctl_table_header *head; - const struct ctl_table *entry; struct rb_node *node =3D dir->root.rb_node; =20 lockdep_assert_held(&sysctl_lock); @@ -127,8 +148,8 @@ static const struct ctl_table *find_entry(struct ctl_ta= ble_header **phead, =20 ctl_node =3D rb_entry(node, struct ctl_node, node); head =3D ctl_node->header; - entry =3D &head->ctl_table[ctl_node - head->node]; - procname =3D entry->procname; + *pindex =3D ctl_node - head->node; + procname =3D sysctl_entry_procname(head, *pindex); =20 cmp =3D namecmp(name, namelen, procname, strlen(procname)); if (cmp < 0) @@ -137,32 +158,32 @@ static const struct ctl_table *find_entry(struct ctl_= table_header **phead, node =3D node->rb_right; else { *phead =3D head; - return entry; + return true; } } - return NULL; + return false; } =20 -static int insert_entry(struct ctl_table_header *head, const struct ctl_ta= ble *entry) +static int insert_entry(struct ctl_table_header *head, size_t index) { - struct rb_node *node =3D &head->node[entry - head->ctl_table].node; + struct rb_node *node =3D &head->node[index].node; struct rb_node **p =3D &head->parent->root.rb_node; struct rb_node *parent =3D NULL; - const char *name =3D entry->procname; + const char *name =3D sysctl_entry_procname(head, index); int namelen =3D strlen(name); =20 while (*p) { struct ctl_table_header *parent_head; - const struct ctl_table *parent_entry; struct ctl_node *parent_node; const char *parent_name; + size_t parent_index; int cmp; =20 parent =3D *p; parent_node =3D rb_entry(parent, struct ctl_node, node); parent_head =3D parent_node->header; - parent_entry =3D &parent_head->ctl_table[parent_node - parent_head->node= ]; - parent_name =3D parent_entry->procname; + parent_index =3D parent_node - parent_head->node; + parent_name =3D sysctl_entry_procname(parent_head, parent_index); =20 cmp =3D namecmp(name, namelen, parent_name, strlen(parent_name)); if (cmp < 0) @@ -172,7 +193,7 @@ static int insert_entry(struct ctl_table_header *head, = const struct ctl_table *e else { pr_err("sysctl duplicate entry: "); sysctl_print_dir(head->parent); - pr_cont("%s\n", entry->procname); + pr_cont("%s\n", name); return -EEXIST; } } @@ -182,9 +203,9 @@ static int insert_entry(struct ctl_table_header *head, = const struct ctl_table *e return 0; } =20 -static void erase_entry(struct ctl_table_header *head, const struct ctl_ta= ble *entry) +static void erase_entry(struct ctl_table_header *head, size_t index) { - struct rb_node *node =3D &head->node[entry - head->ctl_table].node; + struct rb_node *node =3D &head->node[index].node; =20 rb_erase(node, &head->parent->root); } @@ -206,9 +227,7 @@ static void init_header(struct ctl_table_header *head, head->node =3D node; INIT_HLIST_HEAD(&head->inodes); if (node) { - const struct ctl_table *entry; - - list_for_each_table_entry(entry, head) { + list_for_each_table_entry(index, head) { node->header =3D head; node++; } @@ -219,15 +238,12 @@ static void init_header(struct ctl_table_header *head, =20 static void erase_header(struct ctl_table_header *head) { - const struct ctl_table *entry; - list_for_each_table_entry(entry, head) erase_entry(head, entry); } =20 static int insert_header(struct ctl_dir *dir, struct ctl_table_header *hea= der) { - const struct ctl_table *entry; struct ctl_table_header *dir_h =3D &dir->header; int err; =20 @@ -248,8 +264,8 @@ static int insert_header(struct ctl_dir *dir, struct ct= l_table_header *header) err =3D insert_links(header); if (err) goto fail_links; - list_for_each_table_entry(entry, header) { - err =3D insert_entry(header, entry); + list_for_each_table_entry(index, header) { + err =3D insert_entry(header, index); if (err) goto fail; } @@ -258,7 +274,7 @@ static int insert_header(struct ctl_dir *dir, struct ct= l_table_header *header) erase_header(header); put_links(header); fail_links: - if (header->ctl_table =3D=3D sysctl_mount_point) + if (sysctl_is_perm_empty_ctl_header(header)) sysctl_clear_perm_empty_ctl_header(dir_h); header->parent =3D NULL; drop_sysctl_table(dir_h); @@ -350,21 +366,23 @@ lookup_header_set(struct ctl_table_root *root) return set; } =20 -static const struct ctl_table *lookup_entry(struct ctl_table_header **phea= d, - struct ctl_dir *dir, - const char *name, int namelen) +static bool lookup_entry(struct ctl_table_header **phead, size_t *pindex, + struct ctl_dir *dir, const char *name, int namelen) { struct ctl_table_header *head; - const struct ctl_table *entry; + size_t index; + bool found; =20 spin_lock(&sysctl_lock); - entry =3D find_entry(&head, dir, name, namelen); - if (entry && use_table(head)) + found =3D find_entry(&head, &index, dir, name, namelen); + if (found && use_table(head)) { *phead =3D head; - else - entry =3D NULL; + *pindex =3D index; + } else { + found =3D false; + } spin_unlock(&sysctl_lock); - return entry; + return found; } =20 static struct ctl_node *first_usable_entry(struct rb_node *node) @@ -380,10 +398,10 @@ static struct ctl_node *first_usable_entry(struct rb_= node *node) } =20 static void first_entry(struct ctl_dir *dir, - struct ctl_table_header **phead, const struct ctl_table **pentry) + struct ctl_table_header **phead, size_t *pindex) { struct ctl_table_header *head =3D NULL; - const struct ctl_table *entry =3D NULL; + size_t index =3D 0; struct ctl_node *ctl_node; =20 spin_lock(&sysctl_lock); @@ -391,17 +409,16 @@ static void first_entry(struct ctl_dir *dir, spin_unlock(&sysctl_lock); if (ctl_node) { head =3D ctl_node->header; - entry =3D &head->ctl_table[ctl_node - head->node]; + index =3D ctl_node - head->node; } *phead =3D head; - *pentry =3D entry; + *pindex =3D index; } =20 -static void next_entry(struct ctl_table_header **phead, const struct ctl_t= able **pentry) +static void next_entry(struct ctl_table_header **phead, size_t *pindex) { struct ctl_table_header *head =3D *phead; - const struct ctl_table *entry =3D *pentry; - struct ctl_node *ctl_node =3D &head->node[entry - head->ctl_table]; + struct ctl_node *ctl_node =3D &head->node[*pindex]; =20 spin_lock(&sysctl_lock); unuse_table(head); @@ -411,10 +428,9 @@ static void next_entry(struct ctl_table_header **phead= , const struct ctl_table * head =3D NULL; if (ctl_node) { head =3D ctl_node->header; - entry =3D &head->ctl_table[ctl_node - head->node]; + *pindex =3D ctl_node - head->node; } *phead =3D head; - *pentry =3D entry; } =20 /* @@ -433,25 +449,30 @@ static int test_perm(int mode, int op) return -EACCES; } =20 -static int sysctl_perm(struct ctl_table_header *head, const struct ctl_tab= le *table, int op) +static int sysctl_perm(struct ctl_table_header *head, size_t index, int op) { struct ctl_table_root *root =3D head->root; + struct ctl_table table; + const struct ctl_table *entry; int mode; =20 - if (root->permissions) - mode =3D root->permissions(head, table); - else - mode =3D table->mode; + if (root->permissions) { + entry =3D sysctl_entry_table(head, index, &table); + mode =3D root->permissions(head, entry); + } else { + mode =3D sysctl_entry_mode(head, index); + } =20 return test_perm(mode, op); } =20 static struct inode *proc_sys_make_inode(struct super_block *sb, - struct ctl_table_header *head, const struct ctl_table *table) + struct ctl_table_header *head, size_t index) { struct ctl_table_root *root =3D head->root; struct inode *inode; struct proc_inode *ei; + umode_t mode; =20 inode =3D new_inode(sb); if (!inode) @@ -468,14 +489,15 @@ static struct inode *proc_sys_make_inode(struct super= _block *sb, return ERR_PTR(-ENOENT); } ei->sysctl =3D head; - ei->sysctl_entry =3D table; + ei->sysctl_entry_idx =3D index; hlist_add_head_rcu(&ei->sibling_inodes, &head->inodes); head->count++; spin_unlock(&sysctl_lock); =20 + mode =3D sysctl_entry_mode(head, index); simple_inode_init_ts(inode); - inode->i_mode =3D table->mode; - if (!S_ISDIR(table->mode)) { + inode->i_mode =3D mode; + if (!S_ISDIR(mode)) { inode->i_mode |=3D S_IFREG; inode->i_op =3D &proc_sys_inode_operations; inode->i_fop =3D &proc_sys_file_operations; @@ -518,7 +540,7 @@ static struct dentry *proc_sys_lookup(struct inode *dir= , struct dentry *dentry, struct ctl_table_header *head =3D grab_header(dir); struct ctl_table_header *h =3D NULL; const struct qstr *name =3D &dentry->d_name; - const struct ctl_table *p; + size_t index; struct inode *inode; struct dentry *err =3D ERR_PTR(-ENOENT); struct ctl_dir *ctl_dir; @@ -529,18 +551,17 @@ static struct dentry *proc_sys_lookup(struct inode *d= ir, struct dentry *dentry, =20 ctl_dir =3D container_of(head, struct ctl_dir, header); =20 - p =3D lookup_entry(&h, ctl_dir, name->name, name->len); - if (!p) + if (!lookup_entry(&h, &index, ctl_dir, name->name, name->len)) goto out; =20 - if (S_ISLNK(p->mode)) { - ret =3D sysctl_follow_link(&h, &p); + if (S_ISLNK(sysctl_entry_mode(h, index))) { + ret =3D sysctl_follow_link(&h, &index); err =3D ERR_PTR(ret); if (ret) goto out; } =20 - inode =3D proc_sys_make_inode(dir->i_sb, h ? h : head, p); + inode =3D proc_sys_make_inode(dir->i_sb, h ? h : head, index); err =3D d_splice_alias_ops(inode, dentry, &proc_sys_dentry_operations); =20 out: @@ -555,7 +576,9 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb= , struct iov_iter *iter, { struct inode *inode =3D file_inode(iocb->ki_filp); struct ctl_table_header *head =3D grab_header(inode); - const struct ctl_table *table =3D PROC_I(inode)->sysctl_entry; + size_t index =3D PROC_I(inode)->sysctl_entry_idx; + struct ctl_table table; + const struct ctl_table *entry; size_t count =3D iov_iter_count(iter); char *kbuf; ssize_t error; @@ -568,12 +591,14 @@ static ssize_t proc_sys_call_handler(struct kiocb *io= cb, struct iov_iter *iter, * and won't be until we finish. */ error =3D -EPERM; - if (sysctl_perm(head, table, write ? MAY_WRITE : MAY_READ)) + if (sysctl_perm(head, index, write ? MAY_WRITE : MAY_READ)) goto out; =20 + entry =3D sysctl_entry_table(head, index, &table); + /* if that can happen at all, it should be -EINVAL, not -EISDIR */ error =3D -EINVAL; - if (!table->proc_handler) + if (!entry->proc_handler) goto out; =20 /* don't even try if the size is too large */ @@ -591,13 +616,13 @@ static ssize_t proc_sys_call_handler(struct kiocb *io= cb, struct iov_iter *iter, kbuf[count] =3D '\0'; } =20 - error =3D BPF_CGROUP_RUN_PROG_SYSCTL(head, table, write, &kbuf, &count, + error =3D BPF_CGROUP_RUN_PROG_SYSCTL(head, entry, write, &kbuf, &count, &iocb->ki_pos); if (error) goto out_free_buf; =20 /* careful: calling conventions are nasty here */ - error =3D table->proc_handler(table, write, kbuf, &count, &iocb->ki_pos); + error =3D entry->proc_handler(entry, write, kbuf, &count, &iocb->ki_pos); if (error) goto out_free_buf; =20 @@ -629,14 +654,16 @@ static ssize_t proc_sys_write(struct kiocb *iocb, str= uct iov_iter *iter) static int proc_sys_open(struct inode *inode, struct file *filp) { struct ctl_table_header *head =3D grab_header(inode); - const struct ctl_table *table =3D PROC_I(inode)->sysctl_entry; + size_t index =3D PROC_I(inode)->sysctl_entry_idx; + struct ctl_table_poll *poll; =20 /* sysctl was unregistered */ if (IS_ERR(head)) return PTR_ERR(head); =20 - if (table->poll) - filp->private_data =3D proc_sys_poll_event(table->poll); + poll =3D sysctl_entry_poll(head, index); + if (poll) + filp->private_data =3D proc_sys_poll_event(poll); =20 sysctl_head_finish(head); =20 @@ -647,7 +674,10 @@ static __poll_t proc_sys_poll(struct file *filp, poll_= table *wait) { struct inode *inode =3D file_inode(filp); struct ctl_table_header *head =3D grab_header(inode); - const struct ctl_table *table =3D PROC_I(inode)->sysctl_entry; + size_t index =3D PROC_I(inode)->sysctl_entry_idx; + struct ctl_table_poll *poll; + struct ctl_table table; + const struct ctl_table *entry; __poll_t ret =3D DEFAULT_POLLMASK; unsigned long event; =20 @@ -655,17 +685,19 @@ static __poll_t proc_sys_poll(struct file *filp, poll= _table *wait) if (IS_ERR(head)) return EPOLLERR | EPOLLHUP; =20 - if (!table->proc_handler) + entry =3D sysctl_entry_table(head, index, &table); + if (!entry->proc_handler) goto out; =20 - if (!table->poll) + poll =3D sysctl_entry_poll(head, index); + if (!poll) goto out; =20 event =3D (unsigned long)filp->private_data; - poll_wait(filp, &table->poll->wait, wait); + poll_wait(filp, &poll->wait, wait); =20 - if (event !=3D atomic_read(&table->poll->event)) { - filp->private_data =3D proc_sys_poll_event(table->poll); + if (event !=3D atomic_read(&poll->event)) { + filp->private_data =3D proc_sys_poll_event(poll); ret =3D EPOLLIN | EPOLLRDNORM | EPOLLERR | EPOLLPRI; } =20 @@ -678,7 +710,7 @@ static __poll_t proc_sys_poll(struct file *filp, poll_t= able *wait) static bool proc_sys_fill_cache(struct file *file, struct dir_context *ctx, struct ctl_table_header *head, - const struct ctl_table *table) + size_t index) { struct dentry *child, *dir =3D file->f_path.dentry; struct inode *inode; @@ -686,8 +718,8 @@ static bool proc_sys_fill_cache(struct file *file, ino_t ino =3D 0; unsigned type =3D DT_UNKNOWN; =20 - qname.name =3D table->procname; - qname.len =3D strlen(table->procname); + qname.name =3D sysctl_entry_procname(head, index); + qname.len =3D strlen(qname.name); qname.hash =3D full_name_hash(dir, qname.name, qname.len); =20 child =3D d_lookup(dir, &qname); @@ -697,7 +729,7 @@ static bool proc_sys_fill_cache(struct file *file, return false; if (d_in_lookup(child)) { struct dentry *res; - inode =3D proc_sys_make_inode(dir->d_sb, head, table); + inode =3D proc_sys_make_inode(dir->d_sb, head, index); res =3D d_splice_alias_ops(inode, child, &proc_sys_dentry_operations); d_lookup_done(child); @@ -721,7 +753,7 @@ static bool proc_sys_fill_cache(struct file *file, static bool proc_sys_link_fill_cache(struct file *file, struct dir_context *ctx, struct ctl_table_header *head, - const struct ctl_table *table) + size_t index) { bool ret =3D true; =20 @@ -730,16 +762,16 @@ static bool proc_sys_link_fill_cache(struct file *fil= e, return false; =20 /* It is not an error if we can not follow the link ignore it */ - if (sysctl_follow_link(&head, &table)) + if (sysctl_follow_link(&head, &index)) goto out; =20 - ret =3D proc_sys_fill_cache(file, ctx, head, table); + ret =3D proc_sys_fill_cache(file, ctx, head, index); out: sysctl_head_finish(head); return ret; } =20 -static int scan(struct ctl_table_header *head, const struct ctl_table *tab= le, +static int scan(struct ctl_table_header *head, size_t index, unsigned long *pos, struct file *file, struct dir_context *ctx) { @@ -748,10 +780,10 @@ static int scan(struct ctl_table_header *head, const = struct ctl_table *table, if ((*pos)++ < ctx->pos) return true; =20 - if (unlikely(S_ISLNK(table->mode))) - res =3D proc_sys_link_fill_cache(file, ctx, head, table); + if (unlikely(S_ISLNK(sysctl_entry_mode(head, index)))) + res =3D proc_sys_link_fill_cache(file, ctx, head, index); else - res =3D proc_sys_fill_cache(file, ctx, head, table); + res =3D proc_sys_fill_cache(file, ctx, head, index); =20 if (res) ctx->pos =3D *pos; @@ -763,9 +795,9 @@ static int proc_sys_readdir(struct file *file, struct d= ir_context *ctx) { struct ctl_table_header *head =3D grab_header(file_inode(file)); struct ctl_table_header *h =3D NULL; - const struct ctl_table *entry; struct ctl_dir *ctl_dir; unsigned long pos; + size_t index; =20 if (IS_ERR(head)) return PTR_ERR(head); @@ -777,8 +809,8 @@ static int proc_sys_readdir(struct file *file, struct d= ir_context *ctx) =20 pos =3D 2; =20 - for (first_entry(ctl_dir, &h, &entry); h; next_entry(&h, &entry)) { - if (!scan(h, entry, &pos, file, ctx)) { + for (first_entry(ctl_dir, &h, &index); h; next_entry(&h, &index)) { + if (!scan(h, index, &pos, file, ctx)) { sysctl_head_finish(h); break; } @@ -796,7 +828,7 @@ static int proc_sys_permission(struct mnt_idmap *idmap, * are _NOT_ writeable, capabilities or not. */ struct ctl_table_header *head; - const struct ctl_table *table; + size_t index; int error; =20 /* Executable files are not allowed under /proc/sys/ */ @@ -807,11 +839,12 @@ static int proc_sys_permission(struct mnt_idmap *idma= p, if (IS_ERR(head)) return PTR_ERR(head); =20 - table =3D PROC_I(inode)->sysctl_entry; - if (!table) /* global root - r-xr-xr-x */ + if (!PROC_I(inode)->sysctl) { /* global root - r-xr-xr-x */ error =3D mask & MAY_WRITE ? -EACCES : 0; - else /* Use the permissions on the sysctl table entry */ - error =3D sysctl_perm(head, table, mask & ~MAY_NOT_BLOCK); + } else { + index =3D PROC_I(inode)->sysctl_entry_idx; + error =3D sysctl_perm(head, index, mask & ~MAY_NOT_BLOCK); + } =20 sysctl_head_finish(head); return error; @@ -840,14 +873,14 @@ static int proc_sys_getattr(struct mnt_idmap *idmap, { struct inode *inode =3D d_inode(path->dentry); struct ctl_table_header *head =3D grab_header(inode); - const struct ctl_table *table =3D PROC_I(inode)->sysctl_entry; =20 if (IS_ERR(head)) return PTR_ERR(head); =20 generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat); - if (table) - stat->mode =3D (stat->mode & S_IFMT) | table->mode; + if (PROC_I(inode)->sysctl) + stat->mode =3D (stat->mode & S_IFMT) | + sysctl_entry_mode(head, PROC_I(inode)->sysctl_entry_idx); =20 sysctl_head_finish(head); return 0; @@ -944,12 +977,11 @@ static struct ctl_dir *find_subdir(struct ctl_dir *di= r, const char *name, int namelen) { struct ctl_table_header *head; - const struct ctl_table *entry; + size_t index; =20 - entry =3D find_entry(&head, dir, name, namelen); - if (!entry) + if (!find_entry(&head, &index, dir, name, namelen)) return ERR_PTR(-ENOENT); - if (!S_ISDIR(entry->mode)) + if (!S_ISDIR(sysctl_entry_mode(head, index))) return ERR_PTR(-ENOTDIR); return container_of(head, struct ctl_dir, header); } @@ -1050,39 +1082,44 @@ static struct ctl_dir *xlate_dir(struct ctl_table_s= et *set, struct ctl_dir *dir) parent =3D xlate_dir(set, dir->header.parent); if (IS_ERR(parent)) return parent; - procname =3D dir->header.ctl_table[0].procname; + procname =3D sysctl_entry_procname(&dir->header, 0); return find_subdir(parent, procname, strlen(procname)); } =20 -static int sysctl_follow_link(struct ctl_table_header **phead, - const struct ctl_table **pentry) +static int sysctl_follow_link(struct ctl_table_header **phead, size_t *pin= dex) { struct ctl_table_header *head; + struct ctl_table table; const struct ctl_table *entry; struct ctl_table_root *root; struct ctl_table_set *set; struct ctl_dir *dir; + size_t index; int ret; =20 + entry =3D sysctl_entry_table(*phead, *pindex, &table); + root =3D entry->data; spin_lock(&sysctl_lock); - root =3D (*pentry)->data; set =3D lookup_header_set(root); dir =3D xlate_dir(set, (*phead)->parent); if (IS_ERR(dir)) ret =3D PTR_ERR(dir); else { - const char *procname =3D (*pentry)->procname; + const char *procname =3D entry->procname; head =3D NULL; - entry =3D find_entry(&head, dir, procname, strlen(procname)); ret =3D -ENOENT; - if (entry && use_table(head)) { + if (!find_entry(&head, &index, dir, procname, strlen(procname))) + goto out; + ret =3D -ENOENT; + if (use_table(head)) { unuse_table(*phead); *phead =3D head; - *pentry =3D entry; + *pindex =3D index; ret =3D 0; } } =20 +out: spin_unlock(&sysctl_lock); return ret; } @@ -1144,7 +1181,10 @@ static int sysctl_check_table(const char *path, stru= ct ctl_table_header *header) { const struct ctl_table *entry; int err =3D 0; - list_for_each_table_entry(entry, header) { + list_for_each_table_entry(index, header) { + struct ctl_table table; + + entry =3D sysctl_entry_table(header, index, &table); if (!entry->procname) err |=3D sysctl_err(path, entry, "procname is null"); if ((entry->proc_handler =3D=3D proc_dostring) || @@ -1180,14 +1220,13 @@ static struct ctl_table_header *new_links(struct ct= l_dir *dir, struct ctl_table_ { struct ctl_table *link_table, *link; struct ctl_table_header *links; - const struct ctl_table *entry; struct ctl_node *node; char *link_name; int name_bytes; =20 name_bytes =3D 0; - list_for_each_table_entry(entry, head) { - name_bytes +=3D strlen(entry->procname) + 1; + list_for_each_table_entry(index, head) { + name_bytes +=3D strlen(sysctl_entry_procname(head, index)) + 1; } =20 links =3D kzalloc(sizeof(struct ctl_table_header) + @@ -1204,9 +1243,11 @@ static struct ctl_table_header *new_links(struct ctl= _dir *dir, struct ctl_table_ link_name =3D (char *)(link_table + head->ctl_table_size); link =3D link_table; =20 - list_for_each_table_entry(entry, head) { - int len =3D strlen(entry->procname) + 1; - memcpy(link_name, entry->procname, len); + list_for_each_table_entry(index, head) { + const char *procname =3D sysctl_entry_procname(head, index); + int len =3D strlen(procname) + 1; + + memcpy(link_name, procname, len); link->procname =3D link_name; link->mode =3D S_IFLNK|S_IRWXUGO; link->data =3D head->root; @@ -1225,29 +1266,33 @@ static bool get_links(struct ctl_dir *dir, struct ctl_table_root *link_root) { struct ctl_table_header *tmp_head; - const struct ctl_table *entry, *link; + size_t link_index; =20 if (header->ctl_table_size =3D=3D 0 || sysctl_is_perm_empty_ctl_header(header)) return true; =20 /* Are there links available for every entry in table? */ - list_for_each_table_entry(entry, header) { - const char *procname =3D entry->procname; - link =3D find_entry(&tmp_head, dir, procname, strlen(procname)); - if (!link) + list_for_each_table_entry(index, header) { + const char *procname =3D sysctl_entry_procname(header, index); + + if (!find_entry(&tmp_head, &link_index, dir, procname, + strlen(procname))) return false; - if (S_ISDIR(link->mode) && S_ISDIR(entry->mode)) + if (S_ISDIR(sysctl_entry_mode(tmp_head, link_index)) && + S_ISDIR(sysctl_entry_mode(header, index))) continue; - if (S_ISLNK(link->mode) && (link->data =3D=3D link_root)) + if (S_ISLNK(sysctl_entry_mode(tmp_head, link_index)) && + tmp_head->ctl_table[link_index].data =3D=3D link_root) continue; return false; } =20 /* The checks passed. Increase the registration count on the links */ - list_for_each_table_entry(entry, header) { - const char *procname =3D entry->procname; - link =3D find_entry(&tmp_head, dir, procname, strlen(procname)); + list_for_each_table_entry(index, header) { + const char *procname =3D sysctl_entry_procname(header, index); + + find_entry(&tmp_head, &link_index, dir, procname, strlen(procname)); tmp_head->nreg++; } return true; @@ -1374,9 +1419,12 @@ struct ctl_table_header *__register_sysctl_table( struct ctl_table_header *header; struct ctl_dir *dir; struct ctl_node *node; + size_t alloc_size; + + alloc_size =3D sizeof(struct ctl_table_header) + + sizeof(struct ctl_node) * table_size; =20 - header =3D kzalloc(sizeof(struct ctl_table_header) + - sizeof(struct ctl_node)*table_size, GFP_KERNEL_ACCOUNT); + header =3D kzalloc(alloc_size, GFP_KERNEL_ACCOUNT); if (!header) return NULL; =20 @@ -1476,7 +1524,7 @@ static void put_links(struct ctl_table_header *header) struct ctl_table_root *root =3D header->root; struct ctl_dir *parent =3D header->parent; struct ctl_dir *core_parent; - const struct ctl_table *entry; + size_t link_index; =20 if (header->set =3D=3D root_set) return; @@ -1485,18 +1533,24 @@ static void put_links(struct ctl_table_header *head= er) if (IS_ERR(core_parent)) return; =20 - list_for_each_table_entry(entry, header) { + list_for_each_table_entry(index, header) { struct ctl_table_header *link_head; + struct ctl_table table; const struct ctl_table *link; - const char *name =3D entry->procname; + const char *name =3D sysctl_entry_procname(header, index); + + if (!find_entry(&link_head, &link_index, core_parent, name, + strlen(name))) + link =3D NULL; + else + link =3D sysctl_entry_table(link_head, link_index, &table); =20 - link =3D find_entry(&link_head, core_parent, name, strlen(name)); if (link && - ((S_ISDIR(link->mode) && S_ISDIR(entry->mode)) || + ((S_ISDIR(link->mode) && + S_ISDIR(sysctl_entry_mode(header, index))) || (S_ISLNK(link->mode) && (link->data =3D=3D root)))) { drop_sysctl_table(link_head); - } - else { + } else { pr_err("sysctl link missing during unregister: "); sysctl_print_dir(parent); pr_cont("%s\n", name); --=20 2.55.0 From nobody Fri Sep 25 21:02:15 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 83C06483837; Mon, 21 Sep 2026 10:55:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988147; cv=none; b=jpZdrchYfqOgl4hMigjP0dXx0r2hJqL9GBJGGhHNOeg4fKkP0LkmCeZegz2OQsIxPF6I7yvhFJP92406YfESmRGksS2RnU1GW9RpECjW8bH3DrZjLWm0eY3/HHeiHzDVclFfT0j/nJVJbEUGQ0BJtbHIUGgNVu61k/bMM2HIKn8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988147; c=relaxed/simple; bh=8Lfv6XCfTAuYPL6y3JSn7GjLFYAsQsDAdmKVmDaOVmY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SbALyKtEP3jh2NXGF/023wazPjHEhzlmR2BUX+EXVVzb8DwDvaPNXBv2DOWXobU7Rf05ir3bPaWTkjI4XManpTmwdXwSq17UK0o4Au3Yt9nd5mCGpdl78Nm5GJ7AX+m3skTGfGReyZbDKqUlFGVjoFz2vSBejl9X5AgjVQPA5DA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=QvpBgWLn; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="QvpBgWLn" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 173A51F000FF; Mon, 21 Sep 2026 10:55:43 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789988146; bh=jAg6HTThhqMYPxhAkTvD2YZYu+Rj+1Mpyut1XrJJv8Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=QvpBgWLnj2sH2FqV1MxWiJ5nWbPzVIvP3an6rPcvUmOzjAytpGM+7ZsWNUjuORNgo FUadAM8c7KpSKbBVJVI44OuK0lxy6MXXmGaL4OL42l1v+BhwBymk2fz1jBa0cf68RB S/aQjQHpPrEn3149IYS7HqJ7mvVyT+vkmV38E1sP/TN8lTaB2t6BPxR2yEBJKw/k81 B6hLDnTCY7+sapSe0HLwZhzblDvrMKTQ948jNuOVCrRxR33PLBEUv7off5sNXaOiV1 BzKwJ+LnTunhWWMrCIUSTY4zxI6gHk0+biZZcNSwpiBXjEogvYblFOLLlDyOWMzL0n S4fo7tJ9mcAnw== From: Alexey Gladkov To: Joel Granados Cc: =?UTF-8?q?Ondrej=20Mosn=C3=A1=C4=8Dek?= , Andrew Morton , Kees Cook , Ryan Roberts , Serge Hallyn , "Eric W . Biederman" , LKML , linux-fsdevel@vger.kernel.org Subject: [PATCH v2 2/6] sysctl: add unsigned int limit constants Date: Mon, 21 Sep 2026 12:54:49 +0200 Message-ID: <92b31e85fbb91af953aee911a44787142cddee3f.1789987960.git.legion@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Some sysctl handlers use unsigned int storage for their limit arguments. In particular, proc_dou8vec_minmax() expects extra1 and extra2 to point to unsigned int values even though the controlled data is an u8. Provide shared unsigned int constants so typed sysctl descriptors can pass correctly typed min and max pointers without casting the existing int constants. Signed-off-by: Alexey Gladkov --- include/linux/sysctl.h | 7 +++++++ kernel/sysctl.c | 3 +++ 2 files changed, 10 insertions(+) diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index e5d7226ab6f5..7139a4c72736 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -59,6 +59,12 @@ extern const int sysctl_vals[]; #define SYSCTL_LONG_ONE ((void *)&sysctl_long_vals[1]) #define SYSCTL_LONG_MAX ((void *)&sysctl_long_vals[2]) =20 +#define SYSCTL_UINT_ZERO ((unsigned int *)&sysctl_uint_vals[0]) +#define SYSCTL_UINT_ONE ((unsigned int *)&sysctl_uint_vals[1]) +#define SYSCTL_UINT_TWO ((unsigned int *)&sysctl_uint_vals[2]) +#define SYSCTL_UINT_THREE ((unsigned int *)&sysctl_uint_vals[3]) +#define SYSCTL_UINT_FOUR ((unsigned int *)&sysctl_uint_vals[4]) + /* * * "dir" originates from read_iter (dir =3D 0) or write_iter (dir =3D 1) @@ -73,6 +79,7 @@ extern const int sysctl_vals[]; #define SYSCTL_KERN_TO_USER(dir) (!dir) =20 extern const unsigned long sysctl_long_vals[]; +extern const unsigned int sysctl_uint_vals[]; =20 typedef int proc_handler(const struct ctl_table *ctl, int dir, void *buf, size_t *lenp, loff_t *ppos); diff --git a/kernel/sysctl.c b/kernel/sysctl.c index f7b75985d542..54edaa2fd5d5 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -29,6 +29,9 @@ EXPORT_SYMBOL(sysctl_vals); const unsigned long sysctl_long_vals[] =3D { 0, 1, LONG_MAX }; EXPORT_SYMBOL_GPL(sysctl_long_vals); =20 +const unsigned int sysctl_uint_vals[] =3D { 0, 1, 2, 3, 4 }; +EXPORT_SYMBOL_GPL(sysctl_uint_vals); + #if defined(CONFIG_SYSCTL) =20 /* Constants used for minimum and maximum */ --=20 2.55.0 From nobody Fri Sep 25 21:02:15 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CEB8A481FD9; Mon, 21 Sep 2026 10:55:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988151; cv=none; b=iYi81hQvWPVhwv/0ZgeXnjU8KojDJExYoOSCDJqxiXH7e9NKgCeAnywEfupGfCzucCdX8Gfs7VOba68htzcwcS/qgC3okaQKSXoPOznE8ESQUxt0kcQmATlx4bW4zA8M90NPYhInxkdSdZHRYYwXiT7IwXTJowzXaUHzowC6lQY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988151; c=relaxed/simple; bh=Q1EL+U1n/59n+vGAckmGgu8Gnm+JH+VW+JJftRkbjSc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QmQdpbMAQMEIfH4rd30dA55xX9n0LPo9+NNz/n8EnZ8GQESr3XHeuZ/daVecSXuR/DkDz7Cn0v4TCiIqHAEoGCNITl/k3EYxXA2yd/+ld7UDZ0Me4Cnqv9fuOi3cox35RZDCY0aMTHIMu2K0WUHDCbmDQIy0UGJM7k3CL/crB9c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=CrPOTt20; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="CrPOTt20" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7BA0A1F00893; Mon, 21 Sep 2026 10:55:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789988148; bh=2S76UFYJ0HEY2NTvHmAcYa7ZIGdW5SuZRlgl/wEBz5M=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=CrPOTt20edKSs9tNbgszkfywyoaDK3yl/oFbOiWr5Nyg5luSiLZ88msgTNbbdgY5/ 2J3tvBywVcfvHYBIU1voGBYSveVDb0IwB59rJaCiv8XqfeFnlt2f4Lx8rpRuYFavZN b8u0PhAqYu582499Wj7nYRgotCI21bmcxQ+Gx7l0feGzfG+J2kdr1nudr6Gpaq6B8i habk6mhmWY5s8mrx5rWr/ZcIMYZFkxJMNhEL6QWcRP7jvNIGYpYtvgPNzfMi+lqnDP Lt9qZNnIhhLbC7IKLJlB7iXhviAk760bXZzvqqBUeftvyiTIWzM6gl3se52Udp1afE tJ8jV1qslcjAw== From: Alexey Gladkov To: Joel Granados Cc: =?UTF-8?q?Ondrej=20Mosn=C3=A1=C4=8Dek?= , Andrew Morton , Kees Cook , Ryan Roberts , Serge Hallyn , "Eric W . Biederman" , LKML , linux-fsdevel@vger.kernel.org Subject: [PATCH v2 3/6] sysctl: add typed field descriptors Date: Mon, 21 Sep 2026 12:54:50 +0200 Message-ID: X-Mailer: git-send-email 2.55.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Several sysctl users duplicate ctl_table arrays at registration time so data and limit pointers can be redirected to namespace or device state. The copies consume memory for every instance and their index-based fixups silently depend on the source table order. Add sysctl_field as an alternative static descriptor. A field records the value kind and a checked offset into an object selected by a registration context. Type-specific offset helpers verify the backing member type at build time, while the core derives the legacy proc handler, size and limit pointers from the field kind. Keep ctl_table as the interface used by proc handlers, permissions and BPF by materializing one entry on the stack when those paths need it. Existing ctl_table registrations are unchanged, and converted users can share one read-only descriptor array without allocating a table copy. Signed-off-by: Alexey Gladkov --- fs/proc/proc_sysctl.c | 289 +++++++++++++++++++++++++++++++++++++---- include/linux/sysctl.h | 143 +++++++++++++++++++- 2 files changed, 400 insertions(+), 32 deletions(-) diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index b5cb219bcdbc..73f3408b2043 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "internal.h" =20 #define list_for_each_table_entry(index, header) \ @@ -91,28 +92,178 @@ static int sysctl_follow_link(struct ctl_table_header = **phead, size_t *pindex); static int insert_links(struct ctl_table_header *head); static void put_links(struct ctl_table_header *header); =20 +static inline bool is_field_table(const struct ctl_table_header *head) +{ + return head->table_kind =3D=3D SYSCTL_TABLE_KIND_FIELD; +} + static const char *sysctl_entry_procname(struct ctl_table_header *head, size_t index) { + if (is_field_table(head)) + return head->ctl_fields[index].procname; + return head->ctl_table[index].procname; } =20 static umode_t sysctl_entry_mode(struct ctl_table_header *head, size_t ind= ex) { - return head->ctl_table[index].mode; + const struct sysctl_field *field; + + if (!is_field_table(head)) + return head->ctl_table[index].mode; + + field =3D &head->ctl_fields[index]; + + if (field->mode_fn) { + lockdep_assert_not_held(&sysctl_lock); + return field->mode_fn(head->ctx); + } + + return field->mode; +} + +static bool sysctl_entry_is_dir(const struct ctl_table_header *head, + size_t index) +{ + /* Do not call field mode_fn callback while holding sysctl_lock. */ + if (is_field_table(head)) + return false; + + return S_ISDIR(head->ctl_table[index].mode); +} + +static bool sysctl_entry_is_link(const struct ctl_table_header *head, + size_t index) +{ + if (is_field_table(head)) + return false; + + return S_ISLNK(head->ctl_table[index].mode); } =20 static struct ctl_table_poll *sysctl_entry_poll(struct ctl_table_header *h= ead, size_t index) { + if (is_field_table(head)) + return NULL; + return head->ctl_table[index].poll; } =20 +static void *sysctl_context_object(const struct sysctl_context *ctx) +{ + if (!ctx) + return NULL; + if (ctx->object) + return ctx->object(ctx); + + switch (ctx->type) { + case SYSCTL_CONTEXT_USER_NS: + return ctx->ns.user_ns; + case SYSCTL_CONTEXT_IPC_NS: + return ctx->ns.ipc_ns; + case SYSCTL_CONTEXT_PID_NS: + return ctx->ns.pid_ns; + case SYSCTL_CONTEXT_NET_NS: + return ctx->ns.net_ns; + } + + return NULL; +} + +static void *sysctl_context_data(const struct sysctl_context *ctx, + size_t offset, size_t size) +{ + void *object =3D sysctl_context_object(ctx); + + if (!object || offset > ctx->object_size || + size > ctx->object_size - offset) + return NULL; + + return (char *)object + offset; +} + static const struct ctl_table * sysctl_entry_table(struct ctl_table_header *head, size_t index, struct ctl_table *table) { - return &head->ctl_table[index]; + const struct sysctl_field *field; + + if (!is_field_table(head)) + return &head->ctl_table[index]; + + field =3D &head->ctl_fields[index]; + + memset(table, 0, sizeof(*table)); + table->procname =3D field->procname; + table->mode =3D field->mode; + + if (field->mode_fn) + table->mode =3D field->mode_fn(head->ctx); + + switch (field->type) { + case SYSCTL_FIELD_NO_DATA: + break; + case SYSCTL_FIELD_STRING: + table->proc_handler =3D proc_dostring; + table->maxlen =3D field->maxlen; + break; + case SYSCTL_FIELD_BOOL: + table->proc_handler =3D proc_dobool; + table->maxlen =3D sizeof(bool); + break; + case SYSCTL_FIELD_U8: + case SYSCTL_FIELD_U8_MINMAX: + table->proc_handler =3D proc_dou8vec_minmax; + table->maxlen =3D sizeof(u8); + table->extra1 =3D field->u8_limits.min; + table->extra2 =3D field->u8_limits.max; + break; + case SYSCTL_FIELD_INT: + case SYSCTL_FIELD_INT_MINMAX: + table->proc_handler =3D field->type =3D=3D SYSCTL_FIELD_INT ? + proc_dointvec : proc_dointvec_minmax; + table->maxlen =3D sizeof(int); + table->extra1 =3D field->int_limits.min; + table->extra2 =3D field->int_limits.max; + break; + case SYSCTL_FIELD_UINT: + case SYSCTL_FIELD_UINT_MINMAX: + table->proc_handler =3D field->type =3D=3D SYSCTL_FIELD_UINT ? + proc_douintvec : proc_douintvec_minmax; + table->maxlen =3D sizeof(unsigned int); + table->extra1 =3D field->uint_limits.min; + table->extra2 =3D field->uint_limits.max; + break; + case SYSCTL_FIELD_LONG: + case SYSCTL_FIELD_LONG_MINMAX: + table->proc_handler =3D proc_doulongvec_minmax; + table->maxlen =3D sizeof(long); + table->extra1 =3D field->long_limits.min; + table->extra2 =3D field->long_limits.max; + break; + case SYSCTL_FIELD_ULONG: + case SYSCTL_FIELD_ULONG_MINMAX: + table->proc_handler =3D proc_doulongvec_minmax; + table->maxlen =3D sizeof(unsigned long); + table->extra1 =3D field->ulong_limits.min; + table->extra2 =3D field->ulong_limits.max; + break; + case SYSCTL_FIELD_SIZE_T: + table->proc_handler =3D proc_doulongvec_minmax; + table->maxlen =3D sizeof(size_t); + break; + } + + if (field->type !=3D SYSCTL_FIELD_NO_DATA) + table->data =3D sysctl_context_data(head->ctx, field->data_offset, table= ->maxlen); + if (field->proc_handler) + table->proc_handler =3D field->proc_handler; + if (field->maxlen) + table->maxlen =3D field->maxlen; + + return table; } =20 static void sysctl_print_dir(struct ctl_dir *dir) @@ -212,9 +363,17 @@ static void erase_entry(struct ctl_table_header *head,= size_t index) =20 static void init_header(struct ctl_table_header *head, struct ctl_table_root *root, struct ctl_table_set *set, - struct ctl_node *node, const struct ctl_table *table, size_t table_size) + struct ctl_node *node, const struct ctl_table *table, + const struct sysctl_field *fields, size_t table_size, + const struct sysctl_context *ctx) { - head->ctl_table =3D table; + if (fields) { + head->ctl_fields =3D fields; + head->table_kind =3D SYSCTL_TABLE_KIND_FIELD; + } else { + head->ctl_table =3D table; + head->table_kind =3D SYSCTL_TABLE_KIND_TABLE; + } head->ctl_table_size =3D table_size; head->ctl_table_arg =3D table; head->used =3D 0; @@ -223,6 +382,7 @@ static void init_header(struct ctl_table_header *head, head->unregistering =3D NULL; head->root =3D root; head->set =3D set; + head->ctx =3D ctx; head->parent =3D NULL; head->node =3D node; INIT_HLIST_HEAD(&head->inodes); @@ -981,7 +1141,7 @@ static struct ctl_dir *find_subdir(struct ctl_dir *dir, =20 if (!find_entry(&head, &index, dir, name, namelen)) return ERR_PTR(-ENOENT); - if (!S_ISDIR(sysctl_entry_mode(head, index))) + if (!sysctl_entry_is_dir(head, index)) return ERR_PTR(-ENOTDIR); return container_of(head, struct ctl_dir, header); } @@ -1006,7 +1166,8 @@ static struct ctl_dir *new_dir(struct ctl_table_set *= set, memcpy(new_name, name, namelen); table[0].procname =3D new_name; table[0].mode =3D S_IFDIR|S_IRUGO|S_IXUGO; - init_header(&new->header, set->dir.header.root, set, node, table, 1); + init_header(&new->header, set->dir.header.root, set, node, table, NULL, + 1, NULL); =20 return new; } @@ -1187,6 +1348,10 @@ static int sysctl_check_table(const char *path, stru= ct ctl_table_header *header) entry =3D sysctl_entry_table(header, index, &table); if (!entry->procname) err |=3D sysctl_err(path, entry, "procname is null"); + if (is_field_table(header) && + header->ctl_fields[index].type !=3D SYSCTL_FIELD_NO_DATA && + !entry->data) + err |=3D sysctl_err(path, entry, "No data"); if ((entry->proc_handler =3D=3D proc_dostring) || (entry->proc_handler =3D=3D proc_dobool) || (entry->proc_handler =3D=3D proc_dointvec) || @@ -1199,7 +1364,7 @@ static int sysctl_check_table(const char *path, struc= t ctl_table_header *header) (entry->proc_handler =3D=3D proc_dointvec_ms_jiffies) || (entry->proc_handler =3D=3D proc_doulongvec_minmax) || (entry->proc_handler =3D=3D proc_doulongvec_ms_jiffies_minmax)) { - if (!entry->data) + if (!is_field_table(header) && !entry->data) err |=3D sysctl_err(path, entry, "No data"); if (!entry->maxlen) err |=3D sysctl_err(path, entry, "No maxlen"); @@ -1255,7 +1420,7 @@ static struct ctl_table_header *new_links(struct ctl_= dir *dir, struct ctl_table_ link++; } init_header(links, dir->header.root, dir->header.set, node, link_table, - head->ctl_table_size); + NULL, head->ctl_table_size, NULL); links->nreg =3D head->ctl_table_size; =20 return links; @@ -1279,10 +1444,10 @@ static bool get_links(struct ctl_dir *dir, if (!find_entry(&tmp_head, &link_index, dir, procname, strlen(procname))) return false; - if (S_ISDIR(sysctl_entry_mode(tmp_head, link_index)) && - S_ISDIR(sysctl_entry_mode(header, index))) + if (sysctl_entry_is_dir(tmp_head, link_index) && + sysctl_entry_is_dir(header, index)) continue; - if (S_ISLNK(sysctl_entry_mode(tmp_head, link_index)) && + if (sysctl_entry_is_link(tmp_head, link_index) && tmp_head->ctl_table[link_index].data =3D=3D link_root) continue; return false; @@ -1369,18 +1534,25 @@ static struct ctl_dir *sysctl_mkdir_p(struct ctl_di= r *dir, const char *path) } =20 /** - * __register_sysctl_table - register a leaf sysctl table + * __register_sysctl_table_internal - register a leaf sysctl table * @set: Sysctl tree to register on * @path: The path to the directory the sysctl table is in. + * @table: The top-level ctl_table array, or %NULL when registering @field= s. + * @fields: The top-level ctl_field array, or %NULL when registering @tabl= e. + * @table_size: The number of elements in @table or @fields. + * @ctx: Optional context used to resolve @fields entries. + * @ctx_size: Size of @ctx, including any wrapper object that embeds it. * - * @table: the top-level table structure. This table should not be free'd - * after registration. So it should not be used on stack. It can e= ither - * be a global or dynamically allocated by the caller and free'd l= ater - * after sysctl unregistration. - * @table_size : The number of elements in table + * Register a sysctl table hierarchy. One of @table or @fields must be + * provided. The descriptor array should not be freed after registration, = so it + * should not be used on stack. It can either be global or dynamically + * allocated by the caller and freed later after sysctl unregistration. * - * Register a sysctl table hierarchy. @table should be a filled in ctl_tab= le - * array. + * If @ctx points to a wrapper object, &struct sysctl_context must be the = first + * member so @ctx can be copied together with the rest of that object. + * Data offsets stored in @fields are checked against @ctx->object_size and + * applied to the namespace selected by @ctx->ns. A context wrapper may pr= ovide + * @ctx->object for tables whose data belongs to another object. * * The members of the &struct ctl_table structure are used as follows: * procname - the name of the sysctl file under /proc/sys. Set to %NULL to= not @@ -1411,25 +1583,69 @@ static struct ctl_dir *sysctl_mkdir_p(struct ctl_di= r *dir, const char *path) * This routine returns %NULL on a failure to register, and a pointer * to the table header on success. */ -struct ctl_table_header *__register_sysctl_table( - struct ctl_table_set *set, - const char *path, const struct ctl_table *table, size_t table_size) +static struct ctl_table_header * +__register_sysctl_table_internal(struct ctl_table_set *set, const char *pa= th, + const struct ctl_table *table, + const struct sysctl_field *fields, + size_t table_size, + const struct sysctl_context *ctx, size_t ctx_size) { struct ctl_table_root *root =3D set->dir.header.root; struct ctl_table_header *header; struct ctl_dir *dir; struct ctl_node *node; + const struct sysctl_context *header_ctx =3D NULL; + size_t nodes_size; size_t alloc_size; + size_t context_offset; + + if (!!table =3D=3D !!fields) + return NULL; + + if (ctx && ctx_size < sizeof(*ctx)) + return NULL; + + if (!ctx && ctx_size) + return NULL; + + if (fields && (!ctx || !ctx->object_size || + !sysctl_context_object(ctx))) + return NULL; + + if (check_mul_overflow(sizeof(struct ctl_node), table_size, &nodes_size)) + return NULL; + + if (check_add_overflow(sizeof(*header), nodes_size, &context_offset)) + return NULL; + + /* + * Store the copied context after the ctl_node array. struct sysctl_conte= xt + * is the first member of any caller-defined wrapper, whose alignment + * must not exceed that of struct sysctl_context. + */ + if (ctx) { + if (check_add_overflow(context_offset, + __alignof__(*ctx) - 1, &context_offset)) + return NULL; + context_offset =3D ALIGN_DOWN(context_offset, __alignof__(*ctx)); + } =20 - alloc_size =3D sizeof(struct ctl_table_header) + - sizeof(struct ctl_node) * table_size; + if (check_add_overflow(context_offset, ctx_size, &alloc_size)) + return NULL; =20 header =3D kzalloc(alloc_size, GFP_KERNEL_ACCOUNT); if (!header) return NULL; =20 node =3D (struct ctl_node *)(header + 1); - init_header(header, root, set, node, table, table_size); + if (ctx) { + header_ctx =3D (const struct sysctl_context *)((void *)header + + context_offset); + memcpy((void *)header_ctx, ctx, ctx_size); + } + + init_header(header, root, set, node, table, fields, table_size, + header_ctx); if (sysctl_check_table(path, header)) goto fail; =20 @@ -1459,6 +1675,25 @@ struct ctl_table_header *__register_sysctl_table( return NULL; } =20 +struct ctl_table_header * +__register_sysctl_fields(struct ctl_table_set *set, const char *path, + const struct sysctl_field *fields, size_t field_count, + const struct sysctl_context *ctx, size_t ctx_size) +{ + return __register_sysctl_table_internal(set, path, NULL, fields, + field_count, ctx, ctx_size); +} +EXPORT_SYMBOL(__register_sysctl_fields); + +struct ctl_table_header * +__register_sysctl_table(struct ctl_table_set *set, const char *path, + const struct ctl_table *table, size_t table_size) +{ + return __register_sysctl_table_internal(set, path, table, NULL, + table_size, NULL, 0); +} +EXPORT_SYMBOL(__register_sysctl_table); + /** * register_sysctl_sz - register a sysctl table * @path: The path to the directory the sysctl table is in. If the path @@ -1547,7 +1782,7 @@ static void put_links(struct ctl_table_header *header) =20 if (link && ((S_ISDIR(link->mode) && - S_ISDIR(sysctl_entry_mode(header, index))) || + sysctl_entry_is_dir(header, index)) || (S_ISLNK(link->mode) && (link->data =3D=3D root)))) { drop_sysctl_table(link_head); } else { @@ -1603,7 +1838,7 @@ void setup_sysctl_set(struct ctl_table_set *set, { memset(set, 0, sizeof(*set)); set->is_seen =3D is_seen; - init_header(&set->dir.header, root, set, NULL, root_table, 1); + init_header(&set->dir.header, root, set, NULL, root_table, NULL, 1, NULL); } =20 void retire_sysctl_set(struct ctl_table_set *set) diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 7139a4c72736..de59fcd139cd 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -22,20 +22,27 @@ #ifndef _LINUX_SYSCTL_H #define _LINUX_SYSCTL_H =20 +#include #include #include #include #include +#include #include #include =20 /* For the /proc/sys support */ struct completion; struct ctl_table; +struct sysctl_field; struct nsproxy; struct ctl_table_root; struct ctl_table_header; struct ctl_dir; +struct ipc_namespace; +struct net; +struct pid_namespace; +struct user_namespace; =20 /* Keep the same order as in fs/proc/proc_sysctl.c */ #define SYSCTL_ZERO ((void *)&sysctl_vals[0]) @@ -81,6 +88,27 @@ extern const int sysctl_vals[]; extern const unsigned long sysctl_long_vals[]; extern const unsigned int sysctl_uint_vals[]; =20 +enum sysctl_context_type { + SYSCTL_CONTEXT_USER_NS, + SYSCTL_CONTEXT_IPC_NS, + SYSCTL_CONTEXT_PID_NS, + SYSCTL_CONTEXT_NET_NS, +}; + +union sysctl_namespace { + struct user_namespace *user_ns; + struct ipc_namespace *ipc_ns; + struct pid_namespace *pid_ns; + struct net *net_ns; +}; + +struct sysctl_context { + enum sysctl_context_type type; + size_t object_size; + union sysctl_namespace ns; + void *(*object)(const struct sysctl_context *ctx); +}; + typedef int proc_handler(const struct ctl_table *ctl, int dir, void *buf, size_t *lenp, loff_t *ppos); =20 @@ -237,30 +265,114 @@ struct ctl_table { void *extra2; } __randomize_layout; =20 +enum sysctl_field_type { + SYSCTL_FIELD_NO_DATA, + SYSCTL_FIELD_STRING, + SYSCTL_FIELD_BOOL, + SYSCTL_FIELD_U8, + SYSCTL_FIELD_U8_MINMAX, + SYSCTL_FIELD_INT, + SYSCTL_FIELD_INT_MINMAX, + SYSCTL_FIELD_UINT, + SYSCTL_FIELD_UINT_MINMAX, + SYSCTL_FIELD_LONG, + SYSCTL_FIELD_LONG_MINMAX, + SYSCTL_FIELD_ULONG, + SYSCTL_FIELD_ULONG_MINMAX, + SYSCTL_FIELD_SIZE_T, +}; + +#define __SYSCTL_FIELD_OFFSET(_struct, _field, _type) \ + (offsetof(_struct, _field) + \ + BUILD_BUG_ON_ZERO(!__same_type(((_struct *)0)->_field, *(_type *)0))) + +#define SYSCTL_FIELD_INT_OFFSET(_struct, _field) __SYSCTL_FIELD_OFFSET(_st= ruct, _field, int) +#define SYSCTL_FIELD_UINT_OFFSET(_struct, _field) __SYSCTL_FIELD_OFFSET(_s= truct, _field, unsigned int) +#define SYSCTL_FIELD_LONG_OFFSET(_struct, _field) __SYSCTL_FIELD_OFFSET(_s= truct, _field, long) +#define SYSCTL_FIELD_ULONG_OFFSET(_struct, _field) __SYSCTL_FIELD_OFFSET(_= struct, _field, unsigned long) +#define SYSCTL_FIELD_SIZE_T_OFFSET(_struct, _field) __SYSCTL_FIELD_OFFSET(= _struct, _field, size_t) + +struct sysctl_field_u8_limits { + unsigned int *min; + unsigned int *max; +}; + +struct sysctl_field_int_limits { + int *min; + int *max; +}; + +struct sysctl_field_uint_limits { + unsigned int *min; + unsigned int *max; +}; + +struct sysctl_field_long_limits { + long *min; + long *max; +}; + +struct sysctl_field_ulong_limits { + unsigned long *min; + unsigned long *max; +}; + +struct sysctl_field { + const char *procname; + umode_t mode; + enum sysctl_field_type type; + umode_t (*mode_fn)(const struct sysctl_context *ctx); + proc_handler *proc_handler; + int maxlen; + size_t data_offset; + union { + struct sysctl_field_u8_limits u8_limits; + struct sysctl_field_int_limits int_limits; + struct sysctl_field_uint_limits uint_limits; + struct sysctl_field_long_limits long_limits; + struct sysctl_field_ulong_limits ulong_limits; + }; +} __randomize_layout; + struct ctl_node { struct rb_node node; struct ctl_table_header *header; }; =20 /** - * struct ctl_table_header - maintains dynamic lists of struct ctl_table t= rees - * @ctl_table: pointer to the first element in ctl_table array - * @ctl_table_size: number of elements pointed by @ctl_table + * struct ctl_table_header - maintains dynamic lists of sysctl descriptor = trees + * @ctl_table: pointer to the first element in a legacy ctl_table array + * @ctl_fields: pointer to the first element in a ctl_field array + * @ctl_table_size: number of elements pointed to by @ctl_table or @ctl_fi= elds * @used: The entry will never be touched when equal to 0. * @count: Upped every time something is added to @inodes and downed every= time * something is removed from inodes * @nreg: When nreg drops to 0 the ctl_table_header will be unregistered. - * @rcu: Delays the freeing of the inode. Introduced with "unfuck proc_sys= ctl ->d_compare()" + * @rcu: delays freeing the header until after an RCU grace period + * @unregistering: completion used while unregistering the header + * @ctl_table_arg: original legacy ctl_table passed at registration, or NU= LL + * @ctx: copied registration context used to resolve ctl_field entries + * @root: sysctl tree containing this header + * @set: sysctl set containing this header + * @parent: parent directory of this header + * @node: array of nodes corresponding to the descriptor entries + * @inodes: inodes currently referring to this header * * @type: Enumeration to differentiate between ctl target types: * type.SYSCTL_TABLE_TYPE_DEFAULT: ctl target with no special consideratio= ns * type.SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY: Identifies a permanently empt= y dir * target to serve as a mount p= oint + * @table_kind: descriptor format stored in this header + * @table_kind.SYSCTL_TABLE_KIND_TABLE: legacy ctl_table descriptors + * @table_kind.SYSCTL_TABLE_KIND_FIELD: typed ctl_field descriptors */ struct ctl_table_header { union { struct { - const struct ctl_table *ctl_table; + union { + const struct ctl_table *ctl_table; + const struct sysctl_field *ctl_fields; + }; int ctl_table_size; int used; int count; @@ -275,10 +387,15 @@ struct ctl_table_header { struct ctl_dir *parent; struct ctl_node *node; struct hlist_head inodes; /* head for proc_inode->sysctl_inodes */ + const struct sysctl_context *ctx; enum { SYSCTL_TABLE_TYPE_DEFAULT, SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY, } type; + enum { + SYSCTL_TABLE_KIND_TABLE, + SYSCTL_TABLE_KIND_FIELD, + } table_kind; }; =20 struct ctl_dir { @@ -303,6 +420,10 @@ struct ctl_table_root { #define register_sysctl(path, table) \ register_sysctl_sz(path, table, ARRAY_SIZE(table)) =20 +#define register_sysctl_fields(set, path, fields, ctx) \ + __register_sysctl_fields(set, path, fields, ARRAY_SIZE(fields), \ + (ctx), sizeof(*(ctx))) + #ifdef CONFIG_SYSCTL =20 void proc_sys_poll_notify(struct ctl_table_poll *poll); @@ -315,6 +436,10 @@ extern void retire_sysctl_set(struct ctl_table_set *se= t); struct ctl_table_header *__register_sysctl_table( struct ctl_table_set *set, const char *path, const struct ctl_table *table, size_t table_size); +struct ctl_table_header * +__register_sysctl_fields(struct ctl_table_set *set, const char *path, + const struct sysctl_field *fields, size_t field_count, + const struct sysctl_context *ctx, size_t ctx_size); struct ctl_table_header *register_sysctl_sz(const char *path, const struct= ctl_table *table, size_t table_size); void unregister_sysctl_table(struct ctl_table_header * table); @@ -350,6 +475,14 @@ static inline struct ctl_table_header *register_sysctl= _sz(const char *path, return NULL; } =20 +static inline struct ctl_table_header * +__register_sysctl_fields(struct ctl_table_set *set, const char *path, + const struct sysctl_field *fields, size_t field_count, + const struct sysctl_context *ctx, size_t ctx_size) +{ + return NULL; +} + static inline void unregister_sysctl_table(struct ctl_table_header * table) { } --=20 2.55.0 From nobody Fri Sep 25 21:02:15 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3E3EB35C6BE; Mon, 21 Sep 2026 10:55:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988153; cv=none; b=m5sxdN7fPPS1icdxLFcGyxe2KBvxGpozJJT9QqZswM5C585LsFSLXev/0CsXia7kuaJy6Psl5tF3pl29kom1QYVfPVEU0wjzYoA3+ZgYfpRMALFAA4f1/QMorOG+f99lqV7+AGQJ+Yr637575z59YyY5RcjGvoQ8oP6Y/OYqp/w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988153; c=relaxed/simple; bh=V1ZbIWsgBcg1qUA3h0Lv9IW+hwy69HEW3AIcexA/uso=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mwLV6TLD0/DgDAbP7zcHIGxNJzASVo5RR6r8W+3+SR+aHwiSzgxIWJfdri7eu0/M+JoyxVDYsY6FOALQpZDRJS4xUlw+nq6lf8zuNMLV/3HwSSaTR0LnUy9TQ0izP90SODd368g7TmU9HGMKw/ijG8bwEj2oMzjCuMRktlkK+YI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=VFML3jOy; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="VFML3jOy" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E37E81F000FF; Mon, 21 Sep 2026 10:55:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789988150; bh=D76mpVzAbp1Syw0WMegV1hvH55fi7MtW8TByqwzJPi4=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=VFML3jOyyDfh62k7sxh4ycPnzBxYd3UoBG+baX0TzoDg9w84Iq51jitAORgODl1bk LJNYqXyT5UJ08A5hG/b+myZniJC8rAWCgxCyFOmGmZ+33VhjB4Zcp8Iiisz/eL6z65 MATLutjEW1eynqo2/q7HLOnrkDD0g5Gr8aUDtQqlv7rGEFc7xC0wGlseKEESK6Dif8 MCO1swQ8QykoZhcpUwgBCsPWVd8f7wv0HBDiktAQ0KLk3ODVE9xOs3I1ZJBXtW3RGm tFdxFs/veaMGw2mMXbYLm7IYETJYrFhu6CK/QePUTzoh4PFXeMumT2Cm/A13e8lFou DoTvWK1ZPVG0g== From: Alexey Gladkov To: Joel Granados Cc: =?UTF-8?q?Ondrej=20Mosn=C3=A1=C4=8Dek?= , Andrew Morton , Kees Cook , Ryan Roberts , Serge Hallyn , "Eric W . Biederman" , LKML , linux-fsdevel@vger.kernel.org Subject: [PATCH v2 4/6] sysctl: ipc: use typed fields for IPC namespace sysctls Date: Mon, 21 Sep 2026 12:54:51 +0200 Message-ID: <8d545946c65dd3f8fe61cee67335b65f61803532.1789987960.git.legion@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" IPC sysctl registration clones the table and rewrites data and limit pointers for each namespace. This allocates a full ctl_table array per namespace and ties the fixup loop to the table layout. Describe the table with sysctl_field offsets into ipc_namespace instead. Keep the auto_msgmni entry data-less because its handler selects the effective value itself. This removes the per-namespace table allocation while preserving the existing handlers and limits. Signed-off-by: Alexey Gladkov --- ipc/ipc_sysctl.c | 188 +++++++++++++++++++++-------------------------- 1 file changed, 82 insertions(+), 106 deletions(-) diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c index d038d944257f..3a1a6943e14f 100644 --- a/ipc/ipc_sysctl.c +++ b/ipc/ipc_sysctl.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include "util.h" =20 @@ -41,6 +40,8 @@ static int proc_ipc_auto_msgmni(const struct ctl_table *t= able, int write, =20 memcpy(&ipc_table, table, sizeof(ipc_table)); ipc_table.data =3D &dummy; + ipc_table.extra1 =3D SYSCTL_ZERO; + ipc_table.extra2 =3D SYSCTL_ONE; =20 if (write) pr_info_once("writing to auto_msgmni has no effect"); @@ -72,110 +73,129 @@ static int proc_ipc_sem_dointvec(const struct ctl_tab= le *table, int write, int ipc_mni =3D IPCMNI; int ipc_mni_shift =3D IPCMNI_SHIFT; int ipc_min_cycle =3D RADIX_TREE_MAP_SIZE; +static unsigned int ipc_mni_max =3D IPCMNI; +static unsigned int ipc_uint_max =3D INT_MAX; =20 -static const struct ctl_table ipc_sysctls[] =3D { +static const struct sysctl_field ipc_sysctls[] =3D { { .procname =3D "shmmax", - .data =3D &init_ipc_ns.shm_ctlmax, - .maxlen =3D sizeof(init_ipc_ns.shm_ctlmax), .mode =3D 0644, - .proc_handler =3D proc_doulongvec_minmax, + .type =3D SYSCTL_FIELD_SIZE_T, + .data_offset =3D SYSCTL_FIELD_SIZE_T_OFFSET(struct ipc_namespace, + shm_ctlmax), }, { .procname =3D "shmall", - .data =3D &init_ipc_ns.shm_ctlall, - .maxlen =3D sizeof(init_ipc_ns.shm_ctlall), .mode =3D 0644, - .proc_handler =3D proc_doulongvec_minmax, + .type =3D SYSCTL_FIELD_SIZE_T, + .data_offset =3D SYSCTL_FIELD_SIZE_T_OFFSET(struct ipc_namespace, + shm_ctlall), }, { .procname =3D "shmmni", - .data =3D &init_ipc_ns.shm_ctlmni, - .maxlen =3D sizeof(init_ipc_ns.shm_ctlmni), .mode =3D 0644, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D &ipc_mni, + .type =3D SYSCTL_FIELD_INT_MINMAX, + .data_offset =3D SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace, + shm_ctlmni), + .int_limits =3D { + .min =3D SYSCTL_ZERO, + .max =3D &ipc_mni, + }, }, { .procname =3D "shm_rmid_forced", - .data =3D &init_ipc_ns.shm_rmid_forced, - .maxlen =3D sizeof(init_ipc_ns.shm_rmid_forced), .mode =3D 0644, .proc_handler =3D proc_ipc_dointvec_minmax_orphans, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE, + .type =3D SYSCTL_FIELD_INT_MINMAX, + .data_offset =3D SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace, + shm_rmid_forced), + .int_limits =3D { + .min =3D SYSCTL_ZERO, + .max =3D SYSCTL_ONE, + }, }, { .procname =3D "msgmax", - .data =3D &init_ipc_ns.msg_ctlmax, - .maxlen =3D sizeof(init_ipc_ns.msg_ctlmax), .mode =3D 0644, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_INT_MAX, + .type =3D SYSCTL_FIELD_UINT_MINMAX, + .data_offset =3D SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace, + msg_ctlmax), + .uint_limits =3D { + .min =3D SYSCTL_UINT_ZERO, + .max =3D &ipc_uint_max, + }, }, { .procname =3D "msgmni", - .data =3D &init_ipc_ns.msg_ctlmni, - .maxlen =3D sizeof(init_ipc_ns.msg_ctlmni), .mode =3D 0644, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D &ipc_mni, + .type =3D SYSCTL_FIELD_UINT_MINMAX, + .data_offset =3D SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace, + msg_ctlmni), + .uint_limits =3D { + .min =3D SYSCTL_UINT_ZERO, + .max =3D &ipc_mni_max, + }, }, { .procname =3D "auto_msgmni", - .data =3D NULL, - .maxlen =3D sizeof(int), .mode =3D 0644, .proc_handler =3D proc_ipc_auto_msgmni, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_ONE, + .maxlen =3D sizeof(int), + .type =3D SYSCTL_FIELD_NO_DATA, }, { - .procname =3D "msgmnb", - .data =3D &init_ipc_ns.msg_ctlmnb, - .maxlen =3D sizeof(init_ipc_ns.msg_ctlmnb), + .procname =3D "msgmnb", .mode =3D 0644, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_INT_MAX, + .type =3D SYSCTL_FIELD_UINT_MINMAX, + .data_offset =3D SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace, + msg_ctlmnb), + .uint_limits =3D { + .min =3D SYSCTL_UINT_ZERO, + .max =3D &ipc_uint_max, + }, }, { .procname =3D "sem", - .data =3D &init_ipc_ns.sem_ctls, - .maxlen =3D 4*sizeof(int), .mode =3D 0644, .proc_handler =3D proc_ipc_sem_dointvec, + .maxlen =3D 4 * sizeof(int), + .type =3D SYSCTL_FIELD_INT, + .data_offset =3D SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace, + sem_ctls[0]), }, #ifdef CONFIG_CHECKPOINT_RESTORE { .procname =3D "sem_next_id", - .data =3D &init_ipc_ns.ids[IPC_SEM_IDS].next_id, - .maxlen =3D sizeof(init_ipc_ns.ids[IPC_SEM_IDS].next_id), .mode =3D 0444, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_INT_MAX, + .type =3D SYSCTL_FIELD_INT_MINMAX, + .data_offset =3D SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace, + ids[IPC_SEM_IDS].next_id), + .int_limits =3D { + .min =3D SYSCTL_ZERO, + .max =3D SYSCTL_INT_MAX, + }, }, { .procname =3D "msg_next_id", - .data =3D &init_ipc_ns.ids[IPC_MSG_IDS].next_id, - .maxlen =3D sizeof(init_ipc_ns.ids[IPC_MSG_IDS].next_id), .mode =3D 0444, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_INT_MAX, + .type =3D SYSCTL_FIELD_INT_MINMAX, + .data_offset =3D SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace, + ids[IPC_MSG_IDS].next_id), + .int_limits =3D { + .min =3D SYSCTL_ZERO, + .max =3D SYSCTL_INT_MAX, + }, }, { .procname =3D "shm_next_id", - .data =3D &init_ipc_ns.ids[IPC_SHM_IDS].next_id, - .maxlen =3D sizeof(init_ipc_ns.ids[IPC_SHM_IDS].next_id), .mode =3D 0444, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D SYSCTL_ZERO, - .extra2 =3D SYSCTL_INT_MAX, + .type =3D SYSCTL_FIELD_INT_MINMAX, + .data_offset =3D SYSCTL_FIELD_INT_OFFSET(struct ipc_namespace, + ids[IPC_SHM_IDS].next_id), + .int_limits =3D { + .min =3D SYSCTL_ZERO, + .max =3D SYSCTL_INT_MAX, + }, }, #endif }; @@ -244,57 +264,16 @@ static struct ctl_table_root set_root =3D { =20 bool setup_ipc_sysctls(struct ipc_namespace *ns) { - struct ctl_table *tbl; + struct sysctl_context ctx =3D { + .type =3D SYSCTL_CONTEXT_IPC_NS, + .object_size =3D sizeof(*ns), + .ns.ipc_ns =3D ns, + }; =20 setup_sysctl_set(&ns->ipc_set, &set_root, set_is_seen); - - tbl =3D kmemdup(ipc_sysctls, sizeof(ipc_sysctls), GFP_KERNEL); - if (tbl) { - int i; - - for (i =3D 0; i < ARRAY_SIZE(ipc_sysctls); i++) { - if (tbl[i].data =3D=3D &init_ipc_ns.shm_ctlmax) - tbl[i].data =3D &ns->shm_ctlmax; - - else if (tbl[i].data =3D=3D &init_ipc_ns.shm_ctlall) - tbl[i].data =3D &ns->shm_ctlall; - - else if (tbl[i].data =3D=3D &init_ipc_ns.shm_ctlmni) - tbl[i].data =3D &ns->shm_ctlmni; - - else if (tbl[i].data =3D=3D &init_ipc_ns.shm_rmid_forced) - tbl[i].data =3D &ns->shm_rmid_forced; - - else if (tbl[i].data =3D=3D &init_ipc_ns.msg_ctlmax) - tbl[i].data =3D &ns->msg_ctlmax; - - else if (tbl[i].data =3D=3D &init_ipc_ns.msg_ctlmni) - tbl[i].data =3D &ns->msg_ctlmni; - - else if (tbl[i].data =3D=3D &init_ipc_ns.msg_ctlmnb) - tbl[i].data =3D &ns->msg_ctlmnb; - - else if (tbl[i].data =3D=3D &init_ipc_ns.sem_ctls) - tbl[i].data =3D &ns->sem_ctls; -#ifdef CONFIG_CHECKPOINT_RESTORE - else if (tbl[i].data =3D=3D &init_ipc_ns.ids[IPC_SEM_IDS].next_id) - tbl[i].data =3D &ns->ids[IPC_SEM_IDS].next_id; - - else if (tbl[i].data =3D=3D &init_ipc_ns.ids[IPC_MSG_IDS].next_id) - tbl[i].data =3D &ns->ids[IPC_MSG_IDS].next_id; - - else if (tbl[i].data =3D=3D &init_ipc_ns.ids[IPC_SHM_IDS].next_id) - tbl[i].data =3D &ns->ids[IPC_SHM_IDS].next_id; -#endif - else - tbl[i].data =3D NULL; - } - - ns->ipc_sysctls =3D __register_sysctl_table(&ns->ipc_set, "kernel", tbl, - ARRAY_SIZE(ipc_sysctls)); - } + ns->ipc_sysctls =3D register_sysctl_fields(&ns->ipc_set, "kernel", + ipc_sysctls, &ctx); if (!ns->ipc_sysctls) { - kfree(tbl); retire_sysctl_set(&ns->ipc_set); return false; } @@ -304,12 +283,8 @@ bool setup_ipc_sysctls(struct ipc_namespace *ns) =20 void retire_ipc_sysctls(struct ipc_namespace *ns) { - const struct ctl_table *tbl; - - tbl =3D ns->ipc_sysctls->ctl_table_arg; unregister_sysctl_table(ns->ipc_sysctls); retire_sysctl_set(&ns->ipc_set); - kfree(tbl); } =20 static int __init ipc_sysctl_init(void) @@ -326,6 +301,7 @@ device_initcall(ipc_sysctl_init); static int __init ipc_mni_extend(char *str) { ipc_mni =3D IPCMNI_EXTEND; + ipc_mni_max =3D IPCMNI_EXTEND; ipc_mni_shift =3D IPCMNI_EXTEND_SHIFT; ipc_min_cycle =3D IPCMNI_EXTEND_MIN_CYCLE; pr_info("IPCMNI extended to %d.\n", ipc_mni); --=20 2.55.0 From nobody Fri Sep 25 21:02:15 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A34024825DD; Mon, 21 Sep 2026 10:55:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988154; cv=none; b=k+wMKfIy32TGW8oTJKd5B1aUH19B5vKnhXf9SRnShWRP+srCooH7IMz9qaYFceagAi1TY/jjjt2SWXXbzQrMLvplghavvnodDUYcKLFwn24nyzJr5M1fnvcRYp43q5bxGD6eQ1A4EYHBFSxtcWxfuMgQG6m4fYwzc1bNR4qgYzg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988154; c=relaxed/simple; bh=zN8KBXsHg6a8ybZFtItrrB3nc8ZNrd5OBVHijjXA4ZY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lchVwcJasHXlIYDzedQzyZ0tYr8ERfTDDJl9pJGE4kt+PrJhEzyHXLek2QeoAFnpDmcmKd7FAZwBWsdG6wUAvyFojzAqYb4JeEn6g1kJ3JO+vMnX53ekYx9i8UCamrk2jpxhWdPlYhJ2fFo1Cal2sIjXT6Bu3T7GWFt/xs0eW5A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=EijkETDW; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="EijkETDW" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 5339C1F00893; Mon, 21 Sep 2026 10:55:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789988153; bh=8JtYJjScj9aKe5la7osjOS8VClOFtN73fAXQOk2DuXU=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=EijkETDWjFBsQ17El8Pg2mKHFND5w7sLzA7brq//E2SyBz0cbamEZ515Hs/WSTtE7 VxeRFYvk8aAkwAQdItWfWtW6uAryeca2rPgq5U8lALWgMiNIOeB4BwbN0017Wc13uc D8imSYV/uT0QoOxB6/WbOGA62Wm5EQ96OhMBAd/V8NK3gPInsXL36Yug9Ud+xPWEgk alwfExz++N/3kChvjS0goxJDpX+xWqNzFYF+xKJWXuCyTPfuqmsnQuOVfTG/Kgp6+4 46dcZuBD/r5U6AjShRUeRZQCj8ZxtMwTMULb1ujzEwrWIhItJrryo5q7/9Op1p8MVm abGraevpihTgA== From: Alexey Gladkov To: Joel Granados Cc: =?UTF-8?q?Ondrej=20Mosn=C3=A1=C4=8Dek?= , Andrew Morton , Kees Cook , Ryan Roberts , Serge Hallyn , "Eric W . Biederman" , LKML , linux-fsdevel@vger.kernel.org Subject: [PATCH v2 5/6] sysctl: mq: use typed fields for IPC namespace sysctls Date: Mon, 21 Sep 2026 12:54:52 +0200 Message-ID: <7b9a5e7551f214b863b1b30559b7a4c50b44b248.1789987960.git.legion@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Convert mq_sysctls. The table can now share one static array across ipc namespaces instead of allocating and rewriting a ctl_table copy for each registration. Signed-off-by: Alexey Gladkov --- ipc/mq_sysctl.c | 104 ++++++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 61 deletions(-) diff --git a/ipc/mq_sysctl.c b/ipc/mq_sysctl.c index 0dd12e1c9f53..0d57708e9ea7 100644 --- a/ipc/mq_sysctl.c +++ b/ipc/mq_sysctl.c @@ -14,55 +14,63 @@ #include #include =20 -static int msg_max_limit_min =3D MIN_MSGMAX; -static int msg_max_limit_max =3D HARD_MSGMAX; +static unsigned int msg_max_limit_min =3D MIN_MSGMAX; +static unsigned int msg_max_limit_max =3D HARD_MSGMAX; =20 -static int msg_maxsize_limit_min =3D MIN_MSGSIZEMAX; -static int msg_maxsize_limit_max =3D HARD_MSGSIZEMAX; +static unsigned int msg_maxsize_limit_min =3D MIN_MSGSIZEMAX; +static unsigned int msg_maxsize_limit_max =3D HARD_MSGSIZEMAX; =20 -static const struct ctl_table mq_sysctls[] =3D { +static const struct sysctl_field mq_sysctls[] =3D { { .procname =3D "queues_max", - .data =3D &init_ipc_ns.mq_queues_max, - .maxlen =3D sizeof(int), .mode =3D 0644, - .proc_handler =3D proc_dointvec, + .type =3D SYSCTL_FIELD_UINT, + .data_offset =3D SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace, + mq_queues_max), }, { .procname =3D "msg_max", - .data =3D &init_ipc_ns.mq_msg_max, - .maxlen =3D sizeof(int), .mode =3D 0644, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D &msg_max_limit_min, - .extra2 =3D &msg_max_limit_max, + .type =3D SYSCTL_FIELD_UINT_MINMAX, + .data_offset =3D SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace, + mq_msg_max), + .uint_limits =3D { + .min =3D &msg_max_limit_min, + .max =3D &msg_max_limit_max, + }, }, { .procname =3D "msgsize_max", - .data =3D &init_ipc_ns.mq_msgsize_max, - .maxlen =3D sizeof(int), .mode =3D 0644, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D &msg_maxsize_limit_min, - .extra2 =3D &msg_maxsize_limit_max, + .type =3D SYSCTL_FIELD_UINT_MINMAX, + .data_offset =3D SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace, + mq_msgsize_max), + .uint_limits =3D { + .min =3D &msg_maxsize_limit_min, + .max =3D &msg_maxsize_limit_max, + }, }, { .procname =3D "msg_default", - .data =3D &init_ipc_ns.mq_msg_default, - .maxlen =3D sizeof(int), .mode =3D 0644, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D &msg_max_limit_min, - .extra2 =3D &msg_max_limit_max, + .type =3D SYSCTL_FIELD_UINT_MINMAX, + .data_offset =3D SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace, + mq_msg_default), + .uint_limits =3D { + .min =3D &msg_max_limit_min, + .max =3D &msg_max_limit_max, + }, }, { .procname =3D "msgsize_default", - .data =3D &init_ipc_ns.mq_msgsize_default, - .maxlen =3D sizeof(int), .mode =3D 0644, - .proc_handler =3D proc_dointvec_minmax, - .extra1 =3D &msg_maxsize_limit_min, - .extra2 =3D &msg_maxsize_limit_max, + .type =3D SYSCTL_FIELD_UINT_MINMAX, + .data_offset =3D SYSCTL_FIELD_UINT_OFFSET(struct ipc_namespace, + mq_msgsize_default), + .uint_limits =3D { + .min =3D &msg_maxsize_limit_min, + .max =3D &msg_maxsize_limit_max, + }, }, }; =20 @@ -116,39 +124,17 @@ static struct ctl_table_root set_root =3D { =20 bool setup_mq_sysctls(struct ipc_namespace *ns) { - struct ctl_table *tbl; + struct sysctl_context ctx =3D { + .type =3D SYSCTL_CONTEXT_IPC_NS, + .object_size =3D sizeof(*ns), + .ns.ipc_ns =3D ns, + }; =20 setup_sysctl_set(&ns->mq_set, &set_root, set_is_seen); =20 - tbl =3D kmemdup(mq_sysctls, sizeof(mq_sysctls), GFP_KERNEL); - if (tbl) { - int i; - - for (i =3D 0; i < ARRAY_SIZE(mq_sysctls); i++) { - if (tbl[i].data =3D=3D &init_ipc_ns.mq_queues_max) - tbl[i].data =3D &ns->mq_queues_max; - - else if (tbl[i].data =3D=3D &init_ipc_ns.mq_msg_max) - tbl[i].data =3D &ns->mq_msg_max; - - else if (tbl[i].data =3D=3D &init_ipc_ns.mq_msgsize_max) - tbl[i].data =3D &ns->mq_msgsize_max; - - else if (tbl[i].data =3D=3D &init_ipc_ns.mq_msg_default) - tbl[i].data =3D &ns->mq_msg_default; - - else if (tbl[i].data =3D=3D &init_ipc_ns.mq_msgsize_default) - tbl[i].data =3D &ns->mq_msgsize_default; - else - tbl[i].data =3D NULL; - } - - ns->mq_sysctls =3D __register_sysctl_table(&ns->mq_set, - "fs/mqueue", tbl, - ARRAY_SIZE(mq_sysctls)); - } + ns->mq_sysctls =3D register_sysctl_fields(&ns->mq_set, "fs/mqueue", + mq_sysctls, &ctx); if (!ns->mq_sysctls) { - kfree(tbl); retire_sysctl_set(&ns->mq_set); return false; } @@ -158,10 +144,6 @@ bool setup_mq_sysctls(struct ipc_namespace *ns) =20 void retire_mq_sysctls(struct ipc_namespace *ns) { - const struct ctl_table *tbl; - - tbl =3D ns->mq_sysctls->ctl_table_arg; unregister_sysctl_table(ns->mq_sysctls); retire_sysctl_set(&ns->mq_set); - kfree(tbl); } --=20 2.55.0 From nobody Fri Sep 25 21:02:15 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D1853486427; Mon, 21 Sep 2026 10:55:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988157; cv=none; b=XIX8dyjNwK6oR+UMrTpw0ETh0mATgSOsXqhDPUtMJ/rjoLYehQpcLg9weYGA68dQPgSOueFn589JBGqcZeCr1if/gNtW/fJksP2A+W6rd5cSTbfQKwMb7/5OCa3AVjnnz/M1IGwfCxvDCBTa3nrf8/v3jguK/mEclTj4E+CCxL0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789988157; c=relaxed/simple; bh=GsVNMZg9klMMQQwBPxPcPZRAqDUCP94pkiLbCWUKh4Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=DgRMKdGHH3iBjMS2NnuJHBSUwFjoZ3TTqKPtYFKnbGFmr0oAOK8lvuy35RX/qN7cSeVE3BeNduwEYvxXpe7itX4x3CtQGND0tixhtqDsygT8V/mLwSOOAanmyq4453ZyoSc0zWxmcmmdr9G2xgd4MQTGp0Hzqs1NuDhG6OLtWzI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=HaFUfDGN; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="HaFUfDGN" Received: by smtp.kernel.org (Postfix) with ESMTPSA id B99531F000FF; Mon, 21 Sep 2026 10:55:53 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789988155; bh=ig+mKn9ccsLpFcuetfalV5LhPHmt64olnrUCcYh1dlw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=HaFUfDGNvkvFuetIzT6BIoRjbjjROYpSZVYJYNRKKqQ7Pb/KA9nBNRtZhA7PW0W6U tLu0XMDK8wEt6ieR7PbtMvrWrm6HQ6TUo6R9cnVXRRA4A0mJioDrSxFM7y2b+iVgWw Zudw8czLLerzgu3O4Rop5xGgXYbXuRKCNnatZv1BGVLRumXdBHrv6ddKo+wS6pnTd3 PgQ1dMh0DdZJjuXDMoBuEAXe5CbOgpSDCL3A5iCvdJgZdpbNExv8g62d7jOxi5pLUH JKME1TS8Kzcuc3j8Y6m0H2gpKh1Gb9IcE2QEJ3mbwbldfhDG1QaePH/LOKjuOZT/4r etjSFYlSBueqw== From: Alexey Gladkov To: Joel Granados Cc: =?UTF-8?q?Ondrej=20Mosn=C3=A1=C4=8Dek?= , Andrew Morton , Kees Cook , Ryan Roberts , Serge Hallyn , "Eric W . Biederman" , LKML , linux-fsdevel@vger.kernel.org Subject: [PATCH v2 6/6] sysctl: use typed fields for ucount limits Date: Mon, 21 Sep 2026 12:54:53 +0200 Message-ID: <06aa655ff61a31a874b4eae682464055039f4953.1789987960.git.legion@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" User namespace sysctl registration clones the entire ctl_table and assigns ucount_max entries by table index. This allocates a table for every user namespace and makes the data mapping depend on the descriptor order. Use typed field offsets to associate each entry explicitly with its ucount_max element. The static descriptor array can then be shared by all user namespaces without allocating or rewriting a ctl_table copy. Signed-off-by: Alexey Gladkov --- kernel/ucount.c | 64 +++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/kernel/ucount.c b/kernel/ucount.c index ec8b1445e287..9f71ddefd18e 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -64,34 +64,38 @@ static struct ctl_table_root set_root =3D { static long ue_zero =3D 0; static long ue_int_max =3D INT_MAX; =20 -#define UCOUNT_ENTRY(name) \ +#define UCOUNT_ENTRY(name, ucount) \ { \ .procname =3D name, \ - .maxlen =3D sizeof(long), \ .mode =3D 0644, \ - .proc_handler =3D proc_doulongvec_minmax, \ - .extra1 =3D &ue_zero, \ - .extra2 =3D &ue_int_max, \ + .type =3D SYSCTL_FIELD_LONG_MINMAX, \ + .data_offset =3D SYSCTL_FIELD_LONG_OFFSET(struct user_namespace, \ + ucount_max[ucount]), \ + .long_limits =3D { \ + .min =3D &ue_zero, \ + .max =3D &ue_int_max, \ + } \ } -static const struct ctl_table user_table[] =3D { - UCOUNT_ENTRY("max_user_namespaces"), - UCOUNT_ENTRY("max_pid_namespaces"), - UCOUNT_ENTRY("max_uts_namespaces"), - UCOUNT_ENTRY("max_ipc_namespaces"), - UCOUNT_ENTRY("max_net_namespaces"), - UCOUNT_ENTRY("max_mnt_namespaces"), - UCOUNT_ENTRY("max_cgroup_namespaces"), - UCOUNT_ENTRY("max_time_namespaces"), +static const struct sysctl_field user_table[] =3D { + UCOUNT_ENTRY("max_user_namespaces", UCOUNT_USER_NAMESPACES), + UCOUNT_ENTRY("max_pid_namespaces", UCOUNT_PID_NAMESPACES), + UCOUNT_ENTRY("max_uts_namespaces", UCOUNT_UTS_NAMESPACES), + UCOUNT_ENTRY("max_ipc_namespaces", UCOUNT_IPC_NAMESPACES), + UCOUNT_ENTRY("max_net_namespaces", UCOUNT_NET_NAMESPACES), + UCOUNT_ENTRY("max_mnt_namespaces", UCOUNT_MNT_NAMESPACES), + UCOUNT_ENTRY("max_cgroup_namespaces", UCOUNT_CGROUP_NAMESPACES), + UCOUNT_ENTRY("max_time_namespaces", UCOUNT_TIME_NAMESPACES), #ifdef CONFIG_INOTIFY_USER - UCOUNT_ENTRY("max_inotify_instances"), - UCOUNT_ENTRY("max_inotify_watches"), + UCOUNT_ENTRY("max_inotify_instances", UCOUNT_INOTIFY_INSTANCES), + UCOUNT_ENTRY("max_inotify_watches", UCOUNT_INOTIFY_WATCHES), #endif #ifdef CONFIG_FANOTIFY - UCOUNT_ENTRY("max_fanotify_groups"), - UCOUNT_ENTRY("max_fanotify_marks"), + UCOUNT_ENTRY("max_fanotify_groups", UCOUNT_FANOTIFY_GROUPS), + UCOUNT_ENTRY("max_fanotify_marks", UCOUNT_FANOTIFY_MARKS), #endif #if IS_ENABLED(CONFIG_BINFMT_MISC) - UCOUNT_ENTRY("max_binfmt_misc_interpreters"), + UCOUNT_ENTRY("max_binfmt_misc_interpreters", + UCOUNT_BINFMT_MISC_INTERPRETERS), #endif }; #endif /* CONFIG_SYSCTL */ @@ -99,21 +103,17 @@ static const struct ctl_table user_table[] =3D { bool setup_userns_sysctls(struct user_namespace *ns) { #ifdef CONFIG_SYSCTL - struct ctl_table *tbl; + struct sysctl_context ctx =3D { + .type =3D SYSCTL_CONTEXT_USER_NS, + .object_size =3D sizeof(*ns), + .ns.user_ns =3D ns, + }; =20 BUILD_BUG_ON(ARRAY_SIZE(user_table) !=3D UCOUNT_COUNTS); setup_sysctl_set(&ns->set, &set_root, set_is_seen); - tbl =3D kmemdup(user_table, sizeof(user_table), GFP_KERNEL); - if (tbl) { - int i; - for (i =3D 0; i < UCOUNT_COUNTS; i++) { - tbl[i].data =3D &ns->ucount_max[i]; - } - ns->sysctls =3D __register_sysctl_table(&ns->set, "user", tbl, - ARRAY_SIZE(user_table)); - } + ns->sysctls =3D register_sysctl_fields(&ns->set, "user", + user_table, &ctx); if (!ns->sysctls) { - kfree(tbl); retire_sysctl_set(&ns->set); return false; } @@ -124,12 +124,8 @@ bool setup_userns_sysctls(struct user_namespace *ns) void retire_userns_sysctls(struct user_namespace *ns) { #ifdef CONFIG_SYSCTL - const struct ctl_table *tbl; - - tbl =3D ns->sysctls->ctl_table_arg; unregister_sysctl_table(ns->sysctls); retire_sysctl_set(&ns->set); - kfree(tbl); #endif } =20 --=20 2.55.0