Work around an issue with fseek() diversion. Closes #7.

When diverting functions that handle FILE objects, we try to detect whether
the internal buffer contents have changed and therefore need to be fuzzed.
In recent glibc versions, it may happen that an fseek() call is made that
should not invalidate the internal buffer, yet a read() call is still performed.

This is arguably a bug. Considering the following code:

    f = fopen("/dev/null", "r");
    fread(ptr, 1, 1, f);
    fseek(f, 1, SEEK_SET);

The following system calls are made, where the second read() call is
completely unnecessary and is the cause for the buffer refill:

    read(..., 4096);
    lseek(0, SEEK_SET);
    read(..., 4096);

In order to work around this problem, we fill the internal buffer with random
data before calling fseek(), we check whether any bytes have changed, then we
restore it if it was left untouched. This is not 100% robust but honestly it
will do for now.
This commit is contained in:
Sam Hocevar 2016-05-02 19:45:24 +02:00
parent a4625ca02b
commit e413984095

View File

@ -38,6 +38,7 @@
# include <inttypes.h> # include <inttypes.h>
#endif #endif
#include <stdlib.h> #include <stdlib.h>
#include <string.h> /* Needed for memcpy */
#include <stdio.h> #include <stdio.h>
#include <sys/types.h> #include <sys/types.h>
@ -364,6 +365,31 @@ FILE *NEW(__freopen64)(const char *path, const char *mode, FILE *stream)
} }
#endif #endif
/* Quick shuffle table:
* strings /dev/urandom | grep . -nm256 | sort -k2 -t: | sed 's|:.*|,|'
* Then just replace 256 with 0. */
static int const shuffle[256] =
{
111, 14, 180, 186, 221, 114, 219, 79, 66, 46, 152, 81, 246, 200,
141, 172, 85, 244, 112, 92, 34, 106, 218, 205, 236, 7, 121, 115,
109, 131, 10, 96, 188, 148, 17, 107, 94, 182, 235, 163, 143, 63,
248, 202, 52, 154, 37, 241, 53, 129, 25, 159, 242, 38, 171, 213,
6, 203, 255, 193, 42, 209, 28, 176, 210, 60, 54, 144, 3, 71, 89,
116, 12, 237, 67, 216, 252, 178, 174, 164, 98, 234, 32, 26, 175,
24, 130, 128, 113, 99, 212, 62, 11, 75, 185, 73, 93, 31, 30, 44,
122, 173, 139, 91, 136, 162, 194, 41, 56, 101, 68, 69, 211, 151,
97, 55, 83, 33, 50, 119, 156, 149, 208, 157, 253, 247, 161, 133,
230, 166, 225, 204, 224, 13, 110, 123, 142, 64, 65, 155, 215, 9,
197, 140, 58, 77, 214, 126, 195, 179, 220, 232, 125, 147, 8, 39,
187, 27, 217, 100, 134, 199, 88, 206, 231, 250, 74, 2, 135, 120,
21, 245, 118, 243, 82, 183, 238, 150, 158, 61, 4, 177, 146, 153,
117, 249, 254, 233, 90, 222, 207, 48, 15, 18, 20, 16, 47, 0, 51,
165, 138, 127, 169, 72, 1, 201, 145, 191, 192, 239, 49, 19, 160,
226, 228, 84, 181, 251, 36, 87, 22, 43, 70, 45, 105, 5, 189, 95,
40, 196, 59, 57, 190, 80, 104, 167, 78, 124, 103, 240, 184, 170,
137, 29, 23, 223, 108, 102, 86, 198, 227, 35, 229, 76, 168, 132,
};
/* /*
* fseek, fseeko etc. * fseek, fseeko etc.
* fsetpos64, __fsetpos64 * fsetpos64, __fsetpos64
@ -389,16 +415,42 @@ FILE *NEW(__freopen64)(const char *path, const char *mode, FILE *stream)
int64_t oldpos = ZZ_FTELL(stream); \ int64_t oldpos = ZZ_FTELL(stream); \
int oldoff = get_streambuf_offset(stream); \ int oldoff = get_streambuf_offset(stream); \
int oldcnt = get_streambuf_count(stream); \ int oldcnt = get_streambuf_count(stream); \
\
/* backup the internal stream buffer and replace it with
* some random data in order to detect possible changes. */ \
uint8_t seed = shuffle[(fd + rand()) & 0xff]; \
uint8_t oldbuf[oldoff + oldcnt]; \
uint8_t *buf = get_streambuf_base(stream); \
for (int i = 0; i < oldoff + oldcnt; ++i) \
{ \
oldbuf[i] = buf[i]; \
buf[i] = shuffle[(i + seed) & 0xff]; \
} \
\
_zz_lockfd(fd); \ _zz_lockfd(fd); \
ret = ORIG(myfseek)(stream, offset, whence); \ ret = ORIG(myfseek)(stream, offset, whence); \
_zz_unlock(fd); \ _zz_unlock(fd); \
debug_stream("during", stream); \ \
int64_t newpos = ZZ_FTELL(stream); \ int64_t newpos = ZZ_FTELL(stream); \
int newoff = get_streambuf_offset(stream); \
int newcnt = get_streambuf_count(stream); \ int newcnt = get_streambuf_count(stream); \
if (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \ int changed = (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \
|| (newpos == oldpos + oldcnt && newcnt != 0)) \ || (newpos == oldpos + oldcnt && newcnt != 0) \
|| (newoff + newcnt != oldoff + oldcnt)); \
\
/* check whether the buffer contents have changed */ \
uint8_t *newbuf = get_streambuf_base(stream); \
for (int i = 0; !changed && i < newoff + newcnt; ++i) \
if (newbuf[i] != shuffle[(i + seed) & 0xff]) \
changed = 1; \
\
/* if the internal buffer has not changed, restore it */ \
if (!changed) \
memcpy(newbuf, oldbuf, newoff + newcnt); \
\
debug_stream(changed ? "modified" : "unchanged", stream); \
if (changed) \
{ \ { \
debug2("... streambuf change detected!"); \
_zz_setpos(fd, newpos - get_streambuf_offset(stream)); \ _zz_setpos(fd, newpos - get_streambuf_offset(stream)); \
_zz_fuzz(fd, get_streambuf_base(stream), get_streambuf_size(stream)); \ _zz_fuzz(fd, get_streambuf_base(stream), get_streambuf_size(stream)); \
} \ } \
@ -431,13 +483,13 @@ FILE *NEW(__freopen64)(const char *path, const char *mode, FILE *stream)
_zz_lockfd(fd); \ _zz_lockfd(fd); \
ret = ORIG(myfsetpos)(stream, pos); \ ret = ORIG(myfsetpos)(stream, pos); \
_zz_unlock(fd); \ _zz_unlock(fd); \
debug_stream("during", stream); \
int64_t newpos = ZZ_FTELL(stream); \ int64_t newpos = ZZ_FTELL(stream); \
int newcnt = get_streambuf_count(stream); \ int newcnt = get_streambuf_count(stream); \
if (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \ int changed = (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \
|| (newpos == oldpos + oldcnt && newcnt != 0)) \ || (newpos == oldpos + oldcnt && newcnt != 0)); \
debug_stream(changed ? "modified" : "unchanged", stream); \
if (changed) \
{ \ { \
debug2("... streambuf change detected!"); \
_zz_setpos(fd, newpos - get_streambuf_offset(stream)); \ _zz_setpos(fd, newpos - get_streambuf_offset(stream)); \
_zz_fuzz(fd, get_streambuf_base(stream), get_streambuf_size(stream)); \ _zz_fuzz(fd, get_streambuf_base(stream), get_streambuf_size(stream)); \
} \ } \
@ -467,13 +519,13 @@ FILE *NEW(__freopen64)(const char *path, const char *mode, FILE *stream)
_zz_lockfd(fd); \ _zz_lockfd(fd); \
ORIG(rewind)(stream); \ ORIG(rewind)(stream); \
_zz_unlock(fd); \ _zz_unlock(fd); \
debug_stream("during", stream); \
int64_t newpos = ZZ_FTELL(stream); \ int64_t newpos = ZZ_FTELL(stream); \
int newcnt = get_streambuf_count(stream); \ int newcnt = get_streambuf_count(stream); \
if (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \ int changed = (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \
|| (newpos == oldpos + oldcnt && newcnt != 0)) \ || (newpos == oldpos + oldcnt && newcnt != 0)); \
debug_stream(changed ? "modified" : "unchanged", stream); \
if (changed) \
{ \ { \
debug2("... streambuf change detected!"); \
_zz_setpos(fd, newpos - get_streambuf_offset(stream)); \ _zz_setpos(fd, newpos - get_streambuf_offset(stream)); \
_zz_fuzz(fd, get_streambuf_base(stream), get_streambuf_size(stream)); \ _zz_fuzz(fd, get_streambuf_base(stream), get_streambuf_size(stream)); \
} \ } \
@ -560,13 +612,13 @@ void NEW(rewind)(FILE *stream)
_zz_lockfd(fd); \ _zz_lockfd(fd); \
ret = ORIG(myfread) myargs; \ ret = ORIG(myfread) myargs; \
_zz_unlock(fd); \ _zz_unlock(fd); \
debug_stream("during", stream); \
int64_t newpos = ZZ_FTELL(stream); \ int64_t newpos = ZZ_FTELL(stream); \
int newcnt = get_streambuf_count(stream); \ int newcnt = get_streambuf_count(stream); \
if (newpos > oldpos + oldcnt \ int changed = (newpos > oldpos + oldcnt \
|| (newpos == oldpos + oldcnt && newcnt != 0)) \ || (newpos == oldpos + oldcnt && newcnt != 0)); \
debug_stream(changed ? "modified" : "unchanged", stream); \
if (changed) \
{ \ { \
debug2("... streambuf change detected!"); \
/* The internal stream buffer is completely different, so we need /* The internal stream buffer is completely different, so we need
* to fuzz it entirely. */ \ * to fuzz it entirely. */ \
_zz_setpos(fd, newpos - get_streambuf_offset(stream)); \ _zz_setpos(fd, newpos - get_streambuf_offset(stream)); \
@ -650,7 +702,10 @@ size_t NEW(__fread_unlocked_chk)(void *ptr, size_t ptrlen, size_t size,
ret = ORIG(myfgetc)(arg); \ ret = ORIG(myfgetc)(arg); \
_zz_unlock(fd); \ _zz_unlock(fd); \
int64_t newpos = ZZ_FTELL(stream); \ int64_t newpos = ZZ_FTELL(stream); \
debug_stream("during", stream); \ int newcnt = get_streambuf_count(stream); \
int changed = (newpos > oldpos + oldcnt \
|| (newpos == oldpos + oldcnt && newcnt != 0)); \
debug_stream(changed ? "modified" : "unchanged", stream); \
if (oldcnt == 0 && ret != EOF) \ if (oldcnt == 0 && ret != EOF) \
{ \ { \
/* Fuzz returned data that wasn't in the old internal buffer */ \ /* Fuzz returned data that wasn't in the old internal buffer */ \
@ -659,11 +714,8 @@ size_t NEW(__fread_unlocked_chk)(void *ptr, size_t ptrlen, size_t size,
_zz_fuzz(fd, &ch, 1); \ _zz_fuzz(fd, &ch, 1); \
ret = ch; \ ret = ch; \
} \ } \
int newcnt = get_streambuf_count(stream); \ if (changed) \
if (newpos > oldpos + oldcnt \
|| (newpos == oldpos + oldcnt && newcnt != 0)) \
{ \ { \
debug2("... streambuf change detected!"); \
/* Fuzz the internal stream buffer */ \ /* Fuzz the internal stream buffer */ \
_zz_setpos(fd, newpos - get_streambuf_offset(stream)); \ _zz_setpos(fd, newpos - get_streambuf_offset(stream)); \
_zz_fuzz(fd, get_streambuf_base(stream), get_streambuf_size(stream)); \ _zz_fuzz(fd, get_streambuf_base(stream), get_streambuf_size(stream)); \