stack smashing


F

frank

I've heard of stack smashing but never done it myself until about 36
hours ago. Wiki had an example that I'm having problems following, but
it does do the trick:

http://en.wikipedia.org/wiki/Stack_buffer_overflow

[email protected]:~/source$ gcc -std=c99 -Wall -Wextra ss1.c -o out; ./out
a is 4
b is 32
My Float value = 10.500000
My Float value = 10.500000
*** stack smashing detected ***: ./out terminated
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)[0xb7fc0da8]
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x0)[0xb7fc0d60]
../out[0x8048536]
[0x21212067]
======= Memory map: ========
08048000-08049000 r-xp 00000000 08:04 213111 /home/dan/source/out
08049000-0804a000 r--p 00000000 08:04 213111 /home/dan/source/out
0804a000-0804b000 rw-p 00001000 08:04 213111 /home/dan/source/out
09cff000-09d20000 rw-p 09cff000 00:00 0 [heap]
b7ea4000-b7eb1000 r-xp 00000000 08:01 2601 /lib/libgcc_s.so.1
b7eb1000-b7eb2000 r--p 0000c000 08:01 2601 /lib/libgcc_s.so.1
b7eb2000-b7eb3000 rw-p 0000d000 08:01 2601 /lib/libgcc_s.so.1
b7ec2000-b7ec3000 rw-p b7ec2000 00:00 0
b7ec3000-b801f000 r-xp 00000000 08:01 2661
/lib/tls/i686/cmov/libc-2.9.so
b801f000-b8020000 ---p 0015c000 08:01 2661
/lib/tls/i686/cmov/libc-2.9.so
b8020000-b8022000 r--p 0015c000 08:01 2661
/lib/tls/i686/cmov/libc-2.9.so
b8022000-b8023000 rw-p 0015e000 08:01 2661
/lib/tls/i686/cmov/libc-2.9.so
b8023000-b8026000 rw-p b8023000 00:00 0
b8034000-b8037000 rw-p b8034000 00:00 0
b8037000-b8038000 r-xp b8037000 00:00 0 [vdso]
b8038000-b8054000 r-xp 00000000 08:01 8001 /lib/ld-2.9.so
b8054000-b8055000 r--p 0001b000 08:01 8001 /lib/ld-2.9.so
b8055000-b8056000 rw-p 0001c000 08:01 8001 /lib/ld-2.9.so
bf9f6000-bfa0b000 rw-p bffeb000 00:00 0 [stack]
Aborted
[email protected]:~/source$ cat ss1.c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void foo (char *bar)
{
float My_Float = 10.5; // Addr = 0x0023FF4C
char c[12]; // Addr = 0x0023FF30
size_t a, b;

a = sizeof(float);
printf("a is %d\n", a);
b = strlen( bar);
printf("b is %d\n", b);



// Will print 10.500000
printf("My Float value = %f\n", My_Float);




/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Memory map:
@ : c allocated memory
# : My_Float allocated memory
- : other memory


*c *My_Float
0x0023FF30 0x0023FF4C
| |
@@@@@@@@@@@@----------------#####
foo("my string is too long !!!!! XXXXX");

memcpy will put 0x1010C042 in My_Float value.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

memcpy(c, bar, strlen(bar)); // no bounds checking...


// Will print 96.031372
printf("My Float value = %f\n", My_Float);
}

int main (void)
{
foo("my string is too long !!!!! \x10\x10\xC0\x42");
return 0;
}



// gcc -std=c99 -Wall -Wextra ss1.c -o out; ./out

[email protected]:~/source$

I have a couple questions:

1) Does the backtrace and memory map data tell anyone something of
relevance?

2) Why do I not get 96.03 as the wiki promises?

Thanks for your comment.
 
Ad

Advertisements

A

Alan Curry

I've heard of stack smashing but never done it myself until about 36
hours ago. Wiki had an example that I'm having problems following, but
it does do the trick:

http://en.wikipedia.org/wiki/Stack_buffer_overflow
[snip]

1) Does the backtrace and memory map data tell anyone something of
relevance?

A little bit. The backtrace ended at 0x21212067, which is "!! g" in ASCII.
It's a piece of the "too long !!!!!" string, appearing reversed because
you're on a little-endian architecture.
2) Why do I not get 96.03 as the wiki promises?

