3
$\begingroup$

Some virtual machines needs to verify bytecode before running, like JVM.

But, why does the bytecode need to be verified before uploading to a virtual machine?

$\endgroup$

2 Answers 2

8
$\begingroup$

Presumably, JVM bytecode is verified because

  1. Java code is supposed to be memory-safe, i.e. it should not be possible for a well-formed Java program to corrupt the state of the JVM, segfault or access memory it's not supposed to;
  2. Performing these checks at runtime while the program is executing would have unacceptable performance costs;
  3. Java is compiled AOT and distributed in bytecode form, typically packaged in JAR files; and
  4. The JVM's design requirements included a need to run Java software from untrusted sources, e.g. in web applets.

It's not sufficient for only the compiler to perform the relevant checks, because the distributor of the bytecode can't necessarily be trusted. Verifying bytecode at program load-time is therefore a good balance of the desired security and performance concerns. But it's the particular combination of the above four concerns which make this true. Consider:

  • In C, C++ or unsafe Rust, the language gives the programmer powerful tools which could be used violate memory safety, and trusts the programmer to write code which won't. Since the programmer can write code which isn't statically checkable by the compiler, the compiled output is also not statically checkable.
  • The CPython interpreter uses bytecode, and Python libraries can be distributed in bytecode form, but all the necessary checks for safety are performed at runtime, and the cost of these checks is considered acceptable. So in the present design there is no need for CPython to also verify bytecode when the program is loaded, although there have been attempts to introduce a verifier anyway (see the rejected PEP 330).
  • Some Javascript engines compile to bytecode and then interpret it, but Javascript code is distributed in source form, not bytecode. Since the program is compiled to bytecode locally, the compiler's output is trusted and therefore doesn't need to be verified.
  • If only bytecode from trusted sources needs to be run, the author can be trusted to have compiled to correct bytecode, so it need not be verified at program load-time. If the author is trusted but the provenance of the distribution is not, it is still simpler to use signature verification to verify the bytecode's author.

Of course, running untrusted bytecode has other risks besides memory safety. For example, you don't want untrusted code to have full access to your filesystem or network, but bytecode verification can't check for this.

In the era of web applets, the JVM addressed these other risks by performing runtime checks as directed by the user; these checks were implemented in the security manager, which could have a security policy set by the user or by trusted code, before the untrusted third-party code was loaded and executed. However, the SecurityManager API has been deprecated since Java 19 and was removed in Java 21.

$\endgroup$
2
  • 1
    $\begingroup$ Note there have been many incidents where the "local" bytecode compilation in Javascript led to ACE. The strategy used nowadays is to run JS in a special process using OS-specific sandboxing techniques to prevent memory errors from doing harm. $\endgroup$
    – mousetail
    Commented Jun 19, 2023 at 14:30
  • $\begingroup$ @mousetail Bytecode verification would potentially help there if the bug was in the compiler (so that it sometimes output invalid bytecode, and the verifier would catch it). If the bug is in the bytecode interpreter (or the JIT compiler) then a bytecode verifier wouldn't catch it. You're right though that in principle bytecode verification can be useful outside of the narrow circumstances I described in my answer; I was only trying to address when it's needed, not all the cases it could be useful. $\endgroup$
    – kaya3
    Commented Jun 19, 2023 at 18:26
4
$\begingroup$

JVM does not need bytecode to be verified. It allows to verify it, thanks to certain (sometimes quite damaging) design decisions.

With a verified bytecode you can safeguard from a set of runtime errors - you can check if all stack operations are sound, that only allowed system library calls are ever performed (if you need to sandbox the untrustworthy bytecode), etc.

$\endgroup$
2
  • $\begingroup$ Can you elaborate on the "sometimes quite damaging design decisions"? $\endgroup$
    – Seggan
    Commented Jun 19, 2023 at 21:37
  • $\begingroup$ @Seggan compare JVM or CLR VMs to, say, a full-blown Forth. The former are very limited in what you can do with stack. Stack layout cannot change in between function calls - they always consume fixed number of arguments from stack, always return a single value or nothing on a stack. In an unrestricted Forth stack VM any word can do whatever it fancy with the stack, and it gives tons of flexibility. $\endgroup$
    – SK-logic
    Commented Jun 20, 2023 at 8:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .