For Programmers: Free Programming Magazines  


Home > Archive > Unix Programming > January 2007 > mprotect









You are viewing an archived Text-only version of the thread. To view this thread in it's original format and/or if you want to reply to this thread please [click here]

 

Author mprotect
Jan Althaus

2007-01-22, 8:14 am

Hi,
I'm having some trouble using mprotect. A short code snippet to give
you an idea of where this is heading:
char *p;

p = malloc(1024+PAGESIZE-1);
if (!p)
exit(errno);
p = (char *)(((int) p + PAGESIZE-1) & ~(PAGESIZE-1));
memset( (void*)p, 0xc3, 1024 ); /* set everything to "ret" */

/* here we set p to contain some binary code */

if (mprotect(p, 1024, PROT_EXEC|PROT_WRITE|PROT_READ)) {
exit(errno);
}

__asm__ __volatile__ (
"call *%0 \n"
:
: "m"(p)
);
exit(0);

Now the problem is that the call always causes a segfault. I tried
setting the memory to just PROT_EXEC with no luck... so I'm wondering:
Is there something else I need to do in order for Linux to allow me
execution of a certain page? I'm really out of ideas here... :/
When I pass the last asm segment a pointer to a function that I
declared in c++ it works fine. So it has to be the memory protection
not doing what I expect it to...

David T. Ashley

2007-01-22, 8:14 am

"Jan Althaus" <herrwurst@gmail.com> wrote in message
news:1169406456.696898.279520@38g2000cwa.googlegroups.com...
> Hi,
> I'm having some trouble using mprotect. A short code snippet to give
> you an idea of where this is heading:
> char *p;
>
> p = malloc(1024+PAGESIZE-1);
> if (!p)
> exit(errno);
> p = (char *)(((int) p + PAGESIZE-1) & ~(PAGESIZE-1));
> memset( (void*)p, 0xc3, 1024 ); /* set everything to "ret" */
>
> /* here we set p to contain some binary code */
>
> if (mprotect(p, 1024, PROT_EXEC|PROT_WRITE|PROT_READ)) {
> exit(errno);
> }
>
> __asm__ __volatile__ (
> "call *%0 \n"
> :
> : "m"(p)
> );
> exit(0);
>
> Now the problem is that the call always causes a segfault. I tried
> setting the memory to just PROT_EXEC with no luck... so I'm wondering:
> Is there something else I need to do in order for Linux to allow me
> execution of a certain page? I'm really out of ideas here... :/
> When I pass the last asm segment a pointer to a function that I
> declared in c++ it works fine. So it has to be the memory protection
> not doing what I expect it to...


I'm not understanding your code. Specifically:

