mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 1/2] lib: Fix strnlen_user() to not touch memory after specified maximum
@ 2015-06-02 15:10 Jan Kara
  2015-06-02 15:10 ` [PATCH 2/2] lib: Limit strnlen_user() return value to count + 1 Jan Kara
  2015-06-02 17:10 ` [PATCH 1/2] lib: Fix strnlen_user() to not touch memory after specified maximum Linus Torvalds
  0 siblings, 2 replies; 7+ messages in thread
From: Jan Kara @ 2015-06-02 15:10 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Andrew Morton, LKML, Jan Kara

If the specified maximum length of the string is a multiple of unsigned
long, we would load one long behind the specified maximum. If that
happens to be in a next page, we can hit a page fault although we were
not expected to.

Fix the off-by-one bug in the test whether we are at the end of the
specified range.

CC: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Jan Kara <jack@suse.cz>
---
 lib/strnlen_user.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/strnlen_user.c b/lib/strnlen_user.c
index a28df5206d95..fd03ae980013 100644
--- a/lib/strnlen_user.c
+++ b/lib/strnlen_user.c
@@ -57,7 +57,7 @@ static inline long do_strnlen_user(const char __user *src, unsigned long count,
 			return res + find_zero(data) + 1 - align;
 		}
 		res += sizeof(unsigned long);
-		if (unlikely(max < sizeof(unsigned long)))
+		if (unlikely(max <= sizeof(unsigned long)))
 			break;
 		max -= sizeof(unsigned long);
 		if (unlikely(__get_user(c,(unsigned long __user *)(src+res))))
-- 
2.1.4


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 2/2] lib: Limit strnlen_user() return value to count + 1
  2015-06-02 15:10 [PATCH 1/2] lib: Fix strnlen_user() to not touch memory after specified maximum Jan Kara
@ 2015-06-02 15:10 ` Jan Kara
  2015-06-02 17:08   ` Linus Torvalds
  2015-06-02 17:10 ` [PATCH 1/2] lib: Fix strnlen_user() to not touch memory after specified maximum Linus Torvalds
  1 sibling, 1 reply; 7+ messages in thread
From: Jan Kara @ 2015-06-02 15:10 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Andrew Morton, LKML, Jan Kara

Currently strnlen_user() can return numbers between 0 and
count + sizeof(unsigned long) - 1. Currently, no in tree users seem to
care but I have found out of tree users which were broken by this. They
wanted to truncate the string if it was too long to fit into a buffer
and didn't count with the fact that strnlen_user() can return more.
So make the function harder to use wrong and return count + 1 max.

CC: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Jan Kara <jack@suse.cz>
---
 lib/strnlen_user.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/lib/strnlen_user.c b/lib/strnlen_user.c
index fd03ae980013..2e47f9e16a79 100644
--- a/lib/strnlen_user.c
+++ b/lib/strnlen_user.c
@@ -54,7 +54,10 @@ static inline long do_strnlen_user(const char __user *src, unsigned long count,
 		if (has_zero(c, &data, &constants)) {
 			data = prep_zero_mask(c, data, &constants);
 			data = create_zero_mask(data);
-			return res + find_zero(data) + 1 - align;
+			res = res + find_zero(data) + 1 - align;
+			if (res > count)
+				return count + 1;
+			return res;
 		}
 		res += sizeof(unsigned long);
 		if (unlikely(max <= sizeof(unsigned long)))
-- 
2.1.4


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 2/2] lib: Limit strnlen_user() return value to count + 1
  2015-06-02 15:10 ` [PATCH 2/2] lib: Limit strnlen_user() return value to count + 1 Jan Kara
@ 2015-06-02 17:08   ` Linus Torvalds
  2015-06-03  9:21     ` Jan Kara
  0 siblings, 1 reply; 7+ messages in thread
From: Linus Torvalds @ 2015-06-02 17:08 UTC (permalink / raw)
  To: Jan Kara; +Cc: Andrew Morton, LKML

