[PATCH v18 22/23] LSM: Add /proc attr entry for full LSM context

Casey Schaufler casey at schaufler-ca.com
Fri Jul 24 01:08:14 UTC 2020


On 7/8/2020 6:30 PM, Jann Horn wrote:
> On Thu, Jul 9, 2020 at 2:42 AM Casey Schaufler <casey at schaufler-ca.com> wrote:
>> Add an entry /proc/.../attr/context which displays the full
>> process security "context" in compound format:
>>         lsm1\0value\0lsm2\0value\0...
>> This entry is not writable.
>>
>> Reviewed-by: Kees Cook <keescook at chromium.org>
>> Signed-off-by: Casey Schaufler <casey at schaufler-ca.com>
>> Cc: linux-api at vger.kernel.org
> [...]
>> diff --git a/security/security.c b/security/security.c
> [...]
>> +/**
>> + * append_ctx - append a lsm/context pair to a compound context
>> + * @ctx: the existing compound context
>> + * @ctxlen: size of the old context, including terminating nul byte
>> + * @lsm: new lsm name, nul terminated
>> + * @new: new context, possibly nul terminated
>> + * @newlen: maximum size of @new
>> + *
>> + * replace @ctx with a new compound context, appending @newlsm and @new
>> + * to @ctx. On exit the new data replaces the old, which is freed.
>> + * @ctxlen is set to the new size, which includes a trailing nul byte.
>> + *
>> + * Returns 0 on success, -ENOMEM if no memory is available.
>> + */
>> +static int append_ctx(char **ctx, int *ctxlen, const char *lsm, char *new,
>> +                     int newlen)
>> +{
>> +       char *final;
>> +       int llen;
> Please use size_t to represent object sizes

OK.

> , instead of implicitly
> truncating them and assuming that that doesn't wrap. Using "int" here
> not only makes it harder to statically reason about this code, it
> actually can also make the generated code worse:
>
>
> $ cat numtrunc.c
> #include <stddef.h>
>
> size_t my_strlen(char *p);
> void *my_alloc(size_t len);
>
> void *blah_trunc(char *p) {
>   int len = my_strlen(p) + 1;
>   return my_alloc(len);
> }
>
> void *blah_notrunc(char *p) {
>   size_t len = my_strlen(p) + 1;
>   return my_alloc(len);
> }
> $ gcc -O2 -c -o numtrunc.o numtrunc.c
> $ objdump -d numtrunc.o
> [...]
> 0000000000000000 <blah_trunc>:
>    0: 48 83 ec 08          sub    $0x8,%rsp
>    4: e8 00 00 00 00        callq  9 <blah_trunc+0x9>
>    9: 48 83 c4 08          add    $0x8,%rsp
>    d: 8d 78 01              lea    0x1(%rax),%edi
>   10: 48 63 ff              movslq %edi,%rdi    <<<<<<<<unnecessary instruction
>   13: e9 00 00 00 00        jmpq   18 <blah_trunc+0x18>
> [...]
> 0000000000000020 <blah_notrunc>:
>   20: 48 83 ec 08          sub    $0x8,%rsp
>   24: e8 00 00 00 00        callq  29 <blah_notrunc+0x9>
>   29: 48 83 c4 08          add    $0x8,%rsp
>   2d: 48 8d 78 01          lea    0x1(%rax),%rdi
>   31: e9 00 00 00 00        jmpq   36 <blah_notrunc+0x16>
> $
>
> This is because GCC documents
> (https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html) that
> for integer conversions where the value does not fit into the signed
> target type, "the value is reduced modulo 2^N to be within range of
> the type"; so the compiler has to assume that you are actually
> intentionally trying to truncate the more significant bits from the
> length, and therefore may have to insert extra code to ensure that
> this truncation happens.
>
>
>> +       llen = strlen(lsm) + 1;
>> +       newlen = strnlen(new, newlen) + 1;
> This strnlen() call seems dodgy. If an LSM can return a string that
> already contains null bytes, shouldn't that be considered a bug, given
> that it can't be displayed properly? Would it be more appropriate to
> have a WARN_ON(memchr(new, '\0', newlen)) check here and bail out if
> that happens?

Whether or not a security module should include a trailing nul has
been a matter of some discussion. Alas, the discussion has not reached
conscensus. The strnlen() is here to allow modules their own convention. 

>
>> +       final = kzalloc(*ctxlen + llen + newlen, GFP_KERNEL);
>> +       if (final == NULL)
>> +               return -ENOMEM;
>> +       if (*ctxlen)
>> +               memcpy(final, *ctx, *ctxlen);
>> +       memcpy(final + *ctxlen, lsm, llen);
>> +       memcpy(final + *ctxlen + llen, new, newlen);
>> +       kfree(*ctx);
>> +       *ctx = final;
>> +       *ctxlen = *ctxlen + llen + newlen;
>> +       return 0;
>> +}
>> +
>>  /*
>>   * The default value of the LSM hook is defined in linux/lsm_hook_defs.h and
>>   * can be accessed with:
>> @@ -2109,6 +2145,10 @@ int security_getprocattr(struct task_struct *p, const char *lsm, char *name,
>>                                 char **value)
>>  {
>>         struct security_hook_list *hp;
>> +       char *final = NULL;
>> +       char *cp;
>> +       int rc = 0;
>> +       int finallen = 0;
>>         int display = lsm_task_display(current);
>>         int slot = 0;
>>
> [...]
>>                 return -ENOMEM;
>>         }
>>
>> +       if (!strcmp(name, "context")) {
>> +               hlist_for_each_entry(hp, &security_hook_heads.getprocattr,
>> +                                    list) {
>> +                       rc = hp->hook.getprocattr(p, "context", &cp);
>> +                       if (rc == -EINVAL)
>> +                               continue;
>> +                       if (rc < 0) {
>> +                               kfree(final);
>> +                               return rc;
>> +                       }
> This means that if SELinux refuses to give the caller PROCESS__GETATTR
> access to the target process, the entire "context" file will refuse to
> show anything, even if e.g. an AppArmor label would be visible through
> the LSM-specific attribute directory, right?

That is correct.

>  That seems awkward.

Sure is.

>  Can
> you maybe omit context elements for which the access check failed
> instead, or embed an extra flag byte to signal for each element
> whether the lookup failed, or something along those lines?

The SELinux team seems convinced that if their check fails, the
whole thing must fail. 

> If this is an intentional design limitation, it should probably be
> documented in the commit message or so.

Point.

>
>> +                       rc = append_ctx(&final, &finallen, hp->lsmid->lsm,
>> +                                       cp, rc);
>> +                       if (rc < 0) {
>> +                               kfree(final);
>> +                               return rc;
>> +                       }
> Isn't there a memory leak here?

Why yes, there is.

>  `cp` points to memory that was
> allocated by hp->hook.getprocattr(), and you're not freeing it after
> append_ctx(). (And append_ctx() also doesn't free it.)
>
>> +               }
>> +               if (final == NULL)
>> +                       return -EINVAL;
>> +               *value = final;
>> +               return finallen;
>> +       }
>> +
>>         hlist_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
>>                 if (lsm != NULL && strcmp(lsm, hp->lsmid->lsm))
>>                         continue;



More information about the Linux-security-module-archive mailing list