Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Comments on Why does a lack of object encapsulation constitute a security breach?
Parent
Why does a lack of object encapsulation constitute a security breach?
In the current version of OpenJDK's JEP 401: Value Classes and Objects (Preview), it is said that value classes can leak data stored in their fields, and that this is potentially a security concern. Quoting the two parts:
- The ability to compare value objects' fields means that a value object's private data is a little more exposed than it might be in an identity object: someone who wants to determine a value object's field values can (with sufficient time and access) guess at those values, create a new class instance wrapping their guess, and use == to test whether the guess was correct.
- System.identityHashCode: The "identity hash code" of a value object is computed by combining the hash codes of the value object's fields. The default implementation of Object.hashCode continues to return the same value as identityHashCode. (Note that, like ==, this hash code exposes information about a value object's private fields that might otherwise be hidden by an identity object. Developers should be cautious about storing sensitive secrets in value object fields.)
Why is this relevant? If an application is running untrusted code, it is already compromised, at least to a certain degree. The Security Manager is deprecated for removal, because it's been deemed unfit for defending an application or system (IDEs still use it for running plugins), with the verdict that untrusted code should not be executed as part of the same process in the first place. If untrusted code is run nonetheless, it should be done as a separate process, since OS-level protections are more robust. That means no Java objects are shared between the processes, as they don't share the same memory space, so encapsulation has no effect. The conclusion then, is that this is only of concern when some code is running in the same process that uses these Java objects.
When JEP draft: Integrity by Default and other related JEPs speak of safety and security, it is more in the sense that an application cannot crash or invoke undefined behaviour (not the idea of UB in C++, etc). However, that doesn't seem the be the case with JEP 401, which describes two cases of collecting restricted data.
Even when Integrity by Default does speak about security;
Security — Encapsulation is essential for any kind of robust security. Suppose, e.g., that a class in the JDK restricts a sensitive operation:
if (isAuthorized()) doSensitiveOperation();
The restriction is robust only if we can guarantee that doSensitiveOperation() is only ever invoked after a successful isAuthorized() check. If we declare doSensitiveOperation() as private in its declaring class then we know that no code in any other class can directly invoke that method; in other words, the declaring class has integrity with respect to that method. Code reviewers thus need only ensure that all invocations of the method within the declaring class are preceded by an isAuthorized() check; they can ignore all the other code in the program.
– it is about a programming error issue, not an attacker, provided untrusted Java code is not being run in the same process.
But with the Security Manager already on to be removed, the assumption must be that the warnings in JEP 401 only apply in an environment where trusted code is executed. Exploiting the leaks in 401, is an intentional decision, not a simple programmer error, so what is the threat in question?
If a framework or library developer is worried that clients can read data they stored in an object, object encapsulation would already not work, since the client developer can use reflection, a debugger, or get a reference to the Java object in native code, then use this to inspect the data at the address where the fields are stored. That is provided the running Java code has access to call into native code, which is something any trusted code can give itself.
If any sort of executing malicious code has access to the memory, the only safe measure is to not store the data in memory at all. This also applies if an attacker can somehow exploit a vulnerability in a running application to execute Java bytecode. So why does it matter if a Java object leaks the values in its fields?
Post
Let's pretend Java was a capability-safe language.
Now let's consider the IDE scenario and imagine that we want to support Copilot-like AI plugins that use a 3rd-party service. To access these services, the plugins will need an API key.
This is easy to accomplish. The plugin interface simply allows the untrusted plugins to request to be given an ApiRequester
object which they can use to make requests to the AI services. If the API key was stored in a private field of the ApiRequester
object, then simple encapsulation would stop the untrusted code from being able to get at it.
If, for some bizarre reason, ApiRequester
was a value object per this JEP, then the untrusted code could attempt to guess the API key. The most efficient way would likely to be via hashCode
. Either way, the key point is that attempts would not require actually making requests which could be detected and throttled or lead to a lock out[1].
None of the above scenario requires SecurityManager
or sandboxing or even full object capability discipline. It only requires that the untrusted code doesn't have access to encapsulation-breaking capabilities. In a capability-safe language, this property would be easy to verify. Simply don't give such capabilities to the untrusted plugins. Even in actual Java, I don't believe it would be hard to check the bytecode of an untrusted plugin and ensure it only uses a whitelisted set of imports and methods. So, you would disallow reflection methods and access to much or all of java.io
/ java.nio
, for example. This does not stop the plugins from being able to make network requests, say; it just means they can only do it via objects the trusted code provides, e.g. the ApiRequester
above.
While operating at the source level, Joe-E (linked above) shows how we could limit Java to guarantee capability safety. Other than the elimination of ambient authority, these limitation would not effect much Java code. It would certainly leave the experience of writing Java largely the same. Even something like reflection could be provided in a capability-safe manner by simply making mirrors only accessible via a capability (which may itself be attenuated to, for example, disallow reflecting on any trusted code).
The SecurityManager
was an unnecessary, complicated, and only somewhat effective approach security. It going away is probably a good thing for Java security.
-
As an example of attenuation possible in this model, one malicious thing the untrusted plugin could do is make a bunch of spurious requests to waste our money. Again, this is easily solved by simply having
ApiRequester
incorporate some throttling logic of its own. ↩︎
2 comment threads