On Mon, Sep 29, 2025 at 12:49 PM Dan Cross <[email protected]> wrote:
> [snip]
> For example, in your original email, you mention validating the length
> of a 0-terminated C string. You suggest that one may use the `strnlen`
> function to do this, since there's no guarantee that an input buffer
> actually contains a 0 terminator (i.e., it may not be a string).
> Further you say, "For example, you can specify that the minimum length
> of a string argument should be 1 and the maximum length of the string
> argument should be 1024." You then write, "The code will be 'len =
> strnlen(str, 1025); if (len == 1025) { return error; }'". Well, now
> we have an ambiguity; C defines "the string" as containing the 0
> terminating byte (cf eg C18, sec 7.1.1 para 1: "A string is a
> contiguous sequence of characters terminated by and including the
> first null character"). `strlen`, on the other hand, returns the
> number of characters before the terminating zero, which is not the
> same thing. Did you really mean the maximum length of the string, or
> did you mean its size?
>
> Suppose that you truly meant that the maximum length, e.g., as would
> be returned by `strlen`, should be 1024; then that string's _size_
> could be up to 1025, as for a maximally sized string the terminating
> null character would be the 1025'th char. But this code returns an
> error on the value 1025; clearly this rejects a maximum length string.
Oops, and I'm off by one myself here: if b[1024]==0, then the return
value of `strnlen` is 1024, not 1025. But regardless, the subsequent
point stands: the function, as written, is ambiguous with respect to
length and size.
- Dan C.
> Reading between the lines, it appears what you mean is that the
> string's maximum _size_ is 1024, as the code clearly intends to find
> the terminating zero within the first 1024 characters, which would
> yield a maximum length of 1023, not 1024. But in that case, you're
> looking at up to 1025 characters, one beyond the size of the string:
> consider what happens in the case of a pointer that points to 1024
> bytes of validly mapped memory, but those 1024 bytes end on a page
> boundary, and the subsequent page is unmapped.
>
> In either case, your example code appears to exhibit a classic
> off-by-one error, and can be tricked into either looking beyond the
> end of a valid memory object (if max len == 1023 and max size ==
> 1024), or failing to properly accept valid strings (if max len == 1024
> and max size == 1025). The error here is in assuming that the return
> value of `strnlen`, as you have used it, is enough to robustly
> establish that the string ends within the acceptable bounds.
>
> Sure, this is easy enough to fix in this case (hint: read up on the
> `memchr` function). But beyond that simple error, C provides you with
> _no way_ to determine whether a given `char *`, when provided as an
> argument to an arbitrary function, points into a valid object of your
> arbitrarily chosen length. So already your advice is impossible to
> follow in the general case.
>
> > Now, coming to the above point number 2:
> >
> > An example of openssh is given that it first does authentication and then
> > it does encryption and this is insecure. I will investigate this and reply
> > later.
>
> Another area you don't touch on at all are TOCTOU bugs.
>
> I suggest, perhaps, studying a bit more.
>
> - Dan C.