#1: The smallest granularity of mprotect() is a page (presumably a virtual
memory page). What guarantee do you have that the area of memory you've
obtained via malloc() spans only one page? Specifically, it could span two
or more pages (it doesn't have to start at or near a page boundary).

#2: This statement:

> p = (char *)(((int) p + PAGESIZE-1) & ~(PAGESIZE-1));


can lead to serious airthmetic trouble if a pointer on your system is bigger
than an integer.

#3: Where are you getting PAGESIZE? What value are you using?

#4: This statement:

> p = (char *)(((int) p + PAGESIZE-1) & ~(PAGESIZE-1));


seems unnecessary, as the definition of mprotect() inducates it will do this
anyway, i.e. it snags the page on which the address is.

#5: Where are you getting this from:

> if (mprotect(p, 1024, PROT_EXEC|PROT_WRITE|PROT_READ)) {


??

This creates an almost guaranteed failure if the memory returned by malloc()
spans a virtual page boundary.

First, you round p down to be modulo PAGESIZE, then you specify a length
which is almost certainly less than or equal to a PAGESIZE, indicating that
you will give permissions to at most one page. If the memory you got via
malloc() is near a page boundary, you are sunk.

Summary #1: I don't think you're giving permissions to the pages you want
to.

Summary #2: There still may be other problems because the pages you are
changing the permissions on may contain other things, i.e. you haven't
determined that the pages you're messing with are fully free for you to muck
around with. I believe there are uncovered boundary cases.

Dave.

--
David T. Ashley (dta@e3ft.com)
http://www.e3ft.com (Consulting Home Page)
http://www.dtashley.com (Personal Home Page)
http://gpl.e3ft.com (GPL Publications and Projects)


Paul Pluzhnikov

2007-01-22, 8:14 am

"David T. Ashley" <dta@e3ft.com> writes:

> #1: The smallest granularity of mprotect() is a page (presumably a virtual
> memory page). What guarantee do you have that the area of memory you've
> obtained via malloc() spans only one page?


He has a guarantee that his area spans two pages, since he asked
for PAGESIZE+1023

> #2: This statement:
>
>
> can lead to serious airthmetic trouble if a pointer on your system is bigger
> than an integer.


True, but he is (apparenly) on x86, and so can assume
sizeof(int)==sizeof(char*)==4.

> #4: This statement:
>
> seems unnecessary, as the definition of mprotect() inducates it will do this
> anyway,


Which definition of mprotect() is that?
The linux one says mprotect will fail if p is not page-aligned.
So does SUSv3:

>
> ??
>
> This creates an almost guaranteed failure if the memory returned by malloc()
> spans a virtual page boundary.


It doesn't since he page-aligned it.

> First, you round p down to be modulo PAGESIZE, then you specify a length
> which is almost certainly less than or equal to a PAGESIZE, indicating that
> you will give permissions to at most one page. If the memory you got via
> malloc() is near a page boundary, you are sunk.


Huh? I think you are mistaken.

> Summary #1: I don't think you're giving permissions to the pages you want
> to.


No, he appears to do exactly as he should. In fact his code follows
example usage given in the Linux "man mprotect".

> Summary #2: There still may be other problems because the pages you are
> changing the permissions on may contain other things, i.e. you haven't
> determined that the pages you're messing with are fully free for you to muck
> around with. I believe there are uncovered boundary cases.


The pages he got from malloc() are garanteed to have PROT_READ|PROT_WRITE.
Adding PROT_EXEC shouldn't really cause any problems (though it
may modify behaviour of other buggy code that may try to execute
"malloc data").

Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email.
David T. Ashley

2007-01-22, 8:14 am

"Paul Pluzhnikov" <ppluzhnikov-nsp@charter.net> wrote in message
news:m3tzykt6uc.fsf@somewhere.in.california.localhost...
> "David T. Ashley" <dta@e3ft.com> writes:
>
>
> He has a guarantee that his area spans two pages, since he asked
> for PAGESIZE+1023


Agreed. It spans two pages.

>
>
> True, but he is (apparenly) on x86, and so can assume
> sizeof(int)==sizeof(char*)==4.


"Apparently" isn't good enough. He needs to verify this.

>
> Which definition of mprotect() is that?
> The linux one says mprotect will fail if p is not page-aligned.
> So does SUSv3:


You are correct (I missed that and got it with something else):

<BEGIN>
The implementation shall require that addr be a multiple of the page size as
returned by sysconf().
<END>

I agree that p must be on a page boundary.

>
> It doesn't since he page-aligned it.


You are correct. However, an issue seems to be that he is potentially
writing to memory BEFORE the block returned by malloc() (because p was
rounded downwards). I don't know what effect that might have, or if it
could set a chain of events in progress that leads to a segfault.

>
> Huh? I think you are mistaken.


You are correct. I was mistaken.

>
> No, he appears to do exactly as he should. In fact his code follows
> example usage given in the Linux "man mprotect".
>
>
> The pages he got from malloc() are garanteed to have PROT_READ|PROT_WRITE.
> Adding PROT_EXEC shouldn't really cause any problems (though it
> may modify behaviour of other buggy code that may try to execute
> "malloc data").


Summary (and I hope I get it right this time):

a)The pointer arithmetic to round p down needs to be double-checked (32- vs.
64-bit).

b)It appears he will be writing a block potentially before the one obtained
from malloc() (i.e. memory that might belong to something else). It isn't
clear if this will have an effect.

c)On my system documentation, it makes reference to calling mmap() first.
I'm not sure what this means.

