-
-
Notifications
You must be signed in to change notification settings - Fork 70
Description
This line:
Line 558 in caafd3a
| if (spec->prec > (NANOPRINTF_CONVERSION_BUFFER_SIZE - 2)) { goto exit; } |
misbehaves if NANOPRINTF_CONVERSION_BUFFER_SIZE is unsigned.
This may actually happen, as the symbol can be provided by the user. In this case, the failure would be very hard to spot and even to understand, for the user. Also, it's not uncommon for the user to use an unsigned literal, since this symbol determines a buffer size.
The problem arises because in case of a signed-unsigned operation, the signed one gets casted to unsigned (after possible promotions).
Similarly in these places:
Line 510 in caafd3a
| #if (NANOPRINTF_CONVERSION_BUFFER_SIZE <= UINT_FAST8_MAX) && (UINT_FAST8_MAX <= INT_MAX) |
Line 595 in caafd3a
| if (dec >= NANOPRINTF_CONVERSION_BUFFER_SIZE) { goto exit; } |
Line 607 in caafd3a
| if (end >= NANOPRINTF_CONVERSION_BUFFER_SIZE) { goto exit; } |
Line 677 in caafd3a
| if (dec >= NANOPRINTF_CONVERSION_BUFFER_SIZE) { goto exit; } |
Edit: the problem does not exist if
dec and end are never negative, regardless of their being signed. The same for prec above; but, are we sure that it is never negative? Not even when it was indeed negative and it was ignored by setting prec_opt = NPF_FMT_SPEC_OPT_NONE but without setting prec = 0 ?
This is a tricky topic at least for me; maybe the problem is not real at all? However, I think it can arise in some cases and not in others -- ie it might depend on the relative bit-size of NANOPRINTF_CONVERSION_BUFFER_SIZE and the other variables involved in math operations with it.
For instance:
-1 > 23u - 2 is true [unexpected] (-1 > 21u) -> (0xFFFFFFFFu > 21u)
-1 > 23ull - 2 is true [unexpected] (-1 > 21ull) -> (0xFFFFFFFFFFFFFFFFu > 21ull)
-1 > (unsigned short)23 - 2 is false [expected] (-1 > (int)(unsigned short)23 - 2) -> (-1 > 23 - 2) -> (-1 > 21)
I think a cast-to-signed of NANOPRINTF_CONVERSION_BUFFER_SIZE is needed everywhere, possibly with a type tailored to every occurrence. Or maybe a cast to (int) is always acceptable, as long as we change the check here:
Line 72 in caafd3a
| #if NANOPRINTF_CONVERSION_BUFFER_SIZE < 23 |
by adding
&& NANOPRINTF_CONVERSION_BUFFER_SIZE < UINT_MAX / 2
which ensures that it already fits in an (int), so the cast to (int) just removes the unsigned-ness.
Note that the check on line 510 is not enough to prevent the problem, as can be demonstrated by the 23ull example above.
Maybe the code can be kept the same, and we just need the following check. We already know from this:
Line 72 in caafd3a
| #if NANOPRINTF_CONVERSION_BUFFER_SIZE < 23 |
that NANOPRINTF_CONVERSION_BUFFER_SIZE is positive.
So, this check should be enough:
#if (-1 < (NANOPRINTF_CONVERSION_BUFFER_SIZE) && -1l < (NANOPRINTF_CONVERSION_BUFFER_SIZE) && -1ll < (NANOPRINTF_CONVERSION_BUFFER_SIZE))
// ok, it's a signed literal
#else
#error NANOPRINTF_CONVERSION_BUFFER_SIZE can't be an unsigned literal
#endif
Finally: this symbol is provided by the user, so every usage of it should be parenthesized. Especially since the user might compute it from other values they have.
This is not the case for NANOPRINTF_CONVERSION_FLOAT_TYPE, because it is supposed to be a type, and parentheses would not be parsed; also, it is much less likely to be the result of an expression, than it is for a numeric option like the buffer size.