The problem with encapsulating C.val + autoboxing
forax at univ-mlv.fr
forax at univ-mlv.fr
Wed Jul 27 00:28:54 UTC 2022
> From: "John Rose" <john.r.rose at oracle.com>
> To: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "valhalla-spec-experts" <valhalla-spec-experts at openjdk.java.net>
> Sent: Tuesday, July 26, 2022 5:00:09 AM
> Subject: Re: The problem with encapsulating C.val + autoboxing
> This is not a general security problem, but rather an example of how code that
> is inside an encapsulation can violate that encapsulation.
> Because Agenda is allowed to mention the type MyList<Month.val> which includes
> the (package-private) type Month.val , it must be the case that Agenda is
> inside the same package as Month . Since Month has granted access rights to all
> package-mates, if there is a “leakage” of Month.default somewhere, it is only
> because someone in the same package as Month has given away that value. In
> other words, it’s how the encapsulation is written, for better or worse.
> If the author of Month wants to distrust package-mates, that author should
> declare the companion type private , not package-private. You can’t grant
> access to code you distrust, and then complain about security, unless you are
> pointing at yourself!
> This particular example does not stress autoboxing in any interesting way that I
> see. Clearly if somebody has access to Month.val they can then grab
> Month.default by one of several ways, and then it’s up to them to keep the
> secret safe, if it is in fact a secret to be kept safe.
> BTW, this example assumes specialized generics. Since they don’t exist fully
> yet, except on paper, we are assuming properties of specialized generics that
> may not in fact turn out to be true. But I assume that:
> *
> Any non-erased type argument, which is set to a privatized class, must be
> accessible to the code which mentions the type argument (as part of the generic
> type application).
> *
> Any generic that uses non-erased type parameters which may be bound to
> privatized types should document how it materializes externally-visible default
> values and/or arrays of that type, if it in fact does this. (Most won’t need
> to.)
> Perhaps you are pointing to the fact that bugs in generic containers might leak
> non-constructed values from specialized generics? (The missing call to
> checkIndex allows an empty value to leak from getFirst .) We should keep this
> in mind, I guess. But note that the fault is not solely in the buggy generic,
> in your example: There is also some fault in the client which passed a
> privatized type to a generic of unknown quality.
I see two issues with that idea in practice
- Leaking null is perfectly valid for a non-specialized generics, so it means that some generics can not be not upgraded to specialized generics because of their semantics.
Given that a missing checkIndex makes the code buggy or not, it is also undecidable if a specialized generics is buggy or not.
- or sending a privatized type to a generics is considered as too dangerous, and nobody will use C.val if val is defined as private or package-private because you can not using it apart on toy codes.
> If necessary, as part of the “opt in” for specialization we could add another
> layer of “opt in” to handle privatized type arguments. There is always a
> reasonable fallback for MyList<Month.val> if MyList is not expecting to handle
> privatized types. The fallback is to partially erase MyList<Month.val> to
> MyList<Month.ref> . That would be a third, intermediate form of erasure: Lift
> privatized types to their reference types.
> In fact, as I’ve noted before, this is a question for non-flat types as well, in
> the setting of specialized generics: Perhaps specialized generics should never
> specialize on inaccessible type arguments of any sort, neither refs to
> non-public classes nor privatized vals. It’s a fair question to ponder…
You are proposing that T inside a generics and T outside a generics behave differently.
It does not seem realistic to me because it does not work well with the idea that .val is propagated by the T, so it has to be the same T.
By example with,
class Foo<T> {
T getDefault() {
return T.default;
}
}
If privatized types are not specialized, new Foo<Month.val>().getDefault() will return null if Month.val is a privatized type.
> If we do allow specialization on non-public type arguments, it’s probably on the
> grounds that the client “knows what he’s doing” and is consciously sharing
> access to the non-public type by mentioning it in a type argument. The generic
> who gets a non-public type shared with it must handle it with due care, not
> immediately leaking it to the world. It’s part of the user contract for
> specialized generics: What are the rules for non-public types?
> — John
If we take a step back, privatized types has introduced a coupling between those types and specialized generics.
So now, our plan to add specialized generics after value classes is a kind of dangerous.
I think a simpler plan is to not introduce privatized types now to avoid the coupling and to introduce them later when we introduce specialized generics.
Rémi
> On 25 Jul 2022, at 13:33, Remi Forax wrote:
>> One of the idea of encapsulating C.val is that even with a value class C with no
>> good default, accessing C.val is useful.
>> That's true but a unfortunate consequence is that it makes leaking T in a
>> generic code a security issue.
>> It is in fact more secure to use two classes C and CFlat, one with no good
>> default and the other which allow the default value, when using with generics.
>> Here is an example, let say we have a value class Month (1 to 12) and an
>> identity class Agenda that contains several months.
>> WE can declare Month like this, with a package-private value companion.
>> value record Month(int value) {
>> /*package-private*/ companion val;
>> public Month {
>> if (value < 1 || value > 12) throw new IAE();
>> }
>> }
>> So we can flatten the Month when stored in a list
>> class Agenda {
>> private final MyList<Month.val> months = new MyList<>();
>> public void add(Hour hour) {
>> Objects.requireNonNull(hour);
>> months.add(hour);
>> }
>> public Hour getFirst() {
>> return months.isEmpty()? null: months.getFirst();
>> }
>> }
>> Is this code safe ? The trouble is that it depends on the implementation of
>> MyList, by example with
>> class MyList<E> {
>> private E[] array = new E[16];
>> private int size;
>> public boolean isEmpty() { return size == 0; }
>> public void add(E element) {
>> array[size++] = element;
>> }
>> public E getFirst() {
>> // Objects.checkIndex(0, size);
>> return array[0];
>> }
>> }
>> MyList.getFirst() leaks E.default, so the implementation of Agenda is not safe.
>> Using the encapsulation to hide C.val is only safe if the generics code never
>> leak E.default.
>> Weirdly, if i use different classes to represent C and C.val, i do not have that
>> issue.
>> class Agenda {
>> value record MonthFlat(int value) {
>> public companion val;
>> }
>> private final MyList<MonthFlat> months = new MyList<>();
>> public void add(Hour hour) {
>> Objects.requireNonNull(hour);
>> months.add(new MonthFlat(hour.value()));
>> }
>> public Hour getFirst() {
>> return months.isEmpty()? null: new Month(months.getFirst().value);
>> }
>> }
>> because unlike the autoboxing conversion between Month.val to Month, the
>> conversion from MonthFlat to Month does not bypass the constructor.
>> Rémi
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-spec-experts/attachments/20220727/ef185a83/attachment.htm>
More information about the valhalla-spec-experts
mailing list