diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-07-09 13:09:30 -0700 | 
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-07-09 13:09:30 -0700 | 
| commit | ce69fb3b392fbfd6c255aeb0ee371652478c716f (patch) | |
| tree | d043b0839d524c54f6b74b134ee6fb6f5dbaf3b2 | |
| parent | 0bddd227f3dc55975e2b8dfa7fc6f959b062a2c7 (diff) | |
| parent | 2c79583927bb8154ecaa45a67dde97661d895ecd (diff) | |
Merge tag 'kallsyms_show_value-v5.8-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux
Pull kallsyms fix from Kees Cook:
 "Refactor kallsyms_show_value() users for correct cred.
  I'm not delighted by the timing of getting these changes to you, but
  it does fix a handful of kernel address exposures, and no one has
  screamed yet at the patches.
  Several users of kallsyms_show_value() were performing checks not
  during "open". Refactor everything needed to gain proper checks
  against file->f_cred for modules, kprobes, and bpf"
* tag 'kallsyms_show_value-v5.8-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux:
  selftests: kmod: Add module address visibility test
  bpf: Check correct cred for CAP_SYSLOG in bpf_dump_raw_ok()
  kprobes: Do not expose probe addresses to non-CAP_SYSLOG
  module: Do not expose section addresses to non-CAP_SYSLOG
  module: Refactor section attr into bin attribute
  kallsyms: Refactor kallsyms_show_value() to take cred
