* [PATCH RFC 1/4] sysctl: add a registration context to ctl_table_header
2026-09-24 14:34 [PATCH RFC 0/4] sysctl: add a registration context to share ctl_table arrays Joel Granados
@ 2026-09-24 14:34 ` Joel Granados
2026-09-24 14:34 ` [PATCH RFC 2/4] sysctl: Apply sysctl context when flag is active Joel Granados
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Joel Granados @ 2026-09-24 14:34 UTC (permalink / raw)
To: Kees Cook
Cc: linux-kernel, linux-fsdevel, Ondrej Mosnáček,
Andrew Morton, Ryan Roberts, Serge Hallyn, Eric W . Biederman,
Alexey Gladkov, Joel Granados
The sysctl context holds a pointer where the actual data (per-namespace
or per-device) is located. When an entry is marked CTL_TABLE_F_CTX_DATA
it is replaced just before calling proc_handler. Add flags to
each ctl_table entry to pass along CTL_TABLE_F_CTX_DATA.
SYSCTL_CTX() builds the context and fails the build unless the instance
and the template point at the same type.
This is a prep commit, no functional changes intended.
Signed-off-by: Joel Granados <joel.granados@kernel.org>
---
| 29 ++++++++++++++++++++++----
| 55 ++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 78 insertions(+), 6 deletions(-)
--git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 04a382178c657b5af201a838fa15703d9eea9c7b..fe32337892f9badc897a29e9176e66790003fb05 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -1143,10 +1143,18 @@ static int sysctl_check_table_array(const char *path, const struct ctl_table *ta
static int sysctl_check_table(const char *path, struct ctl_table_header *header)
{
const struct ctl_table *entry;
+ u8 flagged = 0;
int err = 0;
list_for_each_table_entry(entry, header) {
if (!entry->procname)
err |= sysctl_err(path, entry, "procname is null");
+ if (entry->flags & CTL_TABLE_F_CTX_DATA) {
+ if (!entry->data)
+ err |= sysctl_err(path, entry, "No data to resolve");
+ if (!header->ctx.inst || !header->ctx.tmpl)
+ err |= sysctl_err(path, entry, "No context to resolve against");
+ }
+ flagged |= entry->flags & CTL_TABLE_F_CTX;
if ((entry->proc_handler == proc_dostring) ||
(entry->proc_handler == proc_dobool) ||
(entry->proc_handler == proc_dointvec) ||
@@ -1173,6 +1181,12 @@ static int sysctl_check_table(const char *path, struct ctl_table_header *header)
err |= sysctl_err(path, entry, "bogus .mode 0%o",
entry->mode);
}
+ /* A context nobody resolves against is a forgotten flag. */
+ if (!flagged && (header->ctx.inst || header->ctx.tmpl)) {
+ pr_err("sysctl table check failed: %s context given but no entry is flagged\n",
+ path);
+ err = -EINVAL;
+ }
return err;
}
@@ -1324,7 +1338,7 @@ static struct ctl_dir *sysctl_mkdir_p(struct ctl_dir *dir, const char *path)
}
/**
- * __register_sysctl_table - register a leaf sysctl table
+ * __register_sysctl_table_ctx - register a leaf sysctl table
* @set: Sysctl tree to register on
* @path: The path to the directory the sysctl table is in.
*
@@ -1333,6 +1347,9 @@ static struct ctl_dir *sysctl_mkdir_p(struct ctl_dir *dir, const char *path)
* be a global or dynamically allocated by the caller and free'd later
* after sysctl unregistration.
* @table_size : The number of elements in table
+ * @ctx: instances that entries flagged CTL_TABLE_F_CTX_* resolve against, see
+ * struct sysctl_context. Copied, so it may be on stack. %NULL when no
+ * entry is flagged.
*
* Register a sysctl table hierarchy. @table should be a filled in ctl_table
* array.
@@ -1343,6 +1360,7 @@ static struct ctl_dir *sysctl_mkdir_p(struct ctl_dir *dir, const char *path)
* data - a pointer to data for use by proc_handler
* maxlen - the maximum size in bytes of the data
* mode - the file permissions for the /proc/sys file
+ * flags - CTL_TABLE_F_* bits naming members to resolve against @ctx
* type - Defines the target type (described in struct definition)
* proc_handler - the text handler routine (described below)
*
@@ -1366,9 +1384,10 @@ static struct ctl_dir *sysctl_mkdir_p(struct ctl_dir *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_header *__register_sysctl_table_ctx(
struct ctl_table_set *set,
- const char *path, const struct ctl_table *table, size_t table_size)
+ const char *path, const struct ctl_table *table, size_t table_size,
+ const struct sysctl_context *ctx)
{
struct ctl_table_root *root = set->dir.header.root;
struct ctl_table_header *header;
@@ -1382,6 +1401,8 @@ struct ctl_table_header *__register_sysctl_table(
node = (struct ctl_node *)(header + 1);
init_header(header, root, set, node, table, table_size);
+ if (ctx)
+ header->ctx = *ctx;
if (sysctl_check_table(path, header))
goto fail;
@@ -1427,7 +1448,7 @@ struct ctl_table_header *__register_sysctl_table(
* Register a sysctl table. @table should be a filled in ctl_table
* array. A completely 0 filled entry terminates the table.
*
- * See __register_sysctl_table for more details.
+ * See __register_sysctl_table_ctx for more details.
*/
struct ctl_table_header *register_sysctl_sz(const char *path, const struct ctl_table *table,
size_t table_size)
--git a/include/linux/sysctl.h b/include/linux/sysctl.h
index e5d7226ab6f5af34a33829883ad5d1b405e19c86..b2c57c530bdc9b3542375b761fec3e02ab38bc7f 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -22,6 +22,8 @@
#ifndef _LINUX_SYSCTL_H
#define _LINUX_SYSCTL_H
+#include <linux/bits.h>
+#include <linux/build_bug.h>
#include <linux/list.h>
#include <linux/rcupdate.h>
#include <linux/wait.h>
@@ -224,12 +226,50 @@ struct ctl_table {
void *data;
int maxlen;
umode_t mode;
+ u8 flags; /* CTL_TABLE_F_* */
proc_handler *proc_handler; /* Callback for text formatting */
struct ctl_table_poll *poll;
void *extra1;
void *extra2;
} __randomize_layout;
+/*
+ * ctl_table::flags. Resolve the corresponding member against the
+ * registration context instead of using it as it stands.
+ */
+#define CTL_TABLE_F_CTX_DATA BIT(0)
+#define CTL_TABLE_F_CTX CTL_TABLE_F_CTX_DATA
+
+/**
+ * struct sysctl_context - ctl_table specific context
+ * @inst: entry location
+ * @tmpl: struct template (used to calculate offset into @inst)
+ *
+ * @inst/@tmpl pair is used to overlay the ctl_table entry before calling
+ * proc_handler. This is relevant when struct members (like ->data) are
+ * somewhere different than the const static ctl_table array (think
+ * namespaces). Build it with SYSCTL_CTX() so the compiler checks that
+ * both point at the same type.
+ */
+struct sysctl_context {
+ void *inst;
+ const void *tmpl;
+};
+
+/**
+ * SYSCTL_CTX - build a struct sysctl_context
+ * @_inst: the instance this registration describes
+ * @_tmpl: the instance the table's data members name
+ *
+ * Fails to build unless @_inst and @_tmpl point at the same type.
+ */
+#define SYSCTL_CTX(_inst, _tmpl) \
+ ((struct sysctl_context){ \
+ .inst = (_inst) + \
+ BUILD_BUG_ON_ZERO(!__same_type(*(_inst), *(_tmpl))), \
+ .tmpl = (_tmpl), \
+ })
+
struct ctl_node {
struct rb_node node;
struct ctl_table_header *header;
@@ -244,6 +284,7 @@ struct ctl_node {
* 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_sysctl ->d_compare()"
+ * @ctx: instances given to __register_sysctl_table_ctx(), see struct sysctl_context
*
* @type: Enumeration to differentiate between ctl target types:
* type.SYSCTL_TABLE_TYPE_DEFAULT: ctl target with no special considerations
@@ -268,6 +309,7 @@ struct ctl_table_header {
struct ctl_dir *parent;
struct ctl_node *node;
struct hlist_head inodes; /* head for proc_inode->sysctl_inodes */
+ struct sysctl_context ctx;
enum {
SYSCTL_TABLE_TYPE_DEFAULT,
SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY,
@@ -305,9 +347,18 @@ extern void setup_sysctl_set(struct ctl_table_set *p,
int (*is_seen)(struct ctl_table_set *));
extern void retire_sysctl_set(struct ctl_table_set *set);
-struct ctl_table_header *__register_sysctl_table(
+struct ctl_table_header *__register_sysctl_table_ctx(
struct ctl_table_set *set,
- const char *path, const struct ctl_table *table, size_t table_size);
+ const char *path, const struct ctl_table *table, size_t table_size,
+ const struct sysctl_context *ctx);
+
+static inline 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_ctx(set, path, table, table_size, NULL);
+}
+
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);
--
2.50.1
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH RFC 2/4] sysctl: Apply sysctl context when flag is active
2026-09-24 14:34 [PATCH RFC 0/4] sysctl: add a registration context to share ctl_table arrays Joel Granados
2026-09-24 14:34 ` [PATCH RFC 1/4] sysctl: add a registration context to ctl_table_header Joel Granados
@ 2026-09-24 14:34 ` Joel Granados
2026-09-24 18:41 ` Alexey Gladkov
2026-09-24 14:34 ` [PATCH RFC 3/4] ipc: Use sysctl context to register ipc namespaces Joel Granados
2026-09-24 14:34 ` [PATCH RFC 4/4] mqueue: Use sysctl context to register mq_sysctls Joel Granados
3 siblings, 1 reply; 6+ messages in thread
From: Joel Granados @ 2026-09-24 14:34 UTC (permalink / raw)
To: Kees Cook
Cc: linux-kernel, linux-fsdevel, Ondrej Mosnáček,
Andrew Morton, Ryan Roberts, Serge Hallyn, Eric W . Biederman,
Alexey Gladkov, Joel Granados
Whenever CTL_TABLE_F_CTX_DATA is set on the entry, overlay the ->data
entry contained in inst on the entry to be passed to proc_handlers. No
table sets the flags yet, so there is no functional change.
Signed-off-by: Joel Granados <joel.granados@kernel.org>
---
| 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
--git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index fe32337892f9badc897a29e9176e66790003fb05..1ed9f4e1a17e28c5db1d07356390cbee968cb91f 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -550,12 +550,31 @@ static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry,
return err;
}
+/* Returns @entry itself when it is not flagged, else a resolved copy in @buf. */
+static const struct ctl_table *sysctl_apply_ctx(struct ctl_table_header *head,
+ const struct ctl_table *entry,
+ struct ctl_table *buf)
+{
+ ptrdiff_t off;
+
+ if (!(entry->flags & CTL_TABLE_F_CTX_DATA))
+ return entry;
+
+ off = (const char *)entry->data - (const char *)head->ctx.tmpl;
+
+ *buf = *entry;
+ buf->data = (char *)head->ctx.inst + off;
+
+ return buf;
+}
+
static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,
int write)
{
struct inode *inode = file_inode(iocb->ki_filp);
struct ctl_table_header *head = grab_header(inode);
const struct ctl_table *table = PROC_I(inode)->sysctl_entry;
+ struct ctl_table ctx_entry;
size_t count = iov_iter_count(iter);
char *kbuf;
ssize_t error;
@@ -567,6 +586,8 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,
* At this point we know that the sysctl was not unregistered
* and won't be until we finish.
*/
+ table = sysctl_apply_ctx(head, table, &ctx_entry);
+
error = -EPERM;
if (sysctl_perm(head, table, write ? MAY_WRITE : MAY_READ))
goto out;
@@ -797,6 +818,7 @@ static int proc_sys_permission(struct mnt_idmap *idmap,
*/
struct ctl_table_header *head;
const struct ctl_table *table;
+ struct ctl_table ctx_entry;
int error;
/* Executable files are not allowed under /proc/sys/ */
@@ -810,8 +832,11 @@ static int proc_sys_permission(struct mnt_idmap *idmap,
table = PROC_I(inode)->sysctl_entry;
if (!table) /* global root - r-xr-xr-x */
error = mask & MAY_WRITE ? -EACCES : 0;
- else /* Use the permissions on the sysctl table entry */
+ else {
+ /* ->permissions may inspect the entry, so resolve it first. */
+ table = sysctl_apply_ctx(head, table, &ctx_entry);
error = sysctl_perm(head, table, mask & ~MAY_NOT_BLOCK);
+ }
sysctl_head_finish(head);
return error;
--
2.50.1
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH RFC 2/4] sysctl: Apply sysctl context when flag is active
2026-09-24 14:34 ` [PATCH RFC 2/4] sysctl: Apply sysctl context when flag is active Joel Granados
@ 2026-09-24 18:41 ` Alexey Gladkov
0 siblings, 0 replies; 6+ messages in thread
From: Alexey Gladkov @ 2026-09-24 18:41 UTC (permalink / raw)
To: Joel Granados
Cc: Kees Cook, linux-kernel, linux-fsdevel,
Ondrej Mosnáček, Andrew Morton, Ryan Roberts,
Serge Hallyn, Eric W . Biederman
On Thu, Sep 24, 2026 at 04:34:10PM +0200, Joel Granados wrote:
> Whenever CTL_TABLE_F_CTX_DATA is set on the entry, overlay the ->data
> entry contained in inst on the entry to be passed to proc_handlers. No
> table sets the flags yet, so there is no functional change.
>
> Signed-off-by: Joel Granados <joel.granados@kernel.org>
> ---
> fs/proc/proc_sysctl.c | 27 ++++++++++++++++++++++++++-
> 1 file changed, 26 insertions(+), 1 deletion(-)
>
> diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
> index fe32337892f9badc897a29e9176e66790003fb05..1ed9f4e1a17e28c5db1d07356390cbee968cb91f 100644
> --- a/fs/proc/proc_sysctl.c
> +++ b/fs/proc/proc_sysctl.c
> @@ -550,12 +550,31 @@ static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry,
> return err;
> }
>
> +/* Returns @entry itself when it is not flagged, else a resolved copy in @buf. */
> +static const struct ctl_table *sysctl_apply_ctx(struct ctl_table_header *head,
> + const struct ctl_table *entry,
> + struct ctl_table *buf)
> +{
> + ptrdiff_t off;
> +
> + if (!(entry->flags & CTL_TABLE_F_CTX_DATA))
> + return entry;
> +
> + off = (const char *)entry->data - (const char *)head->ctx.tmpl;
> +
> + *buf = *entry;
> + buf->data = (char *)head->ctx.inst + off;
> +
> + return buf;
> +}
I had to create a more complex version of the context because,
unfortunately, yours approach doesn't cover all current use cases and does
not check the variable's type based on this offset.
In my RFC [1] I showed Linus, I included three illustrative examples (a
simple one, a worse one, and the ugliest one). In the net/mpls/af_mpls.c
we need to have netns and struct mpls_dev in same time [2]. A namespace
alone isn't enough for us. Unfortunately, this isn't the only place where
this approach is used.
Also, I really wanted to avoid dealing with void pointer arithmetic
as much as possible.
A long time ago, Linus had already pointed out to me that having
"void *ctl_data" was a bad solution [3]:
There is no reason to have some pseudo-generic "void *ctl_data" that
makes it ambiguous and allows for type confusion and isn't
self-documenting. I'd rather have a properly typed pointer that is
just initialized to NULL and is not always used or needed, but always
has a clear case for *what* it would be used for.
Yes, yes, we have f_private etc for things that are really very very
generic and have arbitrary users. But 'sysctl' is not that kind of
truly generic use.
Yes, my version still has similar code, but the offset is calculated at
compile time and a type check is made.
I like my version better because I've checked all the places where the
ctl_table list is cloned, and I know for sure that my version will be able
to handle all those use cases. And, you known, I wrote it :)
But seriously, don't get me wrong, I'm totally fine if you continue with
your version. The main thing is that it should cover all existing
use cases.
[1] https://lore.kernel.org/linux-fsdevel/cover.1788018958.git.legion@kernel.org/
[2] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/mpls/af_mpls.c#n1429
[3] https://lore.kernel.org/all/b0ccbb2489119f1f20c737cf1930c3a9c4e4243a.1644862280.git.legion@kernel.org/T/#m5d0b462c317f9cd0e772b5df60ec8ceba41dcac0
> +
> static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,
> int write)
> {
> struct inode *inode = file_inode(iocb->ki_filp);
> struct ctl_table_header *head = grab_header(inode);
> const struct ctl_table *table = PROC_I(inode)->sysctl_entry;
> + struct ctl_table ctx_entry;
> size_t count = iov_iter_count(iter);
> char *kbuf;
> ssize_t error;
> @@ -567,6 +586,8 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,
> * At this point we know that the sysctl was not unregistered
> * and won't be until we finish.
> */
> + table = sysctl_apply_ctx(head, table, &ctx_entry);
> +
> error = -EPERM;
> if (sysctl_perm(head, table, write ? MAY_WRITE : MAY_READ))
> goto out;
> @@ -797,6 +818,7 @@ static int proc_sys_permission(struct mnt_idmap *idmap,
> */
> struct ctl_table_header *head;
> const struct ctl_table *table;
> + struct ctl_table ctx_entry;
> int error;
>
> /* Executable files are not allowed under /proc/sys/ */
> @@ -810,8 +832,11 @@ static int proc_sys_permission(struct mnt_idmap *idmap,
> table = PROC_I(inode)->sysctl_entry;
> if (!table) /* global root - r-xr-xr-x */
> error = mask & MAY_WRITE ? -EACCES : 0;
> - else /* Use the permissions on the sysctl table entry */
> + else {
> + /* ->permissions may inspect the entry, so resolve it first. */
> + table = sysctl_apply_ctx(head, table, &ctx_entry);
> error = sysctl_perm(head, table, mask & ~MAY_NOT_BLOCK);
> + }
>
> sysctl_head_finish(head);
> return error;
>
> --
> 2.50.1
>
>
--
Rgrds, legion
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH RFC 3/4] ipc: Use sysctl context to register ipc namespaces
2026-09-24 14:34 [PATCH RFC 0/4] sysctl: add a registration context to share ctl_table arrays Joel Granados
2026-09-24 14:34 ` [PATCH RFC 1/4] sysctl: add a registration context to ctl_table_header Joel Granados
2026-09-24 14:34 ` [PATCH RFC 2/4] sysctl: Apply sysctl context when flag is active Joel Granados
@ 2026-09-24 14:34 ` Joel Granados
2026-09-24 14:34 ` [PATCH RFC 4/4] mqueue: Use sysctl context to register mq_sysctls Joel Granados
3 siblings, 0 replies; 6+ messages in thread
From: Joel Granados @ 2026-09-24 14:34 UTC (permalink / raw)
To: Kees Cook
Cc: linux-kernel, linux-fsdevel, Ondrej Mosnáček,
Andrew Morton, Ryan Roberts, Serge Hallyn, Eric W . Biederman,
Alexey Gladkov, Joel Granados
Replace the ipc_sysctls array copy with a sysctl context kept in the
ctl_table_header. Flag each entry with CTL_TABLE_F_CTX_DATA to overlay
the ctl_table entry before calling the proc_handler. auto_msgmni is left
unflagged. Its NULL ->data is handled by its custom proc_handler.
proc_ipc_{dointvec_minmax_orphans,sem_dointvec} and ipc_permissions are
handed a resolved entry, so they keep seeing the namespace entry.
Signed-off-by: Joel Granados <joel.granados@kernel.org>
---
| 68 +++++++++++++-------------------------------------------
1 file changed, 16 insertions(+), 52 deletions(-)
--git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c
index d038d944257f60b3a35b72492c73c41774a73da4..5d73487bb81c027da1ec2b1fa18dfde8242a223f 100644
--- a/ipc/ipc_sysctl.c
+++ b/ipc/ipc_sysctl.c
@@ -13,7 +13,6 @@
#include <linux/capability.h>
#include <linux/ipc_namespace.h>
#include <linux/msg.h>
-#include <linux/slab.h>
#include <linux/cred.h>
#include "util.h"
@@ -79,6 +78,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.shm_ctlmax,
.maxlen = sizeof(init_ipc_ns.shm_ctlmax),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_doulongvec_minmax,
},
{
@@ -86,6 +86,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.shm_ctlall,
.maxlen = sizeof(init_ipc_ns.shm_ctlall),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_doulongvec_minmax,
},
{
@@ -93,6 +94,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.shm_ctlmni,
.maxlen = sizeof(init_ipc_ns.shm_ctlmni),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &ipc_mni,
@@ -102,6 +104,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.shm_rmid_forced,
.maxlen = sizeof(init_ipc_ns.shm_rmid_forced),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_ipc_dointvec_minmax_orphans,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
@@ -111,6 +114,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.msg_ctlmax,
.maxlen = sizeof(init_ipc_ns.msg_ctlmax),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
@@ -120,6 +124,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.msg_ctlmni,
.maxlen = sizeof(init_ipc_ns.msg_ctlmni),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &ipc_mni,
@@ -138,6 +143,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.msg_ctlmnb,
.maxlen = sizeof(init_ipc_ns.msg_ctlmnb),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
@@ -147,6 +153,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.sem_ctls,
.maxlen = 4*sizeof(int),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_ipc_sem_dointvec,
},
#ifdef CONFIG_CHECKPOINT_RESTORE
@@ -155,6 +162,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.ids[IPC_SEM_IDS].next_id,
.maxlen = sizeof(init_ipc_ns.ids[IPC_SEM_IDS].next_id),
.mode = 0444,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
@@ -164,6 +172,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.ids[IPC_MSG_IDS].next_id,
.maxlen = sizeof(init_ipc_ns.ids[IPC_MSG_IDS].next_id),
.mode = 0444,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
@@ -173,6 +182,7 @@ static const struct ctl_table ipc_sysctls[] = {
.data = &init_ipc_ns.ids[IPC_SHM_IDS].next_id,
.maxlen = sizeof(init_ipc_ns.ids[IPC_SHM_IDS].next_id),
.mode = 0444,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
@@ -244,57 +254,15 @@ static struct ctl_table_root set_root = {
bool setup_ipc_sysctls(struct ipc_namespace *ns)
{
- struct ctl_table *tbl;
+ struct sysctl_context ctx = SYSCTL_CTX(ns, &init_ipc_ns);
setup_sysctl_set(&ns->ipc_set, &set_root, set_is_seen);
- tbl = kmemdup(ipc_sysctls, sizeof(ipc_sysctls), GFP_KERNEL);
- if (tbl) {
- int i;
-
- for (i = 0; i < ARRAY_SIZE(ipc_sysctls); i++) {
- if (tbl[i].data == &init_ipc_ns.shm_ctlmax)
- tbl[i].data = &ns->shm_ctlmax;
-
- else if (tbl[i].data == &init_ipc_ns.shm_ctlall)
- tbl[i].data = &ns->shm_ctlall;
-
- else if (tbl[i].data == &init_ipc_ns.shm_ctlmni)
- tbl[i].data = &ns->shm_ctlmni;
-
- else if (tbl[i].data == &init_ipc_ns.shm_rmid_forced)
- tbl[i].data = &ns->shm_rmid_forced;
-
- else if (tbl[i].data == &init_ipc_ns.msg_ctlmax)
- tbl[i].data = &ns->msg_ctlmax;
-
- else if (tbl[i].data == &init_ipc_ns.msg_ctlmni)
- tbl[i].data = &ns->msg_ctlmni;
-
- else if (tbl[i].data == &init_ipc_ns.msg_ctlmnb)
- tbl[i].data = &ns->msg_ctlmnb;
-
- else if (tbl[i].data == &init_ipc_ns.sem_ctls)
- tbl[i].data = &ns->sem_ctls;
-#ifdef CONFIG_CHECKPOINT_RESTORE
- else if (tbl[i].data == &init_ipc_ns.ids[IPC_SEM_IDS].next_id)
- tbl[i].data = &ns->ids[IPC_SEM_IDS].next_id;
-
- else if (tbl[i].data == &init_ipc_ns.ids[IPC_MSG_IDS].next_id)
- tbl[i].data = &ns->ids[IPC_MSG_IDS].next_id;
-
- else if (tbl[i].data == &init_ipc_ns.ids[IPC_SHM_IDS].next_id)
- tbl[i].data = &ns->ids[IPC_SHM_IDS].next_id;
-#endif
- else
- tbl[i].data = NULL;
- }
-
- ns->ipc_sysctls = __register_sysctl_table(&ns->ipc_set, "kernel", tbl,
- ARRAY_SIZE(ipc_sysctls));
- }
+ ns->ipc_sysctls = __register_sysctl_table_ctx(&ns->ipc_set, "kernel",
+ ipc_sysctls,
+ ARRAY_SIZE(ipc_sysctls),
+ &ctx);
if (!ns->ipc_sysctls) {
- kfree(tbl);
retire_sysctl_set(&ns->ipc_set);
return false;
}
@@ -304,12 +272,8 @@ bool setup_ipc_sysctls(struct ipc_namespace *ns)
void retire_ipc_sysctls(struct ipc_namespace *ns)
{
- const struct ctl_table *tbl;
-
- tbl = ns->ipc_sysctls->ctl_table_arg;
unregister_sysctl_table(ns->ipc_sysctls);
retire_sysctl_set(&ns->ipc_set);
- kfree(tbl);
}
static int __init ipc_sysctl_init(void)
--
2.50.1
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH RFC 4/4] mqueue: Use sysctl context to register mq_sysctls
2026-09-24 14:34 [PATCH RFC 0/4] sysctl: add a registration context to share ctl_table arrays Joel Granados
` (2 preceding siblings ...)
2026-09-24 14:34 ` [PATCH RFC 3/4] ipc: Use sysctl context to register ipc namespaces Joel Granados
@ 2026-09-24 14:34 ` Joel Granados
3 siblings, 0 replies; 6+ messages in thread
From: Joel Granados @ 2026-09-24 14:34 UTC (permalink / raw)
To: Kees Cook
Cc: linux-kernel, linux-fsdevel, Ondrej Mosnáček,
Andrew Morton, Ryan Roberts, Serge Hallyn, Eric W . Biederman,
Alexey Gladkov, Joel Granados
Replace the mq_sysctls array copy with a sysctl context kept in the
ctl_table_header. Flag each entry with CTL_TABLE_F_CTX_DATA to overlay
the ctl_table entry before calling the proc_handler.
Signed-off-by: Joel Granados <joel.granados@kernel.org>
---
| 44 ++++++++++----------------------------------
1 file changed, 10 insertions(+), 34 deletions(-)
--git a/ipc/mq_sysctl.c b/ipc/mq_sysctl.c
index 0dd12e1c9f53ef07092ca3b638335a438d273a8c..9919e2d3a11a48a3e84c6c42d564e71dd60711da 100644
--- a/ipc/mq_sysctl.c
+++ b/ipc/mq_sysctl.c
@@ -11,7 +11,6 @@
#include <linux/stat.h>
#include <linux/capability.h>
-#include <linux/slab.h>
#include <linux/cred.h>
static int msg_max_limit_min = MIN_MSGMAX;
@@ -26,6 +25,7 @@ static const struct ctl_table mq_sysctls[] = {
.data = &init_ipc_ns.mq_queues_max,
.maxlen = sizeof(int),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec,
},
{
@@ -33,6 +33,7 @@ static const struct ctl_table mq_sysctls[] = {
.data = &init_ipc_ns.mq_msg_max,
.maxlen = sizeof(int),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = &msg_max_limit_min,
.extra2 = &msg_max_limit_max,
@@ -42,6 +43,7 @@ static const struct ctl_table mq_sysctls[] = {
.data = &init_ipc_ns.mq_msgsize_max,
.maxlen = sizeof(int),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = &msg_maxsize_limit_min,
.extra2 = &msg_maxsize_limit_max,
@@ -51,6 +53,7 @@ static const struct ctl_table mq_sysctls[] = {
.data = &init_ipc_ns.mq_msg_default,
.maxlen = sizeof(int),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = &msg_max_limit_min,
.extra2 = &msg_max_limit_max,
@@ -60,6 +63,7 @@ static const struct ctl_table mq_sysctls[] = {
.data = &init_ipc_ns.mq_msgsize_default,
.maxlen = sizeof(int),
.mode = 0644,
+ .flags = CTL_TABLE_F_CTX_DATA,
.proc_handler = proc_dointvec_minmax,
.extra1 = &msg_maxsize_limit_min,
.extra2 = &msg_maxsize_limit_max,
@@ -116,39 +120,15 @@ static struct ctl_table_root set_root = {
bool setup_mq_sysctls(struct ipc_namespace *ns)
{
- struct ctl_table *tbl;
+ struct sysctl_context ctx = SYSCTL_CTX(ns, &init_ipc_ns);
setup_sysctl_set(&ns->mq_set, &set_root, set_is_seen);
- tbl = kmemdup(mq_sysctls, sizeof(mq_sysctls), GFP_KERNEL);
- if (tbl) {
- int i;
-
- for (i = 0; i < ARRAY_SIZE(mq_sysctls); i++) {
- if (tbl[i].data == &init_ipc_ns.mq_queues_max)
- tbl[i].data = &ns->mq_queues_max;
-
- else if (tbl[i].data == &init_ipc_ns.mq_msg_max)
- tbl[i].data = &ns->mq_msg_max;
-
- else if (tbl[i].data == &init_ipc_ns.mq_msgsize_max)
- tbl[i].data = &ns->mq_msgsize_max;
-
- else if (tbl[i].data == &init_ipc_ns.mq_msg_default)
- tbl[i].data = &ns->mq_msg_default;
-
- else if (tbl[i].data == &init_ipc_ns.mq_msgsize_default)
- tbl[i].data = &ns->mq_msgsize_default;
- else
- tbl[i].data = NULL;
- }
-
- ns->mq_sysctls = __register_sysctl_table(&ns->mq_set,
- "fs/mqueue", tbl,
- ARRAY_SIZE(mq_sysctls));
- }
+ ns->mq_sysctls = __register_sysctl_table_ctx(&ns->mq_set, "fs/mqueue",
+ mq_sysctls,
+ ARRAY_SIZE(mq_sysctls),
+ &ctx);
if (!ns->mq_sysctls) {
- kfree(tbl);
retire_sysctl_set(&ns->mq_set);
return false;
}
@@ -158,10 +138,6 @@ bool setup_mq_sysctls(struct ipc_namespace *ns)
void retire_mq_sysctls(struct ipc_namespace *ns)
{
- const struct ctl_table *tbl;
-
- tbl = ns->mq_sysctls->ctl_table_arg;
unregister_sysctl_table(ns->mq_sysctls);
retire_sysctl_set(&ns->mq_set);
- kfree(tbl);
}
--
2.50.1
^ permalink raw reply [flat|nested] 6+ messages in thread