Skip to content

Commit b5ffbd1

Browse files
Wen YangJoelgranados
authored andcommitted
sysctl: move the extra1/2 boundary check of u8 to sysctl_check_table_array
Move boundary checking for proc_dou8ved_minmax into module loading, thereby reporting errors in advance. And add a kunit test case ensuring the boundary check is done correctly. The boundary check in proc_dou8vec_minmax done to the extra elements in the ctl_table struct is currently performed at runtime. This allows buggy kernel modules to be loaded normally without any errors only to fail when used. This is a buggy example module: #include <linux/kernel.h> #include <linux/module.h> #include <linux/sysctl.h> static struct ctl_table_header *_table_header = NULL; static unsigned char _data = 0; struct ctl_table table[] = { { .procname = "foo", .data = &_data, .maxlen = sizeof(u8), .mode = 0644, .proc_handler = proc_dou8vec_minmax, .extra1 = SYSCTL_ZERO, .extra2 = SYSCTL_ONE_THOUSAND, }, }; static int init_demo(void) { _table_header = register_sysctl("kernel", table); if (!_table_header) return -ENOMEM; return 0; } module_init(init_demo); MODULE_LICENSE("GPL"); And this is the result: # insmod test.ko # cat /proc/sys/kernel/foo cat: /proc/sys/kernel/foo: Invalid argument Suggested-by: Joel Granados <[email protected]> Signed-off-by: Wen Yang <[email protected]> Cc: Luis Chamberlain <[email protected]> Cc: Kees Cook <[email protected]> Cc: Joel Granados <[email protected]> Cc: Eric W. Biederman <[email protected]> Cc: Christian Brauner <[email protected]> Cc: [email protected] Reviewed-by: Joel Granados <[email protected]> Signed-off-by: Joel Granados <[email protected]>
1 parent 98ca62b commit b5ffbd1

File tree

3 files changed

+65
-8
lines changed

3 files changed

+65
-8
lines changed

fs/proc/proc_sysctl.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,7 @@ static int sysctl_err(const char *path, struct ctl_table *table, char *fmt, ...)
10911091

10921092
static int sysctl_check_table_array(const char *path, struct ctl_table *table)
10931093
{
1094+
unsigned int extra;
10941095
int err = 0;
10951096

10961097
if ((table->proc_handler == proc_douintvec) ||
@@ -1102,6 +1103,19 @@ static int sysctl_check_table_array(const char *path, struct ctl_table *table)
11021103
if (table->proc_handler == proc_dou8vec_minmax) {
11031104
if (table->maxlen != sizeof(u8))
11041105
err |= sysctl_err(path, table, "array not allowed");
1106+
1107+
if (table->extra1) {
1108+
extra = *(unsigned int *) table->extra1;
1109+
if (extra > 255U)
1110+
err |= sysctl_err(path, table,
1111+
"range value too large for proc_dou8vec_minmax");
1112+
}
1113+
if (table->extra2) {
1114+
extra = *(unsigned int *) table->extra2;
1115+
if (extra > 255U)
1116+
err |= sysctl_err(path, table,
1117+
"range value too large for proc_dou8vec_minmax");
1118+
}
11051119
}
11061120

11071121
if (table->proc_handler == proc_dobool) {

kernel/sysctl-test.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,54 @@ static void sysctl_test_api_dointvec_write_single_greater_int_max(
367367
KUNIT_EXPECT_EQ(test, 0, *((int *)table.data));
368368
}
369369

370+
/*
371+
* Test that registering an invalid extra value is not allowed.
372+
*/
373+
static void sysctl_test_register_sysctl_sz_invalid_extra_value(
374+
struct kunit *test)
375+
{
376+
unsigned char data = 0;
377+
struct ctl_table table_foo[] = {
378+
{
379+
.procname = "foo",
380+
.data = &data,
381+
.maxlen = sizeof(u8),
382+
.mode = 0644,
383+
.proc_handler = proc_dou8vec_minmax,
384+
.extra1 = SYSCTL_FOUR,
385+
.extra2 = SYSCTL_ONE_THOUSAND,
386+
},
387+
};
388+
389+
struct ctl_table table_bar[] = {
390+
{
391+
.procname = "bar",
392+
.data = &data,
393+
.maxlen = sizeof(u8),
394+
.mode = 0644,
395+
.proc_handler = proc_dou8vec_minmax,
396+
.extra1 = SYSCTL_NEG_ONE,
397+
.extra2 = SYSCTL_ONE_HUNDRED,
398+
},
399+
};
400+
401+
struct ctl_table table_qux[] = {
402+
{
403+
.procname = "qux",
404+
.data = &data,
405+
.maxlen = sizeof(u8),
406+
.mode = 0644,
407+
.proc_handler = proc_dou8vec_minmax,
408+
.extra1 = SYSCTL_ZERO,
409+
.extra2 = SYSCTL_TWO_HUNDRED,
410+
},
411+
};
412+
413+
KUNIT_EXPECT_NULL(test, register_sysctl("foo", table_foo));
414+
KUNIT_EXPECT_NULL(test, register_sysctl("foo", table_bar));
415+
KUNIT_EXPECT_NOT_NULL(test, register_sysctl("foo", table_qux));
416+
}
417+
370418
static struct kunit_case sysctl_test_cases[] = {
371419
KUNIT_CASE(sysctl_test_api_dointvec_null_tbl_data),
372420
KUNIT_CASE(sysctl_test_api_dointvec_table_maxlen_unset),
@@ -378,6 +426,7 @@ static struct kunit_case sysctl_test_cases[] = {
378426
KUNIT_CASE(sysctl_test_dointvec_write_happy_single_negative),
379427
KUNIT_CASE(sysctl_test_api_dointvec_write_single_less_int_min),
380428
KUNIT_CASE(sysctl_test_api_dointvec_write_single_greater_int_max),
429+
KUNIT_CASE(sysctl_test_register_sysctl_sz_invalid_extra_value),
381430
{}
382431
};
383432

kernel/sysctl.c

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -977,16 +977,10 @@ int proc_dou8vec_minmax(struct ctl_table *table, int write,
977977
if (table->maxlen != sizeof(u8))
978978
return -EINVAL;
979979

980-
if (table->extra1) {
980+
if (table->extra1)
981981
min = *(unsigned int *) table->extra1;
982-
if (min > 255U)
983-
return -EINVAL;
984-
}
985-
if (table->extra2) {
982+
if (table->extra2)
986983
max = *(unsigned int *) table->extra2;
987-
if (max > 255U)
988-
return -EINVAL;
989-
}
990984

991985
tmp = *table;
992986

0 commit comments

Comments
 (0)