It's an example, not a promise. Come on, man, this is a demonstration of what
evil things can be done with deliberately invalid code. You can't expect it
to just work out of the box!

Exploiting buffer overflows is delicate work. When the victim doesn't use the
same compile and options that the attacker was expecting, the memory
addresses come out wrong and it doesn't work. The attacker's next step would
be to adjust the length of the string, and the 0x21212067 would be a helpful
clue.

Quit trying to learn how to exploit bad code, at least until you've learned
to write good code. Here's a wikipedia article on that:
http://en.wikipedia.org/wiki/Script_kiddie
 
F

frank

Alan said:
I've heard of stack smashing but never done it myself until about 36
hours ago. Wiki had an example that I'm having problems following, but
it does do the trick:

http://en.wikipedia.org/wiki/Stack_buffer_overflow
[snip]

1) Does the backtrace and memory map data tell anyone something of
relevance?

A little bit. The backtrace ended at 0x21212067, which is "!! g" in ASCII.
It's a piece of the "too long !!!!!" string, appearing reversed because
you're on a little-endian architecture.
Interesting.
2) Why do I not get 96.03 as the wiki promises?

It's an example, not a promise. Come on, man, this is a demonstration of what
evil things can be done with deliberately invalid code. You can't expect it
to just work out of the box!

Exploiting buffer overflows is delicate work. When the victim doesn't use the
same compile and options that the attacker was expecting, the memory
addresses come out wrong and it doesn't work. The attacker's next step would
be to adjust the length of the string, and the 0x21212067 would be a helpful
clue.
Right.

Quit trying to learn how to exploit bad code, at least until you've learned
to write good code. Here's a wikipedia article on that:
http://en.wikipedia.org/wiki/Script_kiddie

It doesn't make much sense for me to hack myself here. I'm trying to
figure out why this is happening in other code:
#define PATH_SIZE 100

int readdir_r(DIR *restrict dirp, struct dirent *restrict entry,
struct dirent **restrict result);

int lstat(const char *restrict path, struct stat *restrict buf);


ssize_t readlink(const char *restrict path, char *restrict buf,
size_t bufsize);


unsigned
process_directory (char *theDir)
{
DIR *dir = NULL;
struct dirent entry;
struct dirent *entryPtr = NULL;
int retval = 0;
unsigned count = 0;
char pathName[PATH_SIZE + 1];

dir = opendir (theDir);
if (dir == NULL)
{
printf ("Error opening %s: %s", theDir, strerror (errno));
return 0;
}

retval = readdir_r (dir, &entry, &entryPtr);
while (entryPtr != NULL)
{
struct stat entryInfo;

if ((strncmp (entry.d_namessize_t readlink(const char *restrict
path, char *restrict buf,
size_t bufsize);, ".", PATH_SIZE) == 0) ||
(strncmp (entry.d_name, "..", PATH_SIZE) == 0))
{
/* Short-circuit the . and .. entries. */
retval = readdir_r (dir, &entry, &entryPtr);
continue;
}

(void) strncpy (pathName, theDir, PATH_SIZE);
(void) strncat (pathName, "/", PATH_SIZE);
(void) strncat (pathName, entry.d_name, PATH_SIZE);

if (lstat (pathName, &entryInfo) == 0)
{
/* stat() succeeded, let's party */
count++;

if (S_ISDIR (entryInfo.st_mode))
{ /* directory */
printf ("processing %s/\n", pathName);
count += process_directory (pathName);
}
else if (S_ISREG (entryInfo.st_mode))
{ /* regular file */
printf ("\t%s has %lld bytes\n",
pathName, (long long) entryInfo.st_size);
}
else if (S_ISLNK (entryInfo.st_mode))
{ /* symbolic link */
char targetName[PATH_SIZE + 1];
if (readlink (pathName, targetName, PATH_SIZE) != -1)
{
printf ("\t%s -> %s\n", pathName, targetName);
}
else
{
printf ("\t%s -> (invalid symbolic link!)\n", pathName);
}
}
}
else
{ssize_t readlink(const char *restrict path, char *restrict buf,
size_t bufsize);
printf ("Error statting %s: %s\n", pathName, strerror (errno));
}

retval = readdir_r (dir, &entry, &entryPtr);
}

/* Close the directory and return the number of entries. */
(void) closedir (dir);
return count;
}

