javajava-native-interfacecode-injectionjvmti

How can I disable JVM bytecode verification at runtime?


I do know the verification can be disabled by specifying argument -Xverify:none, but how can I do that without this starting argument so that JVM won't stop me from using redefineClasses to inject code at runtime? If not so the JVM just gives me jvmtiError JVMTI_ERROR_FAILS_VERIFICATION = 62.

I searched the whole Internet and cannot find what I expect (maybe I missed the corrent solution). I would like something like modifying JVM arguments at runtime, injecting into ClassLoader and remove the verifying codes, or just simply calling some method or modifying the value of some field, which can work at least on Java 8.


Solution

  • You can redefine classes and inject code at runtime just fine even when the verifier is 'on'. The bytecode you inject has to pass the verifier, though. This is normally not particularly difficult. This sounds like an X/Y problem: The underlying issue is that the code you have that injects bytecode is badly written (generates stuff the verifier doesn't like), you therefore run into FAILS_VERIFICATION errors, and you're now asking questions about how to make the verifier shut up. You should be asking questions about your bytecode generating code. Possibly you think it is not possible to inject code if the verifier is on. This is false - the verifier just checks if the bytecode that is being loaded by the JVM fails certain preconditions. As an example, this sequence of bytecode cannot be injected if the verifier is on - not because 'the verifier prevents injection', but because this code just won't pass the verifier. It doesn't matter if you attempt to inject this, or just load it off disk as normal:

    ICONST_0 // push int constant value '0' on the stack
    ICONST_1 // push int constant value '1' on the stack
    FADD     // pop 2 floats off the stack, add them, push result on stack
    

    It wouldn't be allowed because the verifier can tell that at the point of FADD, there should be 2 floats on the stack, but there are 2 ints, and that's not okay: The JVM won't let you treat ints as floats, even though in machine code this would do something (namely, treat the int as a float and add that way). One problem is that the JVM does not control what actually happens here - it depends on architecture, and that's not what java is meant to be doing. It's called verification because the vast majority of these violations would let you access things in memory that the code wasn't meant to, such as pushing an int on stack and then popping that into a field of type Object, letting you do 'pointer arithmatic' which is 'unsafe' for reasons I hopefully don't have to expand on here.

    The simple answer: Stop doing the unsafe thing.

    But... I gotta

    If the thing you are injecting is just java code, then just generate non-verifier-breaking code. And note that there are lots of things that folks think can't be done in a verifier-safe way, but can, so you may want to file a second question to double check before you truly commit to 'this CANNOT be done without turning the verifier off'. But, assuming you really 'have to'..

    Sorry. You can't.

    Java is moving a lot of things that used to be runtime configurable into command line args. With the explicitly stated goal of 'security', i.e. the OpenJDK team considers it a security leak if you can get around it. This is why modules do not allow reflection without a command line --add-opens arg. There are lots of ways around it, but every JDK release turns a few more of those off, and every way to do that either does not work or gets you a 'WARNING: This will no longer work in a future version' notification on the command line.

    If there was a way to do bytecode injection that doesn't have to pass the verifier without a bunch of command line options to the JVM to say that the system operator intended it that way, then that'll be a loophole that will soon be closed. After all, if you were able to do that with the blessing of the OpenJDK APIs and design, then there would be absolutely no point whatsoever to the OpenJDK's considerable efforts in annoying the java community in removing a ton of features that libraries depend on, forcing them to rewrite, or worse, ruin their APIs/release breaking updates. This isn't 'absolute proof', but one that should hopefully suffice: The only way that an OpenJDK blessed (As in, won't be considered a loophole that is closed in the next java release) way to inject without verification and without a command line option would be possible, is if the OpenJDK team just made up excuses to annoy the community and ruin the product. Possible, but.. that'd be extraordinarily weird.

    Possibly this question boils down to: Sure, whatever, no problem. Just tell me the java.exe 0days, I will deal with new java releases closing these loopholes. That's.. probably not a valid SO question, or if it is, is rather unlikely to get you any answers. Such hacks would be coordinated with OpenJDK, or, sold on blackhat market. Not posted on Stack Overflow.