-
Notifications
You must be signed in to change notification settings - Fork 300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
checksec 2.7.0 FORTIFY detection #235
Comments
It's a bit confusing, let me elaborate:
Result:
I also used pull request #230 |
I am not sure if the logic of Partial/Full RELRO applies to FORTIFY_SOURCE. Partial/Full RELRO is clearly defined. Partial RELRO is using the compiler flag "-z,relro" only. Full RELRO additionally uses the compiler flag "-z,now". (See: https://www.redhat.com/de/blog/hardening-elf-binaries-using-relocation-read-only-relro ) FORTIFY_SOURCE will protect suitable function calls only when the context allows fortification. There is no way to force all functions to be fortified by a compiler flag. So the "partial" output suggests the fortification is not complete. I think this will be misleading. The number in the "Fortifiable" column should be understood as "potentially fortifiable" and not that fortifiability is doable with the right flags. Even FORTIFY_SOURCE=3 will only increase the coverage of fortified functions but not fortify everything in real world binaries (see: https://developers.redhat.com/articles/2022/09/17/gccs-new-fortification-level ) I think instead developers need to pay attention to the Fortified/Fortifiable numbers to get an idea how well the coverage is. The distinction in FORTIFY between "partial" or "yes" does not seem helpful to me. There is no action one can take to turn partial into a yes. |
I thought of the "Partial" case for situations where, for example, the compilation takes place with the -D_FORTIFY_SOURCE=1 flag, although there are also the =2 and =3 variants as a way of warning that things can be improved. To say that Fortify=Yes even though the mode of operation is =1 seems to me to be misleading through false indication. How do you differentiate the Fortify source status if Fortified=Fortifiable versus if they are different. "Partial" may mean that the way Fortify works can be improved or it may not be possible but still not 100%. https://www.redhat.com/en/blog/enhance-application-security-fortifysource |
But the thing with Fortify Source is that you can not really tell apart if 1, 2 or 3 was used by just looking at how many functions were hardened. It is impossible to tell the fortify level apart. The examples where all the possible libc functions are hardened in a binary are mostly true for small toy/example binaries or binaries that only have very few libc calls. The "Yes" and "Partial" results only measure the complexity of the binary in the fortify source case in the end now. To illustrate that here are the checksec results for Ubuntu 22.04 (compiled with FORTIFY_SOURCE=2) and Ubuntu 24.04 (compiled with FORTIFY_SOURCE=3). Ubuntu 22.04
Ubuntu 24.04
As you can see there is "some" improvement by using level 3, but "Partial" remains the top result by far.
I think the "Yes" was always meant as "Fortify Source has been used at all" and not about the fortify level since it can not be measured. To measure the quality of the hardening you have the listing of how many of all possible libc calls were hardened. @slimm609: Any thoughts on this? |
A good study for _FORTIFY_SOURCE=3
An analysis of the problem exposed in the previous post here: #246 (comment) |
Just to clarify: I am just trying to understand the need for "Partial" and want to point out the confusion it will cause. It is not about the issue listed here.
Thank you for the link. It describes what i meant to get across quite well. As you can see in the example even with _FORTIFY_SOURCE=3 checksec would currently rate the Bash shell as "Partial" because the coverage is not 100% that would be needed for a "Yes".
The linked comment describes exactly the confusion the new "Partial" result will cause. People see 'Partial' and wonder what they should do about it, but there is no solution because they already use _FORTIFY_SOURCE=3. |
If you are only comparing 2 and 3, then its not the case. Partial is really for |
the difference between 1 and 2 or 1 and 3 can be determined, the difference between 2 and 3 can't be determined. (at least not that I have been able to find). |
But look at the Ubuntu results i posted. You get "Partial" for most binaries. None of these binaries were compiled with _FORTIFY_SOURCE=1 but with _FORTIFY_SOURCE=2 and _FORTIFY_SOURCE=3 in the newer Ubuntu release.
Ok, then I understand what you are trying to do with "Partial". But i do not think that the current implementation actually does that. To my knowledge _FORTIFY_SOURCE=1, 2 or 3 effectively just increase the coverage on higher levels and you will just see an increase in the "Fortified" Number. checksec currently says "Yes" only if coverage is 100%, meaning that everything that is in theory fortifiable has been actually fortified (Fortified==Fortifiable and Fortifiable>0). For any coverage below 100% you get "Partial" (and N/A for 0% coverage). See https://man7.org/linux/man-pages/man7/feature_test_macros.7.html the section on _FORTIFY_SOURCE:
Just the amount of checks (the number of detected *_chk functions) increases between 1 and 2. I see no special property that makes them differentiable. |
Here is a simple counter example that will always produce "Partial" for _FORTIFY_SOURCE=1, 2 or 3: Compile with When you check with the latest checksec |
Partial means everything that is different from Yes or No regardless of the fortification level chosen during compilation. I am open to any proposal that could clarify this situation, I now see no other solution. |
looking at it in some more depth, there is some issue with the check. I think this is a case where the test files are very basic so they all work properly but the case comes out when we have more complex binaries. I agree with the goal of the partial but want to figure out the proper way to detect it more accurately if possible |
https://github.com/gcc-mirror/gcc/blob/master/gcc/builtins.def#L1112 It looks like the list of functions the output from readelf of glibc may not be an accurate way to check. Trying to understand the details more but the list of "fortiable" functions may be much lower than I originally thought |
From investigating a bunch of programs and recompiling each one switch FORTIFY_SOURCE 1, 2, and 3. 2 and 3 differences can't be easily detected but 1 can. looking for printf_chk was the one outlier that I found that always seemed to be accurate between 1 and 2. If there are any fortify functions and printf_chk is included, it was done with either 2 or 3, if printf_chk is not included, then it was done with 1. This was also consistent with the test files as well from everything I could find. This also resolves the ubuntu scan issues pointed out by @petervas see #248 |
But that means that if a program does not use printf you can't tell the difference again. I'll see if there is a way to get a fortified printf with fortify 1 as a demo. Would be interesting if it is impossible and it were actually possible to tell the difference for sure. With the Ubuntu scans I just wanted to point out that even with fortify source 3 you can't guarantee a 'yes' in the current checksec state that needs 100% coverage for a 'yes'. |
If there is a better way, for sure. This is the most accurate way so far but we can continue to investigate better ways for sure. |
trying to go through the GCC code a bit to determine which flags don't get triggered between 1 and 2 but not having luck so far. If I slim it down, 1,2 and 3 all produce the same binary.
|
Yeah that is what i expected too. The higher FORTIFY_SOURCE levels just increase the coverage by being able to address some more special cases in the code, but there should be no single function that only gets fortified at higher levels. So telling the difference between the levels is not really possible. Personally I would just revert back to the behaviour that a 'Yes' is returned if ANY fortified function is found - regardless of the coverage - to just simply mean "FORITFY was used" without trying to evaluate the quality of the fortification. For a deeper analysis one has to look at the fortifiable vs fortified numbers on a case by case basis. To make it easier to analyze on first glance a new column "Fortify Coverage" could be added to display a percentage (Fortified/Fortifiable)*100 maybe? |
An example 7zr, 7-Zip compiled from sources with -D_FORTIFY_SOURCE=3
Missing "printf"? |
If |
After all the attempts, 2 options remain:
|
Fully agreed. This is the way to go.
Partial in this case is "wrong" so many times that it loses its meaning. See my Ubuntu 22.04 example above. Partial was the strong majority although everything was compiled with FORTIFY_SOURCE=3. Having every function in a binary fortified is an outlier. That happens mostly if very few fortifiable functions are used in non complex contexts.
That has always been my understanding, but it is a good suggestion to make explicit in the documentation. The following research results are obsolete now :)I will post them anyways, since they underline that reverting is the right way. I was checking for more functions like printf to tell the difference between FORTIFY_SOURCE=1 and FORTIFY_SOURCE=2/3. I had this example:
But then @teoberi made a good point earlier. What if a binary simply does not use printf (or any of the above)? You can not tell apart FORTIFY_SOURCE=1 or FORTIFY_SOURCE=2/3 in that case. And then there is another case. What if you do use FORTIFY_SOURCE=2/3 but printf is not fortified? A plain "hello world" will do that:
In this simple case printf will not be fortified even if FORTIFY_SOURCE=2/3 is used. Impossible to tell apart FORTIFY_SOURCE=1 or FORTIFY_SOURCE=2/3 if such a simple printf call is part of your binary. |
checksec 2.6.0
./checksec.old --file=/usr/bin/ssh
checksec 2.7.0
./checksec --file=/usr/bin/ssh
In checksec 2.7.0 FORTIFY=No appears for all tests in which version 2.6.0 has FORTIFY=Yes. For example /usr/bin/ssh
Clarified changes in the code!
The text was updated successfully, but these errors were encountered: