Skip to content

Commit

Permalink
libio: Implement vtable verification [BZ #20191]
Browse files Browse the repository at this point in the history
This commit puts all libio vtables in a dedicated, read-only ELF
section, so that they are consecutive in memory.  Before any indirect
jump, the vtable pointer is checked against the section boundaries,
and the process is terminated if the vtable pointer does not fall into
the special ELF section.

To enable backwards compatibility, a special flag variable
(_IO_accept_foreign_vtables), protected by the pointer guard, avoids
process termination if libio stream object constructor functions have
been called earlier.  Such constructor functions are called by the GCC
2.95 libstdc++ library, and this mechanism ensures compatibility with
old binaries.  Existing callers inside glibc of these functions are
adjusted to call the original functions, not the wrappers which enable
vtable compatiblity.

The compatibility mechanism is used to enable passing FILE * objects
across a static dlopen boundary, too.
  • Loading branch information
fweimer-rh committed Jun 23, 2016
1 parent 64ba173 commit db3476a
Show file tree
Hide file tree
Showing 30 changed files with 279 additions and 60 deletions.
74 changes: 74 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,77 @@
2016-06-23 Florian Weimer <fweimer@redhat.com>

[BZ #20191]
Implement vtable verification in libio.
* Makerules (shlib.lds): Place __libc_IO_vtables section.
* debug/obprintf_chk.c (_IO_obstack_jumps): Define as vtable.
* debug/vdprintf_chk.c (__vdprintf_chk): Call
_IO_new_file_init_internal instead of _IO_file_init.
* debug/vsnprintf_chk.c (_IO_strn_jumps): Define as vtable.
* debug/vsprintf_chk.c (_IO_str_chk_jumps): Likewise.
* libio/Makefile (routines): Add vtables.
* libio/libioP.h (_IO_JUMPS_FUNC): Call IO_validate_vtable.
(_IO_init): Remove, not for internal use.
(_IO_init_internal): Declare, internal replacement for _IO_init.
(_IO_file_init): Remove, not for internal use.
(_IO_new_file_init): Remove, not for internal use.
(_IO_new_file_init_internal): Declare, internal replacement for
_IO_new_file_init.
(_IO_old_file_init): Remove, not for internal use.
(_IO_old_file_init_internal): Declare, internal replacement for
_IO_old_file_init.
(_IO_str_init_static, _IO_str_init_readonly): Remove, not for
internal use.
(__libc_IO_vtables, IO_accept_foreign_vtables, _IO_vtable_check):
Declare.
(libio_vtable): New macro.
(IO_set_accept_foreign_vtables, _IO_validate_vtable): New inline
functions.
* libio/fileops.c (_IO_new_file_init_internal): Rename from
_IO_new_file_init.
(_IO_new_file_init): New externally visible wrapper which disables
vtable verification.
(_IO_file_jumps, _IO_file_jumps_mmap, _IO_file_jumps_maybe_mmap):
Define as vtables.
* libio/genops.c (_IO_init_internal): Rename from _IO_init.
(_IO_init): New externally visible wrapper which disables
vtable verification.
* libio/iofdopen.c (_IO_new_fdopen): Call
_IO_new_file_init_internal instead of _IO_file_init. Adjust
comment.
* libio/iofopen.c (__fopen_internal): Call
_IO_new_file_init_internal instead of _IO_file_init.
* libio/iofopncook.c (_IO_cookie_jumps, _IO_old_cookie_jumps):
Define as vtables.
(_IO_cookie_init): Call _IO_init_internal instead of _IO_init,
_IO_new_file_init_internal instead of _IO_file_init.
* libio/iopopen.c (_IO_new_popen): Likewise.
(_IO_proc_jumps): Define as vtable.
* libio/iovdprintf.c (_IO_vdprintf): Call
_IO_new_file_init_internal instead of _IO_file_init.
* libio/memstream.c (_IO_mem_jumps): Define as vtable.
(__open_memstream): Call _IO_init_internal instead of _IO_init.
* libio/obprintf.c (_IO_obstack_jumps): Define as vtable.
* libio/oldfileops.c (_IO_old_file_init_internal): Rename from
_IO_old_file_init.
(_IO_old_file_init): New externally visible wrapper which disables
vtable verification.
(_IO_old_file_jumps): Define as vtable.
* libio/oldiofdopen.c (_IO_old_fdopen): Call
_IO_old_file_init_internal instead of _IO_old_file_init.
* libio/oldiofopen.c (_IO_old_fopen): Likewise.
* libio/oldiopopen.c (_IO_old_popen): Likewise.
(_IO_old_proc_jumps): Define as vtable.
* libio/strops.c (_IO_str_jumps, _IO_strn_jumps, _IO_wstrn_jumps):
Define as vtables.
* libio/vtables.c: New file.
* libio/wfileops.c (_IO_wfile_jumps, _IO_wfile_jumps_mmap)
(_IO_wfile_jumps_maybe_mmap): Define as vtables.
* libio/wmemstream.c (_IO_wmem_jumps): Define as vtable.
* libio/wstrops.c (_IO_wstr_jumps): Likewise.
* stdio-common/vfprintf.c (_IO_helper_jumps): Likewise.
* stdlib/strfmon_l.c (__vstrfmon_l): Call _IO_init_internal
instead of _IO_init.

2016-06-23 Florian Weimer <fweimer@redhat.com>

* test-skeleton.c (xrealloc): Support deallocation with n == 0.
Expand Down
3 changes: 3 additions & 0 deletions Makerules
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,9 @@ $(common-objpfx)shlib.lds: $(common-objpfx)config.make $(..)Makerules
PROVIDE(__start___libc_thread_subfreeres = .);\
__libc_thread_subfreeres : { *(__libc_thread_subfreeres) }\
PROVIDE(__stop___libc_thread_subfreeres = .);\
PROVIDE(__start___libc_IO_vtables = .);\
__libc_IO_vtables : { *(__libc_IO_vtables) }\
PROVIDE(__stop___libc_IO_vtables = .);\
/DISCARD/ : { *(.gnu.glibc-stub.*) }@'
test -s $@T
mv -f $@T $@
Expand Down
2 changes: 1 addition & 1 deletion debug/obprintf_chk.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct _IO_obstack_file
struct obstack *obstack;
};

extern const struct _IO_jump_t _IO_obstack_jumps attribute_hidden;
extern const struct _IO_jump_t _IO_obstack_jumps libio_vtable attribute_hidden;

int
__obstack_vprintf_chk (struct obstack *obstack, int flags, const char *format,
Expand Down
2 changes: 1 addition & 1 deletion debug/vdprintf_chk.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ __vdprintf_chk (int d, int flags, const char *format, va_list arg)
#endif
_IO_no_init (&tmpfil.file, _IO_USER_LOCK, 0, &wd, &_IO_wfile_jumps);
_IO_JUMPS (&tmpfil) = &_IO_file_jumps;
_IO_file_init (&tmpfil);
_IO_new_file_init_internal (&tmpfil);
#if !_IO_UNIFIED_JUMPTABLES
tmpfil.vtable = NULL;
#endif
Expand Down
2 changes: 1 addition & 1 deletion debug/vsnprintf_chk.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include "../libio/libioP.h"
#include "../libio/strfile.h"

extern const struct _IO_jump_t _IO_strn_jumps attribute_hidden;
extern const struct _IO_jump_t _IO_strn_jumps libio_vtable attribute_hidden;

/* Write formatted output into S, according to the format
string FORMAT, writing no more than MAXLEN characters. */
Expand Down
2 changes: 1 addition & 1 deletion debug/vsprintf_chk.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ _IO_str_chk_overflow (_IO_FILE *fp, int c)
}


static const struct _IO_jump_t _IO_str_chk_jumps =
static const struct _IO_jump_t _IO_str_chk_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish),
Expand Down
2 changes: 1 addition & 1 deletion libio/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ routines := \
__fbufsize __freading __fwriting __freadable __fwritable __flbf \
__fpurge __fpending __fsetlocking \
\
libc_fatal fmemopen oldfmemopen
libc_fatal fmemopen oldfmemopen vtables

