2012-02-10 19:52:19

by Dan Smith

[permalink] [raw]
Subject: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

The inner function walk_pte_range() increments "addr" by PAGE_SIZE after
each pte is processed, and only exits the loop if the result is equal to
"end". Current, if either (or both of) the starting or ending addresses
passed to walk_page_range() are not page-aligned, then we will never
satisfy that exit condition and begin calling the pte_entry handler with
bad data.

To be sure that we will land in the right spot, this patch checks that
both "addr" and "end" are page-aligned in walk_page_range() before starting
the traversal.

Signed-off-by: Dan Smith <[email protected]>
Cc: [email protected]
Cc: [email protected]
---
mm/pagewalk.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)

diff --git a/mm/pagewalk.c b/mm/pagewalk.c
index 2f5cf10..9242bfc 100644
--- a/mm/pagewalk.c
+++ b/mm/pagewalk.c
@@ -196,6 +196,8 @@ int walk_page_range(unsigned long addr, unsigned long end,
if (addr >= end)
return err;

+ VM_BUG_ON((addr & ~PAGE_MASK) || (end & ~PAGE_MASK));
+
if (!walk->mm)
return -EINVAL;

--
1.7.9


2012-02-10 19:45:17

by Michal Nazarewicz

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

On Fri, 10 Feb 2012 20:39:56 +0100, Dan Smith <[email protected]> wrote:
> The inner function walk_pte_range() increments "addr" by PAGE_SIZE after

Commit message says about walk_pte_range() but commit changes walk_page_range().

> each pte is processed, and only exits the loop if the result is equal to
> "end". Current, if either (or both of) the starting or ending addresses

So why not change the condition to addr < end?

> passed to walk_page_range() are not page-aligned, then we will never
> satisfy that exit condition and begin calling the pte_entry handler with
> bad data.
>
> To be sure that we will land in the right spot, this patch checks that
> both "addr" and "end" are page-aligned in walk_page_range() before starting
> the traversal.
>
> Signed-off-by: Dan Smith <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> ---
> mm/pagewalk.c | 2 ++
> 1 files changed, 2 insertions(+), 0 deletions(-)
>
> diff --git a/mm/pagewalk.c b/mm/pagewalk.c
> index 2f5cf10..9242bfc 100644
> --- a/mm/pagewalk.c
> +++ b/mm/pagewalk.c
> @@ -196,6 +196,8 @@ int walk_page_range(unsigned long addr, unsigned long end,
> if (addr >= end)
> return err;
>+ VM_BUG_ON((addr & ~PAGE_MASK) || (end & ~PAGE_MASK));
> +
> if (!walk->mm)
> return -EINVAL;
>


--
Best regards, _ _
.o. | Liege of Serenely Enlightened Majesty of o' \,=./ `o
..o | Computer Science, Michał “mina86” Nazarewicz (o o)
ooo +----<email/xmpp: [email protected]>--------------ooO--(_)--Ooo--

2012-02-10 20:13:11

by Michal Nazarewicz

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

On Fri, 10 Feb 2012 20:57:31 +0100, Dan Smith <[email protected]> wrote:
> MN> Commit message says about walk_pte_range() but commit changes
> MN> walk_page_range().
>
> Yep, the issue occurs in walk_pte_range().

OK, it wasn't immediately obvious for me that while loop in walk_page_range()
will actually recover if arguments are not aligned (since pgd_addr_end() caps
returned value).

> The goal was to ensure that
> the external interface to it (which is walk_page_range()) does the check
> and avoids doing the walk entirely. I think the expectation is that
> walk_page_range() is used on aligned addresses. If we put the check in
> walk_pte_range() then only walks with a pte_entry handler would fail on
> unaligned addresses, which is potentially confusing.
>
> MN> So why not change the condition to addr < end?
>
> That would work, of course, but seems sloppier and less precise. The
> existing code was clearly written expecting to walk aligned addresses.

Fair enough.

--
Best regards, _ _
.o. | Liege of Serenely Enlightened Majesty of o' \,=./ `o
..o | Computer Science, Michał “mina86” Nazarewicz (o o)
ooo +----<email/xmpp: [email protected]>--------------ooO--(_)--Ooo--

2012-02-10 20:23:32

by Dan Smith

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

MN> Commit message says about walk_pte_range() but commit changes
MN> walk_page_range().

Yep, the issue occurs in walk_pte_range(). The goal was to ensure that
the external interface to it (which is walk_page_range()) does the check
and avoids doing the walk entirely. I think the expectation is that
walk_page_range() is used on aligned addresses. If we put the check in
walk_pte_range() then only walks with a pte_entry handler would fail on
unaligned addresses, which is potentially confusing.

MN> So why not change the condition to addr < end?

That would work, of course, but seems sloppier and less precise. The
existing code was clearly written expecting to walk aligned addresses.

--
Dan Smith
IBM Linux Technology Center
email: [email protected]

2012-02-13 10:12:26

by David Rientjes

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

On Fri, 10 Feb 2012, Dan Smith wrote:

> The inner function walk_pte_range() increments "addr" by PAGE_SIZE after
> each pte is processed, and only exits the loop if the result is equal to
> "end". Current, if either (or both of) the starting or ending addresses
> passed to walk_page_range() are not page-aligned, then we will never
> satisfy that exit condition and begin calling the pte_entry handler with
> bad data.
>
> To be sure that we will land in the right spot, this patch checks that
> both "addr" and "end" are page-aligned in walk_page_range() before starting
> the traversal.
>

It doesn't "ensure" anything without CONFIG_DEBUG_VM enabled, which isn't
the default.

2012-02-13 14:52:55

by Dan Smith

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

DR> It doesn't "ensure" anything without CONFIG_DEBUG_VM enabled, which
DR> isn't the default.

Are you proposing a change in verbiage or a stronger check? A
VM_BUG_ON() seemed on par with other checks, such as the one in
get_user_pages_fast().

--
Dan Smith
IBM Linux Technology Center
email: [email protected]

2012-02-13 21:55:34

by David Rientjes

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

On Mon, 13 Feb 2012, Dan Smith wrote:

> DR> It doesn't "ensure" anything without CONFIG_DEBUG_VM enabled, which
> DR> isn't the default.
>
> Are you proposing a change in verbiage or a stronger check? A
> VM_BUG_ON() seemed on par with other checks, such as the one in
> get_user_pages_fast().
>

That's not a precedent, there's a big difference between the performance
of gup_fast(), where we can't spare an additional compare and branch, and
walk_page_range(). VM_BUG_ON() is typically used in situations where a
debug kernel has been built, including CONFIG_DEBUG_VM, and the check
helps to isolate a problem that would be otherwise difficult to find. If
that fits the criteria, fine, but it doesn't "ensure" walk_page_range()
always has start and end addresses that are page aligned, so the changelog
needs to be modified to describe why an error in this path wouldn't be
evident.

2012-02-14 14:59:54

by Dan Smith

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

DR> That's not a precedent, there's a big difference between the
DR> performance of gup_fast(), where we can't spare an additional
DR> compare and branch, and walk_page_range(). VM_BUG_ON() is typically
DR> used in situations where a debug kernel has been built, including
DR> CONFIG_DEBUG_VM, and the check helps to isolate a problem that would
DR> be otherwise difficult to find.

Okay, fair enough. I was trying to stay in line with the other
conventions, knowing that the check would only be done with
CONFIG_DEBUG_VM enabled.

I'd rather just make it always do the check in walk_page_range(). Does
that sound reasonable?

--
Dan Smith
IBM Linux Technology Center
email: [email protected]

2012-02-14 21:04:49

by David Rientjes

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

On Tue, 14 Feb 2012, Dan Smith wrote:

> I'd rather just make it always do the check in walk_page_range(). Does
> that sound reasonable?
>

And do what if they're not? What behavior are you trying to fix from the
pagewalk code with respect to page-aligned addresses? Any specific
examples?

2012-02-15 14:39:41

by Dan Smith

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

DR> And do what if they're not? What behavior are you trying to fix
DR> from the pagewalk code with respect to page-aligned addresses? Any
DR> specific examples?

Sorry, I thought I detailed this in the patch header.

In walk_pte_entry(), the exit condition is when the end address is equal
to the start address + n*PAGE_SIZE. If they're not both page aligned,
then we'll never exit the loop and we'll start handing bad pte entries
to the handler function.

As was pointed out earlier in the thread, we could "solve" this by
making the exit condition be > instead of ==. However, that changes the
entirety of walk_page_range() from requiring page-aligned attributes to
silently tolerating them. IMHO, it's better to just
declare/check/enforce that they are.

I hit this recently because I was working with a prototype syscall that
took an address range from userspace and walked the pages. I ended up
passing non-page-aligned addresses, not knowing that walk_page_range()
needed it, and it took me a few days to figure out why my pte_entry
handler got a few good entries and then garbage until I crashed. I
turned on DEBUG_VM and got zero additional help. With the proposed
patch, I would have received a helpful smack in the head.

Does that make sense?

--
Dan Smith
IBM Linux Technology Center
email: [email protected]

2012-02-24 19:19:29

by Dan Smith

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

DR> but it doesn't "ensure" walk_page_range() always has start and end
DR> addresses that are page aligned

Below is a changed version of the patch which always does the
check. Since failing that condition indicates a kernel bug, WARN_ON()
makes sure it gets some visibility.

Andrew, can you take this?

--
Dan Smith
IBM Linux Technology Center
email: [email protected]

commit b06c2032d63f20d5a5513b3890776aeead397aa5
Author: Dan Smith <[email protected]>
Date: Fri Feb 24 11:07:05 2012 -0800

Ensure that walk_page_range()'s start and end are page-aligned

The inner function walk_pte_range() increments "addr" by PAGE_SIZE after
each pte is processed, and only exits the loop if the result is equal to
"end". Current, if either (or both of) the starting or ending addresses
passed to walk_page_range() are not page-aligned, then we will never
satisfy that exit condition and begin calling the pte_entry handler with
bad data.

To be sure that we will land in the right spot, this patch checks that
both "addr" and "end" are page-aligned in walk_page_range() before starting
the traversal.

Signed-off-by: Dan Smith <[email protected]>
Cc: [email protected]
Cc: [email protected]

diff --git a/mm/pagewalk.c b/mm/pagewalk.c
index 2f5cf10..97ee963 100644
--- a/mm/pagewalk.c
+++ b/mm/pagewalk.c
@@ -196,6 +196,11 @@ int walk_page_range(unsigned long addr, unsigned long end,
if (addr >= end)
return err;

+ if (WARN_ONCE((addr & ~PAGE_MASK) || (end & ~PAGE_MASK),
+ "address range is not page-aligned")) {
+ return -EINVAL;
+ }
+
if (!walk->mm)
return -EINVAL;

2012-02-24 20:55:22

by Andrew Morton

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

On Fri, 24 Feb 2012 11:19:25 -0800
Dan Smith <[email protected]> wrote:

>
> ...
>
> The inner function walk_pte_range() increments "addr" by PAGE_SIZE after
> each pte is processed, and only exits the loop if the result is equal to
> "end". Current, if either (or both of) the starting or ending addresses
> passed to walk_page_range() are not page-aligned, then we will never
> satisfy that exit condition and begin calling the pte_entry handler with
> bad data.
>
> To be sure that we will land in the right spot, this patch checks that
> both "addr" and "end" are page-aligned in walk_page_range() before starting
> the traversal.
>
> ...
>
> --- a/mm/pagewalk.c
> +++ b/mm/pagewalk.c
> @@ -196,6 +196,11 @@ int walk_page_range(unsigned long addr, unsigned long end,
> if (addr >= end)
> return err;
>
> + if (WARN_ONCE((addr & ~PAGE_MASK) || (end & ~PAGE_MASK),
> + "address range is not page-aligned")) {
> + return -EINVAL;
> + }
> +
> if (!walk->mm)
> return -EINVAL;

Well... why should we apply the patch? Is there some buggy code which
is triggering the problem? Do you intend to write some buggy code to
trigger the problem? ;)

IOW, what benefit is there to this change?

Also, as it's a developer-only thing we should arrange for the overhead
to vanish when CONFIG_DEBUG_VM=n?

2012-02-24 21:03:16

by Dan Smith

[permalink] [raw]
Subject: Re: [PATCH] Ensure that walk_page_range()'s start and end are page-aligned

AM> Well... why should we apply the patch? Is there some buggy code
AM> which is triggering the problem? Do you intend to write some buggy
AM> code to trigger the problem? ;)

Well, I already did and it took me longer to figure out than it should
have :)

AM> Also, as it's a developer-only thing we should arrange for the
AM> overhead to vanish when CONFIG_DEBUG_VM=n?

Heh, if you like that flavor, see the previous version of the patch at
the top of this thread :)

--
Dan Smith
IBM Linux Technology Center
email: [email protected]