Adapt the internal and external APIs of the sysctl core to handle read-only instances of "struct ctl_table".
Patch 1: Bugfix for the sysctl core, the bug can be reliably triggered with the series applied Patch 2: Trivial preparation commit for the sysctl BPF hook Patch 3: Adapts the internal sysctl APIs Patch 4: Adapts the external sysctl APIs Patch 5: Constifies the sysctl internal tables as proof that it works Patch 6: Updates scripts/const_structs.checkpatch for "struct ctl_table"
Motivation ==========
Moving structures containing function pointers into unmodifiable .rodata prevents attackers or bugs from corrupting and diverting those pointers.
Also the "struct ctl_table" exposed by the sysctl core were never meant to be mutated by users.
For this goal changes to both the sysctl core and "const" qualifiers for various sysctl APIs are necessary.
Full Process ============
* Drop ctl_table modifications from the sysctl core ([0], in mainline) * Constify arguments to ctl_table_root::{set_ownership,permissions} ([1], in mainline) * Migrate users of "ctl_table_header::ctl_table_arg" to "const". (in mainline) * Afterwards convert "ctl_table_header::ctl_table_arg" itself to const. (in mainline) * Prepare helpers used to implement proc_handlers throughout the tree to use "const struct ctl_table *". ([2], in mainline) * Afterwards switch over all proc_handlers callbacks to use "const struct ctl_table *" in one commit. (in mainline) * Switch over the internals of the sysctl core to "const struct ctl_table *" (this series) * Switch include/linux/sysctl.h to "const struct ctl_table *" (this series) * Transition instances of "struct ctl_table" through the tree to const (to be done)
This series is meant to be applied through the sysctl tree.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- Changes in v2: - Avoid spurious permanent empty tables (patch 1) - Link to v1: https://lore.kernel.org/r/20240729-sysctl-const-api-v1-0-ca628c7a942c@weisss...
--- Thomas Weißschuh (6): sysctl: avoid spurious permanent empty tables bpf: Constify ctl_table argument of filter function sysctl: move internal interfaces to const struct ctl_table sysctl: allow registration of const struct ctl_table sysctl: make internal ctl_tables const const_structs.checkpatch: add ctl_table
fs/proc/internal.h | 2 +- fs/proc/proc_sysctl.c | 100 +++++++++++++++++++++------------------ include/linux/bpf-cgroup.h | 2 +- include/linux/sysctl.h | 12 ++--- kernel/bpf/cgroup.c | 2 +- scripts/const_structs.checkpatch | 1 + 6 files changed, 63 insertions(+), 56 deletions(-) --- base-commit: 8400291e289ee6b2bf9779ff1c83a291501f017b change-id: 20240729-sysctl-const-api-73954f3d62c1
Best regards,
The test if a table is a permanently empty one, inspects the address of the registered ctl_table argument. However as sysctl_mount_point is an empty array and does not occupy and space it can end up sharing an address with another object in memory. If that other object itself is a "struct ctl_table" then registering that table will fail as it's incorrectly recognized as permanently empty.
Avoid this issue by adding a dummy element to the array so that is not empty anymore. Explicitly register the table with zero elements as otherwise the dummy element would be recognized as a sentinel element which would lead to a runtime warning from the sysctl core.
While the issue seems not being encountered at this time, this seems mostly to be due to luck. Also a future change, constifying sysctl_mount_point and root_table, can reliably trigger this issue on clang 18.
Given that empty arrays are non-standard in the first place it seems prudent to avoid them if possible.
Fixes: 4a7b29f65094 ("sysctl: move sysctl type to ctl_table_header") Fixes: a35dd3a786f5 ("sysctl: drop now unnecessary out-of-bounds check") Cc: stable@vger.kernel.org Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- fs/proc/proc_sysctl.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index 9553e77c9d31..d11ebc055ce0 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -29,8 +29,13 @@ static const struct inode_operations proc_sys_inode_operations; static const struct file_operations proc_sys_dir_file_operations; static const struct inode_operations proc_sys_dir_operations;
-/* Support for permanently empty directories */ -static struct ctl_table sysctl_mount_point[] = { }; +/* + * Support for permanently empty directories. + * Must be non-empty to avoid sharing an address with other tables. + */ +static struct ctl_table sysctl_mount_point[] = { + { } +};
/** * register_sysctl_mount_point() - registers a sysctl mount point @@ -42,7 +47,7 @@ static struct ctl_table sysctl_mount_point[] = { }; */ struct ctl_table_header *register_sysctl_mount_point(const char *path) { - return register_sysctl(path, sysctl_mount_point); + return register_sysctl_sz(path, sysctl_mount_point, 0); } EXPORT_SYMBOL(register_sysctl_mount_point);
Hi Joel,
On 2024-08-05 11:39:35+0000, Thomas Weißschuh wrote:
The test if a table is a permanently empty one, inspects the address of the registered ctl_table argument. However as sysctl_mount_point is an empty array and does not occupy and space it can end up sharing an address with another object in memory. If that other object itself is a "struct ctl_table" then registering that table will fail as it's incorrectly recognized as permanently empty.
Avoid this issue by adding a dummy element to the array so that is not empty anymore. Explicitly register the table with zero elements as otherwise the dummy element would be recognized as a sentinel element which would lead to a runtime warning from the sysctl core.
While the issue seems not being encountered at this time, this seems mostly to be due to luck. Also a future change, constifying sysctl_mount_point and root_table, can reliably trigger this issue on clang 18.
Given that empty arrays are non-standard in the first place it seems prudent to avoid them if possible.
Fixes: 4a7b29f65094 ("sysctl: move sysctl type to ctl_table_header") Fixes: a35dd3a786f5 ("sysctl: drop now unnecessary out-of-bounds check") Cc: stable@vger.kernel.org Signed-off-by: Thomas Weißschuh linux@weissschuh.net
Any updates on this? I fear it can theoretically also happen on v6.11.
fs/proc/proc_sysctl.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index 9553e77c9d31..d11ebc055ce0 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -29,8 +29,13 @@ static const struct inode_operations proc_sys_inode_operations; static const struct file_operations proc_sys_dir_file_operations; static const struct inode_operations proc_sys_dir_operations; -/* Support for permanently empty directories */ -static struct ctl_table sysctl_mount_point[] = { }; +/*
- Support for permanently empty directories.
- Must be non-empty to avoid sharing an address with other tables.
- */
+static struct ctl_table sysctl_mount_point[] = {
- { }
+}; /**
- register_sysctl_mount_point() - registers a sysctl mount point
@@ -42,7 +47,7 @@ static struct ctl_table sysctl_mount_point[] = { }; */ struct ctl_table_header *register_sysctl_mount_point(const char *path) {
- return register_sysctl(path, sysctl_mount_point);
- return register_sysctl_sz(path, sysctl_mount_point, 0);
} EXPORT_SYMBOL(register_sysctl_mount_point);
-- 2.46.0
On Sat, Aug 24, 2024 at 08:05:08PM +0200, Thomas Weißschuh wrote:
Hi Joel,
On 2024-08-05 11:39:35+0000, Thomas Weißschuh wrote:
The test if a table is a permanently empty one, inspects the address of the registered ctl_table argument. However as sysctl_mount_point is an empty array and does not occupy and space it can end up sharing an address with another object in memory. If that other object itself is a "struct ctl_table" then registering that table will fail as it's incorrectly recognized as permanently empty.
Avoid this issue by adding a dummy element to the array so that is not empty anymore. Explicitly register the table with zero elements as otherwise the dummy element would be recognized as a sentinel element which would lead to a runtime warning from the sysctl core.
While the issue seems not being encountered at this time, this seems mostly to be due to luck. Also a future change, constifying sysctl_mount_point and root_table, can reliably trigger this issue on clang 18.
Given that empty arrays are non-standard in the first place it seems prudent to avoid them if possible.
Fixes: 4a7b29f65094 ("sysctl: move sysctl type to ctl_table_header") Fixes: a35dd3a786f5 ("sysctl: drop now unnecessary out-of-bounds check") Cc: stable@vger.kernel.org Signed-off-by: Thomas Weißschuh linux@weissschuh.net
Any updates on this? I fear it can theoretically also happen on v6.11.
This is already in next and will probably make it for v6.11. The "fixed" tag will make is so it is ported to 6.10.
Best
On Mon, Aug 05, 2024 at 11:39:34AM +0200, Thomas Weißschuh wrote:
Adapt the internal and external APIs of the sysctl core to handle read-only instances of "struct ctl_table".
Finally getting around to this. Testing for this has been done on sysctl-testing base:v6.11-rc6 and now on base:v6.12-rc2. Putting this in sysctl-next so it will get further testing on its way to v6.13. First patch (bugfix) will be ignored as it is already upstream.
Best
Patch 1: Bugfix for the sysctl core, the bug can be reliably triggered with the series applied Patch 2: Trivial preparation commit for the sysctl BPF hook Patch 3: Adapts the internal sysctl APIs Patch 4: Adapts the external sysctl APIs Patch 5: Constifies the sysctl internal tables as proof that it works Patch 6: Updates scripts/const_structs.checkpatch for "struct ctl_table"
Motivation
Moving structures containing function pointers into unmodifiable .rodata prevents attackers or bugs from corrupting and diverting those pointers.
Also the "struct ctl_table" exposed by the sysctl core were never meant to be mutated by users.
For this goal changes to both the sysctl core and "const" qualifiers for various sysctl APIs are necessary.
Full Process
- Drop ctl_table modifications from the sysctl core ([0], in mainline)
- Constify arguments to ctl_table_root::{set_ownership,permissions} ([1], in mainline)
- Migrate users of "ctl_table_header::ctl_table_arg" to "const". (in mainline)
- Afterwards convert "ctl_table_header::ctl_table_arg" itself to const. (in mainline)
- Prepare helpers used to implement proc_handlers throughout the tree to use "const struct ctl_table *". ([2], in mainline)
- Afterwards switch over all proc_handlers callbacks to use "const struct ctl_table *" in one commit. (in mainline)
- Switch over the internals of the sysctl core to "const struct ctl_table *" (this series)
- Switch include/linux/sysctl.h to "const struct ctl_table *" (this series)
- Transition instances of "struct ctl_table" through the tree to const (to be done)
This series is meant to be applied through the sysctl tree.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net
Changes in v2:
- Avoid spurious permanent empty tables (patch 1)
- Link to v1: https://lore.kernel.org/r/20240729-sysctl-const-api-v1-0-ca628c7a942c@weisss...
Thomas Weißschuh (6): sysctl: avoid spurious permanent empty tables bpf: Constify ctl_table argument of filter function sysctl: move internal interfaces to const struct ctl_table sysctl: allow registration of const struct ctl_table sysctl: make internal ctl_tables const const_structs.checkpatch: add ctl_table
fs/proc/internal.h | 2 +- fs/proc/proc_sysctl.c | 100 +++++++++++++++++++++------------------ include/linux/bpf-cgroup.h | 2 +- include/linux/sysctl.h | 12 ++--- kernel/bpf/cgroup.c | 2 +- scripts/const_structs.checkpatch | 1 + 6 files changed, 63 insertions(+), 56 deletions(-)
base-commit: 8400291e289ee6b2bf9779ff1c83a291501f017b change-id: 20240729-sysctl-const-api-73954f3d62c1
Best regards,
Thomas Weißschuh linux@weissschuh.net
linux-stable-mirror@lists.linaro.org