On Tue, Jun 2, 2015 at 8:10 AM, Jan Kara <jack@suse.cz> wrote:
> Currently strnlen_user() can return numbers between 0 and
> count + sizeof(unsigned long) - 1.

This is explicitly documented in the comment at the top of the function.

If there are out-of-tree users that don't check the return value
correctly, then those out-of-tree users are buggy.

Why not fix the real bug? And why are you not talking about *which*
out-of-tree user this is, and instead dancing around the issue.

So NAK on this. If you can actually convince me that the out-of-tree
user has some valid reason for its obvious bug, then dammit, the
comment at the top should also have been fixed.

But as it is, this is documented behavior and makes the code simpler,
and I can't for the life of me see any possible valid reason why
*anybody* could ever rely on anything but "retval > max". Which you
*have* to check anyway. Exactly as documented.

In fact, maybe we should change that

        if (res >= count)
                return count+1;

do return "count < INT_MAX ? INT_MAX : count + 1" or something, to
make sure nobody screws this up and doesn't try to use the value and
depend on "count+1".

Basically strnlen_user() does *not* have the same semantics as
"strlen()". Never has had. Very much unlike strnlen(), it has that "0
for EFAULT" rule, and it includes the final NUL chatacter, _and_ it
has that "retval > max" rule. They are all required, and they are all
documented rules.

               Linus

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 1/2] lib: Fix strnlen_user() to not touch memory after specified maximum
  2015-06-02 15:10 [PATCH 1/2] lib: Fix strnlen_user() to not touch memory after specified maximum Jan Kara
  2015-06-02 15:10 ` [PATCH 2/2] lib: Limit strnlen_user() return value to count + 1 Jan Kara
@ 2015-06-02 17:10 ` Linus Torvalds
  1 sibling, 0 replies; 7+ messages in thread
From: Linus Torvalds @ 2015-06-02 17:10 UTC (permalink / raw)
  To: Jan Kara; +Cc: Andrew Morton, LKML

On Tue, Jun 2, 2015 at 8:10 AM, Jan Kara <jack@suse.cz> wrote:
>
> Fix the off-by-one bug in the test whether we are at the end of the
> specified range.

