https://lwn.net/SubscriberLink/1046840/3eb9029084cc9e1e/ LWN.net Logo LWN .net News from the source LWN * Content + Weekly Edition + Archives + Search + Kernel + Security + Events calendar + Unread comments + ------------------------------------------------------------- + LWN FAQ + Write for us [INS::INS] User: [ ] Password: [ ] [Log in] | [Subscribe] | [Register] Log in / Subscribe / Register Checked-size array parameters in C [LWN subscriber-only content] Welcome to LWN.net The following subscription-only content has been made available to you by an LWN subscriber. Thousands of subscribers depend on LWN for the best news from the Linux and free software communities. If you enjoy this article, please consider subscribing to LWN. Thank you for visiting LWN.net! By Jonathan Corbet December 1, 2025 There are many possible programmer mistakes that are not caught by the minimal checks specified by the C language; among those is passing an array of the wrong size to a function. A recent attempt to add some safety around array parameters within the crypto layer involved the use of some clever tricks, but it turns out that clever tricks are unnecessary in this case. There is an obscure C feature that can cause this checking to happen, and it is already in use in a few places within the kernel. The discussion started when Ard Biesheuvel sought to improve the safety of the poetically named function xchacha20poly1305_encrypt(): void xchacha20poly1305_encrypt(u8 *dst, const u8 *src, const size_t src_len, const u8 *ad, const size_t ad_len, const u8 nonce[XCHACHA20POLY1305_NONCE_SIZE], const u8 key[CHACHA20POLY1305_KEY_SIZE]); A potential problem with this function is that it takes as parameters several pointers to arrays of type u8. As Biesheuvel pointed out, the size of the nonce and key arrays is not checked by the compiler, even though it is clearly specified in the function prototype. That makes it easy to, for example, give the parameters in the wrong order. The resulting vulnerabilities are generally not the outcome developers have in mind when they write cryptographic code. Biesheuvel suggested that it was possible to write the prototype this way instead (differences shown in bold): void xchacha20poly1305_encrypt(u8 *dst, const u8 *src, const size_t src_len, const u8 *ad, const size_t ad_len, const u8 (*nonce)[XCHACHA20POLY1305_NONCE_SIZE], const u8 (*key)[CHACHA20POLY1305_KEY_SIZE]); The types of the last two arguments have changed; there is a new level of pointer indirection, with the argument being a pointer to an array of a given size. Callers must change their calls by adding an additional & operator to obtain the desired pointer type, but the address that is passed is the same. In this case, though, the compiler will check the sizes of the array passed, and will now catch a reordering of the arguments to the function. Jason Donenfeld was interested by the idea, but he knew of an arguably more straightforward way to address this problem. It seems that, buried deep within the C standard, is a strange usage of the static keyword, making it possible to write the prototype as: void xchacha20poly1305_encrypt(u8 *dst, const u8 *src, const size_t src_len, const u8 *ad, const size_t ad_len, const u8 nonce[static XCHACHA20POLY1305_NONCE_SIZE], const u8 key[static CHACHA20POLY1305_KEY_SIZE]); This, too, will cause the compiler to check the sizes of the arrays, and it does not require changes on the caller side. Unlike the pointer trick, which requires an exact match on the array size, use of static will only generate a warning if the passed-in array is too small. So it will not catch all mistakes, though it is sufficient to prevent memory-safety and swapped-argument problems. Eric Biggers pointed out that GCC can often generate "array too small" warnings even without static, but that the kernel currently disables those warnings; that would suppress them when static is used as well. (The warning was disabled in 6.8 due to false positives.) But he thought that adding static was worthwhile to get the warnings in Clang -- if Linus Torvalds would be willing to accept use of "`this relatively obscure feature of C'". Torvalds, as it turns out, has no objection to this usage; he likes the feature, if not the way it was designed: The main issue with the whole 'static' thing is just that the syntax is such a horrible hack, where people obviously picked an existing keyword that made absolutely no sense, but was also guaranteed to have no backwards compatibility issues. He pointed out that there are a number of places in the kernel that are already using it; for a simple example, see getconsxy() in the virtual-terminal driver. He suggested perhaps hiding it with a macro like min_array_size() just to make the usage more obvious, but didn't seem convinced that it was necessary. Donenfeld followed up with a patch to that effect a few days later, but then pivoted to an at_least marker instead. A lot of work has gone into the kernel to make its use of C as safe as possible, but that does not mean that the low-hanging fruit has all been picked. The language has features, such as static when used to define formal array parameters, that can improve safety, but which are not generally known and not often used. In this particular case, though, it would not be surprising to see this "`horrible hack'" come into wider use in the near future. Index entries for this article Kernel C language safety Kernel Releases/6.19 [Send a free link] ----------------------------------------- [Log in] to post comments [static n] should be avoided if possible Posted Dec 2, 2025 0:54 UTC (Tue) by alx.manpages (subscriber, # 145117) [Link] (6 responses) [n] should be as fine as [static n], and even better. It's unfortunate that the kernel had to turn -Wstringop-overflow off; I hope that can be resolved eventually, and code using [n] can be safe again. [static n] is not as good as [n], among other reasons, because it doesn't guarantee that the function will not read beyond the first 'n' elements. GCC diagnoses if a function reads beyond that, but that's a GNU extension. [static n] as designed by the C Committee is bad, and should be avoided. [Reply to this comment] [static n] should be avoided if possible Posted Dec 2, 2025 9:10 UTC (Tue) by danielthompson (subscriber, # 97243) [Link] (4 responses) I'm not sure I understood that last bit. I thought that neither [static n] nor [n] can guarantee the function will not read beyond the first n elements (although either might warn). AFAICT the C standard defines static array indices as an single additional constraint on callers (e.g. there's nothing that [n] does that [static n] doesn't also do). Assuming that constraint is met then I don't see why [static n] is would be not as good as [n] when the function implementation does not honour its prototype. [Reply to this comment] [static n] should be avoided if possible Posted Dec 2, 2025 9:47 UTC (Tue) by alx.manpages (subscriber, # 145117) [Link] (3 responses) It depends on what you call a guarantee. If you use appropriate compiler flags, you can get such guarantees alx@devuan:~/tmp$ cat array-bounds.c void g(int a[43]); void f(int a[42]) { a[100] = 7; g(a); } alx@devuan:~/tmp$ gcc -Warray-parameter=2 -Werror=array-bounds=2 -Werror=stringop-overflow=4 -O2 -S array-bounds.c array-bounds.c: In function 'f': array-bounds.c:8:9: error: 'g' accessing 172 bytes in a region of size 168 [-Werror=stringop-overflow=] 8 | g(a); | ^~~~ array-bounds.c:8:9: note: referencing argument 1 of type 'int[43]' array-bounds.c:1:6: note: in a call to function 'g' 1 | void g(int a[43]); | ^ array-bounds.c:6:10: error: array subscript 100 is outside array bounds of 'int[42]' [-Werror=array-bounds=] 6 | a[100] = 7; | ~^~~~~ array-bounds.c:4:7: note: at offset 400 into object 'a' of size [0, 168] 4 | f(int a[42]) | ~~~~^~~~~ cc1: some warnings being treated as errors The C standard claims [n] has no meaning at all, so it's easy for the C standard to also claim that [static n] has strictly stronger guarantees than [n]. However, the C standard doesn't acknowledge that existing quality compilers (i.e., GCC) assign [n] a stronger meaning than the C standard ackowledge. About why [static n] would be worse than [n], there are a few reasons (plus the one mentioned above): * [static n] implies [[gnu::nonnull()]] on the parameter, which is a UB bomb. * [static n] implies that n is nonzero, and if it is ever zero, you have UB, which is a UB bomb. [Reply to this comment] [static n] should be avoided if possible Posted Dec 2, 2025 10:09 UTC (Tue) by excors (subscriber, #95769) [ Link] (1 responses) The a[100]=7 line only causes a warning/error at -O2, not at -O1 or -O0 (with GCC 13.3.0). So it can sometimes be a useful heuristic (when it's not causing too many false positives), but it's not what anyone would call a guarantee. [Reply to this comment] [static n] should be avoided if possible Posted Dec 2, 2025 10:25 UTC (Tue) by alx.manpages (subscriber, # 145117) [Link] I'm not sure how much heuristics are involved. I haven't seen the implementation. But yeah, I agree the diagnostics about array parameters need some improvements. And GCC is indeed working on improving those diagnostics. On the other hand, -O2 is what most projects use. I personally build my projects in a loop with all different optimization levels, to maximize diagnostics. [Reply to this comment] [static n] should be avoided if possible Posted Dec 2, 2025 15:28 UTC (Tue) by danielthompson (subscriber, # 97243) [Link] I think we're on the same page, although perhaps coming from opposite directions. From the point of view of the standard [static n] is essentially a hint to the optimizer that allows it to optimize the function more aggressively by imposing restrictions on the caller. Nothing to do with diagnostics, although without diagnostics then there is a risk that failing to meet the restrictions would go undiscovered. Given how similar they are, I'd assume the only reason [static n] would ever get "better" diagnostics might be because the false positive rate is lower: if the warnings can be switched independently then people who adopt the weird syntax for diagnostic reasons could simply switch back to [n] if there are false positive diagnostics. [Reply to this comment] [static n] is 100% useless (on top of being bad for safety) Posted Dec 2, 2025 12:32 UTC (Tue) by alx.manpages (subscriber, # 145117) [Link] My previous comment mentioned that [static n] is dangerous. However, it's worse than that. There's nothing that [static n] will enforce that wouldn't be enforced by [n]. Both GCC and Clang entirely ignore [static n] --as they should, it's pure crap-- regarding diagnostics of array bounds. I can only assume they were assuming [static n] would do something it doesn't do but didn't really test, because it's trivial to show that [static n] doesn't add any diagnostics. See [Reply to this comment] Waste of developer mental capacity Posted Dec 2, 2025 8:22 UTC (Tue) by taladar (subscriber, #68407) [ Link] Imagine a world where developers of a complex piece of software like an OS kernel would not have to worry about low level compiler issues like this that language creators and compiler developers knew how to solve for many decades. [Reply to this comment] Unfortunately compilers don't enforce [static N] Posted Dec 2, 2025 13:24 UTC (Tue) by tdz (subscriber, #58733) [Link] (6 responses) I wished that [static N] would be used more widely. We could have had compile-time array-length and null-ptr tests in C for years. ([static 1] is a non-null pointer here.) However, citing C23 6.7.7.4, paragraph 6: "If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression" This is essentially just an optimization hint. There is no requirement to do any bounds checking. When I tried a few years ago, clang did while gcc didn't warn about violations. [Reply to this comment] GCC enforces both [n] and [static N] Posted Dec 2, 2025 13:32 UTC (Tue) by alx.manpages (subscriber, # 145117) [Link] (2 responses) I don't know in the past, but in the present day, Clang is way behind GCC. GCC diagnoses both [n] and [static n] in exactly the same way, and [static n] is unnecessary. alx@devuan:~/tmp$ cat array-bounds.c void g(int a[43]); void f(int a[42]); void f(int a[42]) { a[100] = 7; g(a); } void gs(int a[static 43]); void fs(int a[static 42]); void fs(int a[static 42]) { a[101] = 8; gs(a); } int main(void) { int a[2]; f(a); fs(a); } alx@devuan:~/tmp$ gcc -S -O2 -Wall -Wextra array-bounds.c array-bounds.c: In function 'f': array-bounds.c:7:9: warning: 'g' accessing 172 bytes in a region of size 168 [-Wstringop-overflow=] 7 | g(a); | ^~~~ array-bounds.c:7:9: note: referencing argument 1 of type 'int[43]' array-bounds.c:1:6: note: in a call to function 'g' 1 | void g(int a[43]); | ^ array-bounds.c: In function 'fs': array-bounds.c:16:9: warning: 'gs' accessing 172 bytes in a region of size 168 [-Wstringop-overflow=] 16 | gs(a); | ^~~~~ array-bounds.c:16:9: note: referencing argument 1 of type 'int[43]' array-bounds.c:10:6: note: in a call to function 'gs' 10 | void gs(int a[static 43]); | ^~ array-bounds.c: In function 'main': array-bounds.c:24:9: warning: 'f' accessing 168 bytes in a region of size 8 [-Wstringop-overflow=] 24 | f(a); | ^~~~ array-bounds.c:24:9: note: referencing argument 1 of type 'int[42]' array-bounds.c:4:6: note: in a call to function 'f' 4 | void f(int a[42]) | ^ array-bounds.c:25:9: warning: 'fs' accessing 168 bytes in a region of size 8 [-Wstringop-overflow=] 25 | fs(a); | ^~~~~ array-bounds.c:25:9: note: referencing argument 1 of type 'int[42]' array-bounds.c:13:6: note: in a call to function 'fs' 13 | void fs(int a[static 42]) | ^~ array-bounds.c: In function 'f': array-bounds.c:6:10: warning: array subscript 100 is outside array bounds of 'int[42]' [-Warray-bounds=] 6 | a[100] = 7; | ~^~~~~ array-bounds.c:4:12: note: at offset 400 into object 'a' of size [0, 168] 4 | void f(int a[42]) | ~~~~^~~~~ array-bounds.c: In function 'fs': array-bounds.c:15:10: warning: array subscript 101 is outside array bounds of 'int[42]' [-Warray-bounds=] 15 | a[101] = 8; | ~^~~~~ array-bounds.c:13:13: note: at offset 404 into object 'a' of size [0, 168] 13 | void fs(int a[static 42]) | ~~~~^~~~~~~~~~~~ In function 'f', inlined from 'main' at array-bounds.c:24:2: array-bounds.c:6:16: warning: array subscript 100 is outside array bounds of 'int[2]' [-Warray-bounds=] 6 | a[100] = 7; | ~~~~~~~^~~ array-bounds.c: In function 'main': array-bounds.c:22:13: note: at offset 400 into object 'a' of size 8 22 | int a[2]; | ^ In function 'fs', inlined from 'main' at array-bounds.c:25:2: array-bounds.c:15:16: warning: array subscript 101 is outside array bounds of 'int[2]' [-Warray-bounds=] 15 | a[101] = 8; | ~~~~~~~^~~ array-bounds.c: In function 'main': array-bounds.c:22:13: note: at offset 404 into object 'a' of size 8 22 | int a[2]; | ^ alx@devuan:~/tmp$ clang -S -O2 -Weverything array-bounds.c array-bounds.c:25:2: warning: array argument is too small; contains 2 elements, callee requires at least 42 [-Warray-bounds] 25 | fs(a); | ^ ~ array-bounds.c:13:13: note: callee declares array parameter as static here 13 | void fs(int a[static 42]) | ^~~~~~~~~~~~ array-bounds.c:6:2: warning: unsafe buffer access [-Wunsafe-buffer-usage] 6 | a[100] = 7; | ^ array-bounds.c:15:2: warning: unsafe buffer access [-Wunsafe-buffer-usage] 15 | a[101] = 8; | ^ 3 warnings generated. [Reply to this comment] GCC enforces both [n] and [static N] Posted Dec 2, 2025 16:27 UTC (Tue) by jrtc27 (subscriber, #107748) [ Link] (1 responses) That diagnosis for [n] is wrong though; the C standard is very clear that array parameters are identical to pointer parameters, with static within the square brackets being the sole case where there is any additional meaning conveyed by the use of an array. See C99 6.7.5.3. If anything the correct behaviour would be to warn on the use of [n] in a parameter type as "this does not do what you think it does; if you don't mean [static n], [] or * would be a less misleading way to write it". [Reply to this comment] Quality of implementation Posted Dec 2, 2025 21:32 UTC (Tue) by alx.manpages (subscriber, # 145117) [Link] > That diagnosis for [n] is wrong though; That's what we call quality-of-implementation. It's not a bug, it's a feature. Quality compilers often diagnose things that the standard doesn't restrict. That's expected. You wouldn't want to use a compiler that diagnoses exactly what the standard requires and no more. > the C standard is very clear that array parameters are identical to pointer parameters, That's a bug in the standard. I hope it will be fixed in ISO C2y. [Reply to this comment] Re: [static 1] is a non-null pointer Posted Dec 2, 2025 13:38 UTC (Tue) by alx.manpages (subscriber, # 145117) [Link] (2 responses) [static 1] is a bad version of __attribute__((nonnull())). - It uses array notation for what is really a pointer. - It would cause warnings in the function if it tries to access more than 1 element. (Except that these warnings are still under development, so this won't be triggered easily.) - It's still a UB bomb, just like the attribute. [Reply to this comment] Re: [static 1] is a non-null pointer Posted Dec 2, 2025 21:10 UTC (Tue) by andy_shev (subscriber, #75870) [Link] (1 responses) Have you answered to the original email thread? It seems you have a point, but I don't think people from that thread reading comments under this article. [Reply to this comment] Re: [static 1] is a non-null pointer Posted Dec 2, 2025 21:34 UTC (Tue) by alx.manpages (subscriber, # 145117) [Link] Yup, we're talking about it. [Reply to this comment] Weird corner of C Posted Dec 2, 2025 16:42 UTC (Tue) by jrtc27 (subscriber, #107748) [ Link] (1 responses) Array parameter types are a particularly cursed corner of C. By the standard, they are confusing syntax for a pointer. But that means that you can legally assign to the array itself: int get_second(int a[]) { a++; return *a; } If you want to stop that, like any other variable you want to prevent assignment to, you need to mark it const. But int const a[] makes the [S:array elements:S] pointed-to objects const, not the pointer itself. The cursed solution that C came up with is putting the qualifiers inside the square brackets (where static can also go, as explained in the article), so you can write the following: int get_second(int a[const]) { a++; // error: cannot assign to variable 'a' with const-qualified type 'int *const' return *a; } Other qualifiers work there too (e.g. restrict, which you do sometimes see in the real world). [Reply to this comment] Weird corner of C Posted Dec 2, 2025 19:06 UTC (Tue) by wahern (subscriber, #37304) [ Link] Array-to-pointer decay is the monkey wrench in the C machine, notwithstanding that pointers are C's secret sauce. I wonder how things would have turned out if the committee had adopted Dennis Ritchie's array passing proposal, "Variable-Size Arrays in C", http:/ /jclt.iecc.com/Jct22.pdf#page=5, instead of VLAs. Both proposals had to work around the unfortunate array parameter semantics in similar ways, though. [Reply to this comment] To editor: example using `at_least` needed Posted Dec 3, 2025 3:22 UTC (Wed) by zx2c4 (subscriber, #82519) [Link ] (2 responses) It would be nice if Jon would post an example that uses the new at_least macro, because people often use these LWN articles instructively, and I'd hate to have to cleanup naked uses of static. Details are in the commit: https://git.kernel.org/pub/scm/linux/ kernel/git/torvalds/... [Reply to this comment] To editor: example using `at_least` needed Posted Dec 3, 2025 17:04 UTC (Wed) by corbet (editor, #1) [Link] (1 responses) Sorry, at_least was a late addition. Perhaps the best example is this whole series of changes posted by Eric Biggers. [Reply to this comment] To editor: example using `at_least` needed Posted Dec 3, 2025 17:46 UTC (Wed) by zx2c4 (subscriber, #82519) [ Link] Or just patch 3/3 in the original series? It shows the error messages that a violation would create etc. https://lore.kernel.org/all/20251123054819.2371989-4-Jaso... [Reply to this comment] [INS::INS] Copyright (c) 2025, Eklektix, Inc. Comments and public postings are copyrighted by their creators. Linux is a registered trademark of Linus Torvalds