[SUGGESTION] Prevent implicit conversion of negative values when passed to unsigned parameters in certain functions #649
Replies: 10 comments
-
I think a better solution is to just ban that implicit conversion in all cases (which might already be the case in cppfront?), force the user to make it explicit. |
Beta Was this translation helpful? Give feedback.
-
Cppfront currently doesn't prevent implicit conversions, e.g.
emits:
But this suggested feature is specifically about preventing negative values being implicitly converted when passed as the size/length/count parameters (unsigned) to well-known and frequently-used memory functions which are the repeated source of CVEs (as per above). The following is a minimal, contrived example in Cpp2 code, which is currently permitted. For a realistic example, just translate any of the above CVEs into Cpp2 code:
Unless Herb intends to modify Cppfront and parse headers (and module interfaces?) in order to inspect function declarations (in order to determine if a signed value is passed to a function's unsigned parameter), the best solution I could think of was to have a well-defined list of 'dangerous' memory functions along with their size/length parameters, in order to then insert the |
Beta Was this translation helpful? Give feedback.
-
Interesting idea. Let me noodle on this... I can think of a couple of ways to improve this, but I may sit on it for a while thinking about it first before coding something up.
If it were a fully native compiler I'd do that (and not allow treating character types like
Jordan Peele well captured my reaction to that, and my 4-letter all-caps answer. :) |
Beta Was this translation helpful? Give feedback.
-
Disclaimer: I dislike even mentioning a gross idea like wrapping fundamental types, it just screams "overhead!" But for the sake of discussion... Just as a strawman to show potential semantics and to understand the costs/benefits, how far would something like this (Godbolt) go toward addressing this set of concerns, if hypothetically it were ever feasible to emit a signed fundamental type (e.g., |
Beta Was this translation helpful? Give feedback.
-
Nice work Herb, I like this approach a lot. For the compile time cases I think the benefit definitely outweighs the cost. I’d rather language level errors than a separate static analysis tool, for example. Personally I think eliminating these types of CVEs (as above) is also worth the runtime cost for the runtime case. I can imagine the temptation to cast to remove the compile time error is very high, meaning it would continue to allow those types of CVEs. |
Beta Was this translation helpful? Give feedback.
-
Clever! If one were writing that explicit conversion manually in today's C++, asserting before is the right thing to do. To fail fast on unsatisfied preconditions. I don't know if it's in a guideline somewhere, but it's definitely encouraged inside Google with glog's The runtime cost is optional, because one can disable the assertion with the And if one really wants a What's not to love? |
Beta Was this translation helpful? Give feedback.
-
I assume that it is worth reusing what is already done and reuse template<typename U>
requires (std::is_integral_v<T> && std::is_integral_v<U>
&& !is_narrowing_v<U, T> )
operator U() const { return t; } |
Beta Was this translation helpful? Give feedback.
-
[Edited with additional info]
Thanks for the kind words! But some things to maybe not quite love:
Those are some of the reasons I've left this open to keep thinking about, but haven't taken any action yet... |
Beta Was this translation helpful? Give feedback.
-
Could this be solved by applying the wrapper to integer literals? Like transforming this:
to:
Yeah, I can imagine there would be concerns and push backs about this. It seems to me that Cpp2 has a philosophy of "safety with performance" but "safety over performance -- if necessary". I'm thinking of bounds checking in particular. But one of the nice aspects of your solution is that it only ("only...") introduces a bit of extra compile-time work for what should be the common case where the programmer is passing a signed integer to an argument of signed type. Any extra runtime work (and assembly code generated) only happens if the programmer is doing something that they probably didn't mean to (or didn't consider the corner cases). |
Beta Was this translation helpful? Give feedback.
-
I'm not sure if this a scenario you were thinking of but I created a test to compare passing https://godbolt.org/z/hzMhh4oYs GCC and Clang both produced identical assembly. More details in the link. |
Beta Was this translation helpful? Give feedback.
-
Suggestion
Cppfront could prevent implicit conversion of negative values that are passed to unsigned parameters in well-known, memory-related functions, with the goal of eliminating a certain class of security vulnerabilities (see below).
Details
Many memory-related functions have size/length/count parameters of type
size_t
(or similar), e.g.A frequent source of security vulnerabilities (see below) is when a negative signed value is passed as the argument and implicitly converted to a (very large) unsigned value.
Cppfront could have a built-in list of well-known, memory-related functions (
memcpy
,memset
,memmove
,malloc
,calloc
,std::vector
members,std::string
members,VirtualAlloc
,VirtualAllocEx
, etc) along with the position(s) of their size/length/count unsigned parameters.Cppfront could substitute all arguments passed to these size parameters for these well-known functions with a helper function which tests (at runtime) if the value is negative, and if so, calls
std::terminate
.For example, Cppfront would transform this Cpp2 code:
into something like:
where
cpp2::safe_unsigned_argument
is something like:This will require each documented parameter in the well-known memory functions to have their
size type
also documented, for use in the template return typeResultT
. E.g.Will your feature suggestion eliminate X% of security vulnerabilities of a given kind in current C++ code?
Yes, this feature will eliminate security vulnerabilities caused by a negative signed value being implicitly converted to unsigned when passed to the size/length/count parameter of well-known (and frequently-used) memory functions. These vulnerabilities can cause stack overflow, buffer overflow and/or denial of service.
Example 4 in CWE-195: Signed to Unsigned Conversion Error has an example of the type of security vulnerability:
See also
Example 4
in CWE-131: Incorrect Calculation of Buffer Size.These CVEs are examples of security vulnerabilities that this feature would eliminate:
memcpy
memset
std::vector::resize
std::vector::reserve
Will your feature suggestion automate or eliminate X% of current C++ guidance literature?
Best practice guidance is to enable a high level of warnings in the compiler (and ideally to treat warnings as errors).
For example see:
However, in my tests this was not sufficient to produce a warning on the signed/unsigned conversion in all cases. Given this Cpp1 code:
len
conversion with-Wsign-conversion
but I couldn't find any other way to make it warn (e.g. not with-Wconversion
,-Wextra
or-Wall
)-Wsign-conversion
(or-Wconversion
)/W4
. This is because the relevant warningC4365
is off by default. The warning can be enabled with/W4 /w44365
.This feature will automate this best practice for all users even if they have not enabled the relevant warning in their compiler, which as per the above is not always obvious or trivial.
Describe alternatives you've considered
An alternative for the
cpp2::safe_unsigned_argument
helper would be to avoid an explicit return type:This would have the benefit that Cppfront would not need to document the type for each size/count/length parameter in the list of well-known functions. However, it seemed a better design to be explicit in this case.
Beta Was this translation helpful? Give feedback.
All reactions