Fixed bug 3760 - RWops doesn't check for integer overflow when stdio_fseek only supports 32 bits
Simon Hug
When RWops seeks with fseek or fseeko it uses the types long or off_t which can be 32 bits on some platforms. stdio_seek does not check if the 64-bit integer for the offset fits into a 32-bit integer. Offsets equal or larger than 2 GiB will have implementation-defined behavior and failure states would be very confusing to debug.
The attached patch adds range checking by using the macros from limits.h for long type and some bit shifting for off_t because POSIX couldn't be bothered to specify min and max macros.
It also defines HAVE_FSEEKI64 in SDL_config_windows.h so that the Windows function gets picked up automatically with the default config.
And there's an additional error message for when ftell fails.
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
22 /* We won't get fseeko64 on QNX if _LARGEFILE64_SOURCE is defined, but the
23 configure script knows the C runtime has it and enables it. */
25 /* Need this so Linux systems define fseek64o, ftell64o and off64_t */
26 #define _LARGEFILE64_SOURCE
29 #include "../SDL_internal.h"
31 #if defined(__WIN32__)
32 #include "../core/windows/SDL_windows.h"
43 /* This file provides a general interface for SDL to read and write
44 data sources. It can easily be extended to files, memory, etc.
47 #include "SDL_endian.h"
48 #include "SDL_rwops.h"
51 #include "cocoa/SDL_rwopsbundlesupport.h"
52 #endif /* __APPLE__ */
55 #include "../core/android/SDL_android.h"
56 #include "SDL_system.h"
60 #include "nacl_io/nacl_io.h"
65 /* Functions to read/write Win32 API file pointers */
67 #ifndef INVALID_SET_FILE_POINTER
68 #define INVALID_SET_FILE_POINTER 0xFFFFFFFF
71 #define READAHEAD_BUFFER_SIZE 1024
74 windows_file_open(SDL_RWops * context, const char *filename, const char *mode)
78 DWORD r_right, w_right;
79 DWORD must_exist, truncate;
83 return -1; /* failed (invalid call) */
85 context->hidden.windowsio.h = INVALID_HANDLE_VALUE; /* mark this as unusable */
86 context->hidden.windowsio.buffer.data = NULL;
87 context->hidden.windowsio.buffer.size = 0;
88 context->hidden.windowsio.buffer.left = 0;
90 /* "r" = reading, file must exist */
91 /* "w" = writing, truncate existing, file may not exist */
92 /* "r+"= reading or writing, file must exist */
93 /* "a" = writing, append file may not exist */
94 /* "a+"= append + read, file may not exist */
95 /* "w+" = read, write, truncate. file may not exist */
97 must_exist = (SDL_strchr(mode, 'r') != NULL) ? OPEN_EXISTING : 0;
98 truncate = (SDL_strchr(mode, 'w') != NULL) ? CREATE_ALWAYS : 0;
99 r_right = (SDL_strchr(mode, '+') != NULL
100 || must_exist) ? GENERIC_READ : 0;
101 a_mode = (SDL_strchr(mode, 'a') != NULL) ? OPEN_ALWAYS : 0;
102 w_right = (a_mode || SDL_strchr(mode, '+')
103 || truncate) ? GENERIC_WRITE : 0;
105 if (!r_right && !w_right) /* inconsistent mode */
106 return -1; /* failed (invalid call) */
108 context->hidden.windowsio.buffer.data =
109 (char *) SDL_malloc(READAHEAD_BUFFER_SIZE);
110 if (!context->hidden.windowsio.buffer.data) {
111 return SDL_OutOfMemory();
113 /* Do not open a dialog box if failure */
115 SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
118 LPTSTR tstr = WIN_UTF8ToString(filename);
119 h = CreateFile(tstr, (w_right | r_right),
120 (w_right) ? 0 : FILE_SHARE_READ, NULL,
121 (must_exist | truncate | a_mode),
122 FILE_ATTRIBUTE_NORMAL, NULL);
126 /* restore old behavior */
127 SetErrorMode(old_error_mode);
129 if (h == INVALID_HANDLE_VALUE) {
130 SDL_free(context->hidden.windowsio.buffer.data);
131 context->hidden.windowsio.buffer.data = NULL;
132 SDL_SetError("Couldn't open %s", filename);
133 return -2; /* failed (CreateFile) */
135 context->hidden.windowsio.h = h;
136 context->hidden.windowsio.append = a_mode ? SDL_TRUE : SDL_FALSE;
141 static Sint64 SDLCALL
142 windows_file_size(SDL_RWops * context)
146 if (!context || context->hidden.windowsio.h == INVALID_HANDLE_VALUE) {
147 return SDL_SetError("windows_file_size: invalid context/file not opened");
150 if (!GetFileSizeEx(context->hidden.windowsio.h, &size)) {
151 return WIN_SetError("windows_file_size");
154 return size.QuadPart;
157 static Sint64 SDLCALL
158 windows_file_seek(SDL_RWops * context, Sint64 offset, int whence)
161 LARGE_INTEGER windowsoffset;
163 if (!context || context->hidden.windowsio.h == INVALID_HANDLE_VALUE) {
164 return SDL_SetError("windows_file_seek: invalid context/file not opened");
167 /* FIXME: We may be able to satisfy the seek within buffered data */
168 if (whence == RW_SEEK_CUR && context->hidden.windowsio.buffer.left) {
169 offset -= (long)context->hidden.windowsio.buffer.left;
171 context->hidden.windowsio.buffer.left = 0;
175 windowswhence = FILE_BEGIN;
178 windowswhence = FILE_CURRENT;
181 windowswhence = FILE_END;
184 return SDL_SetError("windows_file_seek: Unknown value for 'whence'");
187 windowsoffset.QuadPart = offset;
188 if (!SetFilePointerEx(context->hidden.windowsio.h, windowsoffset, &windowsoffset, windowswhence)) {
189 return WIN_SetError("windows_file_seek");
191 return windowsoffset.QuadPart;
194 static size_t SDLCALL
195 windows_file_read(SDL_RWops * context, void *ptr, size_t size, size_t maxnum)
198 size_t total_read = 0;
202 total_need = size * maxnum;
204 if (!context || context->hidden.windowsio.h == INVALID_HANDLE_VALUE
208 if (context->hidden.windowsio.buffer.left > 0) {
209 void *data = (char *) context->hidden.windowsio.buffer.data +
210 context->hidden.windowsio.buffer.size -
211 context->hidden.windowsio.buffer.left;
213 SDL_min(total_need, context->hidden.windowsio.buffer.left);
214 SDL_memcpy(ptr, data, read_ahead);
215 context->hidden.windowsio.buffer.left -= read_ahead;
217 if (read_ahead == total_need) {
220 ptr = (char *) ptr + read_ahead;
221 total_need -= read_ahead;
222 total_read += read_ahead;
225 if (total_need < READAHEAD_BUFFER_SIZE) {
227 (context->hidden.windowsio.h, context->hidden.windowsio.buffer.data,
228 READAHEAD_BUFFER_SIZE, &byte_read, NULL)) {
229 SDL_Error(SDL_EFREAD);
232 read_ahead = SDL_min(total_need, (int) byte_read);
233 SDL_memcpy(ptr, context->hidden.windowsio.buffer.data, read_ahead);
234 context->hidden.windowsio.buffer.size = byte_read;
235 context->hidden.windowsio.buffer.left = byte_read - read_ahead;
236 total_read += read_ahead;
239 (context->hidden.windowsio.h, ptr, (DWORD)total_need, &byte_read, NULL)) {
240 SDL_Error(SDL_EFREAD);
243 total_read += byte_read;
245 return (total_read / size);
248 static size_t SDLCALL
249 windows_file_write(SDL_RWops * context, const void *ptr, size_t size,
257 total_bytes = size * num;
259 if (!context || context->hidden.windowsio.h == INVALID_HANDLE_VALUE
260 || total_bytes <= 0 || !size)
263 if (context->hidden.windowsio.buffer.left) {
264 SetFilePointer(context->hidden.windowsio.h,
265 -(LONG)context->hidden.windowsio.buffer.left, NULL,
267 context->hidden.windowsio.buffer.left = 0;
270 /* if in append mode, we must go to the EOF before write */
271 if (context->hidden.windowsio.append) {
272 if (SetFilePointer(context->hidden.windowsio.h, 0L, NULL, FILE_END) ==
273 INVALID_SET_FILE_POINTER) {
274 SDL_Error(SDL_EFWRITE);
280 (context->hidden.windowsio.h, ptr, (DWORD)total_bytes, &byte_written, NULL)) {
281 SDL_Error(SDL_EFWRITE);
285 nwritten = byte_written / size;
290 windows_file_close(SDL_RWops * context)
294 if (context->hidden.windowsio.h != INVALID_HANDLE_VALUE) {
295 CloseHandle(context->hidden.windowsio.h);
296 context->hidden.windowsio.h = INVALID_HANDLE_VALUE; /* to be sure */
298 SDL_free(context->hidden.windowsio.buffer.data);
299 context->hidden.windowsio.buffer.data = NULL;
304 #endif /* __WIN32__ */
309 #define fopen fopen64
312 #define fseek_off_t off64_t
313 #define fseek fseeko64
314 #define ftell ftello64
315 #elif defined(HAVE_FSEEKO)
316 #if defined(OFF_MIN) && defined(OFF_MAX)
317 #define FSEEK_OFF_MIN OFF_MIN
318 #define FSEEK_OFF_MAX OFF_MAX
319 #elif defined(HAVE_LIMITS_H)
320 /* POSIX doesn't specify the minimum and maximum macros for off_t so
321 * we have to improvise and dance around implementation-defined
322 * behavior. This may fail if the off_t type has padding bits or
323 * is not a two's-complement representation. The compilers will detect
324 * and eliminate the dead code if off_t has 64 bits.
326 #define FSEEK_OFF_MAX (((((off_t)1 << (sizeof(off_t) * CHAR_BIT - 2)) - 1) << 1) + 1)
327 #define FSEEK_OFF_MIN (-(FSEEK_OFF_MAX) - 1)
329 #define fseek_off_t off_t
332 #elif defined(HAVE__FSEEKI64)
333 #define fseek_off_t __int64
334 #define fseek _fseeki64
335 #define ftell _ftelli64
338 #define FSEEK_OFF_MIN LONG_MIN
339 #define FSEEK_OFF_MAX LONG_MAX
341 #define fseek_off_t long
344 /* Functions to read/write stdio file pointers */
346 static Sint64 SDLCALL
347 stdio_size(SDL_RWops * context)
351 pos = SDL_RWseek(context, 0, RW_SEEK_CUR);
355 size = SDL_RWseek(context, 0, RW_SEEK_END);
357 SDL_RWseek(context, pos, RW_SEEK_SET);
361 static Sint64 SDLCALL
362 stdio_seek(SDL_RWops * context, Sint64 offset, int whence)
364 #if defined(FSEEK_OFF_MIN) && defined(FSEEK_OFF_MAX)
365 if (offset < (Sint64)(FSEEK_OFF_MIN) || offset > (Sint64)(FSEEK_OFF_MAX)) {
366 return SDL_SetError("Seek offset out of range");
370 if (fseek(context->hidden.stdio.fp, (fseek_off_t)offset, whence) == 0) {
371 Sint64 pos = ftell(context->hidden.stdio.fp);
373 return SDL_SetError("Couldn't get stream offset");
377 return SDL_Error(SDL_EFSEEK);
380 static size_t SDLCALL
381 stdio_read(SDL_RWops * context, void *ptr, size_t size, size_t maxnum)
385 nread = fread(ptr, size, maxnum, context->hidden.stdio.fp);
386 if (nread == 0 && ferror(context->hidden.stdio.fp)) {
387 SDL_Error(SDL_EFREAD);
392 static size_t SDLCALL
393 stdio_write(SDL_RWops * context, const void *ptr, size_t size, size_t num)
397 nwrote = fwrite(ptr, size, num, context->hidden.stdio.fp);
398 if (nwrote == 0 && ferror(context->hidden.stdio.fp)) {
399 SDL_Error(SDL_EFWRITE);
405 stdio_close(SDL_RWops * context)
409 if (context->hidden.stdio.autoclose) {
410 /* WARNING: Check the return value here! */
411 if (fclose(context->hidden.stdio.fp) != 0) {
412 status = SDL_Error(SDL_EFWRITE);
419 #endif /* !HAVE_STDIO_H */
421 /* Functions to read/write memory pointers */
423 static Sint64 SDLCALL
424 mem_size(SDL_RWops * context)
426 return (Sint64)(context->hidden.mem.stop - context->hidden.mem.base);
429 static Sint64 SDLCALL
430 mem_seek(SDL_RWops * context, Sint64 offset, int whence)
436 newpos = context->hidden.mem.base + offset;
439 newpos = context->hidden.mem.here + offset;
442 newpos = context->hidden.mem.stop + offset;
445 return SDL_SetError("Unknown value for 'whence'");
447 if (newpos < context->hidden.mem.base) {
448 newpos = context->hidden.mem.base;
450 if (newpos > context->hidden.mem.stop) {
451 newpos = context->hidden.mem.stop;
453 context->hidden.mem.here = newpos;
454 return (Sint64)(context->hidden.mem.here - context->hidden.mem.base);
457 static size_t SDLCALL
458 mem_read(SDL_RWops * context, void *ptr, size_t size, size_t maxnum)
461 size_t mem_available;
463 total_bytes = (maxnum * size);
464 if ((maxnum <= 0) || (size <= 0)
465 || ((total_bytes / maxnum) != (size_t) size)) {
469 mem_available = (context->hidden.mem.stop - context->hidden.mem.here);
470 if (total_bytes > mem_available) {
471 total_bytes = mem_available;
474 SDL_memcpy(ptr, context->hidden.mem.here, total_bytes);
475 context->hidden.mem.here += total_bytes;
477 return (total_bytes / size);
480 static size_t SDLCALL
481 mem_write(SDL_RWops * context, const void *ptr, size_t size, size_t num)
483 if ((context->hidden.mem.here + (num * size)) > context->hidden.mem.stop) {
484 num = (context->hidden.mem.stop - context->hidden.mem.here) / size;
486 SDL_memcpy(context->hidden.mem.here, ptr, num * size);
487 context->hidden.mem.here += num * size;
491 static size_t SDLCALL
492 mem_writeconst(SDL_RWops * context, const void *ptr, size_t size, size_t num)
494 SDL_SetError("Can't write to read-only memory");
499 mem_close(SDL_RWops * context)
508 /* Functions to create SDL_RWops structures from various data sources */
511 SDL_RWFromFile(const char *file, const char *mode)
513 SDL_RWops *rwops = NULL;
514 if (!file || !*file || !mode || !*mode) {
515 SDL_SetError("SDL_RWFromFile(): No file or no mode specified");
518 #if defined(__ANDROID__)
520 /* Try to open the file on the filesystem first */
522 FILE *fp = fopen(file, mode);
524 return SDL_RWFromFP(fp, 1);
527 /* Try opening it from internal storage if it's a relative path */
531 path = SDL_stack_alloc(char, PATH_MAX);
533 SDL_snprintf(path, PATH_MAX, "%s/%s",
534 SDL_AndroidGetInternalStoragePath(), file);
535 fp = fopen(path, mode);
536 SDL_stack_free(path);
538 return SDL_RWFromFP(fp, 1);
542 #endif /* HAVE_STDIO_H */
544 /* Try to open the file from the asset system */
545 rwops = SDL_AllocRW();
547 return NULL; /* SDL_SetError already setup by SDL_AllocRW() */
548 if (Android_JNI_FileOpen(rwops, file, mode) < 0) {
552 rwops->size = Android_JNI_FileSize;
553 rwops->seek = Android_JNI_FileSeek;
554 rwops->read = Android_JNI_FileRead;
555 rwops->write = Android_JNI_FileWrite;
556 rwops->close = Android_JNI_FileClose;
557 rwops->type = SDL_RWOPS_JNIFILE;
559 #elif defined(__WIN32__)
560 rwops = SDL_AllocRW();
562 return NULL; /* SDL_SetError already setup by SDL_AllocRW() */
563 if (windows_file_open(rwops, file, mode) < 0) {
567 rwops->size = windows_file_size;
568 rwops->seek = windows_file_seek;
569 rwops->read = windows_file_read;
570 rwops->write = windows_file_write;
571 rwops->close = windows_file_close;
572 rwops->type = SDL_RWOPS_WINFILE;
577 FILE *fp = SDL_OpenFPFromBundleOrFallback(file, mode);
580 fopen_s(&fp, file, mode);
582 FILE *fp = fopen(file, mode);
585 SDL_SetError("Couldn't open %s", file);
587 rwops = SDL_RWFromFP(fp, 1);
591 SDL_SetError("SDL not compiled with stdio support");
592 #endif /* !HAVE_STDIO_H */
599 SDL_RWFromFP(FILE * fp, SDL_bool autoclose)
601 SDL_RWops *rwops = NULL;
603 rwops = SDL_AllocRW();
605 rwops->size = stdio_size;
606 rwops->seek = stdio_seek;
607 rwops->read = stdio_read;
608 rwops->write = stdio_write;
609 rwops->close = stdio_close;
610 rwops->hidden.stdio.fp = fp;
611 rwops->hidden.stdio.autoclose = autoclose;
612 rwops->type = SDL_RWOPS_STDFILE;
618 SDL_RWFromFP(void * fp, SDL_bool autoclose)
620 SDL_SetError("SDL not compiled with stdio support");
623 #endif /* HAVE_STDIO_H */
626 SDL_RWFromMem(void *mem, int size)
628 SDL_RWops *rwops = NULL;
630 SDL_InvalidParamError("mem");
634 SDL_InvalidParamError("size");
638 rwops = SDL_AllocRW();
640 rwops->size = mem_size;
641 rwops->seek = mem_seek;
642 rwops->read = mem_read;
643 rwops->write = mem_write;
644 rwops->close = mem_close;
645 rwops->hidden.mem.base = (Uint8 *) mem;
646 rwops->hidden.mem.here = rwops->hidden.mem.base;
647 rwops->hidden.mem.stop = rwops->hidden.mem.base + size;
648 rwops->type = SDL_RWOPS_MEMORY;
654 SDL_RWFromConstMem(const void *mem, int size)
656 SDL_RWops *rwops = NULL;
658 SDL_InvalidParamError("mem");
662 SDL_InvalidParamError("size");
666 rwops = SDL_AllocRW();
668 rwops->size = mem_size;
669 rwops->seek = mem_seek;
670 rwops->read = mem_read;
671 rwops->write = mem_writeconst;
672 rwops->close = mem_close;
673 rwops->hidden.mem.base = (Uint8 *) mem;
674 rwops->hidden.mem.here = rwops->hidden.mem.base;
675 rwops->hidden.mem.stop = rwops->hidden.mem.base + size;
676 rwops->type = SDL_RWOPS_MEMORY_RO;
686 area = (SDL_RWops *) SDL_malloc(sizeof *area);
690 area->type = SDL_RWOPS_UNKNOWN;
696 SDL_FreeRW(SDL_RWops * area)
701 /* Load all the data from an SDL data stream */
703 SDL_LoadFile_RW(SDL_RWops * src, size_t *datasize, int freesrc)
705 const int FILE_CHUNK_SIZE = 1024;
707 size_t size_read, size_total;
708 void *data = NULL, *newdata;
711 SDL_InvalidParamError("src");
715 size = SDL_RWsize(src);
717 size = FILE_CHUNK_SIZE;
719 data = SDL_malloc((size_t)(size + 1));
723 if ((((Sint64)size_total) + FILE_CHUNK_SIZE) > size) {
724 size = (size_total + FILE_CHUNK_SIZE);
725 newdata = SDL_realloc(data, (size_t)(size + 1));
735 size_read = SDL_RWread(src, (char *)data+size_total, 1, (size_t)(size-size_total));
736 if (size_read == 0) {
739 size_total += size_read;
743 *datasize = size_total;
745 ((char *)data)[size_total] = '\0';
748 if (freesrc && src) {
754 /* Functions for dynamically reading and writing endian-specific values */
757 SDL_ReadU8(SDL_RWops * src)
761 SDL_RWread(src, &value, sizeof (value), 1);
766 SDL_ReadLE16(SDL_RWops * src)
770 SDL_RWread(src, &value, sizeof (value), 1);
771 return SDL_SwapLE16(value);
775 SDL_ReadBE16(SDL_RWops * src)
779 SDL_RWread(src, &value, sizeof (value), 1);
780 return SDL_SwapBE16(value);
784 SDL_ReadLE32(SDL_RWops * src)
788 SDL_RWread(src, &value, sizeof (value), 1);
789 return SDL_SwapLE32(value);
793 SDL_ReadBE32(SDL_RWops * src)
797 SDL_RWread(src, &value, sizeof (value), 1);
798 return SDL_SwapBE32(value);
802 SDL_ReadLE64(SDL_RWops * src)
806 SDL_RWread(src, &value, sizeof (value), 1);
807 return SDL_SwapLE64(value);
811 SDL_ReadBE64(SDL_RWops * src)
815 SDL_RWread(src, &value, sizeof (value), 1);
816 return SDL_SwapBE64(value);
820 SDL_WriteU8(SDL_RWops * dst, Uint8 value)
822 return SDL_RWwrite(dst, &value, sizeof (value), 1);
826 SDL_WriteLE16(SDL_RWops * dst, Uint16 value)
828 const Uint16 swapped = SDL_SwapLE16(value);
829 return SDL_RWwrite(dst, &swapped, sizeof (swapped), 1);
833 SDL_WriteBE16(SDL_RWops * dst, Uint16 value)
835 const Uint16 swapped = SDL_SwapBE16(value);
836 return SDL_RWwrite(dst, &swapped, sizeof (swapped), 1);
840 SDL_WriteLE32(SDL_RWops * dst, Uint32 value)
842 const Uint32 swapped = SDL_SwapLE32(value);
843 return SDL_RWwrite(dst, &swapped, sizeof (swapped), 1);
847 SDL_WriteBE32(SDL_RWops * dst, Uint32 value)
849 const Uint32 swapped = SDL_SwapBE32(value);
850 return SDL_RWwrite(dst, &swapped, sizeof (swapped), 1);
854 SDL_WriteLE64(SDL_RWops * dst, Uint64 value)
856 const Uint64 swapped = SDL_SwapLE64(value);
857 return SDL_RWwrite(dst, &swapped, sizeof (swapped), 1);
861 SDL_WriteBE64(SDL_RWops * dst, Uint64 value)
863 const Uint64 swapped = SDL_SwapBE64(value);
864 return SDL_RWwrite(dst, &swapped, sizeof (swapped), 1);
867 /* vi: set ts=4 sw=4 expandtab: */