views:

162

answers:

4

Hi,

Some overflow runtime error happens when my C++ program is trying to write some .png images into a directory.

The directory where the images are written into is given as a command line argument. The program is compiled with gcc -ggdb3 -O3. It is strange that the error disappears if I change the directory to another one when rerun it, or if I compile my program without optimization. I am confused. Even though I can get the images produced by non-optimized executable or in another directory, I doubt if the results are reliable since the optimized executable might have runtime error? Or is it possible that the optimization produces error-prone executable? Anyone could explain that?

I tried to debug the optimized executable since it is compiled with gcc -ggdb3 -O3, but the place it produces overflow error doesn't give source code, which I cannot get some clue from:

(gdb) bt

#0 0x00007fbd29573fb5 in raise () from /lib/libc.so.6

#1 0x00007fbd29575bc3 in abort () from /lib/libc.so.6

#2 0x00007fbd295b3228 in ?? () from /lib/libc.so.6

#3 0x00007fbd296402c7 in __fortify_fail () from /lib/libc.so.6

#4 0x00007fbd2963e170 in __chk_fail () from /lib/libc.so.6

#5 0x00007fbd2963d519 in ?? () from /lib/libc.so.6

#6 0x00007fbd295b7426 in _IO_default_xsputn () from /lib/libc.so.6

#7 0x00007fbd29586fdb in vfprintf () from /lib/libc.so.6

#8 0x00007fbd2963d5b9 in __vsprintf_chk () from /lib/libc.so.6

#9 0x00007fbd2963d500 in __sprintf_chk () from /lib/libc.so.6

#10 0x0000000000408695 in main ()

(gdb) f 10

#10 0x0000000000408695 in main ()

Current language: auto; currently asm

(gdb) list

1 /build/buildd/glibc-2.9/build-tree/amd64-libc/csu/crtn.S: No such file or directory.

in /build/buildd/glibc-2.9/build-tree/amd64-libc/csu/crtn.S

(gdb)

I am not sure if the output of the runtime error could help analyze the problem. If it could, here is how the error message looks like, a little big long though:

* buffer overflow detected *: /cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity terminated

[New Thread 0x7fbd2acd9740 (LWP 2347)]

======= Backtrace: =========

/lib/libc.so.6(__fortify_fail+0x37)[0x7fbd296402c7]

/lib/libc.so.6[0x7fbd2963e170]

/lib/libc.so.6[0x7fbd2963d519]

/lib/libc.so.6(_IO_default_xsputn+0x96)[0x7fbd295b7426]

/lib/libc.so.6(_IO_vfprintf+0x63b)[0x7fbd29586fdb]

/lib/libc.so.6(__vsprintf_chk+0x99)[0x7fbd2963d5b9]

/lib/libc.so.6(__sprintf_chk+0x80)[0x7fbd2963d500]

/cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity[0x408695]

/lib/libc.so.6(__libc_start_main+0xe6)[0x7fbd2955f5a6]

/cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity[0x4045d9]

======= Memory map: ========

00400000-00471000 r-xp 00000000 00:39 52084894 /cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity

00671000-00672000 r--p 00071000 00:39 52084894 /cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity

00672000-00673000 rw-p 00072000 00:39 52084894 /cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity

00673000-00675000 rw-p 00673000 00:00 0

00943000-00964000 rw-p 00943000 00:00 0 [heap]

7fbd273f7000-7fbd29339000 rw-p 7fbd273f7000 00:00 0

7fbd29339000-7fbd29340000 r-xp 00000000 08:01 35791448 /lib/librt-2.9.so

7fbd29340000-7fbd2953f000 ---p 00007000 08:01 35791448 /lib/librt-2.9.so

7fbd2953f000-7fbd29540000 r--p 00006000 08:01 35791448 /lib/librt-2.9.so

7fbd29540000-7fbd29541000 rw-p 00007000 08:01 35791448 /lib/librt-2.9.so

7fbd29541000-7fbd296a9000 r-xp 00000000 08:01 35791428 /lib/libc-2.9.so

7fbd296a9000-7fbd298a9000 ---p 00168000 08:01 35791428 /lib/libc-2.9.so

7fbd298a9000-7fbd298ad000 r--p 00168000 08:01 35791428 /lib/libc-2.9.so

7fbd298ad000-7fbd298ae000 rw-p 0016c000 08:01 35791428 /lib/libc-2.9.so

7fbd298ae000-7fbd298b3000 rw-p 7fbd298ae000 00:00 0

7fbd298b3000-7fbd298c9000 r-xp 00000000 08:01 35790870 /lib/libgcc_s.so.1

7fbd298c9000-7fbd29ac9000 ---p 00016000 08:01 35790870 /lib/libgcc_s.so.1

7fbd29ac9000-7fbd29aca000 r--p 00016000 08:01 35790870 /lib/libgcc_s.so.1

7fbd29aca000-7fbd29acb000 rw-p 00017000 08:01 35790870 /lib/libgcc_s.so.1

7fbd29acb000-7fbd29ad3000 r-xp 00000000 08:01 71705955 /usr/lib/libgomp.so.1.0.0

7fbd29ad3000-7fbd29cd2000 ---p 00008000 08:01 71705955 /usr/lib/libgomp.so.1.0.0

7fbd29cd2000-7fbd29cd3000 r--p 00007000 08:01 71705955 /usr/lib/libgomp.so.1.0.0

7fbd29cd3000-7fbd29cd4000 rw-p 00008000 08:01 71705955 /usr/lib/libgomp.so.1.0.0

7fbd29cd4000-7fbd29d58000 r-xp 00000000 08:01 35791436 /lib/libm-2.9.so

7fbd29d58000-7fbd29f57000 ---p 00084000 08:01 35791436 /lib/libm-2.9.so

7fbd29f57000-7fbd29f58000 r--p 00083000 08:01 35791436 /lib/libm-2.9.so

7fbd29f58000-7fbd29f59000 rw-p 00084000 08:01 35791436 /lib/libm-2.9.so

7fbd29f59000-7fbd2a04a000 r-xp 00000000 08:01 71704918 /usr/lib/libstdc++.so.6.0.10

7fbd2a04a000-7fbd2a24a000 ---p 000f1000 08:01 71704918 /usr/lib/libstdc++.so.6.0.10

7fbd2a24a000-7fbd2a251000 r--p 000f1000 08:01 71704918 /usr/lib/libstdc++.so.6.0.10

7fbd2a251000-7fbd2a253000 rw-p 000f8000 08:01 71704918 /usr/lib/libstdc++.so.6.0.10

7fbd2a253000-7fbd2a266000 rw-p 7fbd2a253000 00:00 0

7fbd2a266000-7fbd2a27d000 r-xp 00000000 08:01 35791446 /lib/libpthread-2.9.so

7fbd2a27d000-7fbd2a47c000 ---p 00017000 08:01 35791446 /lib/libpthread-2.9.so

7fbd2a47c000-7fbd2a47d000 r--p 00016000 08:01 35791446 /lib/libpthread-2.9.so

7fbd2a47d000-7fbd2a47e000 rw-p 00017000 08:01 35791446 /lib/libpthread-2.9.so

7fbd2a47e000-7fbd2a482000 rw-p 7fbd2a47e000 00:00 0

7f

Program received signal SIGABRT, Aborted.

[Switching to Thread 0x7fbd2acd9740 (LWP 2347)]

0x00007fbd29573fb5 in raise () from /lib/libc.so.6

Really appreciate your help!

Thanks and regards!


@@UPDATE@@: You guys are right! I increased the size of the char array for the long filename and now it is fine!

The executable is /cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity. The directory which doesn't work is specified as commandline argument --result-path=../results1/FrancContinuity1/noise0/train-imgs, which is stored in global.result_path in the following.

Could you guys tell me how you suspect it's the problem you mentioned? Are __sprintf_chk () and __vsprintf_chk () always called by sprintf()?

Here is the code.

Part 1:

      char filename[50];
      sprintf(filename, "%s/%d_%d.png", global.result_path, train_samples[n].label, train_samples[n].label==1 ? ++nb_pos : ++nb_neg);
      train_samples[n].write_png(filename);

Part 2:

class Global { //parameters of program
public:
  int niceness; //The process scheduling priority
  int random_seed; //The seed for the random sequence used in the computation
  char result_path[1024]; //Where to store the generated results (images, logs, etc.)
...
}

Global global;
+3  A: 

How long is the directory name, and how long is the buffer you're trying to store it in? You haven't given us much to go on...how about showing some code? Perhaps a call to sprintf somewhere in main(), and the declarations of any variables involved?

Edit: It sure looks like filename needs to be a bigger array, given your input directory and the filename you're appending to it! Quick fix: try declaring it as, say, 1500 characters instead of 50. Better fix: since you're using C++, look into the std::string and ostringstream classes, which will resize themselves to prevent buffer overflows.

To answer your followup questions:

The "../" in your result-path shouldn't get expanded into an absolute path.

My hunch that sprintf() was involved was a lucky guess, based on the "buffer overflow" message and the last few lines in the gdb traceback. I'm not that familiar with glibc internals, but perhaps __sprintf_chk() and __vsprintf_chk() are buffer-overflow-checking variants of sprintf()?

Jim Lewis
Thank you! Please see my update at the end of the original post.
Tim
Thanks! A little curious though, why without optimization the executable runs fine, no reporting buffer overflow?
Tim
@Tim: This bug results in what's known as "undefined behavior" -- meaning: it may crash, it may work fine, it may cause demons to come flying out of your nose. Probably, the compiler has rearranged the layout of data in the optimized version, so when you overrun the buffer it clobbers something else, causing the error message you saw.
Jim Lewis
+1  A: 

Well you use sprintf to print to the buffer 'filename[50]' which of course has length 50. Now the string you are printing is a buffer of size 1024, this strikes me as a potential issue. What happens when global.result_path is longer than 50 (even less actually, as you are also printing integers), well you get an overflow.

Try using C++ std::string and std::stringstream, ie:

//Part 1:

std::stringstream ss;
ss << global.result_path << /* other data */;
train_samples[n].write_png(ss.str().c_str());

//Part 2:

class Global
{
    std::string result_path;
    ...
}

With the above code you will never have to worry about overflowing character buffers or any of that other ugly stuff.

DeusAduro
+2  A: 

I long ago got into the habit of using snprintf everywhere. Learn to love it. It may still fail to write the correct file, but at least it won't leave a security hole.

Then after you start to wonder why your program is creating files named "this_is_a_long_file_na" you can go back and fix it to either use a buffer of PATH_MAX or a dynamic sized malloc'd buffer. snprintf will help you find the right size if the buffer needed to be bigger.

Or you can switch to C++ and use std::string.

Zan Lynx
+1 for the PATH_MAX reference and mentioning that dynamic allocation or std::string would definitely be better
Matthew Iselin
+1  A: 

result_path is too small.

Simply change result_path to 1024. Some systems have the macro MAX_PATH defined. I'd also change sprintf to snprintf with the size as sizeof(result_path).

The snprintf() function is just like sprintf(), except that the length of the buffer is given. This prevents buffer overflows.

The return value is the number of characters written. If the output was truncated due to buff_size limit then the return value is the number of characters (not including the trailing '\0') which would have been written to the final string if enough space had been available.

How I knew you had a problem with sprintf was the backtrace.

i.e.

(gdb) bt

#0 0x00007fbd29573fb5 in raise () from /lib/libc.so.6

#1 0x00007fbd29575bc3 in abort () from /lib/libc.so.6

#2 0x00007fbd295b3228 in ?? () from /lib/libc.so.6

#3 0x00007fbd296402c7 in __fortify_fail () from /lib/libc.so.6

#4 0x00007fbd2963e170 in __chk_fail () from /lib/libc.so.6

#5 0x00007fbd2963d519 in ?? () from /lib/libc.so.6

#6 0x00007fbd295b7426 in _IO_default_xsputn () from /lib/libc.so.6

#7 0x00007fbd29586fdb in vfprintf () from /lib/libc.so.6

#8 0x00007fbd2963d5b9 in __vsprintf_chk () from /lib/libc.so.6

#9 0x00007fbd2963d500 in __sprintf_chk () from /lib/libc.so.6

#10 0x0000000000408695 in main ()

i.e. You have a function main in your code. and __sprintf_chk is where it goes belly up. You had to be calling sprintf. After that it died. So my guess was that you were passing it bad arguments. The only way sprintf can die so badly is with a buffer overflow. So it's a good assumption that the string you're printing to is too small. Use snprintf and it'll be much safer. You can then print to debug the result. If you'd done that you would have seen straight away that the buffer was too small as the result_path would have been truncated at 50 chars and the program wouldn't have crashed (at that point at least :).

Matt H