add fortified implementations of fread/fwrite
A __size_mul_overflow utility is used to take advantage of the checked
overflow intrinsics in Clang and GCC (>= 5). The fallback for older
compilers is the optimized but less than ideal overflow checking pattern
used in OpenBSD.
Change-Id: Ibb0d4fd9b5acb67983e6a9f46844c2fd444f7e69
diff --git a/libc/include/stdio.h b/libc/include/stdio.h
index f5ed652..c4bfb4f 100644
--- a/libc/include/stdio.h
+++ b/libc/include/stdio.h
@@ -382,6 +382,16 @@
__errordecl(__fgets_too_big_error, "fgets called with size bigger than buffer");
__errordecl(__fgets_too_small_error, "fgets called with size less than zero");
+extern size_t __fread_chk(void * __restrict, size_t, size_t, FILE * __restrict, size_t);
+extern size_t __fread_real(void * __restrict, size_t, size_t, FILE * __restrict) __RENAME(fread);
+__errordecl(__fread_too_big_error, "fread called with size * count bigger than buffer");
+__errordecl(__fread_overflow, "fread called with overflowing size * count");
+
+extern size_t __fwrite_chk(const void * __restrict, size_t, size_t, FILE * __restrict, size_t);
+extern size_t __fwrite_real(const void * __restrict, size_t, size_t, FILE * __restrict) __RENAME(fwrite);
+__errordecl(__fwrite_too_big_error, "fwrite called with size * count bigger than buffer");
+__errordecl(__fwrite_overflow, "fwrite called with overflowing size * count");
+
#if defined(__BIONIC_FORTIFY)
__BIONIC_FORTIFY_INLINE
@@ -428,6 +438,58 @@
}
#endif
+__BIONIC_FORTIFY_INLINE
+size_t fread(void * __restrict buf, size_t size, size_t count, FILE * __restrict stream) {
+ size_t bos = __bos0(buf);
+
+#if !defined(__clang__)
+ if (bos == __BIONIC_FORTIFY_UNKNOWN_SIZE) {
+ return __fread_real(buf, size, count, stream);
+ }
+
+ if (__builtin_constant_p(size) && __builtin_constant_p(count)) {
+ size_t total;
+ if (__size_mul_overflow(size, count, &total)) {
+ __fread_overflow();
+ }
+
+ if (total > bos) {
+ __fread_too_big_error();
+ }
+
+ return __fread_real(buf, size, count, stream);
+ }
+#endif
+
+ return __fread_chk(buf, size, count, stream, bos);
+}
+
+__BIONIC_FORTIFY_INLINE
+size_t fwrite(const void * __restrict buf, size_t size, size_t count, FILE * __restrict stream) {
+ size_t bos = __bos0(buf);
+
+#if !defined(__clang__)
+ if (bos == __BIONIC_FORTIFY_UNKNOWN_SIZE) {
+ return __fwrite_real(buf, size, count, stream);
+ }
+
+ if (__builtin_constant_p(size) && __builtin_constant_p(count)) {
+ size_t total;
+ if (__size_mul_overflow(size, count, &total)) {
+ __fwrite_overflow();
+ }
+
+ if (total > bos) {
+ __fwrite_too_big_error();
+ }
+
+ return __fwrite_real(buf, size, count, stream);
+ }
+#endif
+
+ return __fwrite_chk(buf, size, count, stream, bos);
+}
+
#if !defined(__clang__)
__BIONIC_FORTIFY_INLINE
diff --git a/libc/include/sys/cdefs.h b/libc/include/sys/cdefs.h
index 7df8b60..8a11bc6 100644
--- a/libc/include/sys/cdefs.h
+++ b/libc/include/sys/cdefs.h
@@ -578,4 +578,19 @@
#define _BIONIC_NOT_BEFORE_21(x)
#endif /* __ANDROID_API__ >= 21 */
+#if __has_builtin(__builtin_umul_overflow) || __GNUC__ >= 5
+#if __LP64__
+#define __size_mul_overflow(a, b, result) __builtin_umull_overflow(a, b, result)
+#else
+#define __size_mul_overflow(a, b, result) __builtin_umul_overflow(a, b, result)
+#endif
+#else
+static __inline__ __always_inline int __size_mul_overflow(__SIZE_TYPE__ a, __SIZE_TYPE__ b,
+ __SIZE_TYPE__ *result) {
+ *result = a * b;
+ static const __SIZE_TYPE__ mul_no_overflow = 1UL << (sizeof(__SIZE_TYPE__) * 4);
+ return (a >= mul_no_overflow || b >= mul_no_overflow) && a > 0 && (__SIZE_TYPE__)-1 / a < b;
+}
+#endif
+
#endif /* !_SYS_CDEFS_H_ */