tests = tst_swprintf tst_wprintf tst_swscanf tst_wscanf tst_getwc tst_putwc \
tst_wprintf2 tst-widetext test-fmemopen tst-ext tst-ext2 \
Expand Down
18 changes: 13 additions & 5 deletions libio/fileops.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ extern struct __gconv_trans_data __libio_translit attribute_hidden;


void
_IO_new_file_init (struct _IO_FILE_plus *fp)
_IO_new_file_init_internal (struct _IO_FILE_plus *fp)
{
/* POSIX.1 allows another file handle to be used to change the position
of our file descriptor. Hence we actually don't know the actual
Expand All @@ -151,7 +151,15 @@ _IO_new_file_init (struct _IO_FILE_plus *fp)
_IO_link_in (fp);
fp->file._fileno = -1;
}
libc_hidden_ver (_IO_new_file_init, _IO_file_init)

/* External version of _IO_new_file_init_internal which switches off
vtable validation. */
void
_IO_new_file_init (struct _IO_FILE_plus *fp)
{
IO_set_accept_foreign_vtables (&_IO_vtable_check);
_IO_new_file_init_internal (fp);
}

int
_IO_new_file_close_it (_IO_FILE *fp)
Expand Down Expand Up @@ -1534,7 +1542,7 @@ versioned_symbol (libc, _IO_new_file_write, _IO_file_write, GLIBC_2_1);
versioned_symbol (libc, _IO_new_file_xsputn, _IO_file_xsputn, GLIBC_2_1);
#endif

const struct _IO_jump_t _IO_file_jumps =
const struct _IO_jump_t _IO_file_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_file_finish),
Expand All @@ -1559,7 +1567,7 @@ const struct _IO_jump_t _IO_file_jumps =
};
libc_hidden_data_def (_IO_file_jumps)