| -rw-r--r-- | include/linux/filter.h | 4 | ||||
| -rw-r--r-- | include/linux/kallsyms.h | 5 | ||||
| -rw-r--r-- | kernel/bpf/syscall.c | 37 | ||||
| -rw-r--r-- | kernel/kallsyms.c | 17 | ||||
| -rw-r--r-- | kernel/kprobes.c | 4 | ||||
| -rw-r--r-- | kernel/module.c | 51 | ||||
| -rw-r--r-- | net/core/sysctl_net_core.c | 2 | ||||
| -rwxr-xr-x | tools/testing/selftests/kmod/kmod.sh | 36 | 
8 files changed, 103 insertions, 53 deletions
diff --git a/include/linux/filter.h b/include/linux/filter.h index 259377723603..0b0144752d78 100644 --- a/include/linux/filter.h +++ b/include/linux/filter.h @@ -884,12 +884,12 @@ void bpf_jit_compile(struct bpf_prog *prog);  bool bpf_jit_needs_zext(void);  bool bpf_helper_changes_pkt_data(void *func); -static inline bool bpf_dump_raw_ok(void) +static inline bool bpf_dump_raw_ok(const struct cred *cred)  {  	/* Reconstruction of call-sites is dependent on kallsyms,  	 * thus make dump the same restriction.  	 */ -	return kallsyms_show_value() == 1; +	return kallsyms_show_value(cred);  }  struct bpf_prog *bpf_patch_insn_single(struct bpf_prog *prog, u32 off, diff --git a/include/linux/kallsyms.h b/include/linux/kallsyms.h index 98338dc6b5d2..481273f0c72d 100644 --- a/include/linux/kallsyms.h +++ b/include/linux/kallsyms.h @@ -18,6 +18,7 @@  #define KSYM_SYMBOL_LEN (sizeof("%s+%#lx/%#lx [%s]") + (KSYM_NAME_LEN - 1) + \  			 2*(BITS_PER_LONG*3/10) + (MODULE_NAME_LEN - 1) + 1) +struct cred;  struct module;  static inline int is_kernel_inittext(unsigned long addr) @@ -98,7 +99,7 @@ int lookup_symbol_name(unsigned long addr, char *symname);  int lookup_symbol_attrs(unsigned long addr, unsigned long *size, unsigned long *offset, char *modname, char *name);  /* How and when do we show kallsyms values? */ -extern int kallsyms_show_value(void); +extern bool kallsyms_show_value(const struct cred *cred);  #else /* !CONFIG_KALLSYMS */ @@ -158,7 +159,7 @@ static inline int lookup_symbol_attrs(unsigned long addr, unsigned long *size, u  	return -ERANGE;  } -static inline int kallsyms_show_value(void) +static inline bool kallsyms_show_value(const struct cred *cred)  {  	return false;  } diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 8da159936bab..859053ddf05b 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -3139,7 +3139,8 @@ static const struct bpf_map *bpf_map_from_imm(const struct bpf_prog *prog,  	return NULL;  } -static struct bpf_insn *bpf_insn_prepare_dump(const struct bpf_prog *prog) +static struct bpf_insn *bpf_insn_prepare_dump(const struct bpf_prog *prog, +					      const struct cred *f_cred)  {  	const struct bpf_map *map;  	struct bpf_insn *insns; @@ -3165,7 +3166,7 @@ static struct bpf_insn *bpf_insn_prepare_dump(const struct bpf_prog *prog)  		    code == (BPF_JMP | BPF_CALL_ARGS)) {  			if (code == (BPF_JMP | BPF_CALL_ARGS))  				insns[i].code = BPF_JMP | BPF_CALL; -			if (!bpf_dump_raw_ok()) +			if (!bpf_dump_raw_ok(f_cred))  				insns[i].imm = 0;  			continue;  		} @@ -3221,7 +3222,8 @@ static int set_info_rec_size(struct bpf_prog_info *info)  	return 0;  } -static int bpf_prog_get_info_by_fd(struct bpf_prog *prog, +static int bpf_prog_get_info_by_fd(struct file *file, +				   struct bpf_prog *prog,  				   const union bpf_attr *attr,  				   union bpf_attr __user *uattr)  { @@ -3290,11 +3292,11 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,  		struct bpf_insn *insns_sanitized;  		bool fault; -		if (prog->blinded && !bpf_dump_raw_ok()) { +		if (prog->blinded && !bpf_dump_raw_ok(file->f_cred)) {  			info.xlated_prog_insns = 0;  			goto done;  		} -		insns_sanitized = bpf_insn_prepare_dump(prog); +		insns_sanitized = bpf_insn_prepare_dump(prog, file->f_cred);  		if (!insns_sanitized)  			return -ENOMEM;  		uinsns = u64_to_user_ptr(info.xlated_prog_insns); @@ -3328,7 +3330,7 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,  	}  	if (info.jited_prog_len && ulen) { -		if (bpf_dump_raw_ok()) { +		if (bpf_dump_raw_ok(file->f_cred)) {  			uinsns = u64_to_user_ptr(info.jited_prog_insns);  			ulen = min_t(u32, info.jited_prog_len, ulen); @@ -3363,7 +3365,7 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,  	ulen = info.nr_jited_ksyms;  	info.nr_jited_ksyms = prog->aux->func_cnt ? : 1;  	if (ulen) { -		if (bpf_dump_raw_ok()) { +		if (bpf_dump_raw_ok(file->f_cred)) {  			unsigned long ksym_addr;  			u64 __user *user_ksyms;  			u32 i; @@ -3394,7 +3396,7 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,  	ulen = info.nr_jited_func_lens;  	info.nr_jited_func_lens = prog->aux->func_cnt ? : 1;  	if (ulen) { -		if (bpf_dump_raw_ok()) { +		if (bpf_dump_raw_ok(file->f_cred)) {  			u32 __user *user_lens;  			u32 func_len, i; @@ -3451,7 +3453,7 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,  	else  		info.nr_jited_line_info = 0;  	if (info.nr_jited_line_info && ulen) { -		if (bpf_dump_raw_ok()) { +		if (bpf_dump_raw_ok(file->f_cred)) {  			__u64 __user *user_linfo;  			u32 i; @@ -3497,7 +3499,8 @@ done:  	return 0;  } -static int bpf_map_get_info_by_fd(struct bpf_map *map, +static int bpf_map_get_info_by_fd(struct file *file, +				  struct bpf_map *map,  				  const union bpf_attr *attr,  				  union bpf_attr __user *uattr)  { @@ -3540,7 +3543,8 @@ static int bpf_map_get_info_by_fd(struct bpf_map *map,  	return 0;  } -static int bpf_btf_get_info_by_fd(struct btf *btf, +static int bpf_btf_get_info_by_fd(struct file *file, +				  struct btf *btf,  				  const union bpf_attr *attr,  				  union bpf_attr __user *uattr)  { @@ -3555,7 +3559,8 @@ static int bpf_btf_get_info_by_fd(struct btf *btf,  	return btf_get_info_by_fd(btf, attr, uattr);  } -static int bpf_link_get_info_by_fd(struct bpf_link *link, +static int bpf_link_get_info_by_fd(struct file *file, +				  struct bpf_link *link,  				  const union bpf_attr *attr,  				  union bpf_attr __user *uattr)  { @@ -3608,15 +3613,15 @@ static int bpf_obj_get_info_by_fd(const union bpf_attr *attr,  		return -EBADFD;  	if (f.file->f_op == &bpf_prog_fops) -		err = bpf_prog_get_info_by_fd(f.file->private_data, attr, +		err = bpf_prog_get_info_by_fd(f.file, f.file->private_data, attr,  					      uattr);  	else if (f.file->f_op == &bpf_map_fops) -		err = bpf_map_get_info_by_fd(f.file->private_data, attr, +		err = bpf_map_get_info_by_fd(f.file, f.file->private_data, attr,  					     uattr);  	else if (f.file->f_op == &btf_fops) -		err = bpf_btf_get_info_by_fd(f.file->private_data, attr, uattr); +		err = bpf_btf_get_info_by_fd(f.file, f.file->private_data, attr, uattr);  	else if (f.file->f_op == &bpf_link_fops) -		err = bpf_link_get_info_by_fd(f.file->private_data, +		err = bpf_link_get_info_by_fd(f.file, f.file->private_data,  					      attr, uattr);  	else  		err = -EINVAL; diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c index 16c8c605f4b0..bb14e64f62a4 100644 --- a/kernel/kallsyms.c +++ b/kernel/kallsyms.c @@ -644,19 +644,20 @@ static inline int kallsyms_for_perf(void)   * Otherwise, require CAP_SYSLOG (assuming kptr_restrict isn't set to   * block even that).   */ -int kallsyms_show_value(void) +bool kallsyms_show_value(const struct cred *cred)  {  	switch (kptr_restrict) {  	case 0:  		if (kallsyms_for_perf()) -			return 1; +			return true;  	/* fallthrough */  	case 1: -		if (has_capability_noaudit(current, CAP_SYSLOG)) -			return 1; +		if (security_capable(cred, &init_user_ns, CAP_SYSLOG, +				     CAP_OPT_NOAUDIT) == 0) +			return true;  	/* fallthrough */  	default: -		return 0; +		return false;  	}  } @@ -673,7 +674,11 @@ static int kallsyms_open(struct inode *inode, struct file *file)  		return -ENOMEM;  	reset_iter(iter, 0); -	iter->show_value = kallsyms_show_value(); +	/* +	 * Instead of checking this on every s_show() call, cache +	 * the result here at open time. +	 */ +	iter->show_value = kallsyms_show_value(file->f_cred);  	return 0;  } diff --git a/kernel/kprobes.c b/kernel/kprobes.c index 4a904cc56d68..2e97febeef77 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c @@ -2448,7 +2448,7 @@ static void report_probe(struct seq_file *pi, struct kprobe *p,  	else  		kprobe_type = "k"; -	if (!kallsyms_show_value()) +	if (!kallsyms_show_value(pi->file->f_cred))  		addr = NULL;  	if (sym) @@ -2540,7 +2540,7 @@ static int kprobe_blacklist_seq_show(struct seq_file *m, void *v)  	 * If /proc/kallsyms is not showing kernel address, we won't  	 * show them here either.  	 */ -	if (!kallsyms_show_value()) +	if (!kallsyms_show_value(m->file->f_cred))  		seq_printf(m, "0x%px-0x%px\t%ps\n", NULL, NULL,  			   (void *)ent->start_addr);  	else diff --git a/kernel/module.c b/kernel/module.c index bee1c25ca5c5..aa183c9ac0a2 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -1510,8 +1510,7 @@ static inline bool sect_empty(const Elf_Shdr *sect)  }  struct module_sect_attr { -	struct module_attribute mattr; -	char *name; +	struct bin_attribute battr;  	unsigned long address;  }; @@ -1521,13 +1520,18 @@ struct module_sect_attrs {  	struct module_sect_attr attrs[];  }; -static ssize_t module_sect_show(struct module_attribute *mattr, -				struct module_kobject *mk, char *buf) +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(mattr, struct module_sect_attr, mattr); -	return sprintf(buf, "0x%px\n", kptr_restrict < 2 ? -		       (void *)sattr->address : NULL); +		container_of(battr, struct module_sect_attr, battr); + +	if (pos != 0) +		return -EINVAL; + +	return sprintf(buf, "0x%px\n", +		       kallsyms_show_value(file->f_cred) ? (void *)sattr->address : NULL);  }  static void free_sect_attrs(struct module_sect_attrs *sect_attrs) @@ -1535,7 +1539,7 @@ static void free_sect_attrs(struct module_sect_attrs *sect_attrs)  	unsigned int section;  	for (section = 0; section < sect_attrs->nsections; section++) -		kfree(sect_attrs->attrs[section].name); +		kfree(sect_attrs->attrs[section].battr.attr.name);  	kfree(sect_attrs);  } @@ -1544,42 +1548,41 @@ static void add_sect_attrs(struct module *mod, const struct load_info *info)  	unsigned int nloaded = 0, i, size[2];  	struct module_sect_attrs *sect_attrs;  	struct module_sect_attr *sattr; -	struct attribute **gattr; +	struct bin_attribute **gattr;  	/* Count loaded sections and allocate structures */  	for (i = 0; i < info->hdr->e_shnum; i++)  		if (!sect_empty(&info->sechdrs[i]))  			nloaded++;  	size[0] = ALIGN(struct_size(sect_attrs, attrs, nloaded), -			sizeof(sect_attrs->grp.attrs[0])); -	size[1] = (nloaded + 1) * sizeof(sect_attrs->grp.attrs[0]); +			sizeof(sect_attrs->grp.bin_attrs[0])); +	size[1] = (nloaded + 1) * sizeof(sect_attrs->grp.bin_attrs[0]);  	sect_attrs = kzalloc(size[0] + size[1], GFP_KERNEL);  	if (sect_attrs == NULL)  		return;  	/* Setup section attributes. */  	sect_attrs->grp.name = "sections"; -	sect_attrs->grp.attrs = (void *)sect_attrs + size[0]; +	sect_attrs->grp.bin_attrs = (void *)sect_attrs + size[0];  	sect_attrs->nsections = 0;  	sattr = §_attrs->attrs[0]; -	gattr = §_attrs->grp.attrs[0]; +	gattr = §_attrs->grp.bin_attrs[0];  	for (i = 0; i < info->hdr->e_shnum; i++) {  		Elf_Shdr *sec = &info->sechdrs[i];  		if (sect_empty(sec))  			continue; +		sysfs_bin_attr_init(&sattr->battr);  		sattr->address = sec->sh_addr; -		sattr->name = kstrdup(info->secstrings + sec->sh_name, -					GFP_KERNEL); -		if (sattr->name == NULL) +		sattr->battr.attr.name = +			kstrdup(info->secstrings + sec->sh_name, GFP_KERNEL); +		if (sattr->battr.attr.name == NULL)  			goto out;  		sect_attrs->nsections++; -		sysfs_attr_init(&sattr->mattr.attr); -		sattr->mattr.show = module_sect_show; -		sattr->mattr.store = NULL; -		sattr->mattr.attr.name = sattr->name; -		sattr->mattr.attr.mode = S_IRUSR; -		*(gattr++) = &(sattr++)->mattr.attr; +		sattr->battr.read = module_sect_read; +		sattr->battr.size = 3 /* "0x", "\n" */ + (BITS_PER_LONG / 4); +		sattr->battr.attr.mode = 0400; +		*(gattr++) = &(sattr++)->battr;  	}  	*gattr = NULL; @@ -1669,7 +1672,7 @@ static void add_notes_attrs(struct module *mod, const struct load_info *info)  			continue;  		if (info->sechdrs[i].sh_type == SHT_NOTE) {  			sysfs_bin_attr_init(nattr); -			nattr->attr.name = mod->sect_attrs->attrs[loaded].name; +			nattr->attr.name = mod->sect_attrs->attrs[loaded].battr.attr.name;  			nattr->attr.mode = S_IRUGO;  			nattr->size = info->sechdrs[i].sh_size;  			nattr->private = (void *) info->sechdrs[i].sh_addr; @@ -4379,7 +4382,7 @@ static int modules_open(struct inode *inode, struct file *file)  	if (!err) {  		struct seq_file *m = file->private_data; -		m->private = kallsyms_show_value() ? NULL : (void *)8ul; +		m->private = kallsyms_show_value(file->f_cred) ? NULL : (void *)8ul;  	}  	return err; diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c index f93f8ace6c56..6ada114bbcca 100644 --- a/net/core/sysctl_net_core.c +++ b/net/core/sysctl_net_core.c @@ -274,7 +274,7 @@ static int proc_dointvec_minmax_bpf_enable(struct ctl_table *table, int write,  	ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);  	if (write && !ret) {  		if (jit_enable < 2 || -		    (jit_enable == 2 && bpf_dump_raw_ok())) { +		    (jit_enable == 2 && bpf_dump_raw_ok(current_cred()))) {  			*(int *)table->data = jit_enable;  			if (jit_enable == 2)  				pr_warn("bpf_jit_enable = 2 was set! NEVER use this in production, only for JIT debugging!\n"); diff --git a/tools/testing/selftests/kmod/kmod.sh b/tools/testing/selftests/kmod/kmod.sh index 3702dbcc90a7..c82aa77958e5 100755 --- a/tools/testing/selftests/kmod/kmod.sh +++ b/tools/testing/selftests/kmod/kmod.sh @@ -63,6 +63,8 @@ ALL_TESTS="$ALL_TESTS 0008:150:1"  ALL_TESTS="$ALL_TESTS 0009:150:1"  ALL_TESTS="$ALL_TESTS 0010:1:1"  ALL_TESTS="$ALL_TESTS 0011:1:1" +ALL_TESTS="$ALL_TESTS 0012:1:1" +ALL_TESTS="$ALL_TESTS 0013:1:1"  # Kselftest framework requirement - SKIP code is 4.  ksft_skip=4 @@ -470,6 +472,38 @@ kmod_test_0011()  	echo "$MODPROBE" > /proc/sys/kernel/modprobe  } +kmod_check_visibility() +{ +	local name="$1" +	local cmd="$2" + +	modprobe $DEFAULT_KMOD_DRIVER + +	local priv=$(eval $cmd) +	local unpriv=$(capsh --drop=CAP_SYSLOG -- -c "$cmd") + +	if [ "$priv" = "$unpriv" ] || \ +	   [ "${priv:0:3}" = "0x0" ] || \ +	   [ "${unpriv:0:3}" != "0x0" ] ; then +		echo "${FUNCNAME[0]}: FAIL, $name visible to unpriv: '$priv' vs '$unpriv'" >&2 +		exit 1 +	else +		echo "${FUNCNAME[0]}: OK!" +	fi +} + +kmod_test_0012() +{ +	kmod_check_visibility /proc/modules \ +		"grep '^${DEFAULT_KMOD_DRIVER}\b' /proc/modules | awk '{print \$NF}'" +} + +kmod_test_0013() +{ +	kmod_check_visibility '/sys/module/*/sections/*' \ +		"cat /sys/module/${DEFAULT_KMOD_DRIVER}/sections/.*text | head -n1" +} +  list_tests()  {  	echo "Test ID list:" @@ -489,6 +523,8 @@ list_tests()  	echo "0009 x $(get_test_count 0009) - multithreaded - push kmod_concurrent over max_modprobes for get_fs_type()"  	echo "0010 x $(get_test_count 0010) - test nonexistent modprobe path"  	echo "0011 x $(get_test_count 0011) - test completely disabling module autoloading" +	echo "0012 x $(get_test_count 0012) - test /proc/modules address visibility under CAP_SYSLOG" +	echo "0013 x $(get_test_count 0013) - test /sys/module/*/sections/* visibility under CAP_SYSLOG"  }  usage()  | 
