Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strdup returning address out of bounds

Tags:

c

I thought I'd ask before spending more hours on this. Valgrind doesn't report anything, in fact it doesn't crash with Valgrind.

char* a = "HI";
char* b = strdup(a);

(gdb) print b => $8 = 0xffffffffe8003680 <Address 0xffffffffe8003680 out of bounds>

This only happens in my dynamically loaded shared library, (loaded with dlopen). I have no idea what could possibly cause this. I stripped just about everything out of that library, there are only these two lines in it. Can you help me debug this?

If I try to access b now:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff76b3d0a in strchrnul () from /lib64/libc.so.6

What could possibly be going wrong here?

Backtrace:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff76b3d0a in strchrnul () from /lib64/libc.so.6
(gdb) bt
#0  0x00007ffff76b3d0a in strchrnul () from /lib64/libc.so.6
#1  0x00007ffff767088a in vfprintf () from /lib64/libc.so.6
#2  0x00007ffff767af79 in printf () from /lib64/libc.so.6
#3  0x00007ffff722678c in _mkp_stage_30 (plugin=0x61f420, cs=0x7fffe8002040, 
    sr=0x7fffe8003070) at hello.c:68
#4  0x000000000040f93a in mk_plugin_stage_run (hook=16, socket=18, conx=0x0, 
    cs=0x7fffe8002040, sr=0x7fffe8003070) at mk_plugin.c:558
#5  0x000000000040c501 in mk_http_init (cs=0x7fffe8002040, sr=0x7fffe8003070)
    at mk_http.c:255
#6  0x0000000000404870 in mk_request_process (cs=0x7fffe8002040, 
    sr=0x7fffe8003070) at mk_request.c:510
#7  0x0000000000404d75 in mk_handler_write (socket=18, cs=0x7fffe8002040)
    at mk_request.c:630
#8  0x000000000040b446 in mk_conn_write (socket=18) at mk_connection.c:130
#9  0x0000000000409352 in mk_epoll_init (efd=12, handler=0x7fffe8001690, 
    max_events=202) at mk_epoll.c:102
#10 0x0000000000409b4e in mk_sched_launch_worker_loop (thread_conf=0x61a5a0)
    at mk_scheduler.c:196
#11 0x00007ffff79c2f05 in start_thread () from /lib64/libpthread.so.0
#12 0x00007ffff770553d in clone () from /lib64/libc.so.6
like image 401
Blub Avatar asked Dec 16 '22 06:12

Blub


1 Answers

You need to add

#include <string.h>

to get the declaration of strdup().

Or, if you already have that, you need to invoke your compiler in a way that makes strdup() visible (see below for details). strdup() is defined by POSIX, not by ISO C.

gcc enables the appropriate macro(s) by default, but using -ansi or -std=c99 will disable them. You can also add an appropriate #define to the top of your source file.

In the absence of a visible declaration, the compiler assumes (under C90 rules) that strdup() returns int. This results in undefined behavior. In particular, if int is 32 bits and char* is 64 bits, Bad Things Will Happen. (Under C99 rules, calling a function with no visible declaration is a constraint violation.)

You should also crank up your compiler's warning levels and pay attention to the warnings. A problem like this should manifest at compile time; you shouldn't have to diagnose the run-time behavior.

UPDATE :

The following is based on documentation and experiments on my own Ubuntu 11.04 system. It's likely to apply to any system using glibc; some of it might apply to non-glibc systems. man strdup and man feature_test_macros for more information.

To use strdup(), you must have #include <string.h> to make the declaration visible. (The compiler won't necessarily complain if you omit this, but you need it anyway.)

In addition, you need to have one of the following (or equivalent) before the #include <string.h>:

#define _SVID_SOURCE

#define _BSD_SOURCE

#define _XOPEN_SOURCE 500 /* or greater */

#define _XOPEN_SOURCE
#define _XOPEN_SOURCE_EXTENDED

#define _POSIX_C_SOURCE 200809L /* or greater */

The simplest way to do this is just not to use the -ansi or -std=... option for gcc (or the equivalent for clang); gcc enables some of these macros by default.

If you want to compile with -ansi or std=c99, then you can explicitly set one of the above macros (or two if you use the 4th alternative), either with an explicit #define in the source (probably the best solution), or by using, for example, gcc -std=c99 -D_XOPEN_SOURCE=500.

And, as I mentioned before, you should crank up the warning levels on your compiler. For gcc, I typically use:

gcc -std=c99 -pedantic -Wall -Wextra -O3

The -O3 enables optimizations; a side effect is that it enables the analysis necessary to perform those optimizations, which can detect a number of problems that aren't apparent at lower optimization levels.

The reason for all this rigmarole is that strdup() is not defined by the ANSI/ISO C standard (the committee chose not to include it), but it is defined by POSIX -- and its declaration is in <string.h>, one of the ISO C standard headers. In strict ISO C conforming mode, the name strdup() may not be made visible in <string.h>. You have to take these extra steps to tell the compiler to make it visible anyway. (gcc is not a fully conforming C compiler by default, which is why it's able to make strdup() visible by default.)

Since strdup() is such a simple function, you might consider just writing your own implementation in standard ISO C and using that instead. For example (note that names starting with str are reserved):

char *dupstr(const char *s) {
    char *const result = malloc(strlen(s) + 1);
    if (result != NULL) {
        strcpy(result, s);
    }
    return result;
}

(I have only minimally tested this.) If strdup() is the only POSIX-specific function you're using, rolling your own might be the best solution. If you're using other POSIX-specific functions, then you'll have to deal with all this stuff anyway, and you might as well use strdup() itself.

like image 129
Keith Thompson Avatar answered Dec 21 '22 22:12

Keith Thompson