[PATCH RFC v2 03/29] mm: asi: Introduce ASI core API
Brendan Jackman
jackmanb at google.com
Thu Feb 20 00:50:15 AEDT 2025
On Wed, 19 Feb 2025 at 11:57, Borislav Petkov <bp at alien8.de> wrote:
> > + * Runtime usage:
> > + *
> > + * 1. Call asi_enter() to switch to the restricted address space. This
> can't be
> > + * from an interrupt or exception handler and preemption must be
> disabled.
> > + *
> > + * 2. Execute untrusted code.
> > + *
> > + * 3. Call asi_relax() to inform the ASI subsystem that untrusted code
> execution
> > + * is finished. This doesn't cause any address space change. This
> can't be
> > + * from an interrupt or exception handler and preemption must be
> disabled.
> > + *
> > + * 4. Either:
> > + *
> > + * a. Go back to 1.
> > + *
> > + * b. Call asi_exit() before returning to userspace. This immediately
> > + * switches to the unrestricted address space.
>
> So only from reading this, it does sound weird. Maybe the code does it
> differently - I'll see soon - but this basically says:
>
> I asi_enter(), do something, asi_relax() and then I decide to do something
> more and to asi_enter() again!? And then I can end it all with a *single*
> asi_exit() call?
>
> Hm, definitely weird API. Why?
>
OK, sounds like I need to rewrite this explanation! It's only been read
before by people who already knew how this thing worked so this might take
a few attempts to make it clear.
Maybe the best way to make it clear is to explain this with reference to
KVM. At a super high level, That looks like:
ioctl(KVM_RUN) {
enter_from_user_mode()
while !need_userspace_handling() {
asi_enter(); // part 1
vmenter(); // part 2
asi_relax(); // part 3
}
asi _exit(); // part 4b
exit_to_user_mode()
}
So part 4a is just referring to continuation of the loop.
This explanation was written when that was the only user of this API so it
was probably clearer, now we have userspace it seems a bit odd.
With my pseudocode above, does it make more sense? If so I'll try to think
of a better way to explain it.
/*
> * Leave the "tense" state if we are in it, i.e. end the critical section.
> We
> * will stay relaxed until the next asi_enter.
> */
> void asi_relax(void);
>
> Yeah, so there's no API functions balance between enter() and relax()...
>
asi_enter() is actually balanced with asi_relax(). The comment says "if we
are in it" because technically if you call this asi_relax() outside of the
critical section, it's a nop. But, there's no reason to do that, so we
could definitely change the comment and WARN if that happens.
> > +#define ASI_TAINT_OTHER_MM_CONTROL ((asi_taints_t)BIT(6))
> > +#define ASI_NUM_TAINTS 6
> > +static_assert(BITS_PER_BYTE * sizeof(asi_taints_t) >= ASI_NUM_TAINTS);
>
> Why is this a typedef at all to make the code more unreadable than it
> needs to
> be? Why not a simple unsigned int or char or whatever you need?
>
My thinking was just that it's nicer to see asi_taints_t and know that it
means "it holds taint flags and it's big enough" instead of having to
remember the space needed for these flags. But yeah I'm fine with making it
a raw integer type.
> +#define ASI_TAINTS_CONTROL_MASK \
> > + (ASI_TAINT_USER_CONTROL | ASI_TAINT_GUEST_CONTROL |
> ASI_TAINT_OTHER_MM_CONTROL)
> > +
> > +#define ASI_TAINTS_DATA_MASK \
> > + (ASI_TAINT_KERNEL_DATA | ASI_TAINT_USER_DATA |
> ASI_TAINT_OTHER_MM_DATA)
> > +
> > +#define ASI_TAINTS_GUEST_MASK (ASI_TAINT_GUEST_DATA |
> ASI_TAINT_GUEST_CONTROL)
> > +#define ASI_TAINTS_USER_MASK (ASI_TAINT_USER_DATA |
> ASI_TAINT_USER_CONTROL)
> > +
> > +/* The taint policy tells ASI how a class interacts with the CPU taints
> */
> > +struct asi_taint_policy {
> > + /*
> > + * What taints would necessitate a flush when entering the domain,
> to
> > + * protect it from attack by prior domains?
> > + */
> > + asi_taints_t prevent_control;
>
> So if those necessitate a flush, why isn't this var called
> "taints_to_flush"
> or whatever which exactly explains what it is?
>
Well it needs to be disambiguated from the field below (currently
protect_data) but it could be control_to_flush (and data_to_flush).
The downside of that is that having one say "prevent" and one say "protect"
is quite meaningful. prevent_control is describing things we need to do to
protect the system from this domain, protect_data is about protecting the
domain from the system. However, while that difference is meaningful it
might not actually be helpful for the reader of the code so I'm not wed to
it.
Also worth noting that we could just combine these fields. At present they
should have disjoint bits set. But, they're used in separate contexts and
have separate (although conceptually very similar) meanings, so I think
that would reduce clarity.
> Spellchecker please. Go over your whole set.
>
Ack, I've set up a local thingy to spellcheck all my commits so hopefully
you should encounter less of that noise in future.
For the pronouns stuff I will do my best but you might still spot
violations in older text, sorry about that.
> + /* What taints should be set when entering the domain? */
> > + asi_taints_t set;
>
>
> So "required_taints" or so... hm?
>
What this field is describing is: when we run the untrusted code, what
happens? I don't mean "what does the kernel do" but what physically happens
on the CPU from an exploit point of view.
For example setting ASI_TAINT_USER_DATA in this field means "when we run
the untrusted code (i.e. userspace), userspace data gets left behind in
sidechannels".
"Should be set" in the comment means "this field should be set to record
that a thing has happened" not "this field being set is a requirement for
some API" or something. So I don't think "required" is right but this is
hard to name.
That commentary should also be expanded I think, since "should be set" is
pretty ambiguous. And maybe if we called it "to_set" it would be more
obvious that "set" is a verb? I'm very open to suggestions.
> > +
> > +void asi_init_mm_state(struct mm_struct *mm);
> > +
> > +int asi_init_class(enum asi_class_id class_id, struct asi_taint_policy
> *taint_policy);
> > +void asi_uninit_class(enum asi_class_id class_id);
>
> "uninit", meh. "exit" perhaps? or "destroy"
> And you have "asi_destroy" already so I guess you can do:
>
> asi_class_init()
> asi_class_destroy()
>
> to have the namespace correct.
>
Yeah, not sure what I was thinking here!
> > +static __always_inline bool asi_is_tense(void)
> > +{
> > + return !asi_is_relaxed();
> > +}
>
> So can we tone down the silly helpers above? You don't really need
> asi_is_tense() for example. It is still very intuitive if I read
>
> if (!asi_is_relaxed())
>
> Yep that sounds good.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.ozlabs.org/pipermail/linuxppc-dev/attachments/20250219/9bd354b0/attachment.htm>
More information about the Linuxppc-dev
mailing list