From e413984095c76a94ae263add58840095b8d4da59 Mon Sep 17 00:00:00 2001 From: Sam Hocevar Date: Mon, 2 May 2016 19:45:24 +0200 Subject: [PATCH] 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. --- src/libzzuf/lib-stream.c | 94 +++++++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/src/libzzuf/lib-stream.c b/src/libzzuf/lib-stream.c index 06d7d06..a0ab1eb 100644 --- a/src/libzzuf/lib-stream.c +++ b/src/libzzuf/lib-stream.c @@ -38,6 +38,7 @@ # include #endif #include +#include /* Needed for memcpy */ #include #include @@ -364,6 +365,31 @@ FILE *NEW(__freopen64)(const char *path, const char *mode, FILE *stream) } #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. * fsetpos64, __fsetpos64 @@ -389,16 +415,42 @@ FILE *NEW(__freopen64)(const char *path, const char *mode, FILE *stream) int64_t oldpos = ZZ_FTELL(stream); \ int oldoff = get_streambuf_offset(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); \ ret = ORIG(myfseek)(stream, offset, whence); \ _zz_unlock(fd); \ - debug_stream("during", stream); \ + \ int64_t newpos = ZZ_FTELL(stream); \ + int newoff = get_streambuf_offset(stream); \ int newcnt = get_streambuf_count(stream); \ - if (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \ - || (newpos == oldpos + oldcnt && newcnt != 0)) \ + int changed = (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \ + || (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_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); \ ret = ORIG(myfsetpos)(stream, pos); \ _zz_unlock(fd); \ - debug_stream("during", stream); \ int64_t newpos = ZZ_FTELL(stream); \ int newcnt = get_streambuf_count(stream); \ - if (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \ - || (newpos == oldpos + oldcnt && newcnt != 0)) \ + int changed = (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \ + || (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_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); \ ORIG(rewind)(stream); \ _zz_unlock(fd); \ - debug_stream("during", stream); \ int64_t newpos = ZZ_FTELL(stream); \ int newcnt = get_streambuf_count(stream); \ - if (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \ - || (newpos == oldpos + oldcnt && newcnt != 0)) \ + int changed = (newpos > oldpos + oldcnt || newpos < oldpos - oldoff \ + || (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_fuzz(fd, get_streambuf_base(stream), get_streambuf_size(stream)); \ } \ @@ -560,13 +612,13 @@ void NEW(rewind)(FILE *stream) _zz_lockfd(fd); \ ret = ORIG(myfread) myargs; \ _zz_unlock(fd); \ - debug_stream("during", stream); \ int64_t newpos = ZZ_FTELL(stream); \ int newcnt = get_streambuf_count(stream); \ - if (newpos > oldpos + oldcnt \ - || (newpos == oldpos + oldcnt && newcnt != 0)) \ + int changed = (newpos > oldpos + oldcnt \ + || (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 * to fuzz it entirely. */ \ _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); \ _zz_unlock(fd); \ 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) \ { \ /* 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); \ ret = ch; \ } \ - int newcnt = get_streambuf_count(stream); \ - if (newpos > oldpos + oldcnt \ - || (newpos == oldpos + oldcnt && newcnt != 0)) \ + if (changed) \ { \ - debug2("... streambuf change detected!"); \ /* Fuzz the internal stream buffer */ \ _zz_setpos(fd, newpos - get_streambuf_offset(stream)); \ _zz_fuzz(fd, get_streambuf_base(stream), get_streambuf_size(stream)); \