Git Product home page Git Product logo

unsafe-address-sanitizer's Introduction

Java Unsafe address sanitizer

Java Agent which validates memory access performed using sun.misc.Unsafe. Unsafe is a semi-public JDK class which allows among others allocating native memory and directly accessing memory without bounds checks. It is sometimes used by libraries for better performance.

The issue with Unsafe is that it does not detect out-of-bounds reads and writes and performs little to no argument validation. Therefore invalid arguments can break the correctness of an application or even represent a security vulnerability.

Note that the memory access methods of Unsafe will probably be deprecated and removed in future JDK versions, see JEP draft JDK-8323072. Libraries targeting newer Java versions should prefer java.lang.foreign.MemorySegment, which is a safer alternative to Unsafe.

Why this sanitizer?

Invalid Unsafe arguments can be noticeable when the JVM crashes due to SIGSEGV respectively EXCEPTION_ACCESS_VIOLATION. However, if a unit test or fuzzer runs in the same JVM, then it can most likely not properly report the failure in case of a JVM crash.
And even if no JVM crash occurs, Unsafe might have performed out-of-bounds access in the memory of the Java process. Out-of-bounds reads can lead to non-deterministic behavior due to reading arbitrary data, or it could leak sensitive information from other parts of the memory. Out-of-bounds writes can corrupt data, which can lead to incorrect behavior or crashes at completely unrelated code locations later on.

This sanitizer injects validation checks into the Unsafe methods, throwing errors when invalid arguments are provided. The following is detected:

  • Arrays:
    • Out-of-bounds reads and writes
    • Bad aligned access (e.g. reading in the middle of a long element of a long[])
  • Fields:
    • No field at the specified offset
    • Out-of-bounds reads and writes
  • Native memory:

Warning

This library is experimental and only intended for testing and fuzzing. Do not use it in production, especially do not rely on it as security measure in production.

How does it work?

This project is implemented as Java Agent which uses instrumentation (more specifically the Byte Buddy library) to instrument sun.misc.Unsafe and related classes. The Unsafe methods are transformed so that all calls are intercepted to first check if the memory access is valid, handling invalid access depending on the ErrorAction configured for the sanitizer. This allows the sanitizer to react to invalid memory access before it occurs, and before the JVM might crash.

Limitations

  • Only usage of the 'public' sun.misc.Unsafe is checked, usage of the JDK-internal jdk.internal.misc.Unsafe is not checked
    This is normally not an issue because third-party code does not (and in recent JDK versions cannot) access the JDK-internal Unsafe class.
  • Not all invalid memory access might be detected
    For example, if there is a dangling pointer but in the meantime another part of the application coincidentally allocates memory at that address, access with the originally dangling pointer would be considered valid. Similarly, if out-of-bounds access coincidentally happens to access another unrelated allocated memory section, it would be considered valid as well.
  • Sanitizer is unaware of allocations which occurred before it was installed, and memory which is allocated or freed through other means than Unsafe or ByteBuffer#allocateDirect
    If that allocated memory is accessed afterwards, the sanitizer will consider it invalid access. There are multiple ways to work around this, such as:
    • Installing the agent when the JVM starts, instead of at runtime
    • Disabling native memory access sanitization (AgentSettings.withGlobalNativeMemorySanitizer(false)), and optionally instead using UnsafeSanitizer#withScopedNativeMemoryTracking
    • Manually registering the allocated memory with UnsafeSanitizer#registerAllocatedMemory
  • This library has mainly been written for the Hotspot JVM
    It might not work for other JVMs, but bug reports for this are appreciated!

Usage

Note

This library is currently not published to Maven Central. You have to build it locally, see the Building section.

(requires Java 17 or newer)

The sanitizer Java agent has to be installed once to become active. It can either be installed at runtime by calling UnsafeSanitizer.installAgent(...), or when the JVM is started by adding -javaagent to the arguments:

java -javaagent:unsafe-address-sanitizer-standalone-agent.jar -jar my-application.jar

Using -javaagent should be preferred, if possible, because UnsafeSanitizer.installAgent(...) might not be supported by all JVMs and future JDK versions, and it might miss allocations which occurred before the sanitizer was installed, which could lead to spurious invalid memory access errors.

When using -javaagent, invalid memory access will cause an error by default. The behavior can be customized; to view all possible options and examples, start the agent as regular JAR (without any additional arguments):

java -jar unsafe-address-sanitizer-standalone-agent.jar

Usage with Jazzer

This sanitizer can be used in combination with the Java fuzzing library Jazzer, especially its JUnit 5 integration.

When installing the Unsafe Sanitizer at runtime using UnsafeSanitizer.installAgent(...), it should be called in a static { ... } block in the test class, to only call it once and not for every executed test method.

Jazzer itself internally uses sun.misc.Unsafe. If the Unsafe Sanitizer agent is installed at runtime it might therefore be necessary to disable sanitization of native memory by using AgentSettings.withGlobalNativeMemorySanitizer(false).
If the Unsafe Sanitizer agent has been installed using -javaagent this might not be a problem. However, the sanitizer might nonetheless decrease the Jazzer performance. So unless needed, it might be useful to disable native memory sanitization.

Building

This project uses Gradle for building. JDK 17 is recommended, but Gradle toolchains are used, so any needed JDK is downloaded by Gradle automatically.

./gradlew build

This generates the file build/libs/unsafe-address-sanitizer-<version>-standalone-agent.jar which you can use with the -javaagent JVM argument. Or you can add it as JAR dependency to your project and then install the agent at runtime.

