Hi,
This fixes my sysfs module sections refactoring to take into account the case where the output buffer is not PAGE_SIZE. :( Thanks to 0day and trinity for noticing.
I'll let this sit in -next for a few days and then send it to Linus.
-Kees
Kees Cook (2): module: Correctly truncate sysfs sections output selftests: splice: Check behavior of full and short splices
kernel/module.c | 22 ++++++- tools/testing/selftests/splice/.gitignore | 1 + tools/testing/selftests/splice/Makefile | 4 +- tools/testing/selftests/splice/config | 1 + tools/testing/selftests/splice/settings | 1 + .../selftests/splice/short_splice_read.sh | 56 ++++++++++++++++++ tools/testing/selftests/splice/splice_read.c | 57 +++++++++++++++++++ 7 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 tools/testing/selftests/splice/config create mode 100644 tools/testing/selftests/splice/settings create mode 100755 tools/testing/selftests/splice/short_splice_read.sh create mode 100644 tools/testing/selftests/splice/splice_read.c
The only-root-readable /sys/module/$module/sections/$section files did not truncate their output to the available buffer size. While most paths into the kernfs read handlers end up using PAGE_SIZE buffers, it's possible to get there through other paths (e.g. splice, sendfile). Actually limit the output to the "count" passed into the read function, and report it back correctly. *sigh*
Reported-by: kernel test robot lkp@intel.com Link: https://lore.kernel.org/lkml/20200805002015.GE23458@shao2-debian Fixes: ed66f991bb19 ("module: Refactor section attr into bin attribute") Cc: stable@vger.kernel.org Cc: Jessica Yu jeyu@kernel.org Signed-off-by: Kees Cook keescook@chromium.org --- kernel/module.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/kernel/module.c b/kernel/module.c index aa183c9ac0a2..08c46084d8cc 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -1520,18 +1520,34 @@ struct module_sect_attrs { struct module_sect_attr attrs[]; };
+#define MODULE_SECT_READ_SIZE (3 /* "0x", "\n" */ + (BITS_PER_LONG / 4)) static ssize_t module_sect_read(struct file *file, struct kobject *kobj, struct bin_attribute *battr, char *buf, loff_t pos, size_t count) { struct module_sect_attr *sattr = container_of(battr, struct module_sect_attr, battr); + char bounce[MODULE_SECT_READ_SIZE + 1]; + size_t wrote;
if (pos != 0) return -EINVAL;
- return sprintf(buf, "0x%px\n", - kallsyms_show_value(file->f_cred) ? (void *)sattr->address : NULL); + /* + * Since we're a binary read handler, we must account for the + * trailing NUL byte that sprintf will write: if "buf" is + * too small to hold the NUL, or the NUL is exactly the last + * byte, the read will look like it got truncated by one byte. + * Since there is no way to ask sprintf nicely to not write + * the NUL, we have to use a bounce buffer. + */ + wrote = scnprintf(bounce, sizeof(bounce), "0x%px\n", + kallsyms_show_value(file->f_cred) + ? (void *)sattr->address : NULL); + count = min(count, wrote); + memcpy(buf, bounce, count); + + return count; }
static void free_sect_attrs(struct module_sect_attrs *sect_attrs) @@ -1580,7 +1596,7 @@ static void add_sect_attrs(struct module *mod, const struct load_info *info) goto out; sect_attrs->nsections++; sattr->battr.read = module_sect_read; - sattr->battr.size = 3 /* "0x", "\n" */ + (BITS_PER_LONG / 4); + sattr->battr.size = MODULE_SECT_READ_SIZE; sattr->battr.attr.mode = 0400; *(gattr++) = &(sattr++)->battr; }
On Thu, Aug 06, 2020 at 11:35:38PM -0700, Kees Cook wrote:
The only-root-readable /sys/module/$module/sections/$section files did not truncate their output to the available buffer size. While most paths into the kernfs read handlers end up using PAGE_SIZE buffers, it's possible to get there through other paths (e.g. splice, sendfile). Actually limit the output to the "count" passed into the read function, and report it back correctly. *sigh*
Ugh, never thought about that...
Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org
+++ Kees Cook [06/08/20 23:35 -0700]:
The only-root-readable /sys/module/$module/sections/$section files did not truncate their output to the available buffer size. While most paths into the kernfs read handlers end up using PAGE_SIZE buffers, it's possible to get there through other paths (e.g. splice, sendfile). Actually limit the output to the "count" passed into the read function, and report it back correctly. *sigh*
Reported-by: kernel test robot lkp@intel.com Link: https://lore.kernel.org/lkml/20200805002015.GE23458@shao2-debian Fixes: ed66f991bb19 ("module: Refactor section attr into bin attribute") Cc: stable@vger.kernel.org Cc: Jessica Yu jeyu@kernel.org Signed-off-by: Kees Cook keescook@chromium.org
Oof, thanks for fixing this!
Acked-by: Jessica Yu jeyu@kernel.org
kernel/module.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/kernel/module.c b/kernel/module.c index aa183c9ac0a2..08c46084d8cc 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -1520,18 +1520,34 @@ struct module_sect_attrs { struct module_sect_attr attrs[]; };
+#define MODULE_SECT_READ_SIZE (3 /* "0x", "\n" */ + (BITS_PER_LONG / 4)) static ssize_t module_sect_read(struct file *file, struct kobject *kobj, struct bin_attribute *battr, char *buf, loff_t pos, size_t count) { struct module_sect_attr *sattr = container_of(battr, struct module_sect_attr, battr);
char bounce[MODULE_SECT_READ_SIZE + 1];
size_t wrote;
if (pos != 0) return -EINVAL;
- return sprintf(buf, "0x%px\n",
kallsyms_show_value(file->f_cred) ? (void *)sattr->address : NULL);
- /*
* Since we're a binary read handler, we must account for the
* trailing NUL byte that sprintf will write: if "buf" is
* too small to hold the NUL, or the NUL is exactly the last
* byte, the read will look like it got truncated by one byte.
* Since there is no way to ask sprintf nicely to not write
* the NUL, we have to use a bounce buffer.
*/
- wrote = scnprintf(bounce, sizeof(bounce), "0x%px\n",
kallsyms_show_value(file->f_cred)
? (void *)sattr->address : NULL);
- count = min(count, wrote);
- memcpy(buf, bounce, count);
- return count;
}
static void free_sect_attrs(struct module_sect_attrs *sect_attrs) @@ -1580,7 +1596,7 @@ static void add_sect_attrs(struct module *mod, const struct load_info *info) goto out; sect_attrs->nsections++; sattr->battr.read = module_sect_read;
sattr->battr.size = 3 /* "0x", "\n" */ + (BITS_PER_LONG / 4);
sattr->battr.attr.mode = 0400; *(gattr++) = &(sattr++)->battr; }sattr->battr.size = MODULE_SECT_READ_SIZE;
-- 2.25.1
In order to help catch regressions in splice vs read behavior in certain special files, test a few with various different kinds of internal kernel helpers.
Cc: Shuah Khan shuah@kernel.org Signed-off-by: Kees Cook keescook@chromium.org --- tools/testing/selftests/splice/.gitignore | 1 + tools/testing/selftests/splice/Makefile | 4 +- tools/testing/selftests/splice/config | 1 + tools/testing/selftests/splice/settings | 1 + .../selftests/splice/short_splice_read.sh | 56 ++++++++++++++++++ tools/testing/selftests/splice/splice_read.c | 57 +++++++++++++++++++ 6 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 tools/testing/selftests/splice/config create mode 100644 tools/testing/selftests/splice/settings create mode 100755 tools/testing/selftests/splice/short_splice_read.sh create mode 100644 tools/testing/selftests/splice/splice_read.c
diff --git a/tools/testing/selftests/splice/.gitignore b/tools/testing/selftests/splice/.gitignore index d5a2da428752..be8266f5d04c 100644 --- a/tools/testing/selftests/splice/.gitignore +++ b/tools/testing/selftests/splice/.gitignore @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only default_file_splice_read +splice_read diff --git a/tools/testing/selftests/splice/Makefile b/tools/testing/selftests/splice/Makefile index e519b159b60d..541cd826d5a5 100644 --- a/tools/testing/selftests/splice/Makefile +++ b/tools/testing/selftests/splice/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -TEST_PROGS := default_file_splice_read.sh -TEST_GEN_PROGS_EXTENDED := default_file_splice_read +TEST_PROGS := default_file_splice_read.sh short_splice_read.sh +TEST_GEN_PROGS_EXTENDED := default_file_splice_read splice_read
include ../lib.mk diff --git a/tools/testing/selftests/splice/config b/tools/testing/selftests/splice/config new file mode 100644 index 000000000000..058c928368b8 --- /dev/null +++ b/tools/testing/selftests/splice/config @@ -0,0 +1 @@ +CONFIG_TEST_LKM=m diff --git a/tools/testing/selftests/splice/settings b/tools/testing/selftests/splice/settings new file mode 100644 index 000000000000..89cedfc0d12b --- /dev/null +++ b/tools/testing/selftests/splice/settings @@ -0,0 +1 @@ +timeout=5 diff --git a/tools/testing/selftests/splice/short_splice_read.sh b/tools/testing/selftests/splice/short_splice_read.sh new file mode 100755 index 000000000000..7810d3589d9a --- /dev/null +++ b/tools/testing/selftests/splice/short_splice_read.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +set -e + +ret=0 + +do_splice() +{ + filename="$1" + bytes="$2" + expected="$3" + + out=$(./splice_read "$filename" "$bytes" | cat) + if [ "$out" = "$expected" ] ; then + echo "ok: $filename $bytes" + else + echo "FAIL: $filename $bytes" + ret=1 + fi +} + +test_splice() +{ + filename="$1" + + full=$(cat "$filename") + two=$(echo "$full" | grep -m1 . | cut -c-2) + + # Make sure full splice has the same contents as a standard read. + do_splice "$filename" 4096 "$full" + + # Make sure a partial splice see the first two characters. + do_splice "$filename" 2 "$two" +} + +# proc_single_open(), seq_read() +test_splice /proc/$$/limits +# special open, seq_read() +test_splice /proc/$$/comm + +# proc_handler, proc_dointvec_minmax +test_splice /proc/sys/fs/nr_open +# proc_handler, proc_dostring +test_splice /proc/sys/kernel/modprobe +# proc_handler, special read +test_splice /proc/sys/kernel/version + +if ! [ -d /sys/module/test_module/sections ] ; then + modprobe test_module +fi +# kernfs, attr +test_splice /sys/module/test_module/coresize +# kernfs, binattr +test_splice /sys/module/test_module/sections/.init.text + +exit $ret diff --git a/tools/testing/selftests/splice/splice_read.c b/tools/testing/selftests/splice/splice_read.c new file mode 100644 index 000000000000..46dae6a25cfb --- /dev/null +++ b/tools/testing/selftests/splice/splice_read.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +int main(int argc, char *argv[]) +{ + int fd; + size_t size; + ssize_t spliced; + + if (argc < 2) { + fprintf(stderr, "Usage: %s INPUT [BYTES]\n", argv[0]); + return EXIT_FAILURE; + } + + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror(argv[1]); + return EXIT_FAILURE; + } + + if (argc == 3) + size = atol(argv[2]); + else { + struct stat statbuf; + + if (fstat(fd, &statbuf) < 0) { + perror(argv[1]); + return EXIT_FAILURE; + } + + if (statbuf.st_size > INT_MAX) { + fprintf(stderr, "%s: Too big\n", argv[1]); + return EXIT_FAILURE; + } + + size = statbuf.st_size; + } + + /* splice(2) file to stdout. */ + spliced = splice(fd, NULL, STDOUT_FILENO, NULL, + size, SPLICE_F_MOVE); + if (spliced < 0) { + perror("splice"); + return EXIT_FAILURE; + } + + close(fd); + return EXIT_SUCCESS; +}
linux-kselftest-mirror@lists.linaro.org