<table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Issue</th>
<td>
<a href=https://github.com/llvm/llvm-project/issues/77813>77813</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>
Compile time fortify warning in Linux kernel after commit d77067d08a3f56d
</td>
</tr>
<tr>
<th>Labels</th>
<td>
missed-optimization
</td>
</tr>
<tr>
<th>Assignees</th>
<td>
</td>
</tr>
<tr>
<th>Reporter</th>
<td>
nathanchance
</td>
</tr>
</table>
<pre>
After https://github.com/llvm/llvm-project/commit/d77067d08a3f56dc2d0e6c95bd2852c943df743a, there is a new warning in the Linux kernel from its compile time fortify protections (from [`include/linux/fortify-string.h`](https://elixir.bootlin.com/linux/v6.7/source/include/linux/fortify-string.h)) in [`fs/smb/client/cifsencrypt.c`](https://elixir.bootlin.com/linux/v6.7/source/fs/smb/client/cifsencrypt.c#L532), which indicates that a condition that can be checked at compile time was triggered. However, it seems to be a false positive.
I've come up with a standalone reproducer from the kernel sources directly, which can be run in user space:
<details>
<summary><code>repro.c</code></summary>
```c
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
typedef unsigned long __kernel_ulong_t;
typedef __kernel_ulong_t __kernel_size_t;
typedef __kernel_size_t size_t;
#define SIZE_MAX (~(size_t)0)
#define __alloc_size(x, ...) __attribute__((__alloc_size__(x, ## __VA_ARGS__))) __attribute__((__malloc__))
#define __cold __attribute__((__cold__))
#define __compiletime_error(msg) __attribute__((__error__(msg)))
#define __compiletime_warning(msg) __attribute__((__warning__(msg)))
#define __diagnose_as(builtin...) __attribute__((__diagnose_as_builtin__(builtin)))
#define __pass_dynamic_object_size(type) __attribute__((__pass_dynamic_object_size__(type)))
#define __gnu_inline __attribute__((__gnu_inline__))
#define __noreturn __attribute__((__noreturn__))
#define __overloadable __attribute__((__overloadable__))
#define __FORTIFY_INLINE extern __always_inline __gnu_inline __overloadable
#define __RENAME(x) __asm__(#x)
#define __struct_size(p) __builtin_dynamic_object_size(p, 0)
#define __member_size(p) __builtin_dynamic_object_size(p, 1)
#define POS __pass_dynamic_object_size(1)
#define POS0 __pass_dynamic_object_size(0)
void fortify_panic(const char *name) __noreturn __cold;
void __write_overflow(void) __compiletime_error("detected write beyond size of object (1st parameter)");
void __write_overflow_field(size_t avail, size_t wanted) __compiletime_warning("detected write beyond size of field (1st parameter); maybe use struct_group()?");
#define __compiletime_lessthan(bounds, length) ( \
__builtin_constant_p((bounds) < (length)) && \
(bounds) < (length) \
)
#define __compiletime_strlen(p) \
({ \
char *__p = (char *)(p); \
size_t __ret = SIZE_MAX; \
const size_t __p_size = __member_size(p); \
if (__p_size != SIZE_MAX && \
__builtin_constant_p(*__p)) { \
size_t __p_len = __p_size - 1; \
if (__builtin_constant_p(__p[__p_len]) && \
__p[__p_len] == '\0') \
__ret = __builtin_strlen(__p); \
} \
__ret; \
})
#define __is_constexpr(x) \
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
#define strlen(p) \
__builtin_choose_expr(__is_constexpr(__builtin_strlen(p)), \
__builtin_strlen(p), __fortify_strlen(p))
#define __underlying_memset __builtin_memset
#define __underlying_strlen __builtin_strlen
extern __kernel_size_t __real_strnlen(const char *, __kernel_size_t) __RENAME(strnlen);
/**
* strnlen - Return bounded count of characters in a NUL-terminated string
*
* @p: pointer to NUL-terminated string to count.
* @maxlen: maximum number of characters to count.
*
* Returns number of characters in @p (NOT including the final NUL), or
* @maxlen, if no NUL has been found up to there.
*
*/
__FORTIFY_INLINE __kernel_size_t strnlen(const char * const POS p, __kernel_size_t maxlen)
{
const size_t p_size = __member_size(p);
const size_t p_len = __compiletime_strlen(p);
size_t ret;
/* We can take compile-time actions when maxlen is const. */
if (__builtin_constant_p(maxlen) && p_len != SIZE_MAX) {
/* If p is const, we can use its compile-time-known len. */
if (maxlen >= p_size)
return p_len;
}
/* Do not check characters beyond the end of p. */
ret = __real_strnlen(p, maxlen < p_size ? maxlen : p_size);
if (p_size <= ret && maxlen != ret)
fortify_panic(__func__);
return ret;
}
__FORTIFY_INLINE __diagnose_as(__builtin_strlen, 1)
__kernel_size_t __fortify_strlen(const char * const POS p)
{
const size_t p_size = __member_size(p);
__kernel_size_t ret;
/* Give up if we don't know how large p is. */
if (p_size == SIZE_MAX)
return __underlying_strlen(p);
ret = strnlen(p, p_size);
if (p_size <= ret)
fortify_panic(__func__);
return ret;
}
__FORTIFY_INLINE void fortify_memset_chk(__kernel_size_t size,
const size_t p_size,
const size_t p_size_field)
{
if (__builtin_constant_p(size)) {
/*
* Length argument is a constant expression, so we
* can perform compile-time bounds checking where
* buffer sizes are also known at compile time.
*/
/* Error when size is larger than enclosing struct. */
if (__compiletime_lessthan(p_size_field, p_size) &&
__compiletime_lessthan(p_size, size))
__write_overflow();
/* Warn when write size is larger than dest field. */
if (__compiletime_lessthan(p_size_field, size))
__write_overflow_field(p_size_field, size);
}
/*
* At this point, length argument may not be a constant expression,
* so run-time bounds checking can be done where buffer sizes are
* known. (This is not an "else" because the above checks may only
* be compile-time warnings, and we want to still warn for run-time
* overflows.)
*/
/*
* Always stop accesses beyond the struct that contains the
* field, when the buffer's remaining size is known.
* (The SIZE_MAX test is to optimize away checks where the buffer
* lengths are unknown.)
*/
if (p_size != SIZE_MAX && p_size < size)
fortify_panic("memset");
}
#define __fortify_memset_chk(p, c, size, p_size, p_size_field) ({ \
size_t __fortify_size = (size_t)(size); \
fortify_memset_chk(__fortify_size, p_size, p_size_field), \
__underlying_memset(p, c, __fortify_size); \
})
#define memset(p, c, s) __fortify_memset_chk(p, c, s, \
__struct_size(p), __member_size(p))
#define show(exp) printf(#exp ": %zu\n", exp)
extern int crypto_shash_update(const unsigned char *buffer, unsigned int len);
static __always_inline __alloc_size(1) void *kmalloc(size_t size)
{
return malloc(size);
}
int main(int argc, char **argv)
{
unsigned short *user;
char *user_name = NULL;
int len;
if (argc > 1)
user_name = argv[1];
len = user_name ? strlen(user_name) : 0;
user = kmalloc(2 + (len * 2));
if (!user)
return -1;
show(__struct_size(user));
show(__member_size(user));
if (len)
printf("Length of user_name is not zero\n");
else
memset(user, '\0', 2);
return crypto_shash_update((unsigned char *)user, len * 2);
}
```
</details>
<details>
<summary><code>lib.c</code></summary>
```c
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
void fortify_panic(const char *name)
{
fprintf(stderr, "detected buffer overflow in %s\n", name);
exit(1);
}
size_t strnlen(const char *s, size_t count)
{
const char *sc;
for (sc = s; count-- && *sc != '\0'; ++sc)
/* nothing */;
return sc - s;
}
int crypto_shash_update(const unsigned char *buffer, unsigned int len)
{
return len % 2;
}
void __write_overflow_field(size_t avail, size_t wanted)
{
}
```
</details>
```
$ clang -O2 repro.c lib.c
repro.c:127:4: warning: call to '__write_overflow_field' declared with 'warning' attribute: detected write beyond size of field (1st parameter); maybe use struct_group()? [-Wattribute-warning]
127 | __write_overflow_field(p_size_field, size);
| ^
1 warning generated.
$ ./a.out test
__struct_size(user): 10
__member_size(user): 10
Length of user_name is not zero
$ ./a.out
__struct_size(user): 2
__member_size(user): 2
```
In the `else` block, `__builtin_dynamic_object_size()` can only ever return `2` because we know `len` is `zero` (i.e., `p_size_field == 2` due to `__alloc_size(1)` on `kmalloc()`) and the size to `memset()` is `2` (i.e., `size == 2`), so the condition `p_size_field < size` is always false, but it appears that after that change it is no longer eliminated (or sunk in some other manner?).
Some other potentially relevant information is in [the downstream issue](https://github.com/ClangBuiltLinux/linux/issues/1966).
cc @nikic @nickdesaulniers @kees
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzMWltz47hy_jXwS5dVFGTdHvxgy-OTqZozc2pnTzbJCwsiWxJiElABoGXvQ357qgGQ4k2yd7NJZcvrMUl0o9H4-goIa-VeId6z-SObP92Iyh20uVfCHYTK6H-82er8_f5h59DAwbmjZbMHxp8Zf95Ld6i2k0yXjD8XxWv9z-3R6P_EzDH-nOmylPRHvlwmi2WerMRsN1_kGc8TXGTr-TbnqznP1nezfLe8mwnGN-AOaBCkBQEKT3ASRkm1B6noC3yTqnqDFzQKC9gZXYJ0FjJdHmWB4GSJsNPGyd07HI12mDmplQXGV34wLXSRSJUVVY4kMLFj_DnS3FpnpNpPDmyRsPkT46vumrGQb9JMtlq7Qqp67ZHH62KyZPzZ6spkxPsTs_A142taWpBrZ4m-3JLqConK61DuLKrMvB_dJPsrxPp4Ej77Np9xL9wGTgeZHUCqXGbCoQV3EA4EZFrlknQbXmRCwRYhO2D2gjnQm_aWnIQFZ-R-jwbzCfyLPuErGmIvHVjE0oLTxEDAThQW4aitdPIVJyx5YslD-P2V8eUrEmeE6ggn6Q4gwDqhclFohWDwaHReZWgCNAgwESph-RZyaTBzxft5aVF0UynaiMqiAXsUGZJ2W5Oz2SZHJ2Rh2exL88pWZSnMO72abTKdI5t98VJMMjbbeBPw78JDa3Sb8yIJP1l85rMIHaAZXJ7jbnI4E_U_S33tayG37c_-t3s_Yo47qJS3_xwKrfYsWadp0FZa0YvUsdljl6A_4PzCyt_xGkH8nqxZsu6NbQTPcScVws-v__El_fvDv9FgvvovxleRgK8TAuUoUZqKotCZn4fx1Rvt72Qy8ePjT5oK54zcVg7TlPEV46s2lX_n6RifMT6DNP3Xh_Thl7_9pC_raKujTMrApR42EC3TRd6IcUUYGneNi7coMqgUjdGG8VVp980SR1n6gf4xDv0E8-hyP8M-Dv3EBLkUe6UtpsIyvtpWsnBStXdolH-LKo00_mP8-8p8R2Ftmr8rUcos1VuKSDU4CJxh2tE5L1H6ETXthVn3qkqlKqTCDzf7PPTylitt0FVGfcisHniZlX5FU2iRi23xsWztwT2WA8bPP3759evzv6dfv3_7-v0L4JtDo7w9nsS7jUvsqGYgTZ_lL1--P_z9SzBHb3K2jLLN3q5IYp2pzrt8rLe4xs04GI5k8cmoykost2j-DL_pCL9__PjZUvoVeF4gTj5H3XeRr1rmdUqUHoWSGeOrTCvrIDsIA4w_KFFiUHQNo9pn1S7aM0nTk5EO_d7tCn1ifEXvA-WYc2Kc50gpGObgSWGL71rlQKKC3kGQnJKzqXVwFEaU6CgvWDPu84-r86c7iUXehAcQr0IWpP34fBLK4Yh4Z_f2oYB-hlH5Zo9QivctUsIAEXh7o6ujZ7tms-feEq673AKtpaSbXJuuVG5pHQWqvTtEB8lX9Hu-qbm1gOh3UyiXhskbDmtKAUj6MyN6xxf002V2naoduhqiy3bYXph1pkB1Np8LvFZs-dj_OhyWrGvIpukR2OyJxKxf-eWFaWaPl-gjNNLUoPMMmmTjMk0wloby6C3NE4-6iIZRi4XcQQgtkZZP23O3tmRsfgCAi5tNmqg3tq3C9t62ln1MC1RR9ijMLUw7i-9S1pKPTk9zzx8jV1-YXIJXvYrueBIk7OKSzTcJ_cPHdVA7vnrbzgI1AIuKGFM_Wz59iCzPu4-C4cDl0xXgSxv0g29HU0evnpWRyvWO8ZWkwmtda6B57YG8WgGbPUOwZu_7GnyTXVKy7h_CBPwBkqLGwOwBAvNIsuonKz2hr9rniJIaHBw0pWVxpYOlj-zPsZZkM9zbi8M3kKZ18BpwurALlcrRFO-UlpZYWnSdCepXV6jiRCNitWZs8pxOgeM9iyhovArCdkNtWFG3JvIBqkl5GspW6KAi_4F-_KPf8TgMbuGXELG9-8YcMl0pR7GLphSZQ2OprhXw_Z_fbh2aUipB8S70IM4cW7zZXXIkIB21VA4NleajxPTBTzfpEpfijVYwe4BSvMmyKkFV5CZ7Uo1Qt_iEZdlxUqm8kAT17z9-hVDxeokOCDupREESRwRpMyoe34DcgfJrg4OwsEVUsCM1QnUk6XwnalQ62hH_MEh_-3i4BAQIj__48ROOI6CAWsga5cvH8Xj0mWg0TncOBJfjdYs60tVOsmV65NcIoPAb-maKEy9Yt39ufftHxC7c6YAqrgykDSqYdPT5UcRp1BLjTL2ObkBtomEregQRv-7g2Ezte0BBZsriWo1EL_bti9InRVnYQMZGzLgY3-F5ipvRck3RhcakOoS9lk59XBrT5JMGpV1op7VxH9NTQjmqnIziOJDtHCJ7nsjjrBF4c4bO8_ntQ2sNLUHDYhuCDfH384RNqMnDLhBEuirolx9puqtU7Je0Zolq6kCsq6ERc-v2FYaBpF2MDZ31ILZcs9K_0Bb7klwzq7_JV9_vlDtCa64V40sHBE446BMUwuzRo_qCKZ3F6tlIe4uaum8YB4fS1xDrYeuPQOf_DCGd-jcE_zQ7vPg5eq7aC9_POWP2OrLRf2RsXauOQOiqv6sVesmhdTJsAss3X7SBMPuqROXCOUrNEig7Q2ul9oZhNZxwwIHc4RHNTpuy68VDgRicEsXaE4XHAfm22u3Q-NVbEAZBFFZDcKW9Y4FJn7iF3YHn_mKMNiGCeChJG5BvgOpmQJUV2pJYoRi_7LIvFt7dnWrBualp-tXMVUZ1J6KbrDaVzKCXMtIsaK__N2FUWH7oVIwpIUfrQs_iL1j-p2Vv-jCXOIxHvD6CPXoeHLiDtCH1PPdAznguxbsPjf6saBzXXY5Wg6nUOILj0U-uFQY0D9Db5eVRTKpd_UpCSutFERT5OBYWGeewxUxQNkExWmz1azwUs15yrYr3LsttL1WKzSnf_xEqJ49_ohU6DdbJovADyJ81q-ryqzfFTtobd8G4xjbAd23BOn0EkWVoLXbSjmBd8dBPKyeksvShy6WBgAcs0QXFMr60YLAU0p_p1hgOeu2y8Eo-HwaBI2xLXzXoo5MlkYqTeK_VG_avNVWHW4BRcEiVivNdVlAvao22a87h_oz0KxGNcR5Lz15fsJcDtkrS0ajlI212Nq-zn9r0Yw2c-2pjLbAm96mTlvZJWyv2XO6MXQirbcZXBRz2Asaq9_aa-8zX462rK12aEaY2lOAfqTtKO9abGj178OKOpIEXWzEHHwfwjUbB0UjlQkdohm9U6vLQ3Jn_XrH5RnkYbSCMHmlMSOXAH-jr1B6EPaTVMRcOmxS3Ofutc93aQjfnT8Sj34vwv60TTmYjhzydQ1hKvKFuX72EQ9Jzu75rM8tBktcZf8VipI8KUsW2lzB7v13njsuDMPvX0ZmahdqDNr5jVlk0nZo5cqH3qRJlMJTv__z2rZPjRj0NwndwIyQSlYjtYyEPmy5XL-b8ccrmT0NOdbXeJnk-d--a13UTMGnL5y81EPV5Ezgw_hi7_N5D8gjNQerO-NRrZbReuJ0ORY047ttEzaTXVqgHdw1ldHBHrE53xIvUshge02C9aykshuvf0ejGgDqy-BDeYti4iiDNptOn3sDY8U6jmHHTI159u2N8XfPv7MUQ7vU9kc6M_mLJ4GZK_e2TV1YKuf1_fWHl84eYQyPfNbiwLkcTN_J88BdTvjpx8r1FPrctH1uzbkPlTbr6qPaCX_qgAWhbJ5W-DXqlv9DQZEO8USpIa8tCRc5mj4Hd7W2dp3jCOolpQXhG9v_I-KPN-kl-KDmUdgdK02JuNKzDbQa3fsqLjvkvjECX4kQwmjnwS3L8zw6P-_P-eWvsEfA7yAqh9nD7g0O8MwbBEP2A5hrZw5Q26-GO3Hp9dj2jOr0oKBtmfHlpcUvIMSuEwTxclmN82Rx-L6G590Hc_vfOwYHNH29_aya7rSWYR0UCTPkS2HJzeY8-KizB_0cs2DzqfNpcHN2jQiMc5pNu2nUHE8afxURXzhcXdf9oPGrNHmCa1EPGY1VryEfh55IgH8rAPxSBX4Hn11CLsUXiQ90igW2hsxfvERfJRzdbaIJF4otmqmMBX9FANEK2SLjnF6vfE4YGJVskZL6LhJbPFolf_SLxZ5QTnMSZ2_tb9yk9u7xCD3ESbpBa0gDtpz6nNeE1JUGiLlgJwoFHE88jcRCJD-Vp90t54Bf7ZcTxfPV1KHrEZmAekuNwo5Xot5UD6UAcjyhMfZPW36wO5fRBqD3SCA8VfykTDWAh62M3xlfagK3UC0Upq0sE7Q5ooBRKUeL6zPi6A_Of5zFH7VA5KYriHQwW-CqUA6l22pTCr0baeBOZFpnrk7LOoChBWlvh2LXjzg3wDfmyRwLQt3jxuL6A7Okt48_T9WLRFzDLgN0lSr7I-Ef2kqMVVaEkGkuvXhAt3OT3s3w9W4sbvJ8uk7v1erm6S24O93nOsyTLVvluLfJdvlisVnfzVTLl_G62zTJxI-95wu-S6XQ6Xc2S2WoyT5aY7JY83yaz6V0u2F2CpZDFpChey4k2-xsv8P1yuZrObgqxxcL6C_Kcl9JazG9j_8FrjVKE-dONufdX37fV3rK7pJDW2TM_J12B95uxO-qt2-2dm-0BFuH-PPRuz99Uprj_w3fxm13w6_rvAAAA___je3fn">