-
Notifications
You must be signed in to change notification settings - Fork 155
Add Unsafe
array access sanitizer
#932
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
base: main
Are you sure you want to change the base?
Add Unsafe
array access sanitizer
#932
Conversation
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel
Outdated
Show resolved
Hide resolved
if (!componentType.isPrimitive()) { | ||
// Reading or writing bytes to an array of references; might be possible but seems | ||
// rather unreliable and might mess with the garbage collector? | ||
report("Reading or writing bytes from a " + objClass.getTypeName()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is too strict. On the other hand there might be no guarantees on how large object references are so any access where object references are treated as bytes seems error-prone.
"com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical", | ||
], | ||
target_class = "com.example.UnsafeArrayOutOfBounds", | ||
verify_crash_reproducer = False, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
During my testing when reproducer verification was enabled no sanitizer exception was thrown. Are sanitizers generally not enabled when running in reproducer / regression (?) mode?
That might be rather problematic; in the case of Unsafe
that might crash the JVM, and I guess for the other sanitizers (such as the path traversal one) it could also have undesired consequences (e.g. if due to path traversal files are placed at random paths on the machine).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are sanitizers generally not enabled when running in reproducer / regression (?) mode?
They are, but the bazel rule java_fuzz_target_test
in the Jazzer repo cannot handle FuzzerSecurityIssueCritical
findings in regression mode (we correctly get exit code 77, but no finding information). Testing with maven/gradle + JUnit, everything works as expected (each @FuzzTest
throws findings in regression mode).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the clarification! So keeping verify_crash_reproducer = False
here is fine, right?
A bit related: Maybe the comments "not clear why reproducer doesn't work TODO -- fix this" for the FilePathTraversal
tests was then also a misunderstanding of how this works?
(though maybe those comments should be removed in a separate PR and not as part of this one)
java_fuzz_target_test( | ||
name = "UnsafeArrayOutOfBoundsValid", | ||
srcs = [ | ||
"UnsafeArrayOutOfBoundsValid.java", | ||
], | ||
allowed_findings = [], | ||
fuzzer_args = [ | ||
# Test does not depend on fuzzing input; just run it once | ||
"-runs=1", | ||
], | ||
target_class = "com.example.UnsafeArrayOutOfBoundsValid", | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is a good approach to ensure that the Unsafe
sanitizer does not report errors for valid access.
sanitizers/src/test/java/com/example/UnsafeArrayOutOfBounds.java
Outdated
Show resolved
Hide resolved
I think this PR is now ready for an initial review; any feedback is appreciated! Please also let me know what you think about the points I mentioned above in my review comments. Also, is there a way to let Bazel install the Maven artifacts (especially the Jazzer JUnit artifact and its dependencies) as SNAPSHOT to the local Maven repository (similar to what |
|
Thanks! That only works for Linux though because I will see if I can maybe get it working with WSL 2 or a Docker container. But if that then only produces the Linux and not the Windows native libraries for Jazzer (not completely sure how the native build for Jazzer works) it will also be a bit cumbersome to then use these artifacts. (Side note: I had to update locally some of the Edit: Was able to build it now in a Docker container, but had to copy the native libraries which I had previously built on Windows into the JAR. The sanitizer seems to work. |
If you follow the steps at https://bazel.build/install/windows#install-compilers to install MSYS2 and Visual Studio, The publishing script for |
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java
Outdated
Show resolved
Hide resolved
Thanks for the hints!
I worked around it now by building within a Docker container, but maybe other users would benefit from that as well, so thanks for your work on this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this awesome contribution!
I am not familiar with the unsafe, but this PR looks good already!
} else if (c == long.class || c == double.class) { | ||
bytesCount = 8; | ||
} else { | ||
throw new AssertionError("Unexpected type: " + c); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sanitizers are only allowed to throw/report findings.
If the program reaches here, the hook should simply return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean the hook should return and do not perform any sanitization?
Though if this AssertionError
occurs here it would indicate a bug in this sanitizer implementation. And it is unlikely that it will occur given that the method hooks explicitly define the names of the Unsafe
methods to intercept; this error would only occur if new sun.misc.Unsafe
method overloads are added which break the assumptions of this sanitizer implementation.
So is it fine to keep the AssertionError
here?
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel
Outdated
Show resolved
Hide resolved
// Assumes that `Unsafe.ARRAY_BOOLEAN_INDEX_SCALE > 0` | ||
bytesCount = 1; | ||
} else if (c == byte.class) { | ||
bytesCount = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to hard-code these? Why not use Unsafe.ARRAY_BOOLEAN_INDEX_SCALE
, etc.?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess technically they should be identical1, though this method is more about "how large is an int
, ...".
So would you prefer usage of the ..._INDEX_SCALE
values here then?
Footnotes
-
If for some reason the
..._INDEX_SCALE
was larger than the corresponding size of the primitive (meaning there are gaps between array elements), then probably lots ofUnsafe
usage would be broken anyway. So I doubt that this is the case for any JDK implementation, and it might not be permitted by theUnsafe
documentation in the first place. ↩
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java
Outdated
Show resolved
Hide resolved
"com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical", | ||
], | ||
target_class = "com.example.UnsafeArrayOutOfBounds", | ||
verify_crash_reproducer = False, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are sanitizers generally not enabled when running in reproducer / regression (?) mode?
They are, but the bazel rule java_fuzz_target_test
in the Jazzer repo cannot handle FuzzerSecurityIssueCritical
findings in regression mode (we correctly get exit code 77, but no finding information). Testing with maven/gradle + JUnit, everything works as expected (each @FuzzTest
throws findings in regression mode).
sanitizers/src/test/java/com/example/UnsafeArrayOutOfBounds.java
Outdated
Show resolved
Hide resolved
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/UnsafeArrayOutOfBounds.java
Outdated
Show resolved
Hide resolved
Side note: The current test failure for the GitHub workflow seems to be unrelated to my changes:
|
Adds a sanitizer which looks for invalid array access performed by
sun.misc.Unsafe
. Unlike native memory access performed byUnsafe
, array access can be sanitized in a stateless way just based on the arguments passed to theUnsafe
method. And multiple Java libraries have usedUnsafe
for arrays in the past to improve performance, see related security advisories GHSA-8wh2-6qhj-h7j9 and GHSA-973x-65j7-xcf4.Note that fuzzing likely cannot find all such invalid accesses because some of it occurs for numeric overflow respectively multiple MB of processed data, which the fuzzer might be unable to generate.
See related #891
I used #915 as reference for how to implement a sanitizer. However, I am not very familiar with Bazel and this project setup here, and also have / had issues with building it locally on Windows. Therefore I have marked this PR as Draft for now.
Any feedback is appreciated!