This is the mail archive of the glibc-bugs@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[Bug stdio/17908] New: freopen() from shared library coredumps when all symbols are hidden in main program


https://sourceware.org/bugzilla/show_bug.cgi?id=17908

            Bug ID: 17908
           Summary: freopen() from shared library coredumps when all
                    symbols are hidden in main program
           Product: glibc
           Version: 2.21
            Status: NEW
          Severity: normal
          Priority: P2
         Component: stdio
          Assignee: unassigned at sourceware dot org
          Reporter: nick.alcock at oracle dot com

(This affects all glibc versions I've tested, from 2.12 right up to trunk as of
yesterday. Most likely it affects all versions from 2.1 up.)

If you compile a trivial main program:

extern void foo (void);
int main (void)
{
  foo();
  return 0;
}

and link it with a version script that hides all symbols, on a platform with an
earliest symbol set older than GLIBC_2.2, such as x86-32 or sparc:

SOMETHING_PRIVATE {
  local:
      *;
};

then you will find that you can cause all sorts of fun by calling freopen() or
wide-char functions in the shared library containing foo(). Many wide char
operations (e.g. fwprintf()) do nothing rather than what you might expect them
to do, but you can cause coredumps as well e.g. with a libfoo.so containing one
function reading

#include <stdio.h>
int foo (void)
{
  freopen ("/dev/stdout", "a", stdout);
}

we see a coredump with this backtrace:

#0  0xf7e60146 in _IO_flush_all_lockp () from /lib32/libc.so.6
#1  0xf7e602e5 in _IO_cleanup () from /lib32/libc.so.6
#2  0xf7e1ea8a in __run_exit_handlers () from /lib32/libc.so.6
#3  0xf7e1eadd in exit () from /lib32/libc.so.6
#4  0xf7e04a6d in __libc_start_main () from /lib32/libc.so.6
#5  0x08048401 in _start ()

and valgrind says:

==19151== Invalid read of size 4
==19151==    at 0x40E5146: _IO_flush_all_lockp (in /lib32/libc-2.18.so)
==19151==    by 0x40E52E4: _IO_cleanup (in /lib32/libc-2.18.so)
==19151==    by 0x40A3A89: __run_exit_handlers (in /lib32/libc-2.18.so)
==19151==    by 0x40A3ADC: exit (in /lib32/libc-2.18.so)
==19151==    by 0x4089A6C: (below main) (in /lib32/libc-2.18.so)
==19151==  Address 0x85386ef3 is not stack'd, malloc'd or (recently) free'd
==19151==
==19151==
==19151== Process terminating with default action of signal 11 (SIGSEGV):
dumping core
==19151==  Access not within mapped region at address 0x85386EF3
==19151==    at 0x40E5146: _IO_flush_all_lockp (in /lib32/libc-2.18.so)
==19151==    by 0x40E52E4: _IO_cleanup (in /lib32/libc-2.18.so)
==19151==    by 0x40A3A89: __run_exit_handlers (in /lib32/libc-2.18.so)
==19151==    by 0x40A3ADC: exit (in /lib32/libc-2.18.so)
==19151==    by 0x4089A6C: (below main) (in /lib32/libc-2.18.so)

With a libfoo.so that opens a local FILE and then freopen()s it

#include <stdio.h>
#include <stdlib.h>

void foo (void)
{
  FILE *foo;
  foo = fopen("/dev/null", "a");
  freopen("/dev/stdout", "a", foo);
}

we see a quite different coredump:

#0  0xf7f1b569 in _IO_old_file_fopen () from /lib32/libc.so.6
#1  0xf7e55852 in freopen () from /lib32/libc.so.6
#2  0xf7fd6657 in foo () at foo.c:9
#3  0x080484f0 in main () at main-nodyn.c:3

==19068== Invalid read of size 4
==19068==    at 0x41A0569: _IO_file_fopen@GLIBC_2.0 (in /lib32/libc-2.18.so)
==19068==    by 0x40DA851: freopen (in /lib32/libc-2.18.so)
==19068==    by 0x4034656: foo (foo.c:9)
==19068==    by 0x80484EF: main (main-nodyn.c:3)
==19068==  Address 0x24 is not stack'd, malloc'd or (recently) free'd
==19068==
==19068==
==19068== Process terminating with default action of signal 11 (SIGSEGV):
dumping core
==19068==  Access not within mapped region at address 0x24
==19068==    at 0x41A0569: _IO_file_fopen@GLIBC_2.0 (in /lib32/libc-2.18.so)
==19068==    by 0x40DA851: freopen (in /lib32/libc-2.18.so)
==19068==    by 0x4034656: foo (foo.c:9)
==19068==    by 0x80484EF: main (main-nodyn.c:3)

I suspect that a large number of other failures are also possible, quite
possibly up to and including buffer overflows (though I have not proved it,
libio's state is confused enough that it seems to me that it might happen).

The underlying problem is that libio depends on looking up the symbol
_IO_stdin_used to tell what version of libio was used by the glibc the main
program was linked against, so it can avoid silently changing behaviour when
glibc is upgraded; ancient glibc 2.0 had no such symbol, so if it's not found,
the assumption is that glibc 2.0 is in use. But when the main program has all
its symbols hidden, this fires incorrectly, overwriting a new libio operations
vector with an old libio operations vector, following which chaos results.

I don't see how to fix this without breaking compatibility with ancient glibc
libio users, or inserting a special case in rtld to prevent STV_HIDDEN on
_IO_stdin_used from being respected, which would slow down all other users and
thus is presumably unacceptable. We could work around this if we had a single
additional obscure internal symbol to work with which dated back to the glibc
2.0 days, and which is not also exported by shared libraries (i.e. another
symbol like _IO_stdin_used): we could use the visibility of that symbol to tell
if _IO_stdin_used was invisible because it was never there, or because it was
hidden. But there is no such symbol, as far as I can tell.


It is arguable whether this is even worth fixing. Should we cater for people
hiding symbols in the range reserved for the implementation? As far as I can
see, though, we have little choice. ld's own manual documents 'local: *' as a
legitimate use of version scripts, without caveats. A version script that
localized [^_]* and _[_A-Z]* rather than straight * might suffice, but we can't
expect users to be that pedantic: more importantly, they haven't been in the
past. (I can find no examples of such hyper-pedantic version scripts anywhere.)

The question also arises how common it is to apply a version script containig
local: * to a main program, as opposed to a library, at all: perhaps this is so
uncommon a problem that it can be ignored. Unfortunately it is not unheard of.
On my system, at least LLVM, Mono, and parts of bluez do it: some versions of
the JVM reportedly do too. These presumably don't call freopen(), or don't do
it often, or they'd be bitten.

It would be interesting to know how many other examples exist on others'
systems. Something like

eu-readelf -s /bin/* /usr/bin/* /sbin/* /usr/sbin/* 2>/dev/null | grep -v
'^Symbol table' | grep -E ':$|LOCAL.*_IO_stdin_used' | awk 'BEGIN {
last_colon=""; } /:$/ { last_colon=$0; next; } /_IO_stdin_used/ { print
last_colon; print $0; }'

might give us a clue.

-- 
You are receiving this mail because:
You are on the CC list for the bug.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]