const struct _IO_jump_t _IO_file_jumps_mmap =
const struct _IO_jump_t _IO_file_jumps_mmap libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_file_finish),
Expand All @@ -1583,7 +1591,7 @@ const struct _IO_jump_t _IO_file_jumps_mmap =
JUMP_INIT(imbue, _IO_default_imbue)
};

const struct _IO_jump_t _IO_file_jumps_maybe_mmap =
const struct _IO_jump_t _IO_file_jumps_maybe_mmap libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_file_finish),
Expand Down
10 changes: 8 additions & 2 deletions libio/genops.c
Original file line number Diff line number Diff line change
Expand Up @@ -558,11 +558,17 @@ _IO_default_doallocate (_IO_FILE *fp)
libc_hidden_def (_IO_default_doallocate)

void
_IO_init (_IO_FILE *fp, int flags)
_IO_init_internal (_IO_FILE *fp, int flags)
{
_IO_no_init (fp, flags, -1, NULL, NULL);
}
libc_hidden_def (_IO_init)

void
_IO_init (_IO_FILE *fp, int flags)
{
IO_set_accept_foreign_vtables (&_IO_vtable_check);
_IO_init_internal (fp, flags);
}

void
_IO_old_init (_IO_FILE *fp, int flags)
Expand Down
12 changes: 6 additions & 6 deletions libio/iofdopen.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,15 @@ _IO_new_fdopen (int fd, const char *mode)
(use_mmap && (read_write & _IO_NO_WRITES)) ? &_IO_file_jumps_maybe_mmap :
#endif
&_IO_file_jumps;
_IO_file_init (&new_f->fp);
_IO_new_file_init_internal (&new_f->fp);
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
/* We only need to record the fd because _IO_file_init will have unset the
offset. It is important to unset the cached offset because the real
offset in the file could change between now and when the handle is
activated and we would then mislead ftell into believing that we have a
valid offset. */
/* We only need to record the fd because _IO_file_init_internal will
have unset the offset. It is important to unset the cached
offset because the real offset in the file could change between
now and when the handle is activated and we would then mislead
ftell into believing that we have a valid offset. */
new_f->fp.file._fileno = fd;
new_f->fp.file._flags &= ~_IO_DELETE_DONT_CLOSE;

