diff mbox

[v2,net-next,1/2] lib: introduce strncpy_from_unsafe()

Message ID 1440791509-5450-2-git-send-email-ast@plumgrid.com
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

Alexei Starovoitov Aug. 28, 2015, 7:51 p.m. UTC
generalize FETCH_FUNC_NAME(memory, string) into
strncpy_from_unsafe() and fix sparse warnings that were
present in original implementation.

Signed-off-by: Alexei Starovoitov <ast@plumgrid.com>
---
 include/linux/uaccess.h     |    2 ++
 kernel/trace/trace_kprobe.c |   20 ++++----------------
 lib/strncpy_from_user.c     |   41 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 47 insertions(+), 16 deletions(-)

Comments

Steven Rostedt Aug. 28, 2015, 9:48 p.m. UTC | #1
On Fri, 28 Aug 2015 12:51:48 -0700
Alexei Starovoitov <ast@plumgrid.com> wrote:

>  EXPORT_SYMBOL(strncpy_from_user);
> +
> +/**
> + * strncpy_from_unsafe: - Copy a NUL terminated string from unsafe address.
> + * @dst:   Destination address, in kernel space.  This buffer must be at
> + *         least @count bytes long.
> + * @src:   Unsafe address.
> + * @count: Maximum number of bytes to copy, including the trailing NUL.
> + *
> + * Copies a NUL-terminated string from unsafe address to kernel buffer.
> + *
> + * On success, returns the length of the string (not including the trailing
> + * NUL).

I think it includes the NUL.

> + *
> + * If access fails, returns -EFAULT (some data may have been copied).
> + *
> + * If @count is smaller than the length of the string, copies @count bytes
> + * and returns @count.
> + */
> +long strncpy_from_unsafe(char *dst, const void *unsafe_addr, long count)
> +{
> +	mm_segment_t old_fs = get_fs();
> +	const void *src = unsafe_addr;

src = unsafe_addr = 0x100;

*unsafe_addr = "1\0";

> +	long ret;
> +
> +	if (unlikely(count <= 0))
> +		return 0;
> +
> +	set_fs(KERNEL_DS);
> +	pagefault_disable();
> +
> +	do {
> +		ret = __copy_from_user_inatomic(dst++,
> +						(const void __user __force *)src++, 1);

First loop:

  dst[-1] = '1'
  src = 0x101

Second loop:

  dst[-1] = '\0'
  src = 0x102


> +	} while (dst[-1] && ret == 0 && src - unsafe_addr < count);
> +
> +	dst[-1] = '\0';
> +	pagefault_enable();
> +	set_fs(old_fs);
> +
> +	return ret < 0 ? ret : src - unsafe_addr;

  src - unsafe_addr = 0x102 - 0x100 = 2

Included the NUL.

-- Steve

> +}

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexei Starovoitov Aug. 28, 2015, 10:08 p.m. UTC | #2
On 8/28/15 2:48 PM, Steven Rostedt wrote:
>>   * On success, returns the length of the string (not including the trailing
>> >+ * NUL).
> I think it includes the NUL.

oops. yes. that was a copy paste from strncpy_from_user comment.
trace_kprobe usage wants NUL to be counted, so I intended to have it
counted, but that brings the question what should be the semantics.
Should it be similar to strncpy_from_user (not counting NUL) or
similar to strlen_user (counts NUL) ?
imo counting NUL makes a little bit more sense, since when a user says
strncpy_from_unsafe(..., ..., 32)
and it returns 32 as the whole buffer was filled, it looks cleaner.
Thoughts?


--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Steven Rostedt Aug. 28, 2015, 10:11 p.m. UTC | #3
On Fri, 28 Aug 2015 15:08:35 -0700
Alexei Starovoitov <ast@plumgrid.com> wrote:

> On 8/28/15 2:48 PM, Steven Rostedt wrote:
> >>   * On success, returns the length of the string (not including the trailing
> >> >+ * NUL).
> > I think it includes the NUL.
> 
> oops. yes. that was a copy paste from strncpy_from_user comment.
> trace_kprobe usage wants NUL to be counted, so I intended to have it
> counted, but that brings the question what should be the semantics.
> Should it be similar to strncpy_from_user (not counting NUL) or
> similar to strlen_user (counts NUL) ?
> imo counting NUL makes a little bit more sense, since when a user says
> strncpy_from_unsafe(..., ..., 32)
> and it returns 32 as the whole buffer was filled, it looks cleaner.
> Thoughts?
> 

I personally prefer counting the NUL. I've had issues in the past with
the strncpy_from_user() not counting it :-p

