|
|
|
commit db3476aff19b75c4fdefbe65fcd5f0a90588ba51
|
|
|
|
Author: Florian Weimer <fweimer@redhat.com>
|
|
|
|
Date: Thu Jun 23 20:01:40 2016 +0200
|
|
|
|
|
|
|
|
libio: Implement vtable verification [BZ #20191]
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
diff -rupN a/Makerules b/Makerules
|
|
|
|
--- a/Makerules 2017-08-24 14:50:27.000000000 -0400
|
|
|
|
+++ b/Makerules 2017-08-24 14:56:40.194819878 -0400
|
|
|
|
@@ -520,6 +520,9 @@ $(common-objpfx)shlib.lds: $(common-objp
|
|
|
|
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 $@
|
|
|
|
diff -rupN a/debug/obprintf_chk.c b/debug/obprintf_chk.c
|
|
|
|
--- a/debug/obprintf_chk.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/debug/obprintf_chk.c 2017-08-24 14:56:40.194819878 -0400
|
|
|
|
@@ -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,
|
|
|
|
diff -rupN a/debug/vdprintf_chk.c b/debug/vdprintf_chk.c
|
|
|
|
--- a/debug/vdprintf_chk.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/debug/vdprintf_chk.c 2017-08-24 14:56:40.194819878 -0400
|
|
|
|
@@ -39,7 +39,7 @@ __vdprintf_chk (int d, int flags, const
|
|
|
|
#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
|
|
|
|
diff -rupN a/debug/vsnprintf_chk.c b/debug/vsnprintf_chk.c
|
|
|
|
--- a/debug/vsnprintf_chk.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/debug/vsnprintf_chk.c 2017-08-24 14:56:40.194819878 -0400
|
|
|
|
@@ -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. */
|
|
|
|
diff -rupN a/debug/vsprintf_chk.c b/debug/vsprintf_chk.c
|
|
|
|
--- a/debug/vsprintf_chk.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/debug/vsprintf_chk.c 2017-08-24 14:56:40.194819878 -0400
|
|
|
|
@@ -34,7 +34,7 @@ _IO_str_chk_overflow (fp, 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),
|
|
|
|
diff -rupN a/libio/Makefile b/libio/Makefile
|
|
|
|
--- a/libio/Makefile 2017-08-24 14:50:22.000000000 -0400
|
|
|
|
+++ b/libio/Makefile 2017-08-24 17:29:03.754025266 -0400
|
|
|
|
@@ -44,7 +44,7 @@ routines := \
|
|
|
|
__fbufsize __freading __fwriting __freadable __fwritable __flbf \
|
|
|
|
__fpurge __fpending __fsetlocking \
|
|
|
|
\
|
|
|
|
- libc_fatal fmemopen
|
|
|
|
+ libc_fatal fmemopen vtables
|
|
|
|
|
|
|
|
include ../Makeconfig
|
|
|
|
|
|
|
|
diff -rupN a/libio/fileops.c b/libio/fileops.c
|
|
|
|
--- a/libio/fileops.c 2017-08-24 14:50:23.000000000 -0400
|
|
|
|
+++ b/libio/fileops.c 2017-08-24 14:57:57.765902179 -0400
|
|
|
|
@@ -139,7 +139,7 @@ extern struct __gconv_trans_data __libio
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
-_IO_new_file_init (fp)
|
|
|
|
+_IO_new_file_init_internal (fp)
|
|
|
|
struct _IO_FILE_plus *fp;
|
|
|
|
{
|
|
|
|
/* POSIX.1 allows another file handle to be used to change the position
|
|
|
|
@@ -151,7 +151,15 @@ _IO_new_file_init (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 (fp)
|
|
|
|
@@ -1586,7 +1594,7 @@ versioned_symbol (libc, _IO_new_file_wri
|
|
|
|
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),
|
|
|
|
@@ -1611,7 +1619,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),
|
|
|
|
@@ -1635,7 +1643,7 @@ const struct _IO_jump_t _IO_file_jumps_m
|
|
|
|
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),
|
|
|
|
diff -rupN a/libio/genops.c b/libio/genops.c
|
|
|
|
--- a/libio/genops.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/genops.c 2017-08-24 15:44:28.757584527 -0400
|
|
|
|
@@ -595,13 +595,19 @@ _IO_default_doallocate (fp)
|
|
|
|
libc_hidden_def (_IO_default_doallocate)
|
|
|
|
|
|
|
|
void
|
|
|
|
-_IO_init (fp, flags)
|
|
|
|
+_IO_init_internal (fp, flags)
|
|
|
|
_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 (fp, flags)
|
|
|
|
diff -rupN a/libio/iofdopen.c b/libio/iofdopen.c
|
|
|
|
--- a/libio/iofdopen.c 2017-08-24 14:50:21.000000000 -0400
|
|
|
|
+++ b/libio/iofdopen.c 2017-08-24 14:56:40.220819906 -0400
|
|
|
|
@@ -158,15 +158,15 @@ _IO_new_fdopen (fd, 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;
|
|
|
|
|
|
|
|
diff -rupN a/libio/iofopen.c b/libio/iofopen.c
|
|
|
|
--- a/libio/iofopen.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/iofopen.c 2017-08-24 14:56:40.223819909 -0400
|
|
|
|
@@ -83,7 +83,7 @@ __fopen_internal (filename, mode, 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
|
|
|
|
diff -rupN a/libio/iofopncook.c b/libio/iofopncook.c
|
|
|
|
--- a/libio/iofopncook.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/iofopncook.c 2017-08-24 15:46:49.061694225 -0400
|
|
|
|
@@ -116,7 +116,7 @@ _IO_cookie_seekoff (fp, offset, dir, mod
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
-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),
|
|
|
|
@@ -144,13 +144,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;
|
|
|
|
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);
|
|
|
|
@@ -235,7 +235,7 @@ _IO_old_cookie_seek (fp, offset, 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),
|
|
|
|
diff -rupN a/libio/iopopen.c b/libio/iopopen.c
|
|
|
|
--- a/libio/iopopen.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/iopopen.c 2017-08-24 14:56:40.234819921 -0400
|
|
|
|
@@ -292,9 +292,9 @@ _IO_new_popen (command, 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
|
|
|
|
@@ -350,7 +350,7 @@ _IO_new_proc_close (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),
|
|
|
|
diff -rupN a/libio/iovdprintf.c b/libio/iovdprintf.c
|
|
|
|
--- a/libio/iovdprintf.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/iovdprintf.c 2017-08-24 14:56:40.237819924 -0400
|
|
|
|
@@ -42,7 +42,7 @@ _IO_vdprintf (d, format, 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
|
|
|
|
diff -rupN a/libio/libioP.h b/libio/libioP.h
|
|
|
|
--- a/libio/libioP.h 2017-08-24 14:50:26.000000000 -0400
|
|
|
|
+++ b/libio/libioP.h 2017-08-24 17:26:39.918165252 -0400
|
|
|
|
@@ -108,11 +108,12 @@ extern "C" {
|
|
|
|
|
|
|
|
#if _IO_JUMPS_OFFSET
|
|
|
|
# define _IO_JUMPS_FUNC(THIS) \
|
|
|
|
- (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS ((struct _IO_FILE_plus *) (THIS)) \
|
|
|
|
- + (THIS)->_vtable_offset))
|
|
|
|
+ (IO_validate_vtable \
|
|
|
|
+ (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS ((struct _IO_FILE_plus *) (THIS)) \
|
|
|
|
+ + (THIS)->_vtable_offset)))
|
|
|
|
# define _IO_vtable_offset(THIS) (THIS)->_vtable_offset
|
|
|
|
#else
|
|
|
|
-# define _IO_JUMPS_FUNC(THIS) _IO_JUMPS ((struct _IO_FILE_plus *) (THIS))
|
|
|
|
+# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS ((struct _IO_FILE_plus *) (THIS))))
|
|
|
|
# define _IO_vtable_offset(THIS) 0
|
|
|
|
#endif
|
|
|
|
#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)
|
|
|
|
@@ -361,8 +362,7 @@ extern void _IO_switch_to_main_get_area
|
|
|
|
extern void _IO_switch_to_backup_area (_IO_FILE *) __THROW;
|
|
|
|
extern int _IO_switch_to_get_mode (_IO_FILE *);
|
|
|
|
libc_hidden_proto (_IO_switch_to_get_mode)
|
|
|
|
-extern void _IO_init (_IO_FILE *, int) __THROW;
|
|
|
|
-libc_hidden_proto (_IO_init)
|
|
|
|
+extern void _IO_init_internal (_IO_FILE *, int) attribute_hidden;
|
|
|
|
extern int _IO_sputbackc (_IO_FILE *, int) __THROW;
|
|
|
|
libc_hidden_proto (_IO_sputbackc)
|
|
|
|
extern int _IO_sungetc (_IO_FILE *) __THROW;
|
|
|
|
@@ -570,8 +570,6 @@ extern int _IO_file_underflow_maybe_mmap
|
|
|
|
extern int _IO_file_overflow (_IO_FILE *, int);
|
|
|
|
libc_hidden_proto (_IO_file_overflow)
|
|
|
|
#define _IO_file_is_open(__fp) ((__fp)->_fileno != -1)
|
|
|
|
-extern void _IO_file_init (struct _IO_FILE_plus *) __THROW;
|
|
|
|
-libc_hidden_proto (_IO_file_init)
|
|
|
|
extern _IO_FILE* _IO_file_attach (_IO_FILE *, int);
|
|
|
|
libc_hidden_proto (_IO_file_attach)
|
|
|
|
extern _IO_FILE* _IO_file_open (_IO_FILE *, const char *, int, int, int, int);
|
|
|
|
@@ -597,7 +595,8 @@ extern _IO_FILE* _IO_new_file_fopen (_IO
|
|
|
|
int);
|
|
|
|
extern void _IO_no_init (_IO_FILE *, int, int, struct _IO_wide_data *,
|
|
|
|
const struct _IO_jump_t *) __THROW;
|
|
|
|
-extern void _IO_new_file_init (struct _IO_FILE_plus *) __THROW;
|
|
|
|
+extern void _IO_new_file_init_internal (struct _IO_FILE_plus *)
|
|
|
|
+ __THROW attribute_hidden;
|
|
|
|
extern _IO_FILE* _IO_new_file_setbuf (_IO_FILE *, char *, _IO_ssize_t);
|
|
|
|
extern _IO_FILE* _IO_file_setbuf_mmap (_IO_FILE *, char *, _IO_ssize_t);
|
|
|
|
extern int _IO_new_file_sync (_IO_FILE *);
|
|
|
|
@@ -612,7 +611,8 @@ extern _IO_off64_t _IO_old_file_seekoff
|
|
|
|
extern _IO_size_t _IO_old_file_xsputn (_IO_FILE *, const void *, _IO_size_t);
|
|
|
|
extern int _IO_old_file_underflow (_IO_FILE *);
|
|
|
|
extern int _IO_old_file_overflow (_IO_FILE *, int);
|
|
|
|
-extern void _IO_old_file_init (struct _IO_FILE_plus *) __THROW;
|
|
|
|
+extern void _IO_old_file_init_internal (struct _IO_FILE_plus *)
|
|
|
|
+ __THROW attribute_hidden;
|
|
|
|
extern _IO_FILE* _IO_old_file_attach (_IO_FILE *, int);
|
|
|
|
extern _IO_FILE* _IO_old_file_fopen (_IO_FILE *, const char *, const char *);
|
|
|
|
extern _IO_ssize_t _IO_old_file_write (_IO_FILE *, const void *, _IO_ssize_t);
|
|
|
|
@@ -656,10 +656,6 @@ extern void _IO_str_finish (_IO_FILE *,
|
|
|
|
|
|
|
|
/* Other strfile functions */
|
|
|
|
struct _IO_strfile_;
|
|
|
|
-extern void _IO_str_init_static (struct _IO_strfile_ *, char *, int, char *)
|
|
|
|
- __THROW;
|
|
|
|
-extern void _IO_str_init_readonly (struct _IO_strfile_ *, const char *, int)
|
|
|
|
- __THROW;
|
|
|
|
extern _IO_ssize_t _IO_str_count (_IO_FILE *) __THROW;
|
|
|
|
|
|
|
|
/* And the wide character versions. */
|
|
|
|
@@ -913,3 +909,57 @@ _IO_acquire_lock_clear_flags2_fct (_IO_F
|
|
|
|
| _IO_FLAGS2_SCANF_STD); \
|
|
|
|
} while (0)
|
|
|
|
#endif
|
|
|
|
+
|
|
|
|
+/* Collect all vtables in a special section for vtable verification.
|
|
|
|
+ These symbols cover the extent of this section. */
|
|
|
|
+symbol_set_declare (__libc_IO_vtables)
|
|
|
|
+
|
|
|
|
+/* libio vtables need to carry this attribute so that they pass
|
|
|
|
+ validation. */
|
|
|
|
+#define libio_vtable __attribute__ ((section ("__libc_IO_vtables")))
|
|
|
|
+
|
|
|
|
+#ifdef SHARED
|
|
|
|
+/* If equal to &_IO_vtable_check (with pointer guard protection),
|
|
|
|
+ unknown vtable pointers are valid. This function pointer is solely
|
|
|
|
+ used as a flag. */
|
|
|
|
+extern void (*IO_accept_foreign_vtables) (void) attribute_hidden;
|
|
|
|
+
|
|
|
|
+/* Assigns the passed function pointer (either NULL or
|
|
|
|
+ &_IO_vtable_check) to IO_accept_foreign_vtables. */
|
|
|
|
+static inline void
|
|
|
|
+IO_set_accept_foreign_vtables (void (*flag) (void))
|
|
|
|
+{
|
|
|
|
+ PTR_MANGLE (flag);
|
|
|
|
+ atomic_store_relaxed (&IO_accept_foreign_vtables, flag);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#else /* !SHARED */
|
|
|
|
+
|
|
|
|
+/* The statically-linked version does nothing. */
|
|
|
|
+static inline void
|
|
|
|
+IO_set_accept_foreign_vtables (void (*flag) (void))
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+/* Check if unknown vtable pointers are permitted; otherwise,
|
|
|
|
+ terminate the process. */
|
|
|
|
+void _IO_vtable_check (void) attribute_hidden;
|
|
|
|
+
|
|
|
|
+/* Perform vtable pointer validation. If validation fails, terminate
|
|
|
|
+ the process. */
|
|
|
|
+static inline const struct _IO_jump_t *
|
|
|
|
+IO_validate_vtable (const struct _IO_jump_t *vtable)
|
|
|
|
+{
|
|
|
|
+ /* Fast path: The vtable pointer is within the __libc_IO_vtables
|
|
|
|
+ section. */
|
|
|
|
+ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
|
|
|
|
+ const char *ptr = (const char *) vtable;
|
|
|
|
+ uintptr_t offset = ptr - __start___libc_IO_vtables;
|
|
|
|
+ if (__glibc_unlikely (offset >= section_length))
|
|
|
|
+ /* The vtable pointer is not in the expected section. Use the
|
|
|
|
+ slow path, which will terminate the process if necessary. */
|
|
|
|
+ _IO_vtable_check ();
|
|
|
|
+ return vtable;
|
|
|
|
+}
|
|
|
|
diff -rupN a/libio/memstream.c b/libio/memstream.c
|
|
|
|
--- a/libio/memstream.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/memstream.c 2017-08-24 15:56:02.643120827 -0400
|
|
|
|
@@ -33,7 +33,7 @@ static int _IO_mem_sync (_IO_FILE* fp) _
|
|
|
|
static void _IO_mem_finish (_IO_FILE* fp, int) __THROW;
|
|
|
|
|
|
|
|
|
|
|
|
-static const struct _IO_jump_t _IO_mem_jumps =
|
|
|
|
+static const struct _IO_jump_t _IO_mem_jumps libio_vtable =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT (finish, _IO_mem_finish),
|
|
|
|
@@ -85,7 +85,7 @@ open_memstream (bufloc, sizeloc)
|
|
|
|
buf = calloc (1, _IO_BUFSIZ);
|
|
|
|
if (buf == NULL)
|
|
|
|
return NULL;
|
|
|
|
- _IO_init (&new_f->fp._sf._sbf._f, 0);
|
|
|
|
+ _IO_init_internal (&new_f->fp._sf._sbf._f, 0);
|
|
|
|
_IO_JUMPS ((struct _IO_FILE_plus *) &new_f->fp._sf._sbf) = &_IO_mem_jumps;
|
|
|
|
_IO_str_init_static_internal (&new_f->fp._sf, buf, _IO_BUFSIZ, buf);
|
|
|
|
new_f->fp._sf._sbf._f._flags &= ~_IO_USER_BUF;
|
|
|
|
diff -rupN a/libio/obprintf.c b/libio/obprintf.c
|
|
|
|
--- a/libio/obprintf.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/obprintf.c 2017-08-24 14:56:40.255819944 -0400
|
|
|
|
@@ -91,7 +91,7 @@ _IO_obstack_xsputn (_IO_FILE *fp, const
|
|
|
|
|
|
|
|
|
|
|
|
/* the jump table. */
|
|
|
|
-const struct _IO_jump_t _IO_obstack_jumps attribute_hidden =
|
|
|
|
+const struct _IO_jump_t _IO_obstack_jumps libio_vtable attribute_hidden =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT(finish, NULL),
|
|
|
|
diff -rupN a/libio/oldfileops.c b/libio/oldfileops.c
|
|
|
|
--- a/libio/oldfileops.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/oldfileops.c 2017-08-24 15:56:27.333139635 -0400
|
|
|
|
@@ -113,7 +113,7 @@ extern int errno;
|
|
|
|
|
|
|
|
void
|
|
|
|
attribute_compat_text_section
|
|
|
|
-_IO_old_file_init (fp)
|
|
|
|
+_IO_old_file_init_internal (fp)
|
|
|
|
struct _IO_FILE_plus *fp;
|
|
|
|
{
|
|
|
|
/* POSIX.1 allows another file handle to be used to change the position
|
|
|
|
@@ -138,6 +138,14 @@ _IO_old_file_init (fp)
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
+void
|
|
|
|
+attribute_compat_text_section
|
|
|
|
+_IO_old_file_init (struct _IO_FILE_plus *fp)
|
|
|
|
+{
|
|
|
|
+ IO_set_accept_foreign_vtables (&_IO_vtable_check);
|
|
|
|
+ _IO_old_file_init_internal (fp);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
int
|
|
|
|
attribute_compat_text_section
|
|
|
|
_IO_old_file_close_it (fp)
|
|
|
|
@@ -776,7 +784,7 @@ _IO_old_file_xsputn (f, data, n)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
-const struct _IO_jump_t _IO_old_file_jumps =
|
|
|
|
+const struct _IO_jump_t _IO_old_file_jumps libio_vtable =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT(finish, _IO_old_file_finish),
|
|
|
|
diff -rupN a/libio/oldiofdopen.c b/libio/oldiofdopen.c
|
|
|
|
--- a/libio/oldiofdopen.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/oldiofdopen.c 2017-08-24 15:57:15.823176524 -0400
|
|
|
|
@@ -114,7 +114,7 @@ _IO_old_fdopen (fd, mode)
|
|
|
|
#endif
|
|
|
|
_IO_old_init (&new_f->fp.file._file, 0);
|
|
|
|
_IO_JUMPS ((struct _IO_FILE_plus *) &new_f->fp) = &_IO_old_file_jumps;
|
|
|
|
- _IO_old_file_init ((struct _IO_FILE_plus *) &new_f->fp);
|
|
|
|
+ _IO_old_file_init_internal ((struct _IO_FILE_plus *) &new_f->fp);
|
|
|
|
#if !_IO_UNIFIED_JUMPTABLES
|
|
|
|
new_f->fp.vtable = NULL;
|
|
|
|
#endif
|
|
|
|
diff -rupN a/libio/oldiofopen.c b/libio/oldiofopen.c
|
|
|
|
--- a/libio/oldiofopen.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/oldiofopen.c 2017-08-24 15:58:13.978220676 -0400
|
|
|
|
@@ -53,7 +53,7 @@ _IO_old_fopen (filename, mode)
|
|
|
|
#endif
|
|
|
|
_IO_old_init (&new_f->fp.file._file, 0);
|
|
|
|
_IO_JUMPS ((struct _IO_FILE_plus *) &new_f->fp) = &_IO_old_file_jumps;
|
|
|
|
- _IO_old_file_init ((struct _IO_FILE_plus *) &new_f->fp);
|
|
|
|
+ _IO_old_file_init_internal ((struct _IO_FILE_plus *) &new_f->fp);
|
|
|
|
#if !_IO_UNIFIED_JUMPTABLES
|
|
|
|
new_f->fp.vtable = NULL;
|
|
|
|
#endif
|
|
|
|
diff -rupN a/libio/oldiopopen.c b/libio/oldiopopen.c
|
|
|
|
--- a/libio/oldiopopen.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/oldiopopen.c 2017-08-24 15:58:32.060234383 -0400
|
|
|
|
@@ -216,7 +216,7 @@ _IO_old_popen (command, mode)
|
|
|
|
fp = &new_f->fpx.file.file._file;
|
|
|
|
_IO_old_init (fp, 0);
|
|
|
|
_IO_JUMPS ((struct _IO_FILE_plus *) &new_f->fpx.file) = &_IO_old_proc_jumps;
|
|
|
|
- _IO_old_file_init ((struct _IO_FILE_plus *) &new_f->fpx.file);
|
|
|
|
+ _IO_old_file_init_internal ((struct _IO_FILE_plus *) &new_f->fpx.file);
|
|
|
|
#if !_IO_UNIFIED_JUMPTABLES
|
|
|
|
new_f->fpx.file.vtable = NULL;
|
|
|
|
#endif
|
|
|
|
@@ -273,7 +273,7 @@ _IO_old_proc_close (fp)
|
|
|
|
return wstatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
-const struct _IO_jump_t _IO_old_proc_jumps = {
|
|
|
|
+const struct _IO_jump_t _IO_old_proc_jumps libio_vtable = {
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT(finish, _IO_old_file_finish),
|
|
|
|
JUMP_INIT(overflow, _IO_old_file_overflow),
|
|
|
|
diff -rupN a/libio/strops.c b/libio/strops.c
|
|
|
|
--- a/libio/strops.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/strops.c 2017-08-24 14:56:40.289819980 -0400
|
|
|
|
@@ -345,7 +345,7 @@ _IO_str_finish (fp, dummy)
|
|
|
|
_IO_default_finish (fp, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
-const struct _IO_jump_t _IO_str_jumps =
|
|
|
|
+const struct _IO_jump_t _IO_str_jumps libio_vtable =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT(finish, _IO_str_finish),
|
|
|
|
diff -rupN a/libio/vsnprintf.c b/libio/vsnprintf.c
|
|
|
|
--- a/libio/vsnprintf.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/vsnprintf.c 2017-08-24 14:56:40.292819983 -0400
|
|
|
|
@@ -66,7 +66,7 @@ _IO_strn_overflow (fp, c)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
-const struct _IO_jump_t _IO_strn_jumps attribute_hidden =
|
|
|
|
+const struct _IO_jump_t _IO_strn_jumps libio_vtable attribute_hidden =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT(finish, _IO_str_finish),
|
|
|
|
diff -rupN a/libio/vswprintf.c b/libio/vswprintf.c
|
|
|
|
--- a/libio/vswprintf.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/vswprintf.c 2017-08-24 14:56:40.296819988 -0400
|
|
|
|
@@ -65,7 +65,7 @@ _IO_wstrn_overflow (fp, c)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
-const struct _IO_jump_t _IO_wstrn_jumps attribute_hidden =
|
|
|
|
+const struct _IO_jump_t _IO_wstrn_jumps libio_vtable attribute_hidden =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT(finish, _IO_wstr_finish),
|
|
|
|
diff -rupN a/libio/vtables.c b/libio/vtables.c
|
|
|
|
--- a/libio/vtables.c 1969-12-31 19:00:00.000000000 -0500
|
|
|
|
+++ b/libio/vtables.c 2017-08-24 14:56:40.299819991 -0400
|
|
|
|
@@ -0,0 +1,70 @@
|
|
|
|
+/* libio vtable validation.
|
|
|
|
+ Copyright (C) 2016 Free Software Foundation, Inc.
|
|
|
|
+ This file is part of the GNU C Library.
|
|
|
|
+
|
|
|
|
+ The GNU C Library is free software; you can redistribute it and/or
|
|
|
|
+ modify it under the terms of the GNU Lesser General Public
|
|
|
|
+ License as published by the Free Software Foundation; either
|
|
|
|
+ version 2.1 of the License, or (at your option) any later version.
|
|
|
|
+
|
|
|
|
+ The GNU C Library is distributed in the hope that it will be useful,
|
|
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
+ Lesser General Public License for more details.
|
|
|
|
+
|
|
|
|
+ You should have received a copy of the GNU Lesser General Public
|
|
|
|
+ License along with the GNU C Library; if not, see
|
|
|
|
+ <http://www.gnu.org/licenses/>. */
|
|
|
|
+
|
|
|
|
+#include <dlfcn.h>
|
|
|
|
+#include <libioP.h>
|
|
|
|
+#include <stdio.h>
|
|
|
|
+
|
|
|
|
+#ifdef SHARED
|
|
|
|
+
|
|
|
|
+void (*IO_accept_foreign_vtables) (void) attribute_hidden;
|
|
|
|
+
|
|
|
|
+/* Used to detected multiple libcs. */
|
|
|
|
+extern struct dl_open_hook *_dl_open_hook;
|
|
|
|
+libc_hidden_proto (_dl_open_hook);
|
|
|
|
+
|
|
|
|
+#else /* !SHARED */
|
|
|
|
+
|
|
|
|
+/* Used to check whether static dlopen support is needed. */
|
|
|
|
+# pragma weak __dlopen
|
|
|
|
+
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+void attribute_hidden
|
|
|
|
+_IO_vtable_check (void)
|
|
|
|
+{
|
|
|
|
+#ifdef SHARED
|
|
|
|
+ /* Honor the compatibility flag. */
|
|
|
|
+ void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
|
|
|
|
+ PTR_DEMANGLE (flag);
|
|
|
|
+ if (flag == &_IO_vtable_check)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ /* In case this libc copy is in a non-default namespace, we always
|
|
|
|
+ need to accept foreign vtables because there is always a
|
|
|
|
+ possibility that FILE * objects are passed across the linking
|
|
|
|
+ boundary. */
|
|
|
|
+ {
|
|
|
|
+ Dl_info di;
|
|
|
|
+ struct link_map *l;
|
|
|
|
+ if (_dl_open_hook != NULL
|
|
|
|
+ || (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
|
|
|
|
+ && l->l_ns != LM_ID_BASE))
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+#else /* !SHARED */
|
|
|
|
+ /* We cannot perform vtable validation in the static dlopen case
|
|
|
|
+ because FILE * handles might be passed back and forth across the
|
|
|
|
+ boundary. Therefore, we disable checking in this case. */
|
|
|
|
+ if (__dlopen != NULL)
|
|
|
|
+ return;
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+ __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
|
|
|
|
+}
|
|
|
|
diff -rupN a/libio/wfileops.c b/libio/wfileops.c
|
|
|
|
--- a/libio/wfileops.c 2017-08-24 14:50:25.000000000 -0400
|
|
|
|
+++ b/libio/wfileops.c 2017-08-24 14:56:40.303819995 -0400
|
|
|
|
@@ -1035,7 +1035,7 @@ _IO_wfile_xsputn (f, data, n)
|
|
|
|
libc_hidden_def (_IO_wfile_xsputn)
|
|
|
|
|
|
|
|
|
|
|
|
-const struct _IO_jump_t _IO_wfile_jumps =
|
|
|
|
+const struct _IO_jump_t _IO_wfile_jumps libio_vtable =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT(finish, _IO_new_file_finish),
|
|
|
|
@@ -1061,7 +1061,7 @@ const struct _IO_jump_t _IO_wfile_jumps
|
|
|
|
libc_hidden_data_def (_IO_wfile_jumps)
|
|
|
|
|
|
|
|
|
|
|
|
-const struct _IO_jump_t _IO_wfile_jumps_mmap =
|
|
|
|
+const struct _IO_jump_t _IO_wfile_jumps_mmap libio_vtable =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT(finish, _IO_new_file_finish),
|
|
|
|
@@ -1085,7 +1085,7 @@ const struct _IO_jump_t _IO_wfile_jumps_
|
|
|
|
JUMP_INIT(imbue, _IO_default_imbue)
|
|
|
|
};
|
|
|
|
|
|
|
|
-const struct _IO_jump_t _IO_wfile_jumps_maybe_mmap =
|
|
|
|
+const struct _IO_jump_t _IO_wfile_jumps_maybe_mmap libio_vtable =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT(finish, _IO_new_file_finish),
|
|
|
|
diff -rupN a/libio/wmemstream.c b/libio/wmemstream.c
|
|
|
|
--- a/libio/wmemstream.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/libio/wmemstream.c 2017-08-24 14:56:40.307819999 -0400
|
|
|
|
@@ -34,7 +34,7 @@ static int _IO_wmem_sync (_IO_FILE* fp)
|
|
|
|
static void _IO_wmem_finish (_IO_FILE* fp, int) __THROW;
|
|
|
|
|
|
|
|
|
|
|
|
-static const struct _IO_jump_t _IO_wmem_jumps =
|
|
|
|
+static const struct _IO_jump_t _IO_wmem_jumps libio_vtable =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT (finish, _IO_wmem_finish),
|
|
|
|
diff -rupN a/libio/wstrops.c b/libio/wstrops.c
|
|
|
|
--- a/libio/wstrops.c 2017-08-24 14:50:23.000000000 -0400
|
|
|
|
+++ b/libio/wstrops.c 2017-08-24 14:56:40.310820002 -0400
|
|
|
|
@@ -347,7 +347,7 @@ _IO_wstr_finish (fp, dummy)
|
|
|
|
_IO_wdefault_finish (fp, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
-const struct _IO_jump_t _IO_wstr_jumps =
|
|
|
|
+const struct _IO_jump_t _IO_wstr_jumps libio_vtable =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT(finish, _IO_wstr_finish),
|
|
|
|
diff -rupN a/stdio-common/vfprintf.c b/stdio-common/vfprintf.c
|
|
|
|
--- a/stdio-common/vfprintf.c 2017-08-24 14:50:20.000000000 -0400
|
|
|
|
+++ b/stdio-common/vfprintf.c 2017-08-24 14:56:40.314820007 -0400
|
|
|
|
@@ -2234,7 +2234,7 @@ _IO_helper_overflow (_IO_FILE *s, int c)
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef COMPILE_WPRINTF
|
|
|
|
-static const struct _IO_jump_t _IO_helper_jumps =
|
|
|
|
+static const struct _IO_jump_t _IO_helper_jumps libio_vtable =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT (finish, _IO_wdefault_finish),
|
|
|
|
@@ -2256,7 +2256,7 @@ static const struct _IO_jump_t _IO_helpe
|
|
|
|
JUMP_INIT (stat, _IO_default_stat)
|
|
|
|
};
|
|
|
|
#else
|
|
|
|
-static const struct _IO_jump_t _IO_helper_jumps =
|
|
|
|
+static const struct _IO_jump_t _IO_helper_jumps libio_vtable =
|
|
|
|
{
|
|
|
|
JUMP_INIT_DUMMY,
|
|
|
|
JUMP_INIT (finish, _IO_default_finish),
|
|
|
|
diff -rupN a/stdlib/strfmon_l.c b/stdlib/strfmon_l.c
|
|
|
|
--- a/stdlib/strfmon_l.c 2012-12-24 22:02:13.000000000 -0500
|
|
|
|
+++ b/stdlib/strfmon_l.c 2017-08-24 14:56:40.314820007 -0400
|
|
|
|
@@ -515,7 +515,7 @@ __vstrfmon_l (char *s, size_t maxsize, _
|
|
|
|
#ifdef _IO_MTSAFE_IO
|
|
|
|
f._sbf._f._lock = NULL;
|
|
|
|
#endif
|
|
|
|
- _IO_init (&f._sbf._f, 0);
|
|
|
|
+ _IO_init_internal (&f._sbf._f, 0);
|
|
|
|
_IO_JUMPS (&f._sbf) = &_IO_str_jumps;
|
|
|
|
_IO_str_init_static_internal (&f, dest, (s + maxsize) - dest, dest);
|
|
|
|
/* We clear the last available byte so we can find out whether
|