The problem with encapsulating C.val + autoboxing
John Rose
john.r.rose at oracle.com
Tue Jul 26 03:00:09 UTC 2022
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.
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…
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
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-observers/attachments/20220725/d39dc802/attachment-0001.htm>
More information about the valhalla-spec-observers
mailing list