[v4,3/7] kprobes: validate the symbol name provided during probe registration

Submitted by Naveen N. Rao on April 21, 2017, 12:32 p.m.

Details

Message ID 20170421123234.6895-1-naveen.n.rao@linux.vnet.ibm.com
State Superseded
Headers show

Commit Message

Naveen N. Rao April 21, 2017, 12:32 p.m.
When a kprobe is being registered, we use the symbol_name field to
lookup the address where the probe should be placed. Since this is a
user-provided field, let's ensure that the length of the string is
within expected limits.

Signed-off-by: Naveen N. Rao <naveen.n.rao@linux.vnet.ibm.com>
---
Masami, Michael,
Here's a simplified version that should hopefully be fine. I have
simplified the logic to keep the code compact and simple.

- Naveen


 include/linux/kprobes.h     |  1 +
 kernel/kprobes.c            | 30 ++++++++++++++++++++++++++++++
 kernel/trace/trace_kprobe.c |  4 ++++
 3 files changed, 35 insertions(+)

Comments

Paul Clarke April 21, 2017, 1:11 p.m.
a nit or two, below...

On 04/21/2017 07:32 AM, Naveen N. Rao wrote:
> diff --git a/kernel/kprobes.c b/kernel/kprobes.c
> index 6a128f3a7ed1..ff9b1ac72a38 100644
> --- a/kernel/kprobes.c
> +++ b/kernel/kprobes.c
> @@ -1383,6 +1383,34 @@ bool within_kprobe_blacklist(unsigned long addr)
>  }
> 
>  /*
> + * We mainly want to ensure that the provided string is of a reasonable length
> + * and is of the form [<mod_name>:]<sym_name>, so that this is safe to process
> + * further.
> + * We don't worry about invalid characters as those will just prevent
> + * matching existing kallsyms.
> + */
> +bool is_valid_kprobe_symbol_name(const char *name)
> +{
> +	size_t sym_len;
> +	const char *s;
> +
> +	s = strnchr(name, ':', MODULE_NAME_LEN + KSYM_NAME_LEN + 1);
> +	if (s) {
> +		sym_len = (size_t)(s - name);
> +		if (sym_len <= 0  || sym_len >= MODULE_NAME_LEN)

"sym_len <= 0" looks odd here, since sym_len is likely unsigned and would never be less than zero, anyway.

> +			return false;
> +		s++;
> +	} else
> +		s = name;
> +
> +	sym_len = strnlen(s, KSYM_NAME_LEN);
> +	if (sym_len <= 0 || sym_len >= KSYM_NAME_LEN)

here, too.

> +		return false;
> +
> +	return true;
> +}