--
David T. Ashley (dta@e3ft.com)
http://www.e3ft.com (Consulting Home Page)
http://www.dtashley.com (Personal Home Page)
http://gpl.e3ft.com (GPL Publications and Projects)


loic-dev@gmx.net

2007-01-22, 8:14 am

Hello Jan,

> I'm having some trouble using mprotect. A short code snippet to give
> you an idea of where this is heading:
> char *p;
>
> p = malloc(1024+PAGESIZE-1);
> if (!p)
> exit(errno);
> p = (char *)(((int) p + PAGESIZE-1) & ~(PAGESIZE-1));
> memset( (void*)p, 0xc3, 1024 ); /* set everything to "ret" */
>
> /* here we set p to contain some binary code */
>
> if (mprotect(p, 1024, PROT_EXEC|PROT_WRITE|PROT_READ)) {
> exit(errno);
> }
>
> __asm__ __volatile__ (
> "call *%0 \n"
> :
> : "m"(p)
> );
> exit(0);
>
> Now the problem is that the call always causes a segfault. I tried
> setting the memory to just PROT_EXEC with no luck... so I'm wondering:
> Is there something else I need to do in order for Linux to allow me
> execution of a certain page? I'm really out of ideas here... :/
> When I pass the last asm segment a pointer to a function that I
> declared in c++ it works fine. So it has to be the memory protection
> not doing what I expect it to...


On which Linux system do you get the SIGSEGV? I tried out on my FC5 /
i686 / kernel 2.6.17, works fine for me.

Cheers,
Loic.

David Schwartz

2007-01-22, 8:14 am


Paul Pluzhnikov wrote:

> True, but he is (apparenly) on x86, and so can assume
> sizeof(int)==sizeof(char*)==4.


Eh? What evidence do you have that he's not on x86-64, where pointers
are too big to fit into integers.

DS

Paul Pluzhnikov

2007-01-22, 8:14 am

"David Schwartz" <davids@webmaster.com> writes:

> Paul Pluzhnikov wrote:
>
>
> Eh? What evidence do you have that he's not on x86-64, where pointers
> are too big to fit into integers.


This one (from the original post; I'll grant you it's
circumstantial):

X-HTTP-UserAgent: Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.8.1.1) Gecko/20061208 Firefox/2.0.0.1,gzip(gfe),gzip(gfe)

Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email.
Paul Pluzhnikov

2007-01-22, 8:14 am

Paul Pluzhnikov <ppluzhnikov-nsp@charter.net> writes:
[color=darkred]
> "David Schwartz" <davids@webmaster.com> writes:
>

In addition, if he was on x86_64 and had trucated the pointer by
converting it to int and back, it is quite unlikely that his
mprotect() call would then succeed.

Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email.
Paul Pluzhnikov

2007-01-22, 8:14 am

loic-dev@gmx.net writes:

> On which Linux system do you get the SIGSEGV? I tried out on my FC5 /
> i686 / kernel 2.6.17, works fine for me.


Also succeeds on Linux/x86 FC2 (2.6.10-1.771_FC2smp, gcc 3.3.3
20040412 (Red Hat Linux 3.3.3-7)) and RHEL-4 (2.6.9-5.EL, gcc 3.4.3
20041212 (Red Hat 3.4.3-9.EL4))

Jan, you should post your complete test case -- there is got to
be something wrong with *it* -- when we add missing pieces, we fix
whatever bug you had in it.

Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email.
Paul Pluzhnikov

2007-01-22, 8:14 am

"David T. Ashley" <dta@e3ft.com> writes:

[color=darkred]
> You are correct. However, an issue seems to be that he is potentially
> writing to memory BEFORE the block returned by malloc() (because p was
> rounded downwards).


The construct above is a common way to round *up*, not down.
There is no "potential underflow" here.

> a)The pointer arithmetic to round p down needs to be double-checked (32- vs.
> 64-bit).


Yes.

> b)It appears he will be writing a block potentially before the one obtained
> from malloc() (i.e. memory that might belong to something else). It isn't
> clear if this will have an effect.


Incorrect.

> c)On my system documentation, it makes reference to calling mmap() first.
> I'm not sure what this means.