-- Steve
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexei Starovoitov Aug. 28, 2015, 10:13 p.m. UTC | #4
On 8/28/15 3:11 PM, Steven Rostedt wrote:
> I personally prefer counting the NUL. I've had issues in the past with
> the strncpy_from_user() not counting it :-p

agreed. will respin with comment fixed. thank you much for review!

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/include/linux/uaccess.h b/include/linux/uaccess.h
index ae572c138607..d6f2c2c5b043 100644
--- a/include/linux/uaccess.h
+++ b/include/linux/uaccess.h
@@ -129,4 +129,6 @@  extern long __probe_kernel_read(void *dst, const void *src, size_t size);
 extern long notrace probe_kernel_write(void *dst, const void *src, size_t size);
 extern long notrace __probe_kernel_write(void *dst, const void *src, size_t size);
 
+extern long strncpy_from_unsafe(char *dst, const void *unsafe_addr, long count);
+
 #endif		/* __LINUX_UACCESS_H__ */
diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c
index b7d0cdd9906c..c9956440d0e6 100644
--- a/kernel/trace/trace_kprobe.c
+++ b/kernel/trace/trace_kprobe.c
@@ -165,11 +165,9 @@  DEFINE_BASIC_FETCH_FUNCS(memory)
 static void FETCH_FUNC_NAME(memory, string)(struct pt_regs *regs,
 					    void *addr, void *dest)
 {
-	long ret;
 	int maxlen = get_rloc_len(*(u32 *)dest);
 	u8 *dst = get_rloc_data(dest);
-	u8 *src = addr;
-	mm_segment_t old_fs = get_fs();
+	long ret;
 
 	if (!maxlen)
 		return;
@@ -178,23 +176,13 @@  static void FETCH_FUNC_NAME(memory, string)(struct pt_regs *regs,
 	 * Try to get string again, since the string can be changed while
 	 * probing.
 	 */
-	set_fs(KERNEL_DS);
-	pagefault_disable();
-
-	do
-		ret = __copy_from_user_inatomic(dst++, src++, 1);
-	while (dst[-1] && ret == 0 && src - (u8 *)addr < maxlen);
-
-	dst[-1] = '\0';
-	pagefault_enable();
-	set_fs(old_fs);
+	ret = strncpy_from_unsafe(dst, addr, maxlen);
 
 	if (ret < 0) {	/* Failed to fetch string */
-		((u8 *)get_rloc_data(dest))[0] = '\0';
+		dst[0] = '\0';
 		*(u32 *)dest = make_data_rloc(0, get_rloc_offs(*(u32 *)dest));
 	} else {
-		*(u32 *)dest = make_data_rloc(src - (u8 *)addr,
-					      get_rloc_offs(*(u32 *)dest));
+		*(u32 *)dest = make_data_rloc(ret, get_rloc_offs(*(u32 *)dest));
 	}
 }
 NOKPROBE_SYMBOL(FETCH_FUNC_NAME(memory, string));
diff --git a/lib/strncpy_from_user.c b/lib/strncpy_from_user.c
index e0af6ff73d14..89dce35ee6cf 100644
--- a/lib/strncpy_from_user.c
+++ b/lib/strncpy_from_user.c
@@ -112,3 +112,44 @@  long strncpy_from_user(char *dst, const char __user *src, long count)
 	return -EFAULT;
 }
 EXPORT_SYMBOL(strncpy_from_user);
+
+/**
+ * strncpy_from_unsafe: - Copy a NUL terminated string from unsafe address.
+ * @dst:   Destination address, in kernel space.  This buffer must be at
+ *         least @count bytes long.
+ * @src:   Unsafe address.
+ * @count: Maximum number of bytes to copy, including the trailing NUL.
+ *
+ * Copies a NUL-terminated string from unsafe address to kernel buffer.
+ *
+ * On success, returns the length of the string (not including the trailing
+ * NUL).
+ *
+ * If access fails, returns -EFAULT (some data may have been copied).
+ *
+ * If @count is smaller than the length of the string, copies @count bytes
+ * and returns @count.
+ */
+long strncpy_from_unsafe(char *dst, const void *unsafe_addr, long count)
+{
+	mm_segment_t old_fs = get_fs();
+	const void *src = unsafe_addr;
+	long ret;
+
+	if (unlikely(count <= 0))
+		return 0;
+
+	set_fs(KERNEL_DS);
+	pagefault_disable();
+
+	do {
+		ret = __copy_from_user_inatomic(dst++,
+						(const void __user __force *)src++, 1);
+	} while (dst[-1] && ret == 0 && src - unsafe_addr < count);
+
+	dst[-1] = '\0';
+	pagefault_enable();
+	set_fs(old_fs);
+
+	return ret < 0 ? ret : src - unsafe_addr;
+}