PC
Naveen N. Rao April 21, 2017, 1:25 p.m.
Excerpts from Paul Clarke's message of April 21, 2017 18:41:
> a nit or two, below...
> 
> On 04/21/2017 07:32 AM, Naveen N. Rao wrote:
>> diff --git a/kernel/kprobes.c b/kernel/kprobes.c
>> index 6a128f3a7ed1..ff9b1ac72a38 100644
>> --- a/kernel/kprobes.c
>> +++ b/kernel/kprobes.c
>> @@ -1383,6 +1383,34 @@ bool within_kprobe_blacklist(unsigned long addr)
>>  }
>> 
>>  /*
>> + * We mainly want to ensure that the provided string is of a reasonable length
>> + * and is of the form [<mod_name>:]<sym_name>, so that this is safe to process
>> + * further.
>> + * We don't worry about invalid characters as those will just prevent
>> + * matching existing kallsyms.
>> + */
>> +bool is_valid_kprobe_symbol_name(const char *name)
>> +{
>> +	size_t sym_len;
>> +	const char *s;
>> +
>> +	s = strnchr(name, ':', MODULE_NAME_LEN + KSYM_NAME_LEN + 1);
>> +	if (s) {
>> +		sym_len = (size_t)(s - name);
>> +		if (sym_len <= 0  || sym_len >= MODULE_NAME_LEN)
> 
> "sym_len <= 0" looks odd here, since sym_len is likely unsigned and would never be less than zero, anyway.

Ugh.. habits :/
I'll wait for Masami/Michael's feedback before re-spinning.

Thanks for the review,
- Naveen
Masami Hiramatsu April 21, 2017, 1:54 p.m.
On Fri, 21 Apr 2017 18:02:33 +0530
"Naveen N. Rao" <naveen.n.rao@linux.vnet.ibm.com> wrote:

> When a kprobe is being registered, we use the symbol_name field to
> lookup the address where the probe should be placed. Since this is a
> user-provided field, let's ensure that the length of the string is
> within expected limits.
> 
> Signed-off-by: Naveen N. Rao <naveen.n.rao@linux.vnet.ibm.com>
> ---
> Masami, Michael,
> Here's a simplified version that should hopefully be fine. I have
> simplified the logic to keep the code compact and simple.

Ok, so as I replied to older version,
 - Ensure name != NULL at first.
 - Define it in kernel/kallsyms.c as a general routine.

Since this validate the string which will be passed to kallsyms,
I think it should be provided by kallsyms.

Thanks, 

> 
> - Naveen
> 
> 
>  include/linux/kprobes.h     |  1 +
>  kernel/kprobes.c            | 30 ++++++++++++++++++++++++++++++
>  kernel/trace/trace_kprobe.c |  4 ++++
>  3 files changed, 35 insertions(+)
> 
> diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h
> index 1f82a3db00b1..4ee10fef5135 100644
> --- a/include/linux/kprobes.h
> +++ b/include/linux/kprobes.h
> @@ -405,6 +405,7 @@ int disable_kprobe(struct kprobe *kp);
>  int enable_kprobe(struct kprobe *kp);
>  
>  void dump_kprobe(struct kprobe *kp);
> +bool is_valid_kprobe_symbol_name(const char *name);
>  
>  #else /* !CONFIG_KPROBES: */
>  
> diff --git a/kernel/kprobes.c b/kernel/kprobes.c
> index 6a128f3a7ed1..ff9b1ac72a38 100644
> --- a/kernel/kprobes.c
> +++ b/kernel/kprobes.c
> @@ -1383,6 +1383,34 @@ bool within_kprobe_blacklist(unsigned long addr)
>  }
>  
>  /*
> + * We mainly want to ensure that the provided string is of a reasonable length
> + * and is of the form [<mod_name>:]<sym_name>, so that this is safe to process
> + * further.
> + * We don't worry about invalid characters as those will just prevent
> + * matching existing kallsyms.
> + */
> +bool is_valid_kprobe_symbol_name(const char *name)
> +{
> +	size_t sym_len;
> +	const char *s;
> +
> +	s = strnchr(name, ':', MODULE_NAME_LEN + KSYM_NAME_LEN + 1);
> +	if (s) {
> +		sym_len = (size_t)(s - name);
> +		if (sym_len <= 0  || sym_len >= MODULE_NAME_LEN)
> +			return false;
> +		s++;
> +	} else
> +		s = name;
> +
> +	sym_len = strnlen(s, KSYM_NAME_LEN);
> +	if (sym_len <= 0 || sym_len >= KSYM_NAME_LEN)
> +		return false;
> +
> +	return true;
> +}
> +
> +/*
>   * If we have a symbol_name argument, look it up and add the offset field
>   * to it. This way, we can specify a relative address to a symbol.
>   * This returns encoded errors if it fails to look up symbol or invalid
> @@ -1397,6 +1425,8 @@ static kprobe_opcode_t *kprobe_addr(struct kprobe *p)
>  		goto invalid;
>  
>  	if (p->symbol_name) {
> +		if (!is_valid_kprobe_symbol_name(p->symbol_name))
> +			return ERR_PTR(-EINVAL);
>  		addr = kprobe_lookup_name(p->symbol_name, p->offset);
>  		if (!addr)
>  			return ERR_PTR(-ENOENT);
> diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
> index 5f688cc724f0..bf73e5f31128 100644
> --- a/kernel/trace/trace_kprobe.c
> +++ b/kernel/trace/trace_kprobe.c
> @@ -704,6 +704,10 @@ static int create_trace_kprobe(int argc, char **argv)
>  			pr_info("Return probe must be used without offset.\n");
>  			return -EINVAL;
>  		}
> +		if (!is_valid_kprobe_symbol_name(symbol)) {
> +			pr_info("Symbol name is too long.\n");
> +			return -EINVAL;
> +		}
>  	}
>  	argc -= 2; argv += 2;
>  
> -- 
> 2.12.1
>
Michael Ellerman April 22, 2017, 5:55 a.m.
"Naveen N. Rao" <naveen.n.rao@linux.vnet.ibm.com> writes:

> When a kprobe is being registered, we use the symbol_name field to
> lookup the address where the probe should be placed. Since this is a
> user-provided field, let's ensure that the length of the string is
> within expected limits.

What are we actually trying to protect against here?

If you ignore powerpc for a moment, kprobe_lookup_name() is just
kallsyms_lookup_name().

All kallsyms_lookup_name() does with name is strcmp() it against a
legitimate symbol name which is at most KSYM_NAME_LEN.

So I don't think any of this validation helps in that case?

In the powerpc version of kprobe_lookup_name() we do need to do some
string juggling, for which it helps to know the input is sane. But I
think we should just make that code more robust by checking the input
before we do anything with it.

cheers
Naveen N. Rao April 23, 2017, 5:41 p.m.
Excerpts from Michael Ellerman's message of April 22, 2017 11:25:
> "Naveen N. Rao" <naveen.n.rao@linux.vnet.ibm.com> writes:
> 
>> When a kprobe is being registered, we use the symbol_name field to
>> lookup the address where the probe should be placed. Since this is a
>> user-provided field, let's ensure that the length of the string is
>> within expected limits.
> 
> What are we actually trying to protect against here?
> 
> If you ignore powerpc for a moment, kprobe_lookup_name() is just
> kallsyms_lookup_name().
> 
> All kallsyms_lookup_name() does with name is strcmp() it against a
> legitimate symbol name which is at most KSYM_NAME_LEN.
> 
> So I don't think any of this validation helps in that case?

It does:
https://patchwork.kernel.org/patch/9695139/

It is far too easy to cause a OOPS due to the above, though this is 
root-only (for modules).

As I stated earlier, I think it is always a good practice to validate 
inputs right on entry, rather than later. Code gets refactored, 
different arch support gets added, and so on.

Doing this validation here ensures we don't have to worry about how we 
process this later, or if another arch has to over-ride 
kprobe_lookup_name().

> 
> In the powerpc version of kprobe_lookup_name() we do need to do some
> string juggling, for which it helps to know the input is sane. But I
> think we should just make that code more robust by checking the input
> before we do anything with it.

Ok, I will fold those tests in with the powerpc implementation for now 
and consider a patch against kallsyms_lookup_name(), like Masami 
recommends.


- Naveen

Patch hide | download patch | download mbox

diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h
index 1f82a3db00b1..4ee10fef5135 100644
--- a/include/linux/kprobes.h
+++ b/include/linux/kprobes.h
@@ -405,6 +405,7 @@  int disable_kprobe(struct kprobe *kp);
 int enable_kprobe(struct kprobe *kp);
 
 void dump_kprobe(struct kprobe *kp);
+bool is_valid_kprobe_symbol_name(const char *name);
 
 #else /* !CONFIG_KPROBES: */
 
diff --git a/kernel/kprobes.c b/kernel/kprobes.c
index 6a128f3a7ed1..ff9b1ac72a38 100644
--- a/kernel/kprobes.c
+++ b/kernel/kprobes.c
@@ -1383,6 +1383,34 @@  bool within_kprobe_blacklist(unsigned long addr)
 }
 
 /*
+ * We mainly want to ensure that the provided string is of a reasonable length
+ * and is of the form [<mod_name>:]<sym_name>, so that this is safe to process
+ * further.
+ * We don't worry about invalid characters as those will just prevent
+ * matching existing kallsyms.
+ */
+bool is_valid_kprobe_symbol_name(const char *name)
+{
+	size_t sym_len;
+	const char *s;
+
+	s = strnchr(name, ':', MODULE_NAME_LEN + KSYM_NAME_LEN + 1);
+	if (s) {
+		sym_len = (size_t)(s - name);
+		if (sym_len <= 0  || sym_len >= MODULE_NAME_LEN)
+			return false;
+		s++;
+	} else
+		s = name;
+
+	sym_len = strnlen(s, KSYM_NAME_LEN);
+	if (sym_len <= 0 || sym_len >= KSYM_NAME_LEN)
+		return false;
+
+	return true;
+}
+
+/*
  * If we have a symbol_name argument, look it up and add the offset field
  * to it. This way, we can specify a relative address to a symbol.
  * This returns encoded errors if it fails to look up symbol or invalid
@@ -1397,6 +1425,8 @@  static kprobe_opcode_t *kprobe_addr(struct kprobe *p)
 		goto invalid;
 
 	if (p->symbol_name) {
+		if (!is_valid_kprobe_symbol_name(p->symbol_name))
+			return ERR_PTR(-EINVAL);
 		addr = kprobe_lookup_name(p->symbol_name, p->offset);
 		if (!addr)
 			return ERR_PTR(-ENOENT);
diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
index 5f688cc724f0..bf73e5f31128 100644
--- a/kernel/trace/trace_kprobe.c
+++ b/kernel/trace/trace_kprobe.c
@@ -704,6 +704,10 @@  static int create_trace_kprobe(int argc, char **argv)
 			pr_info("Return probe must be used without offset.\n");
 			return -EINVAL;
 		}
+		if (!is_valid_kprobe_symbol_name(symbol)) {
+			pr_info("Symbol name is too long.\n");
+			return -EINVAL;
+		}
 	}
 	argc -= 2; argv += 2;