Ack. I'll apply it with an added comment.

             Linus

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 2/2] lib: Limit strnlen_user() return value to count + 1
  2015-06-02 17:08   ` Linus Torvalds
@ 2015-06-03  9:21     ` Jan Kara
  2015-06-03 13:21       ` Linus Torvalds
  0 siblings, 1 reply; 7+ messages in thread
From: Jan Kara @ 2015-06-03  9:21 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Jan Kara, Andrew Morton, LKML

On Tue 02-06-15 10:08:11, Linus Torvalds wrote:
> On Tue, Jun 2, 2015 at 8:10 AM, Jan Kara <jack@suse.cz> wrote:
> > Currently strnlen_user() can return numbers between 0 and
> > count + sizeof(unsigned long) - 1.
> 
> This is explicitly documented in the comment at the top of the function.

The comment is:

 * strnlen_user: - Get the size of a user string INCLUDING final NUL.
 * @str: The string to measure.
 * @count: Maximum count (including NUL character)
 *
 * Context: User context only.  This function may sleep.
 *
 * Get the size of a NUL-terminated string in user space.
 *
 * Returns the size of the string INCLUDING the terminating NUL.
 * If the string is too long, returns 'count+1'.
 * On exception (or invalid count), returns 0.

My interpretation of the sentence "If the string is too long, returns
'count+1'." is that the function will never return more than count+1. But
that's not true. So either we should clarify the comment or fix the
function.

> If there are out-of-tree users that don't check the return value
> correctly, then those out-of-tree users are buggy.
> 
> Why not fix the real bug? And why are you not talking about *which*
> out-of-tree user this is, and instead dancing around the issue.

The buggy user is XFS DMAPI patches from SGI we carry in SUSE kernel. And
yes, we have fixed those (actually SGI did, I just merged the patches from
them).

> So NAK on this. If you can actually convince me that the out-of-tree
> user has some valid reason for its obvious bug, then dammit, the
> comment at the top should also have been fixed.

What they roughly did was:

	char buf[DM_ATTR_NAME_SIZE + 1];

	len = strnlen_user(from, DM_ATTR_NAME_SIZE);
	if (!len)
		return -EFAULT;
	if (copy_from_user(buf, from, len))
		return -EFAULT;
	buf[len - 1] = 0;

Which doesn't look that stupid to me (well, besides the fact that it's IMHO
better to return error than silently truncate the user provided string but
they do this for ages).

> But as it is, this is documented behavior and makes the code simpler,
> and I can't for the life of me see any possible valid reason why
> *anybody* could ever rely on anything but "retval > max". Which you
> *have* to check anyway. Exactly as documented.
> 
> In fact, maybe we should change that
> 
>         if (res >= count)
>                 return count+1;
> 
> do return "count < INT_MAX ? INT_MAX : count + 1" or something, to
> make sure nobody screws this up and doesn't try to use the value and
> depend on "count+1".
> 
> Basically strnlen_user() does *not* have the same semantics as
> "strlen()". Never has had. Very much unlike strnlen(), it has that "0
> for EFAULT" rule, and it includes the final NUL chatacter, _and_ it
> has that "retval > max" rule. They are all required, and they are all
> documented rules.
  Agreed, except for the fact that I don't think the comment explains well
that the return value larger than count+1 is possible.

								Honza
-- 
Jan Kara <jack@suse.cz>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 2/2] lib: Limit strnlen_user() return value to count + 1
  2015-06-03  9:21     ` Jan Kara
@ 2015-06-03 13:21       ` Linus Torvalds
  2015-06-03 13:42         ` Jan Kara
  0 siblings, 1 reply; 7+ messages in thread
From: Linus Torvalds @ 2015-06-03 13:21 UTC (permalink / raw)
  To: Jan Kara; +Cc: Andrew Morton, LKML

On Wed, Jun 3, 2015 at 2:21 AM, Jan Kara <jack@suse.cz> wrote:
>
> The comment is:

Yeah, and the comment right above do_strnlen_user() that you ignored is:

 * NOTE! We can sometimes overshoot the user-supplied maximum
 * if it fits in a aligned 'long'. The caller needs to check
 * the return value against "> max".

Which is pretty unambiguous.

The thing is, "retval > max" shiould be considered an error condition.
Exactly like 0 is, and the caller should check for that.

I do agree that we should change the other comment too, though. I
think there may have been some cutting-and-pasting when the code was

> What they roughly did was:
>
>         char buf[DM_ATTR_NAME_SIZE + 1];
>
>         len = strnlen_user(from, DM_ATTR_NAME_SIZE);
>         if (!len)
>                 return -EFAULT;
>         if (copy_from_user(buf, from, len))
>                 return -EFAULT;
>         buf[len - 1] = 0;

Yeah, don't do that.

It's stupid code anyway.

If what you wanted was "strncpy_from_user()", that's what you should have used.

That function actually takes care to be exact, because it obviously
has a destination buffer that it really cannot overshoot.

So

        char buf[DM_ATTR_NAME_SIZE + 1];

        if (strncpy_from_user(buf, from, len) < 0)
            return -EFAULT;
        buf[DM_ATTR_NAME_SIZE] = 0;

should actually work.

[ Side note: the generic strncpy_from_user() routine can be
inefficient on architectures that handle unaligned accesses badly, but
considering that it's used for copying pathnames from user space, I
hope such architectures have their own optimized version ]