int
main (void)
{
process_directory ("/home/dan/source");
return EXIT_SUCCESS;
}

I wonder if my kludge to work around PATH_MAX is causing the stack
corruption.
 
B

Ben Bacarisse

frank said:
(void) strncpy (pathName, theDir, PATH_SIZE);
(void) strncat (pathName, "/", PATH_SIZE);
(void) strncat (pathName, entry.d_name, PATH_SIZE);

Just a quick heads up: strncpy is almost always the wrong function to
use. For one thing, you may end up with something that is not a
string.

Also, strncat does not do what you seem to think. The last argument
is the maximum number of (non-null) characters to append to the
buffer. Using PATH_SIZE (one less than the buffer size) is therefore
not useful. There is really no way to do this without knowing the
sizes of the string involved.

<snip>
 
F

frank

Ben said:
Just a quick heads up: strncpy is almost always the wrong function to
use. For one thing, you may end up with something that is not a
string.

Ben, I'm not strong at all with string processing in C. I see no
warnings about strncpy in H&S. What should I use instead?
Also, strncat does not do what you seem to think. The last argument
is the maximum number of (non-null) characters to append to the
buffer. Using PATH_SIZE (one less than the buffer size) is therefore
not useful. There is really no way to do this without knowing the
sizes of the string involved.

It makes some sense to me. It does however look flawed in general
because you'd have
/home/dan/source/ and 400 null characters then
/home/dan/source// and 399 null characters then
/home/dan/source//rd7.c and 395 null characters

I don't know. I'm trying to break down this source and was hoping that
it was written by an expert, but it appears to have been written by
someone with little experience or ability.
 
F

frank

Richard said:
In this case, malloc (to build a sufficiently long buffer), followed by
a check to ensure it succeeded, followed either by error handling or
sprintf.

Well yes and no. My buffer wasn't long enough. When I switched from
100, which I thought was plenty, to 300 for PATH_SIZE, the program
compiled and behaved as far as I could tell. I got a good tip from
yowie in alt.os.linux.ubuntu there.

So there is a proper value for this declaration:
char pathName[PATH_SIZE + 1];
so that one doesn't overwrite pathName, which is precisely what I was doing.

I haven't yet figured out how to get the information that has supplanted
PATH_MAX. I'm told this:

So instead of using PATH_MAX, you call pathconf(filename, _PC_PATH_MAX),
where filename is the name of a file on the filesystem whose maximum
pathname length you're interested in.

end quote. How do I know where any file is without a path? So I need a
path to find the file that is going to tell me how large _PC_PATH_MAX is
going to be for that directory.

