Re: [PATCH v28 22/25] Audit: Add record for multiple process LSM attributes

From: Casey Schaufler
Date: Thu Aug 12 2021 - 18:38:17 EST


On 8/12/2021 1:59 PM, Paul Moore wrote:
> On Wed, Jul 21, 2021 at 9:12 PM Casey Schaufler <casey@xxxxxxxxxxxxxxxx> wrote:
>> Create a new audit record type to contain the subject information
>> when there are multiple security modules that require such data.
>> This record is linked with the same timestamp and serial number
>> using the audit_alloc_local() mechanism.
>> The record is produced only in cases where there is more than one
>> security module with a process "context".
>> In cases where this record is produced the subj= fields of
>> other records in the audit event will be set to "subj=?".
>>
>> An example of the MAC_TASK_CONTEXTS (1420) record is:
>>
>> type=UNKNOWN[1420]
>> msg=audit(1600880931.832:113)
>> subj_apparmor="=unconfined"
>> subj_smack="_"
>>
>> There will be a subj_$LSM= entry for each security module
>> LSM that supports the secid_to_secctx and secctx_to_secid
>> hooks. The BPF security module implements secid/secctx
>> translation hooks, so it has to be considered to provide a
>> secctx even though it may not actually do so.
>>
>> Signed-off-by: Casey Schaufler <casey@xxxxxxxxxxxxxxxx>
>> To: paul@xxxxxxxxxxxxxx
>> To: linux-audit@xxxxxxxxxx
>> To: rgb@xxxxxxxxxx
>> Cc: netdev@xxxxxxxxxxxxxxx
>> ---
>> drivers/android/binder.c | 2 +-
>> include/linux/audit.h | 16 +++++
>> include/linux/security.h | 16 ++++-
>> include/net/netlabel.h | 2 +-
>> include/net/scm.h | 2 +-
>> include/net/xfrm.h | 13 +++-
>> include/uapi/linux/audit.h | 1 +
>> kernel/audit.c | 90 +++++++++++++++++++------
>> kernel/auditfilter.c | 5 +-
>> kernel/auditsc.c | 27 ++++++--
>> net/ipv4/ip_sockglue.c | 2 +-
>> net/netfilter/nf_conntrack_netlink.c | 4 +-
>> net/netfilter/nf_conntrack_standalone.c | 2 +-
>> net/netfilter/nfnetlink_queue.c | 2 +-
>> net/netlabel/netlabel_unlabeled.c | 21 +++---
>> net/netlabel/netlabel_user.c | 14 ++--
>> net/netlabel/netlabel_user.h | 6 +-
>> net/xfrm/xfrm_policy.c | 8 ++-
>> net/xfrm/xfrm_state.c | 18 +++--
>> security/integrity/ima/ima_api.c | 6 +-
>> security/integrity/integrity_audit.c | 5 +-
>> security/security.c | 46 ++++++++-----
>> security/smack/smackfs.c | 3 +-
>> 23 files changed, 221 insertions(+), 90 deletions(-)
>>
>> diff --git a/drivers/android/binder.c b/drivers/android/binder.c
>> index 2c3a2348a144..3520caa0260c 100644
>> --- a/drivers/android/binder.c
>> +++ b/drivers/android/binder.c
>> @@ -2722,7 +2722,7 @@ static void binder_transaction(struct binder_proc *proc,
>> * case well anyway.
>> */
>> security_task_getsecid_obj(proc->tsk, &blob);
>> - ret = security_secid_to_secctx(&blob, &lsmctx);
>> + ret = security_secid_to_secctx(&blob, &lsmctx, LSMBLOB_DISPLAY);
>> if (ret) {
>> return_error = BR_FAILED_REPLY;
>> return_error_param = ret;
>> diff --git a/include/linux/audit.h b/include/linux/audit.h
>> index 97cd7471e572..85eb87f6f92d 100644
>> --- a/include/linux/audit.h
>> +++ b/include/linux/audit.h
>> @@ -291,6 +291,7 @@ extern int audit_alloc(struct task_struct *task);
>> extern void __audit_free(struct task_struct *task);
>> extern struct audit_context *audit_alloc_local(gfp_t gfpflags);
>> extern void audit_free_context(struct audit_context *context);
>> +extern void audit_free_local(struct audit_context *context);
>> extern void __audit_syscall_entry(int major, unsigned long a0, unsigned long a1,
>> unsigned long a2, unsigned long a3);
>> extern void __audit_syscall_exit(int ret_success, long ret_value);
>> @@ -386,6 +387,19 @@ static inline void audit_ptrace(struct task_struct *t)
>> __audit_ptrace(t);
>> }
>>
>> +static inline struct audit_context *audit_alloc_for_lsm(gfp_t gfp)
>> +{
>> + struct audit_context *context = audit_context();
>> +
>> + if (context)
>> + return context;
>> +
>> + if (lsm_multiple_contexts())
>> + return audit_alloc_local(gfp);
>> +
>> + return NULL;
>> +}
> We don't want to do this, at least not as it is written above. We
> shouldn't have a function which abstracts away the creation of a local
> audit_context. Usage of a local audit_context should be explicit in
> the caller, and if the caller's execution context is ambiguous enough
> that it can require both a task_struct audit_context and a local
> audit_context then we need to handle that on a case-by-case basis.
> Hiding it like this is guaranteed to cause problems later.

OK. Please understand that *every case* where I've used audit_alloc_for_lsm()
is a case where I have *verified* that context may be NULL. I will make
the change.

> I probably did a poor job of explaining what a local context is during
> the last patchset; I'll try to do a better job here but also let me
> say this as clear as I can ... if the "current" task struct is valid
> for a given code path, *never* use a local audit context.

I probably did a poor job of demonstrating that I never use a local
context where there's a valid current context.

> The local
> audit context is a hack that is made necessary by the fact that we
> have to audit things which happen outside the scope of an executing
> task, e.g. the netfilter audit hooks, it should *never* be used when
> there is a valid task_struct.

In the existing audit code a "current context" is only needed for
syscall events, so that's the only case where it's allocated. Would
you suggest that I track down the non-syscall events that include
subj= fields and add allocate a "current context" for them? I looked
into doing that, and it wouldn't be simple.

> It's the audit_context which helps bind multiple audit records into a
> single event, creating a new, "local" audit_context destroys that
> binding

... if there's a current context. There often isn't. That's why I'm
using a local context. There is not a "current" context available.

> as audit records created using that local audit_context have a
> different timestamp from those records created using the current
> task_struct's audit_context.

(Weeps) I only introduce a local context where there is no current
context available, so this is never a problem.

> Hopefully that makes sense?

Yes, it makes sense. Methinks you may believe that the current context
is available more regularly than it actually is.

I instrumented the audit event functions with:

WARN_ONCE(audit_context, "%s has context\n", __func__);
WARN_ONCE(!audit_context, "%s lacks context\n", __func__);

I only used local contexts where the 2nd WARN_ONCE was hit.


>> /* Private API (for audit.c only) */
>> extern void __audit_ipc_obj(struct kern_ipc_perm *ipcp);
>> extern void __audit_ipc_set_perm(unsigned long qbytes, uid_t uid, gid_t gid, umode_t mode);
>> @@ -560,6 +574,8 @@ extern int audit_signals;
>> }
>> static inline void audit_free_context(struct audit_context *context)
>> { }
>> +static inline void audit_free_local(struct audit_context *context)
>> +{ }
>> static inline int audit_alloc(struct task_struct *task)
>> {
>> return 0;
>> diff --git a/include/linux/security.h b/include/linux/security.h
>> index 3e9743118fb9..b3cf68cf2bd6 100644
>> --- a/include/linux/security.h
>> +++ b/include/linux/security.h
>> @@ -182,6 +182,8 @@ struct lsmblob {
>> #define LSMBLOB_INVALID -1 /* Not a valid LSM slot number */
>> #define LSMBLOB_NEEDED -2 /* Slot requested on initialization */
>> #define LSMBLOB_NOT_NEEDED -3 /* Slot not requested */
>> +#define LSMBLOB_DISPLAY -4 /* Use the "display" slot */
>> +#define LSMBLOB_FIRST -5 /* Use the default "display" slot */
>>
>> /**
>> * lsmblob_init - initialize an lsmblob structure
>> @@ -248,6 +250,15 @@ static inline u32 lsmblob_value(const struct lsmblob *blob)
>> return 0;
>> }
>>
>> +static inline bool lsm_multiple_contexts(void)
>> +{
>> +#ifdef CONFIG_SECURITY
>> + return lsm_slot_to_name(1) != NULL;
>> +#else
>> + return false;
>> +#endif
>> +}
>> +
>> /* These functions are in security/commoncap.c */
>> extern int cap_capable(const struct cred *cred, struct user_namespace *ns,
>> int cap, unsigned int opts);
>> @@ -578,7 +589,8 @@ int security_setprocattr(const char *lsm, const char *name, void *value,
>> size_t size);
>> int security_netlink_send(struct sock *sk, struct sk_buff *skb);
>> int security_ismaclabel(const char *name);
>> -int security_secid_to_secctx(struct lsmblob *blob, struct lsmcontext *cp);
>> +int security_secid_to_secctx(struct lsmblob *blob, struct lsmcontext *cp,
>> + int display);
>> int security_secctx_to_secid(const char *secdata, u32 seclen,
>> struct lsmblob *blob);
>> void security_release_secctx(struct lsmcontext *cp);
>> @@ -1433,7 +1445,7 @@ static inline int security_ismaclabel(const char *name)
>> }
>>
>> static inline int security_secid_to_secctx(struct lsmblob *blob,
>> - struct lsmcontext *cp)
>> + struct lsmcontext *cp, int display)
>> {
>> return -EOPNOTSUPP;
>> }
>> diff --git a/include/net/netlabel.h b/include/net/netlabel.h
>> index 73fc25b4042b..216cb1ffc8f0 100644
>> --- a/include/net/netlabel.h
>> +++ b/include/net/netlabel.h
>> @@ -97,7 +97,7 @@ struct calipso_doi;
>>
>> /* NetLabel audit information */
>> struct netlbl_audit {
>> - u32 secid;
>> + struct lsmblob lsmdata;
>> kuid_t loginuid;
>> unsigned int sessionid;
>> };
> This chunk seems lost here, does it belong in another patch?

