Re: [RFC PATCH v19 2/5] security: Add new SHOULD_EXEC_CHECK and SHOULD_EXEC_RESTRICT securebits

Jeff Xu
2024-07-09T21:58:24Z

On Tue, Jul 9, 2024 at 1:42 PM Mickaël Salaün <mic@digikod.net> wrote:
>
> On Mon, Jul 08, 2024 at 03:07:24PM -0700, Jeff Xu wrote:
> > On Mon, Jul 8, 2024 at 2:25 PM Steve Dower <steve.dower@python.org> wrote:
> > >
> > > On 08/07/2024 22:15, Jeff Xu wrote:
> > > > IIUC:
> > > > CHECK=0, RESTRICT=0: do nothing, current behavior
> > > > CHECK=1, RESTRICT=0: permissive mode - ignore AT_CHECK results.
> > > > CHECK=0, RESTRICT=1: call AT_CHECK, deny if AT_CHECK failed, no exception.
> > > > CHECK=1, RESTRICT=1: call AT_CHECK, deny if AT_CHECK failed, except
> > > > those in the "checked-and-allowed" list.
> > >
> > > I had much the same question for Mickaël while working on this.
> > >
> > > Essentially, "CHECK=0, RESTRICT=1" means to restrict without checking.
> > > In the context of a script or macro interpreter, this just means it will
> > > never interpret any scripts. Non-binary code execution is fully disabled
> > > in any part of the process that respects these bits.
> > >
> > I see, so Mickaël does mean this will block all scripts.
>
> That is the initial idea.
>
> > I guess, in the context of dynamic linker, this means: no more .so
> > loading, even "dlopen" is called by an app ? But this will make the
> > execve() fail.
>
> Hmm, I'm not sure this "CHECK=0, RESTRICT=1" configuration would make
> sense for a dynamic linker except maybe if we want to only allow static
> binaries?
>
> The CHECK and RESTRICT securebits are designed to make it possible a
> "permissive mode" and an enforcement mode with the related locked
> securebits. This is why this "CHECK=0, RESTRICT=1" combination looks a
> bit weird. We can replace these securebits with others but I didn't
> find a better (and simple) option. I don't think this is an issue
> because with any security policy we can create unusable combinations.
> The three other combinations makes a lot of sense though.
>
If we need only handle 3 combinations, I would think something like
below is easier to understand, and don't have wield state like
CHECK=0, RESTRICT=1

XX_RESTRICT: when true: Perform the AT_CHECK, and deny the executable
after AT_CHECK fails.
XX_RESTRICT_PERMISSIVE: take effect when XX_RESTRICT is true. True
means Ignoring the AT_CHECK result.

Or

XX_CHECK: when true: Perform the AT_CHECK.
XX_CHECK_ENFORCE takes effect only when XX_CHECK is true. True means
restrict the executable when AT_CHECK failed; false means ignore the
AT_CHECK failure.

Of course, we can replace XX_CHECK_ENFORCE with XX_RESTRICT.
Personally I think having _CHECK_ in the name implies the XX_CHECK
needs to be true as a prerequisite for this flag , but that is my
opinion only. As long as the semantics are clear as part of the
comments of definition in code, it is fine.

Thanks
-Jeff

> >
> > > "CHECK=1, RESTRICT=1" means to restrict unless AT_CHECK passes. This
> > > case is the allow list (or whatever mechanism is being used to determine
> > > the result of an AT_CHECK check). The actual mechanism isn't the
> > > business of the script interpreter at all, it just has to refuse to
> > > execute anything that doesn't pass the check. So a generic interpreter
> > > can implement a generic mechanism and leave the specifics to whoever
> > > configures the machine.
> > >
> > In the context of dynamic linker. this means:
> > if .so passed the AT_CHECK, ldopen() can still load it.
> > If .so fails the AT_CHECK, ldopen() will fail too.
>
> Correct
>
> >
> > Thanks
> > -Jeff
> >
> > > The other two case are more obvious. "CHECK=0, RESTRICT=0" is the
> > > zero-overhead case, while "CHECK=1, RESTRICT=0" might log, warn, or
> > > otherwise audit the result of the check, but it won't restrict execution.
> > >
> > > Cheers,
> > > Steve