Skip to content

Commit 239d873

Browse files
committed
fortify: Hide run-time copy size from value range tracking
GCC performs value range tracking for variables as a way to provide better diagnostics. One place this is regularly seen is with warnings associated with bounds-checking, e.g. -Wstringop-overflow, -Wstringop-overread, -Warray-bounds, etc. In order to keep the signal-to-noise ratio high, warnings aren't emitted when a value range spans the entire value range representable by a given variable. For example: unsigned int len; char dst[8]; ... memcpy(dst, src, len); If len's value is unknown, it has the full "unsigned int" range of [0, UINT_MAX], and GCC's compile-time bounds checks against memcpy() will be ignored. However, when a code path has been able to narrow the range: if (len > 16) return; memcpy(dst, src, len); Then the range will be updated for the execution path. Above, len is now [0, 16] when reading memcpy(), so depending on other optimizations, we might see a -Wstringop-overflow warning like: error: '__builtin_memcpy' writing between 9 and 16 bytes into region of size 8 [-Werror=stringop-overflow] When building with CONFIG_FORTIFY_SOURCE, the fortified run-time bounds checking can appear to narrow value ranges of lengths for memcpy(), depending on how the compiler constructs the execution paths during optimization passes, due to the checks against the field sizes. For example: if (p_size_field != SIZE_MAX && p_size != p_size_field && p_size_field < size) As intentionally designed, these checks only affect the kernel warnings emitted at run-time and do not block the potentially overflowing memcpy(), so GCC thinks it needs to produce a warning about the resulting value range that might be reaching the memcpy(). We have seen this manifest a few times now, with the most recent being with cpumasks: In function ‘bitmap_copy’, inlined from ‘cpumask_copy’ at ./include/linux/cpumask.h:839:2, inlined from ‘__padata_set_cpumasks’ at kernel/padata.c:730:2: ./include/linux/fortify-string.h:114:33: error: ‘__builtin_memcpy’ reading between 257 and 536870904 bytes from a region of size 256 [-Werror=stringop-overread] 114 | #define __underlying_memcpy __builtin_memcpy | ^ ./include/linux/fortify-string.h:633:9: note: in expansion of macro ‘__underlying_memcpy’ 633 | __underlying_##op(p, q, __fortify_size); \ | ^~~~~~~~~~~~~ ./include/linux/fortify-string.h:678:26: note: in expansion of macro ‘__fortify_memcpy_chk’ 678 | #define memcpy(p, q, s) __fortify_memcpy_chk(p, q, s, \ | ^~~~~~~~~~~~~~~~~~~~ ./include/linux/bitmap.h:259:17: note: in expansion of macro ‘memcpy’ 259 | memcpy(dst, src, len); | ^~~~~~ kernel/padata.c: In function ‘__padata_set_cpumasks’: kernel/padata.c:713:48: note: source object ‘pcpumask’ of size [0, 256] 713 | cpumask_var_t pcpumask, | ~~~~~~~~~~~~~~^~~~~~~~ This warning is _not_ emitted when CONFIG_FORTIFY_SOURCE is disabled, and with the recent -fdiagnostics-details we can confirm the origin of the warning is due to FORTIFY's bounds checking: ../include/linux/bitmap.h:259:17: note: in expansion of macro 'memcpy' 259 | memcpy(dst, src, len); | ^~~~~~ '__padata_set_cpumasks': events 1-2 ../include/linux/fortify-string.h:613:36: 612 | if (p_size_field != SIZE_MAX && | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 613 | p_size != p_size_field && p_size_field < size) | ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~ | | | (1) when the condition is evaluated to false | (2) when the condition is evaluated to true '__padata_set_cpumasks': event 3 114 | #define __underlying_memcpy __builtin_memcpy | ^ | | | (3) out of array bounds here Note that the cpumask warning started appearing since bitmap functions were recently marked __always_inline in commit ed8cd2b ("bitmap: Switch from inline to __always_inline"), which allowed GCC to gain visibility into the variables as they passed through the FORTIFY implementation. In order to silence these false positives but keep otherwise deterministic compile-time warnings intact, hide the length variable from GCC with OPTIMIZE_HIDE_VAR() before calling the builtin memcpy. Additionally add a comment about why all the macro args have copies with const storage. Reported-by: "Thomas Weißschuh" <[email protected]> Closes: https://lore.kernel.org/all/[email protected]/ Reported-by: Nilay Shroff <[email protected]> Closes: https://lore.kernel.org/all/[email protected]/ Tested-by: Nilay Shroff <[email protected]> Acked-by: Yury Norov <[email protected]> Acked-by: Greg Kroah-Hartman <[email protected]> Signed-off-by: Kees Cook <[email protected]>
1 parent f06e108 commit 239d873

File tree

1 file changed

+13
-1
lines changed

1 file changed

+13
-1
lines changed

include/linux/fortify-string.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,21 +616,33 @@ __FORTIFY_INLINE bool fortify_memcpy_chk(__kernel_size_t size,
616616
return false;
617617
}
618618

619+
/*
620+
* To work around what seems to be an optimizer bug, the macro arguments
621+
* need to have const copies or the values end up changed by the time they
622+
* reach fortify_warn_once(). See commit 6f7630b1b5bc ("fortify: Capture
623+
* __bos() results in const temp vars") for more details.
624+
*/
619625
#define __fortify_memcpy_chk(p, q, size, p_size, q_size, \
620626
p_size_field, q_size_field, op) ({ \
621627
const size_t __fortify_size = (size_t)(size); \
622628
const size_t __p_size = (p_size); \
623629
const size_t __q_size = (q_size); \
624630
const size_t __p_size_field = (p_size_field); \
625631
const size_t __q_size_field = (q_size_field); \
632+
/* Keep a mutable version of the size for the final copy. */ \
633+
size_t __copy_size = __fortify_size; \
626634
fortify_warn_once(fortify_memcpy_chk(__fortify_size, __p_size, \
627635
__q_size, __p_size_field, \
628636
__q_size_field, FORTIFY_FUNC_ ##op), \
629637
#op ": detected field-spanning write (size %zu) of single %s (size %zu)\n", \
630638
__fortify_size, \
631639
"field \"" #p "\" at " FILE_LINE, \
632640
__p_size_field); \
633-
__underlying_##op(p, q, __fortify_size); \
641+
/* Hide only the run-time size from value range tracking to */ \
642+
/* silence compile-time false positive bounds warnings. */ \
643+
if (!__builtin_constant_p(__copy_size)) \
644+
OPTIMIZER_HIDE_VAR(__copy_size); \
645+
__underlying_##op(p, q, __copy_size); \
634646
})
635647

636648
/*

0 commit comments

Comments
 (0)