Probably. I am getting a touch of patch-rot showing up.


>> diff --git a/include/net/scm.h b/include/net/scm.h
>> index b77a52f93389..f4d567d4885e 100644
>> --- a/include/net/scm.h
>> +++ b/include/net/scm.h
>> @@ -101,7 +101,7 @@ static inline void scm_passec(struct socket *sock, struct msghdr *msg, struct sc
>> * and the infrastructure will know which it is.
>> */
>> lsmblob_init(&lb, scm->secid);
>> - err = security_secid_to_secctx(&lb, &context);
>> + err = security_secid_to_secctx(&lb, &context, LSMBLOB_DISPLAY);
> Misplaced code change?

Same here.


>> if (!err) {
>> put_cmsg(msg, SOL_SOCKET, SCM_SECURITY, context.len,
>> diff --git a/include/net/xfrm.h b/include/net/xfrm.h
>> index cbff7c2a9724..a10fa01f7bf4 100644
>> --- a/include/net/xfrm.h
>> +++ b/include/net/xfrm.h
>> @@ -660,13 +660,22 @@ struct xfrm_spi_skb_cb {
>> #define XFRM_SPI_SKB_CB(__skb) ((struct xfrm_spi_skb_cb *)&((__skb)->cb[0]))
>>
>> #ifdef CONFIG_AUDITSYSCALL
>> -static inline struct audit_buffer *xfrm_audit_start(const char *op)
>> +static inline struct audit_buffer *xfrm_audit_start(const char *op,
>> + struct audit_context **lac)
>> {
>> + struct audit_context *context;
>> struct audit_buffer *audit_buf = NULL;
>>
>> if (audit_enabled == AUDIT_OFF)
>> return NULL;
>> - audit_buf = audit_log_start(audit_context(), GFP_ATOMIC,
>> + context = audit_context();
>> + if (lac != NULL) {
>> + if (lsm_multiple_contexts() && context == NULL)
>> + context = audit_alloc_local(GFP_ATOMIC);
>> + *lac = context;
>> + }
>> +
>> + audit_buf = audit_log_start(context, GFP_ATOMIC,
>> AUDIT_MAC_IPSEC_EVENT);
>> if (audit_buf == NULL)
>> return NULL;
> Related to the other comments around local audit_contexts, we don't
> want to do this; use the existing audit_context, @lac in this case, so
> that this audit record is bound to the other associated records into a
> single audit event (all have the same timestamp).

Hmm. This is clearly a problem. Looks like a change came in
that I didn't see.

>> diff --git a/kernel/audit.c b/kernel/audit.c
>> index 841123390d41..cba63789a164 100644
>> --- a/kernel/audit.c
>> +++ b/kernel/audit.c
>> @@ -386,10 +386,12 @@ void audit_log_lost(const char *message)
>> static int audit_log_config_change(char *function_name, u32 new, u32 old,
>> int allow_changes)
>> {
>> + struct audit_context *context;
>> struct audit_buffer *ab;
>> int rc = 0;
>>
>> - ab = audit_log_start(audit_context(), GFP_KERNEL, AUDIT_CONFIG_CHANGE);
>> + context = audit_alloc_for_lsm(GFP_KERNEL);
>> + ab = audit_log_start(context, GFP_KERNEL, AUDIT_CONFIG_CHANGE);
> We shouldn't need to do this for the reasons discussed up near the top
> of this email (and elsewhere as well).

Here and elsewhere, I only put audit_alloc_for_lsm() in because there
are cases where audit_context() returns NULL. Really.

>
> I'm going to refrain from commenting on the other uses of
> audit_alloc_for_lsm() in this patch unless there is something unique
> to the code path, so if you don't see me comment about a use of
> audit_alloc_for_lsm() you can assume it should be removed and the
> existing audit_context used instead.
>
>> @@ -399,6 +401,7 @@ static int audit_log_config_change(char *function_name, u32 new, u32 old,
>> allow_changes = 0; /* Something weird, deny request */
>> audit_log_format(ab, " res=%d", allow_changes);
>> audit_log_end(ab);
>> + audit_free_local(context);
> See my comment directly above regarding usage of
> audit_alloc_for_lsm(), it obviously applies here too.
>
>> @@ -2128,6 +2136,36 @@ void audit_log_key(struct audit_buffer *ab, char *key)
>> audit_log_format(ab, "(null)");
>> }
>>
>> +static void audit_log_lsm(struct audit_context *context, struct lsmblob *blob)
> See my note below about moving this into audit_log_task_context(),

Either works for me. This seemed consistent with the rest of the audit
code, but I'm happy to change it if you like that better.

> but
> if we really need to keep this as a separate function, let's consider
> changing the name to something which indicates that it logs the LSM
> data as *subject* fields. How about audit_log_lsm_subj()?
>
>> +{
>> + struct audit_buffer *ab;
>> + struct lsmcontext lsmdata;
>> + bool sep = false;
>> + int error;
>> + int i;
>> +
>> + ab = audit_log_start(context, GFP_ATOMIC, AUDIT_MAC_TASK_CONTEXTS);
>> + if (!ab)
>> + return; /* audit_panic or being filtered */
>> +
>> + for (i = 0; i < LSMBLOB_ENTRIES; i++) {
>> + if (blob->secid[i] == 0)
>> + continue;
>> + error = security_secid_to_secctx(blob, &lsmdata, i);
>> + if (error && error != -EINVAL) {
>> + audit_panic("error in audit_log_lsm");
>> + return;
>> + }
>> +
>> + audit_log_format(ab, "%ssubj_%s=\"%s\"", sep ? " " : "",
>> + lsm_slot_to_name(i), lsmdata.context);
>> + sep = true;
> Since @i starts at zero, you can get rid of @sep by replacing it with @i:
>
> audit_log_format(ab, ..., (i ? " " : ""), ...);

Clever.

>
>> + security_release_secctx(&lsmdata);
>> + }
>> + audit_log_end(ab);
>> +}
>> @@ -2138,7 +2176,18 @@ int audit_log_task_context(struct audit_buffer *ab)
>> if (!lsmblob_is_set(&blob))
>> return 0;
>>
>> - error = security_secid_to_secctx(&blob, &context);
>> + /*
>> + * If there is more than one security module that has a
>> + * subject "context" it's necessary to put the subject data
>> + * into a separate record to maintain compatibility.
>> + */
>> + if (lsm_multiple_contexts()) {
>> + audit_log_format(ab, " subj=?");
>> + audit_log_lsm(ab->ctx, &blob);
>> + return 0;
>> + }
>> +
>> + error = security_secid_to_secctx(&blob, &context, LSMBLOB_FIRST);
> Instead of the lsm_multiple_contexts() case bailing on the rest of the
> function with a return inside the if-block, let's made the code a bit
> more robust by organizing it like this:

Sure, why not?

>
> int audit_log_task_context(ab)
> {
> /* common stuff at the start */
>
> if (lsm_multiple_contexts()) {
> /* multi-LSM stuff */
> } else {
> /* single LSM stuff */
> }
>
> /* common stuff at the end */
> }
>
> ... it also may make sense to just move the body of audit_log_lsm()
> into audit_log_task_context() and do away with audit_log_lsm()
> entirely; is it ever going to be called from anywhere else?
>
>> diff --git a/kernel/auditsc.c b/kernel/auditsc.c
>> index 0e58a3ab56f5..01fdcbf468c0 100644
>> --- a/kernel/auditsc.c
>> +++ b/kernel/auditsc.c
>> @@ -993,12 +993,11 @@ struct audit_context *audit_alloc_local(gfp_t gfpflags)
>> context = audit_alloc_context(AUDIT_STATE_BUILD, gfpflags);
>> if (!context) {
>> audit_log_lost("out of memory in audit_alloc_local");
>> - goto out;
>> + return NULL;
>> }
>> context->serial = audit_serial();
>> ktime_get_coarse_real_ts64(&context->ctime);
>> context->local = true;
>> -out:
>> return context;
>> }
> This chunk should be moved to 21/25 when audit_alloc_local() is first defined.

True. I was trying to minimize the change to the original audit_alloc_local()
patch on the assumption that it was coming in for other reasons, but that hasn't
happened.


>> @@ -1019,6 +1018,13 @@ void audit_free_context(struct audit_context *context)
>> }
>> EXPORT_SYMBOL(audit_free_context);
>>
>> +void audit_free_local(struct audit_context *context)
>> +{
>> + if (context && context->local)
>> + audit_free_context(context);
>> +}
>> +EXPORT_SYMBOL(audit_free_local);
> If this is strictly necessary, and I don't think it is, it should also
> be moved to patch 21/25 with the original definition of a local
> audit_context. However, there really should be no reason why we have
> to distinguish between a proper and local audtit_context when it comes
> to free'ing the memory, just call audit_free_context() in both cases.
>
>> @@ -1036,7 +1042,7 @@ static int audit_log_pid_context(struct audit_context *context, pid_t pid,
>> from_kuid(&init_user_ns, auid),
>> from_kuid(&init_user_ns, uid), sessionid);
>> if (lsmblob_is_set(blob)) {
>> - if (security_secid_to_secctx(blob, &lsmctx)) {
>> + if (security_secid_to_secctx(blob, &lsmctx, LSMBLOB_FIRST)) {
> Misplaced code change?
>
> Actually, there are a lot of these below, I'm not going to comment on
> all of them as I think you get the idea ... and I very well may be
> wrong so I'll save you all of my wrongness in that case :)
>
>> diff --git a/security/security.c b/security/security.c
>> index cb359e185d1a..5d7fd982f84a 100644
>> --- a/security/security.c
>> +++ b/security/security.c
>> @@ -2309,7 +2309,7 @@ int security_setprocattr(const char *lsm, const char *name, void *value,
>> hlist_for_each_entry(hp, &security_hook_heads.setprocattr,
>> list) {
>> rc = hp->hook.setprocattr(name, value, size);
>> - if (rc < 0)
>> + if (rc < 0 && rc != -EINVAL)
>> return rc;
>> }
> This really looks misplaced ... ?

Yeah, you're not the first person to notice these bits of patch-rot.
I have some clean-up to do.

Thank you for the review.