SUSv3 says that 'result of mprotect is undefined if memory was not
obtained via mmap() to begin with', but Linux appears not to make
such a restriction, and (as I said before) the original code comes
almost verbatim from Linux "man mprotect" page.

Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email.
Igmar Palsenberg

2007-01-22, 8:14 am

Jan Althaus wrote:

> char *p;
>
> p = malloc(1024+PAGESIZE-1);
> if (!p)
> exit(errno);
> p = (char *)(((int) p + PAGESIZE-1) & ~(PAGESIZE-1));
> memset( (void*)p, 0xc3, 1024 ); /* set everything to "ret" */
>
> /* here we set p to contain some binary code */
>
> if (mprotect(p, 1024, PROT_EXEC|PROT_WRITE|PROT_READ)) {
> exit(errno);
> }


You assume here that malloc() will *always* return a mmap()'ed area.
That assumption is basically flawed : it is implementation defined.

On my system, the malloc() implementation uses brk() to allocate the
memory in the malloc() call. mprotect() *may* decide to ignore the
request, since it is not allocated by mmap() in this case.

Result : The protection doesn't change --> segfault.



Igmar
Casper H.S. Dik

2007-01-22, 8:14 am

Igmar Palsenberg <igmar@palsenberg.local> writes:

>You assume here that malloc() will *always* return a mmap()'ed area.
>That assumption is basically flawed : it is implementation defined.


You are, OTOH, assuming that mprotect() only works on mmap'ed memory.
That, too, is a flawed assumption. On your typical VM system all
memory is created equal and mprotect can be used to change the mappings.
In some cases, though, the maximum permissions available are less than
you may want.

>On my system, the malloc() implementation uses brk() to allocate the
>memory in the malloc() call. mprotect() *may* decide to ignore the
>request, since it is not allocated by mmap() in this case.


No, mprotect cannot "decide to ignore" the request. It must return an
error when the permissions are not as requested after the call completes.

Casper
--
Expressed in this posting are my opinions. They are in no way related
to opinions held by my employer, Sun Microsystems.
Statements on Sun products included here are not gospel and may
be fiction rather than truth.
David T. Ashley

2007-01-22, 7:05 pm

"Paul Pluzhnikov" <ppluzhnikov-nsp@charter.net> wrote in message
news:m3odorsimo.fsf@somewhere.in.california.localhost...
> "David T. Ashley" <dta@e3ft.com> writes:
>
>
> Incorrect.


Hi Paul,

Could you let me know your logic on this one?

It seems to me that if malloc() returns a pointer with any 1's in the last
several bits, these two statements:

p = (char *)(((int) p + PAGESIZE-1) & ~(PAGESIZE-1));
memset( (void*)p, 0xc3, 1024 ); /* set everything to "ret" */

are potentially a disaster. In the first statement, the pointer may be
rounded down, and in the second statement, that pointer is used. It isn't
clear to me why you believe this would be guaranteed to have no effect.

This seems very likely to overwrite memory that might belong to something
else (static variables from other software modules, etc.).

Thanks, Dave.
--
David T. Ashley (dta@e3ft.com)
http://www.e3ft.com (Consulting Home Page)
http://www.dtashley.com (Personal Home Page)
http://gpl.e3ft.com (GPL Publications and Projects)


Paul Pluzhnikov

2007-01-22, 7:05 pm

"David T. Ashley" <dta@e3ft.com> writes:

> It seems to me that if malloc() returns a pointer with any 1's in the last
> several bits, these two statements:
>
> p = (char *)(((int) p + PAGESIZE-1) & ~(PAGESIZE-1));
> memset( (void*)p, 0xc3, 1024 ); /* set everything to "ret" */
>
> are potentially a disaster.


Let's consider an example. Assume:

PAGESIZE == 4096 == 0x1000
p = malloc(4096+1023) == 0x12340128

Then:

(int)p + (PAGESIZE-1) == 0x12340128 + 0x00000FFF == 0x12341127
((int)p + (PAGESIZE-1)) & ~(PAGESIZE-1) == 0x12341127 & 0xFFFFF000 == 0x12341000

So the result is that pointer is rounded *up* to the page boundary.

> In the first statement, the pointer may be rounded down