I actually would like to get rid of "strnlen_user()" users as much as
humanly possible. It's a fundamentally racy interface, since we don't
control user memory, and another thread could change the string as it
is being counted. There are cases where we have to use it (execve
argument handling is I think the only real case of "yeah, we have no
alternatives"), so we can't get rid of it entirely, but I basically
don't believe in trying to make that interface at all easier to use.

I'd almost be inclined to unexport it. From a quick look, we don't
have any module users.

                  Linus

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 2/2] lib: Limit strnlen_user() return value to count + 1
  2015-06-03 13:21       ` Linus Torvalds
@ 2015-06-03 13:42         ` Jan Kara
  0 siblings, 0 replies; 7+ messages in thread
From: Jan Kara @ 2015-06-03 13:42 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Jan Kara, Andrew Morton, LKML

On Wed 03-06-15 06:21:45, Linus Torvalds wrote:
> On Wed, Jun 3, 2015 at 2:21 AM, Jan Kara <jack@suse.cz> wrote:
> >
> > The comment is:
> 
> Yeah, and the comment right above do_strnlen_user() that you ignored is:
> 
>  * NOTE! We can sometimes overshoot the user-supplied maximum
>  * if it fits in a aligned 'long'. The caller needs to check
>  * the return value against "> max".
> 
> Which is pretty unambiguous.
> 
> The thing is, "retval > max" shiould be considered an error condition.
> Exactly like 0 is, and the caller should check for that.
> 
> I do agree that we should change the other comment too, though. I
> think there may have been some cutting-and-pasting when the code was
  OK, I'll send a fix.

> > What they roughly did was:
> >
> >         char buf[DM_ATTR_NAME_SIZE + 1];
> >
> >         len = strnlen_user(from, DM_ATTR_NAME_SIZE);
> >         if (!len)
> >                 return -EFAULT;
> >         if (copy_from_user(buf, from, len))
> >                 return -EFAULT;
> >         buf[len - 1] = 0;
> 
> Yeah, don't do that.
> 
> It's stupid code anyway.
> 
> If what you wanted was "strncpy_from_user()", that's what you should have used.
> 
> That function actually takes care to be exact, because it obviously
> has a destination buffer that it really cannot overshoot.
> 
> So
> 
>         char buf[DM_ATTR_NAME_SIZE + 1];
> 
>         if (strncpy_from_user(buf, from, len) < 0)
>             return -EFAULT;
>         buf[DM_ATTR_NAME_SIZE] = 0;
> 
> should actually work.
  Yup, that's a good point.

> [ Side note: the generic strncpy_from_user() routine can be
> inefficient on architectures that handle unaligned accesses badly, but
> considering that it's used for copying pathnames from user space, I
> hope such architectures have their own optimized version ]
> 
> I actually would like to get rid of "strnlen_user()" users as much as
> humanly possible. It's a fundamentally racy interface, since we don't
> control user memory, and another thread could change the string as it
> is being counted. There are cases where we have to use it (execve
> argument handling is I think the only real case of "yeah, we have no
> alternatives"), so we can't get rid of it entirely, but I basically
> don't believe in trying to make that interface at all easier to use.
  Fair enough.

> I'd almost be inclined to unexport it. From a quick look, we don't
> have any module users.
  Audit code (kernel/auditsc.c) uses it for arguments of executables so
that looks like a valid use from a module...

								Honza
-- 
Jan Kara <jack@suse.cz>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2015-06-03 13:42 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-06-02 15:10 [PATCH 1/2] lib: Fix strnlen_user() to not touch memory after specified maximum Jan Kara
2015-06-02 15:10 ` [PATCH 2/2] lib: Limit strnlen_user() return value to count + 1 Jan Kara
2015-06-02 17:08   ` Linus Torvalds
2015-06-03  9:21     ` Jan Kara
2015-06-03 13:21       ` Linus Torvalds
2015-06-03 13:42         ` Jan Kara
2015-06-02 17:10 ` [PATCH 1/2] lib: Fix strnlen_user() to not touch memory after specified maximum Linus Torvalds

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®