You can use ./gradlew publishToMavenLocal to add the library to your local Maven repository. The artifact coordinates are marcono1234.unsafe_sanitizer:unsafe-address-sanitizer:<version>.

Similar third-party projects

  • Project https://github.com/serkan-ozal/mysafe
    Offers more functionality for native memory access tracking, but does not validate array and field access.
  • Paper: "Use at your own risk: the Java unsafe API in the wild"
    Authors: Luis Mastrangelo, Luca Ponzanelli, Andrea Mocci, Michele Lanza, Matthias Hauswirth, Nathaniel Nystrom
    DOI: 10.1145/2858965.2814313
  • Paper: "SafeCheck: safety enhancement of Java unsafe API"
    Authors: Shiyou Huang, Jianmei Guo, Sanhong Li, Xiang Li, Yumin Qi, Kingsum Chow, Jeff Huang
    DOI: 10.1109/ICSE.2019.00095

unsafe-address-sanitizer's People

Contributors

marcono1234 avatar

Stargazers

Dominik Stadler avatar

Watchers

Dominik Stadler avatar  avatar

unsafe-address-sanitizer's Issues

Cover more cases where JDK creates `DirectByteBuffer`

Problem solved by the enhancement

Currently only ByteBuffer#allocateDirect is intercepted. However, there are more cases where the JDK creates a DirectByteBuffer (and more importantly, where ByteBuffer.isDirect() returns true). For example memory mapped files.

Obtaining the address from these buffers and then using Unsafe will currently most likely be erroneously reported as invalid memory access by the sanitizer.

Enhancement description

Before adjusting the sanitizer implementation, first check if using Unsafe with these buffers actually works properly.

Then consider adding interceptors for the other DirectByteBuffer constructors, respectively for the other methods calling them.

Alternatives / workarounds

Don't handle these other cases, assuming that they are not common enough, and intercepting the internal DirectByteBuffer constructors might not be worth the effort and might be too error-prone.

Native memory sanitizer throws error for Jazzer

Unsafe Sanitizer version

80cb8a6

Agent settings

Agent installation:

  • At JVM start (-javaagent)
  • At runtime (UnsafeSanitizer.installAgent(...))

Settings:

  • global-native-memory-sanitizer: true
  • uninitialized-memory-tracking: true
  • error-action: throw

Java version

Java 17 Temurin

Description

Using -javaagent (through Maven Surefire's <argLine>) and global native memory sanitization with Jazzer reports bad memory access directly at the start:

marcono1234.unsafe_sanitizer.agent_impl.BadMemoryAccessError: Invalid address: 0
        at marcono1234.unsafe_sanitizer.agent_impl.BadMemoryAccessError.reportError(BadMemoryAccessError.java:35)
        at marcono1234.unsafe_sanitizer.agent_impl.MemoryTracker.onAccess(MemoryTracker.java:216)
        at marcono1234.unsafe_sanitizer.agent_impl.UnsafeSanitizerImpl.onAccess(UnsafeSanitizerImpl.java:362)
        at marcono1234.unsafe_sanitizer.agent_impl.UnsafeSanitizerImpl.onWriteAccess(UnsafeSanitizerImpl.java:355)
        at marcono1234.unsafe_sanitizer.agent_impl.UnsafeSanitizerImpl.onCopy(UnsafeSanitizerImpl.java:383)
        at jdk.unsupported/sun.misc.Unsafe.copyMemory(Unsafe.java:573)
        at com.code_intelligence.jazzer.driver.FuzzedDataProviderImpl.allocateNativeCopy(FuzzedDataProviderImpl.java:134)
        at com.code_intelligence.jazzer.driver.FuzzedDataProviderImpl.withJavaData(FuzzedDataProviderImpl.java:59)
        at com.code_intelligence.jazzer.junit.FuzzedDataProviderSeedSerializer.read(SeedSerializer.java:81)
        at com.code_intelligence.jazzer.junit.SeedArgumentsProvider.lambda$provideArguments$0(SeedArgumentsProvider.java:74)

(using Jazzer's @FuzzTest with JAZZER_FUZZ=1; jazzer-junit 0.22.1)

The code in Jazzer seems to copy the content of a byte[] to native memory (source). Based on the memory access error above, most likely an empty byte[] is used, which causes a Unsafe.allocateMemory(0) call which returns 0. And that is then used as destination address for Unsafe#copyMemory.

It seems this has no effect because Unsafe#copyMemory does not perform copying if bytes == 0.

So maybe Unsafe Sanitizer should be more lenient when bytes == 0 for copyMemory (and possibly other methods as well)?

Reduce boxing of primitives for intercepted `Unsafe` methods

Problem solved by the enhancement

Currently the interceptors use Byte Buddy's Typing.DYNAMIC. It seems this causes primitive values to be boxed as corresponding wrapper objects, which might decrease performance.

Enhancement description

First check whether this is really a problem, and whether this affects all intercepted methods using Typing.DYNAMIC. For example maybe for @Return(typing = DYNAMIC, readOnly = false) the boxing only occurs when overwriting the return value, which is normally only done for bad memory access (with ErrorAction.PRINT_SKIP). And assuming that these error cases are rare, the boxing might be acceptable.

This can probably be verified by checking allocations for example with VisualVM, or maybe even setting breakpoints in the IDE for the wrapper object constructors / valueOf methods.

Then if necessary try to split the interceptors so that the separate interceptors can use the exact types and using Typing.DYNAMIC is not necessary anymore.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.