It can't -- the "+ PAGESIZE-1" prevents that.

Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email.
Igmar Palsenberg

2007-01-22, 7:05 pm

Casper H.S. Dik wrote:

> You are, OTOH, assuming that mprotect() only works on mmap'ed memory.
> That, too, is a flawed assumption.


On Linux it isn't. mprotect() only works on mmap()'ed paged there.

> On your typical VM system all
> memory is created equal and mprotect can be used to change the mappings.
> In some cases, though, the maximum permissions available are less than
> you may want.


That indeed might be true on some systems. The topic starter mentions
Linux, so my remarks still apply.

>
> No, mprotect cannot "decide to ignore" the request. It must return an
> error when the permissions are not as requested after the call completes.


Not according to the manpage :

The behavior of this function is unspecified if the mapping was not
established by a call to mmap().

'unspecified' also includes ignoring the request. That's what I'm seeing
: mprotect() isn't returning an error.



Igmar
David T. Ashley

2007-01-22, 7:05 pm

"Paul Pluzhnikov" <ppluzhnikov-nsp@charter.net> wrote in message
news:m3y7nunc6u.fsf@somewhere.in.california.localhost...
> "David T. Ashley" <dta@e3ft.com> writes:
>
>
> Let's consider an example. Assume:
>
> PAGESIZE == 4096 == 0x1000
> p = malloc(4096+1023) == 0x12340128
>
> Then:
>
> (int)p + (PAGESIZE-1) == 0x12340128 + 0x00000FFF ==
> 0x12341127
> ((int)p + (PAGESIZE-1)) & ~(PAGESIZE-1) == 0x12341127 & 0xFFFFF000 ==
> 0x12341000
>
> So the result is that pointer is rounded *up* to the page boundary.
>
>
> It can't -- the "+ PAGESIZE-1" prevents that.


OK, I agree. I am having a low-caffeine w.

Somehow I was having a mental block and just didn't see the "+ PAGESIZE -
1".

So, it appears that whatever result is returned by malloc(), the caller has
in the neighborhood of 1024 bytes he should be able to play with.

I'm on the train now.

Thanks for the generous investment of your time.

--
David T. Ashley (dta@e3ft.com)
http://www.e3ft.com (Consulting Home Page)
http://www.dtashley.com (Personal Home Page)
http://gpl.e3ft.com (GPL Publications and Projects)


Paul Pluzhnikov

2007-01-22, 7:05 pm

Igmar Palsenberg <igmar@palsenberg.local> writes:

> Casper H.S. Dik wrote:
>
>
> On Linux it isn't. mprotect() only works on mmap()'ed paged there.


The statement above is very unlikely to be true.

Please your statement by posting your test case and exact system
details for a Linux system on which mprotect() fails to work on
malloc()ed memory.

Two people (myself and Loic) have confirmed in this thread that
mprotect()ing malloc()ed memory *does* work on Linux.

And the test case itself comes almost verbatim from *Linux*
"man mprotect".

Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email.
Paul Pluzhnikov

2007-01-22, 7:05 pm

Paul Pluzhnikov <ppluzhnikov-nsp@charter.net> writes:

> Please your statement by posting your test case and exact system


Should be "Please prove your statement by ..."

Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email.
Jan Althaus

2007-01-23, 4:10 am

Wow, first of all: Thank you guys a _lot_ for all those replies. Very
much appreciated!
Reading your comments about it working on several other systems I had
the definite feeling that there must've been something very stupid I
did wrong; and indeed so!
I used kdbg for testing the program and for some reason that is
absolutely beyond me it'd always quit a sigabrt or sigsegv... until I
restarted it... ARGH!!!
I can finally rest easy... :)
Again, thank you guys for all the help! Hope I didn't waste too much of
your time :)


Paul Pluzhnikov wrote:

> Paul Pluzhnikov <ppluzhnikov-nsp@charter.net> writes:
>
>
> Should be "Please prove your statement by ..."
>
> Cheers,
> --
> In order to understand recursion you must first understand recursion.
> Remove /-nsp/ for email.


Sponsored Links







Also available: Server administration forum archive | Web Design forum archive | Software forum archive | Hardware reviews archive

Copyright 2010 codecomments.com