Sounds like a circle to me. :-(
 
Ad

Advertisements

A

Antoninus Twink

size_t is an unsigned integral type. If you want to pass a size_t to
printf to match a %d format specifier, cast it to int.

This is exceptionally poor advice - and as usual, the famous "clc peer
review" that would have seen half a dozen people piling in to point out
that the resulting int might have been a trap representation or some
such nonsense if a newbie had posted this remains strangely silent when
it is one of their chums who's boobooed.

Casting to an int so that you can use %d is utterly stupid when you can
just use the %zu format specifier and not risk printing nonsense in the
quite likely event that size_t is wider than int.

If you happen to be stuck with C90 (and hopefully you've got a better
reason for this than Heathfield's one of irrational prejudice), then
using %lu and casting to unsigned long int is a much better option than
using %d and casting to int.
 
N

Nobody

Ben, I'm not strong at all with string processing in C. I see no
warnings about strncpy in H&S. What should I use instead?

In C99 (or on Unix), you can use snprintf(), e.g.:

snprintf(pathName, sizeof pathName, "%s/%s", theDir, entry.d_name);

Unlike the strn* functions, snprintf() always includes a NUL byte.
Unfortunately, it's not in C89.

[Where a function takes a pointer to a buffer and the size of the buffer,
it's usually a good idea to use sizeof rather than the macro you used when
defining the buffer. That way, you don't have to update the rest of the
code when you change the declaration.]

However, even if you don't overflow the buffer, you can end up with a
truncated value, which can mean that your program silently produces bogus
output (if the constructed pathname was being used to open a file for
write, or to remove a file, you could end up overwriting or removing the
wrong file).

If you must use fixed-sized buffers, it's probably better to calculate
the length of the resulting string (adding the results of various strlen()
calls, plus 1 for the terminating NUL) and simply report an error if the
result won't fit into the buffer.

E.g.:

int dir_len = strlen(theDir);
int name_len = strlen(entry.d_name);
int total_len = dir_len + 1 + name_len + 1;
if (total_len > sizeof pathName) {
fprintf(stderr, "pathname too long\n");
return -1;
}
sprintf(pathName, "%s/%s", theDir, entry.d_name);
 
B

Ben Bacarisse

frank said:
Ben, I'm not strong at all with string processing in C. I see no
warnings about strncpy in H&S. What should I use instead?

I'd use memcpy, but that's not really the central issue.
It makes some sense to me. It does however look flawed in general
because you'd have
/home/dan/source/ and 400 null characters then
/home/dan/source// and 399 null characters then
/home/dan/source//rd7.c and 395 null characters

I don't know. I'm trying to break down this source and was hoping
that it was written by an expert, but it appears to have been written
by someone with little experience or ability.

The code seems to be putting effort into preventing buffer overflow
(it fails to so that but that is almost incidental) but the real issue
is that it should decided if there is room and stop if there is not
(the alternative is to allocate space, of course). It seems pointless
to try pack as much of the string into the buffer as possible since
even if one character won't fit, the program won't work.

It would be better to start off by testing if the size of the
two strings plus 1 for the '/' and one for the '\0' will fit. Since
this involves finding the two lengths you then have all the
information you need to copy safely (using, say, memcpy) or to
allocate space (if that's your preferred solution).
 
I

Ike Naar

Well yes and no. My buffer wasn't long enough. When I switched from
100, which I thought was plenty, to 300 for PATH_SIZE, the program
compiled and behaved as far as I could tell.

Apparently the code that you compiled is not the code that you posted.
The posted code has parts that are almost, but not quite, entirely unlike C:

if ((strncmp (entry.d_namessize_t readlink(const char *restrict
path, char *restrict buf,
size_t bufsize);, ".", PATH_SIZE) == 0) ||
(strncmp (entry.d_name, "..", PATH_SIZE) == 0))
 
F

frank

Richard said:
io_x wrote:



No, the float value will be converted to double anyway prior to the
call. Look up "default argument promotions" in the Standard.

It seems like floats have floated away. I can't think of a reason a
person would want a float as opposed to a double: anything that float
can represent is also represented by a double.

6.3.1.7 is a good place to read up on this.
 
Ad

Advertisements

J

jacob navia

frank a écrit :
It seems like floats have floated away. I can't think of a reason a
person would want a float as opposed to a double: anything that float
can represent is also represented by a double.

6.3.1.7 is a good place to read up on this.

Please, do not generalize too much. Float use 50% of the memory
needed by a double, and when memory is important and precision is not,
it is better to use float.

NVIDIA has proposed a new 16 bit floating point format (used now internally
in their GPUs). For game/graphic applications, it is not important to know
with extreme precision where or what color a ray will pass. But using
half the memory it is, since you can output twice as much.

I agree that in "normal" applications double is much better, but float
(and even "short float" have their uses. The new x86 machines will
feature those and treat a lot of them in parallel. They feature now float
support in the XMM registers.

jacob
 
F

frank

Ike said:
Apparently the code that you compiled is not the code that you posted.
The posted code has parts that are almost, but not quite, entirely unlike C:

if ((strncmp (entry.d_namessize_t readlink(const char *restrict
path, char *restrict buf,
size_t bufsize);, ".", PATH_SIZE) == 0) ||
(strncmp (entry.d_name, "..", PATH_SIZE) == 0))

It is true that I posted something different than I compiled to minimize
the extent of the posix extension, providing, for example, function
declarations for readlink as opposed to #including a non-c-standard
header. Otherwise, it's not unlike C precisely because it is C.

I'm trying to work out ways to make my current project more topical for
clc. Hence the stack-smashing example from wiki. It seems that with my
implementation, anytime you write past the storage extent of a variable,
you get told that you are stack smashing, and the OS shuts you down.

I am, however, pleasantly surprised to see a lot of the usual suspects
in comp.unix.programmer.
 
F

frank

Gordon said:
Hint: in FreeBSD, FILENAME_MAX is defined as 1024, along with a
comment that it must be <= PATH_MAX. Linux has similar filesystems
to FreeBSD. If you can't find PATH_MAX, consider using FILENAME_MAX
as a minimum for it (or perhaps 10*FILENAME_MAX). That's at least
a starter for the length of a filename of the mount point of the
relevant filesystem.

Thanks, Gordon, this might be a way for me to work around this until I
can implement something fancier:

[email protected]:~/source$ gcc -std=c99 -Wall -Wextra ss3.c -o out
[email protected]:~/source$ ./out
FILENAME_MAX is 4096
[email protected]:~/source$ cat ss3.c
#include <stdio.h>

int main (void)
{
printf("FILENAME_MAX is %d\n", FILENAME_MAX);
return 0;
}



// gcc -std=c99 -Wall -Wextra ss3.c -o out

[email protected]:~/source$
 
J

john

Antoninus said:
This is exceptionally poor advice - and as usual, the famous "clc peer
review" that would have seen half a dozen people piling in to point out
that the resulting int might have been a trap representation or some
such nonsense if a newbie had posted this remains strangely silent when
it is one of their chums who's boobooed.

Casting to an int so that you can use %d is utterly stupid when you can
just use the %zu format specifier and not risk printing nonsense in the
quite likely event that size_t is wider than int.

If you happen to be stuck with C90 (and hopefully you've got a better
reason for this than Heathfield's one of irrational prejudice), then
using %lu and casting to unsigned long int is a much better option than
using %d and casting to int.

As usual you are completely wrong Twink. Why dont you FOD?
 
F

frank

john said:
As usual you are completely wrong Twink. Why dont you FOD?

a's value isn't going to exceed one hundred. If size_t is wider than
int, doesn't it "demote" appropriately?
==
frank
 
Ad

Advertisements

S

Seebs

This is nearly good advice, except for the small issue that %zu hasn't seen
the widespread adoption I would have liked.
As usual you are completely wrong Twink. Why dont you FOD?

Uh, can you explain what's wrong there? I'm not seeing something wrong with
it. I have certainly used systems where size_t was larger than int, and
where objects could have a size larger than the range expressible in int,
signed or unsigned.

Heathfield's advice was correct, technically -- if you want to match a %d
format specifier, yes, cast to int. There may be circumstances in which the
format specifier is externally determined for some reason, so you might
actually need to know this. But Twink's position seems reasonable to me;
if you really do need to support systems without %zu, then %lu/unsigned long
is probably the next best thing.

-s
 
S

Seebs

a's value isn't going to exceed one hundred. If size_t is wider than
int, doesn't it "demote" appropriately?

Not in the argument list of a variable-arguments function, because there's
no way for the compiler to know in advance what type it would demote to.
(Actually, to be super picky, gcc often does know, and so do some other
compilers, but as a matter of design, there is no such demotion in place,
and there are easily-generated examples of cases where it's impossible
to tell except at runtime.)

-s
 
B

Ben Bacarisse

frank said:
Ike said:
It is true that I posted something different than I compiled to
minimize the extent of the posix extension, providing, for example,
function declarations for readlink as opposed to #including a
non-c-standard header. Otherwise, it's not unlike C precisely because
it is C.

It's C, Jim, but not as we know it (to use what may be a more familiar
reference). It looks like a function prototype has been pasted into a
strncmp call. BTW, why strncmp? It seems to me that strcmp is
obvious choice here.

<snip>
 
Ad

Advertisements

B

Ben Bacarisse

Seebs said:
This is nearly good advice, except for the small issue that %zu hasn't seen
the widespread adoption I would have liked.



Uh, can you explain what's wrong there?

Nothing wrong expect what I can only see as deliberately provocative
snipping. The context (just one line above) was:

a = sizeof(float);

If Richard Heathfield had suggested that int was wrong and unsigned
long was the type to use, AT would quite likely have gone off on one
about absurd fears that floats might be longer than 32,767 bytes.
Since int is perfectly reasonable here, AT had to snip the context to
go off on one. The common thread here is that AT will go off on one
no matter what one says.

<snip>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top