Re: [PATCH v3 seccomp 2/5] seccomp/cache: Add "emulator" to check if filter is constant allow

From: YiFei Zhu
Date: Thu Oct 01 2020 - 07:54:01 EST


On Wed, Sep 30, 2020 at 5:40 PM Kees Cook <keescook@xxxxxxxxxxxx> wrote:
> I don't want this config: there is only 1 caching mechanism happening
> in this series and I do not want to have it buildable as "off": it
> should be available for all supported architectures. When further caching
> methods happen, the config can be introduced then (though I'll likely
> argue it should then be a boot param to allow distro kernels to make it
> selectable).

Alright, we can think about configuration (or boot param) when more
methods happen then.

> The guiding principle with seccomp's designs is to always make things
> _more_ restrictive, never less. While we can never escape the
> consequences of having seccomp_is_const_allow() report the wrong
> answer, we can at least follow the basic principles, hopefully
> minimizing the impact.
>
> When the bitmap starts with "always allowed" and we only flip it towards
> "run full filters", we're only ever making things more restrictive. If
> we instead go from "run full filters" towards "always allowed", we run
> the risk of making things less restrictive. For example: a process that
> maliciously adds a filter that the emulator mistakenly evaluates to
> "always allow" doesn't suddenly cause all the prior filters to stop running.
> (i.e. this isolates the flaw outcome, and doesn't depend on the early
> "do not emulate if we already know we have to run filters" case before
> the emulation call: there is no code path that allows the cache to
> weaken: it can only maintain it being wrong).
>
> Without any seccomp filter installed, all syscalls are "always allowed"
> (from the perspective of the seccomp boundary), so the default of the
> cache needs to be "always allowed".

I cannot follow this. If a 'process that maliciously adds a filter
that the emulator mistakenly evaluates to "always allow" doesn't
suddenly cause all the prior filters to stop running', hence, you
want, by default, the cache to be as transparent as possible. You
would lift the restriction if and only if you are absolutely sure it
does not cause an impact.

In this patch, if there are prior filters, it goes through this logic:

if (bitmap_prev && !test_bit(nr, bitmap_prev))
continue;

Hence, if the malicious filter were to happen, and prior filters were
supposed to run, then seccomp_is_const_allow is simply not invoked --
what it returns cannot be used maliciously by an adversary.

> if (bitmap_prev) {
> /* The new filter must be as restrictive as the last. */
> bitmap_copy(bitmap, bitmap_prev, bitmap_size);
> } else {
> /* Before any filters, all syscalls are always allowed. */
> bitmap_fill(bitmap, bitmap_size);
> }
>
> for (nr = 0; nr < bitmap_size; nr++) {
> /* No bitmap change: not a cacheable action. */
> if (!test_bit(nr, bitmap_prev) ||
> continue;
>
> /* No bitmap change: continue to always allow. */
> if (seccomp_is_const_allow(fprog, &sd))
> continue;
>
> /* Not a cacheable action: always run filters. */
> clear_bit(nr, bitmap);

I'm not strongly against this logic. I just feel unconvinced that this
is any different with a slightly increased complexity.

YiFei Zhu