So what, are you willing to go away from von-neumann architecture where instructions are data and data are instructions, i.e. the instruction-data hominocity that underpins JIT compilation? Are you willing to go to a pseudo-Harvard architecture where the ability of JIT compiling is soft locked by other means like VM or strong code authentication or policy protection, which is what Apple is doing.
Fun fact: even Apple themselves have JIT. JavaScriptCore on iOS has JIT, it's just that the App Store policies forbid any application submissions with JIT or trying to mmap/mprotect an executable region. There used to be apps on TrollStore that runs JIT
You're entirely correct because JIT requires violating Write xor Execute security policy. This is the reason on iOS, it is limited to Apple shipped software.
- Android Runtime Just-In-Time (JIT) compilation/profiling is fully disabled and replaced with full ahead-of-time (AOT) compilation. The only JIT compilation in the base OS is the V8 JavaScript JIT which is disabled by default for the Vanadium browser with per-site exception support.
- Dynamic code loading for both native code or Java/Kotlin classes is blocked for nearly the entire base OS. […]
- Dynamic code loading for both native code or Java/Kotlin classes can be disabled for user installed apps via 3 exploit protection toggles: […]
As someone who has written a jit compiler, I am puzzled by the claim that jitting requires write/execute permissions. When I have written a jit, I loaded some memory with read/write permissions using mmap. Once I filled in the generated code, I mprotected the region to read/execute before executing.
The drawback to this approach is there can be some bloat because you can only mprotect at page granulariy so a jitted function that only takes say 10 bytes to represent would take up a full page in memory, but this is extreme and in practice, the overhead is unlikely to be worth worrying about.
There's more to it than that though. Defects in JIT logic can lead to nasty low-level bugs. Plain old interpreters, especially if written in a safe language, are unlikely to have similar issues. This is important when the input code is untrusted. JIT bugs are a major source of browser vulnerabilities.
JITs are an attack surface in that process. They are still restricted to things that process was already allowed to do, no matter how badly implemented the JIT is.
In this example usage, one would hope that authentication already happened before the JIT processed the command. So an authorized user can attack themselves is the only realistic risk, which is hardly significant
There's plenty of scope for harm just within the process, even ignoring the possibility of escaping the process. In the case of a database server, essentially everything of value takes place within the database process (or processes). That process presumably has both access to the raw database data, and network access. We wouldn't want it sending data to an attacker's server.
one would hope that authentication already happened before the JIT processed the command
We'd hope, yes, but SQL injection issues are still somewhat common. Also, an organisation might trust their DBMS to enforce permissions, and a JIT bug is the kind of thing that might allow non-permissioned data access. A DBMS should be hardened against malicious queries, just as a browser should be hardened against malicious JavaScript.
In web browsers, the numbers show JIT compilers are a major cause of security issues. I don't know if there are hard numbers on JIT engines causing security issues in DBMSs though.
There's plenty of scope for harm just within the process,
Of course, but the process has every right to decide for itself if it wants to take that risk. Just like it decides if it wants to take the risk of a memory unsafe language, forgoing fuzzing, or going all out with formal verification.
Sure, but it still means that the OS has to decide who is allowed to do it and to what extent. Sophisticated worms like Stuxnet would be much harder with strict W^X for example, since CVE-2010-2568 and the like would be much harder to execute.
Capabilities are a way to control that, and the point being that only responsible proven applications get the certificate, hence how it all goes on iOS.
You're making the assumption that "responsible" is something provable, but that is not the case, it is specially not easy to prove software is secure from tampering its behaviour.
Nonsense, there's no "system wide implications". Mappings are per process, and W^X is just a strategy to help harden individual processes, not the entire system. There's no herd immunity here.
JITs do not grant the ability to bypass any OS/system sandboxes. The lack of W^X doesn't do that, either. If a process opts out of W^X, such as to enable a JIT, it's voluntarily making itself less hardened, but at the end of the day this isn't any more meaningful than the program being allowed to be written in, say, C, which also voluntarily reduces the processes security hardening.
No, you're not understanding mine. Nobody builds an OS/system expecting that every executable is perfectly well behaved with zero bugs and zero ill intent. Applications are allowed to run code. JITs just run code in that same process. They are already limited to what the process was already allowed to do in the first place.
And my point about C is literally that even without a JIT, applications can still have arbitrary execution vulnerabilities.
A JIT intended to run untrusted code as part of a sandbox, like a browser, is a big risk. But that's because of the untrusted code part, not the JIT. By comparison, something like a Python or Java JIT is as near as makes no difference completely risk free. The JIT is working on exclusively "trusted" code. Same basic concept applies here with this database usage.
Yes, but your claim is that applications can still have arbitrary execution vulnerabilities only on operating systems that allow dynamic executable code.
I am refuting that statement by showing that not allowing dynamic executable code is not enough. It is irrelevant to that end that you can mitigate ROP by other means.
If a process is allowed to execute code, it's always allowed to execute arbitrary code as well. Those two are fully intertwined, be it via dynamic executable code, ROP chains, because it has an interpreter, or because the initial binary itself already is the malicious payload in the first place.
To the OS those are all identical scenarios, it's irrelevant what caused the arbitrary code execution to happen.
Read the code in the post. Everything is written in unsafe Rust. The assembly itself knows no memory safety at all and is completely up to the programmer skill whether it can be trusted to not mess up.
But then we'd have to admit that it's not the JIT that's a problem, it's the lack of guardrails and analysis features in the machine code interfaces that higher level languages expose!
Comments
JIT compilation is unsecure.
So what, are you willing to go away from von-neumann architecture where instructions are data and data are instructions, i.e. the instruction-data hominocity that underpins JIT compilation? Are you willing to go to a pseudo-Harvard architecture where the ability of JIT compiling is soft locked by other means like VM or strong code authentication or policy protection, which is what Apple is doing.
Fun fact: even Apple themselves have JIT. JavaScriptCore on iOS has JIT, it's just that the App Store policies forbid any application submissions with JIT or trying to mmap/mprotect an executable region. There used to be apps on TrollStore that runs JIT
Typo: it is Homoiconicity, not hominocity. I typed too fast
You're entirely correct because JIT requires violating Write xor Execute security policy. This is the reason on iOS, it is limited to Apple shipped software.
https://en.wikipedia.org/wiki/W%5EX
GrapheneOS heavily restricts JIT usage, too:
https://grapheneos.org/features
As someone who has written a jit compiler, I am puzzled by the claim that jitting requires write/execute permissions. When I have written a jit, I loaded some memory with read/write permissions using mmap. Once I filled in the generated code, I mprotected the region to read/execute before executing.
The drawback to this approach is there can be some bloat because you can only mprotect at page granulariy so a jitted function that only takes say 10 bytes to represent would take up a full page in memory, but this is extreme and in practice, the overhead is unlikely to be worth worrying about.
The wiki page mentions this is only a minor problem. Because everyone just writes, then switches and executes.
But this means that you have to decide who is allowed to switch.
VirtualProtect/mprotect aren't privileged calls on any modern OS.
It's how you do JIT on macOS, where W^X is enforced, for example.
No, it isn't. JITs don't grant capabilities abilities an equivalent interpreter doesn't already have.
Allowing code execution allows code execution, that's it, that's the entirety of it.
There's more to it than that though. Defects in JIT logic can lead to nasty low-level bugs. Plain old interpreters, especially if written in a safe language, are unlikely to have similar issues. This is important when the input code is untrusted. JIT bugs are a major source of browser vulnerabilities.
JITs are an attack surface in that process. They are still restricted to things that process was already allowed to do, no matter how badly implemented the JIT is.
In this example usage, one would hope that authentication already happened before the JIT processed the command. So an authorized user can attack themselves is the only realistic risk, which is hardly significant
There's plenty of scope for harm just within the process, even ignoring the possibility of escaping the process. In the case of a database server, essentially everything of value takes place within the database process (or processes). That process presumably has both access to the raw database data, and network access. We wouldn't want it sending data to an attacker's server.
We'd hope, yes, but SQL injection issues are still somewhat common. Also, an organisation might trust their DBMS to enforce permissions, and a JIT bug is the kind of thing that might allow non-permissioned data access. A DBMS should be hardened against malicious queries, just as a browser should be hardened against malicious JavaScript.
In web browsers, the numbers show JIT compilers are a major cause of security issues. I don't know if there are hard numbers on JIT engines causing security issues in DBMSs though.
Of course, but the process has every right to decide for itself if it wants to take that risk. Just like it decides if it wants to take the risk of a memory unsafe language, forgoing fuzzing, or going all out with formal verification.
It is the core of ClickHouse for example, for many years. Is it secure enough in your opinion?
Maybe, but surely there are users who are willing to trust all users of their db instance.
The issue is that it restricts from locking-down and securing the system with Write xor Execute memory. So it has system wide implication.
https://en.wikipedia.org/wiki/W%5EX
W^X is typically per mapping, not per memory page and does not interfere with JIT compilation.
Sure, but it still means that the OS has to decide who is allowed to do it and to what extent. Sophisticated worms like Stuxnet would be much harder with strict W^X for example, since CVE-2010-2568 and the like would be much harder to execute.
It has to do that anyway?
Only if it wants to allow Writable Memory to become Executable, or basically, allow JIT.
How do you propose preventing that without a check?
Signed binaries with the proper assigned OS capabilities.
Yes, but with JIT, you can't really verify what the application does upfront. That is the entire point.
Capabilities are a way to control that, and the point being that only responsible proven applications get the certificate, hence how it all goes on iOS.
You're making the assumption that "responsible" is something provable, but that is not the case, it is specially not easy to prove software is secure from tampering its behaviour.
For that there is bytecode verification as intermediate step, and if you want to go crazy with security, hardware memory tagging with capabilities.
Which at this point most companies would rather save money and forbid JIT altogether.
Note that mainframes and micros have JIT environments that aren't at the same safety level as regular desktop PCs.
For example,
https://medium.com/@dhemanthc/ibm-i-architecture-how-timi-an...
Nonsense, there's no "system wide implications". Mappings are per process, and W^X is just a strategy to help harden individual processes, not the entire system. There's no herd immunity here.
JITs do not grant the ability to bypass any OS/system sandboxes. The lack of W^X doesn't do that, either. If a process opts out of W^X, such as to enable a JIT, it's voluntarily making itself less hardened, but at the end of the day this isn't any more meaningful than the program being allowed to be written in, say, C, which also voluntarily reduces the processes security hardening.
You don’t understand OP’s point because you’re assuming vulnerabilities don’t exist. That is utter nonsense.
No, you're not understanding mine. Nobody builds an OS/system expecting that every executable is perfectly well behaved with zero bugs and zero ill intent. Applications are allowed to run code. JITs just run code in that same process. They are already limited to what the process was already allowed to do in the first place.
And my point about C is literally that even without a JIT, applications can still have arbitrary execution vulnerabilities.
A JIT intended to run untrusted code as part of a sandbox, like a browser, is a big risk. But that's because of the untrusted code part, not the JIT. By comparison, something like a Python or Java JIT is as near as makes no difference completely risk free. The JIT is working on exclusively "trusted" code. Same basic concept applies here with this database usage.
Only on operating systems that allow dynamic executable code.
ROP https://en.wikipedia.org/wiki/Return-oriented_programming
The page lists a long list of defences.
Yes, but your claim is that applications can still have arbitrary execution vulnerabilities only on operating systems that allow dynamic executable code.
I am refuting that statement by showing that not allowing dynamic executable code is not enough. It is irrelevant to that end that you can mitigate ROP by other means.
If a process is allowed to execute code, it's always allowed to execute arbitrary code as well. Those two are fully intertwined, be it via dynamic executable code, ROP chains, because it has an interpreter, or because the initial binary itself already is the malicious payload in the first place.
To the OS those are all identical scenarios, it's irrelevant what caused the arbitrary code execution to happen.
In rust, is jit equivalent to an "unsafe" block ?
Read the code in the post. Everything is written in unsafe Rust. The assembly itself knows no memory safety at all and is completely up to the programmer skill whether it can be trusted to not mess up.
Machine code is insecure, we should all run interpreted code in a formally verified interpreter.
Alternatively, only allow for the execution of cryptographly signed static linked binaries, this naturally includes the interpreter above.
If we are sprinkling formal verification on things, we can sprinkle it on a JIT.
But then we'd have to admit that it's not the JIT that's a problem, it's the lack of guardrails and analysis features in the machine code interfaces that higher level languages expose!
Only if it goes through verified bytecode, and the set of instructions is provable.
The JIT must also only be allowed to call into specific code, controlled by the runtime, and nothing else.