Expand Down
2 changes: 1 addition & 1 deletion libio/iofopen.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ __fopen_internal (const char *filename, const char *mode, int is32)
_IO_no_init (&new_f->fp.file, 1, 0, NULL, NULL);
#endif
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_file_init (&new_f->fp);
_IO_new_file_init_internal (&new_f->fp);
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
Expand Down
8 changes: 4 additions & 4 deletions libio/iofopncook.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ _IO_cookie_seekoff (_IO_FILE *fp, _IO_off64_t offset, int dir, int mode)
}


static const struct _IO_jump_t _IO_cookie_jumps = {
static const struct _IO_jump_t _IO_cookie_jumps libio_vtable = {
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_file_finish),
JUMP_INIT(overflow, _IO_file_overflow),
Expand Down Expand Up @@ -151,13 +151,13 @@ void
_IO_cookie_init (struct _IO_cookie_file *cfile, int read_write,
void *cookie, _IO_cookie_io_functions_t io_functions)
{
_IO_init (&cfile->__fp.file, 0);
_IO_init_internal (&cfile->__fp.file, 0);
_IO_JUMPS (&cfile->__fp) = &_IO_cookie_jumps;

cfile->__cookie = cookie;
set_callbacks (&cfile->__io_functions, io_functions);

_IO_file_init (&cfile->__fp);
_IO_new_file_init_internal (&cfile->__fp);

_IO_mask_flags (&cfile->__fp.file, read_write,
_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
Expand Down Expand Up @@ -238,7 +238,7 @@ _IO_old_cookie_seek (_IO_FILE *fp, _IO_off64_t offset, int dir)
return (ret == -1) ? _IO_pos_BAD : ret;
}

static const struct _IO_jump_t _IO_old_cookie_jumps = {
static const struct _IO_jump_t _IO_old_cookie_jumps libio_vtable = {
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_file_finish),
JUMP_INIT(overflow, _IO_file_overflow),
Expand Down
6 changes: 3 additions & 3 deletions libio/iopopen.c
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,9 @@ _IO_new_popen (const char *command, const char *mode)
new_f->fpx.file.file._lock = &new_f->lock;
#endif
fp = &new_f->fpx.file.file;
_IO_init (fp, 0);
_IO_init_internal (fp, 0);
_IO_JUMPS (&new_f->fpx.file) = &_IO_proc_jumps;
_IO_new_file_init (&new_f->fpx.file);
_IO_new_file_init_internal (&new_f->fpx.file);
#if !_IO_UNIFIED_JUMPTABLES
new_f->fpx.file.vtable = NULL;
#endif
Expand Down Expand Up @@ -344,7 +344,7 @@ _IO_new_proc_close (_IO_FILE *fp)
return wstatus;
}

static const struct _IO_jump_t _IO_proc_jumps = {
static const struct _IO_jump_t _IO_proc_jumps libio_vtable = {
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_new_file_finish),
JUMP_INIT(overflow, _IO_new_file_overflow),
Expand Down
2 changes: 1 addition & 1 deletion libio/iovdprintf.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ _IO_vdprintf (int d, const char *format, _IO_va_list arg)
#endif
_IO_no_init (&tmpfil.file, _IO_USER_LOCK, 0, &wd, &_IO_wfile_jumps);
_IO_JUMPS (&tmpfil) = &_IO_file_jumps;
_IO_file_init (&tmpfil);
_IO_new_file_init_internal (&tmpfil);
#if !_IO_UNIFIED_JUMPTABLES
tmpfil.vtable = NULL;
#endif
Expand Down
Loading

0 comments on commit db3476a

Please sign in to comment.