Re: [PATCH v26 22/25] Audit: Add new record for multiple process LSM attributes
From: Paul Moore
Date: Fri May 21 2021 - 16:20:06 EST
On Thu, May 13, 2021 at 4:32 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 linked with the other associated records into a single
event, it doesn't matter if it gets the timestamp/serial from
audit_alloc_local() or an existing audit event, e.g. ongoing syscall.
> 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
It should be just a single "=" in the line above.
> 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 | 24 ++++++++
> include/linux/security.h | 16 ++++-
> include/net/netlabel.h | 3 +-
> include/net/scm.h | 2 +-
> include/net/xfrm.h | 13 +++-
> include/uapi/linux/audit.h | 1 +
> kernel/audit.c | 80 ++++++++++++++++++-------
> kernel/audit.h | 3 +
> kernel/auditfilter.c | 6 +-
> kernel/auditsc.c | 75 ++++++++++++++++++++---
> 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_domainhash.c | 4 +-
> net/netlabel/netlabel_unlabeled.c | 24 ++++----
> net/netlabel/netlabel_user.c | 20 ++++---
> net/netlabel/netlabel_user.h | 6 +-
> net/xfrm/xfrm_policy.c | 10 ++--
> net/xfrm/xfrm_state.c | 20 ++++---
> security/integrity/ima/ima_api.c | 7 ++-
> security/integrity/integrity_audit.c | 6 +-
> security/security.c | 46 +++++++++-----
> security/smack/smackfs.c | 3 +-
> 25 files changed, 274 insertions(+), 107 deletions(-)
...
> diff --git a/include/linux/audit.h b/include/linux/audit.h
> index 97cd7471e572..229cd71fbf09 100644
> --- a/include/linux/audit.h
> +++ b/include/linux/audit.h
> @@ -386,6 +395,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;
> +}
See my other comments, but this seems wrong at face value. The
additional LSM record should happen as part of the existing audit log
functions.
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 0129400ff6e9..ddab456e93d3 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..9bc1f969a25d 100644
> --- a/include/net/netlabel.h
> +++ b/include/net/netlabel.h
> @@ -97,7 +97,8 @@ struct calipso_doi;
>
> /* NetLabel audit information */
> struct netlbl_audit {
> - u32 secid;
> + struct audit_context *localcontext;
> + struct lsmblob lsmdata;
> kuid_t loginuid;
> unsigned int sessionid;
> };
> 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);
>
> if (!err) {
> put_cmsg(msg, SOL_SOCKET, SCM_SECURITY, context.len,
> diff --git a/include/net/xfrm.h b/include/net/xfrm.h
> index c58a6d4eb610..f8ad20d34498 100644
> --- a/include/net/xfrm.h
> +++ b/include/net/xfrm.h
> @@ -669,13 +669,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;
> + }
Okay, we've got a disconnect here regarding "audit contexts" and
"local contexts", skip down below where I attempt to explain things a
little more but basically if there is a place that uses this pattern:
audit_log_start(audit_context(), ...);
... you don't need, or want, a "local context". You might need a
local context if you see the following pattern:
audit_log_start(NULL, ...);
The "local context" idea is a hack and should be avoided whenever
possible; if you have an existing audit context from a syscall, or
something else, you *really* should use it ... or have a *really* good
explanation as to why you can not.
> + audit_buf = audit_log_start(context, GFP_ATOMIC,
> AUDIT_MAC_IPSEC_EVENT);
> if (audit_buf == NULL)
> return NULL;
> diff --git a/kernel/audit.c b/kernel/audit.c
> index 841123390d41..60c027d7759c 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);
Use the existing context, don't create your own, it breaks the record
associations in the audit event stream.
> if (unlikely(!ab))
> return rc;
> audit_log_format(ab, "op=set %s=%u old=%u ", function_name, new, old);
> @@ -398,7 +400,7 @@ static int audit_log_config_change(char *function_name, u32 new, u32 old,
> if (rc)
> allow_changes = 0; /* Something weird, deny request */
> audit_log_format(ab, " res=%d", allow_changes);
> - audit_log_end(ab);
> + audit_log_end_local(ab, context);
More on this below, but we really should just use audit_log_end(),
"local contexts" are not special, the are regular audit contexts ...
although if they are used properly (limited scope) you do need to free
them when you are done.
> return rc;
> }
>
> @@ -1357,7 +1355,8 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
> if (err)
> break;
> }
> - audit_log_user_recv_msg(&ab, msg_type);
> + lcontext = audit_alloc_for_lsm(GFP_KERNEL);
> + audit_log_common_recv_msg(lcontext, &ab, msg_type);
Same.
> if (msg_type != AUDIT_USER_TTY) {
> /* ensure NULL termination */
> str[data_len - 1] = '\0';
> @@ -1370,7 +1369,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
> data_len--;
> audit_log_n_untrustedstring(ab, str, data_len);
> }
> - audit_log_end(ab);
> + audit_log_end_local(ab, lcontext);
Same.
> }
> break;
> case AUDIT_ADD_RULE:
> @@ -1378,13 +1377,14 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
> if (data_len < sizeof(struct audit_rule_data))
> return -EINVAL;
> if (audit_enabled == AUDIT_LOCKED) {
> - audit_log_common_recv_msg(audit_context(), &ab,
> + lcontext = audit_alloc_for_lsm(GFP_KERNEL);
> + audit_log_common_recv_msg(lcontext, &ab,
> AUDIT_CONFIG_CHANGE);
> audit_log_format(ab, " op=%s audit_enabled=%d res=0",
> msg_type == AUDIT_ADD_RULE ?
> "add_rule" : "remove_rule",
> audit_enabled);
> - audit_log_end(ab);
> + audit_log_end_local(ab, lcontext);
Same. I'm going to stop calling these out, I think you get the idea.
> @@ -2396,6 +2415,21 @@ void audit_log_end(struct audit_buffer *ab)
> audit_buffer_free(ab);
> }
>
> +/**
> + * audit_log_end_local - end one audit record with local context
> + * @ab: the audit_buffer
> + * @context: the local context
> + *
> + * Emit an LSM context record if appropriate, then end the audit event
> + * in the usual way.
> + */
> +void audit_log_end_local(struct audit_buffer *ab, struct audit_context *context)
> +{
> + audit_log_end(ab);
> + audit_log_lsm_common(context);
> + audit_free_local(context);
> +}
Eeesh, no, not this please.
First, some background on audit contexts and the idea of a "local
context" as we have been using it in the audit container ID work,
which is where this originated. An audit context contains a few
things, but likely the most important for this discussion is the audit
event timestamp and serial number (I may refer to this combo as just a
"timestamp" in the future); this timestamp/serial is shared across all
of the audit records that make up this audit event, linking them
together. A shared timestamp is what allows you to group an open()
SYSCALL record with the PATH record that provides the file's pathname
info.
While there are some exceptions in the current code, most audit events
occur as a result of a syscall, and their audit context in this case
is the syscall's audit context (see the open() example above), but
there are some cases being discussed where we have an audit event that
does not occur as a result of a syscall but there is a need to group
multiple audit records together in a single event. This is where the
"local context" comes into play, it allows us to create an audit
context outside of a syscall and share that context across multiple
audit records, allowing the records to share a timestamp/serial and
grouping them together as a single audit event in the audit stream.
While a function like audit_alloc_local() make sense, there really
shouldn't be an audit_log_end_local() function, the normal
audit_log_end() function should be used.
Does that make sense?
> diff --git a/kernel/audit.h b/kernel/audit.h
> index 27ef690afd30..5ad0c6819aa8 100644
> --- a/kernel/audit.h
> +++ b/kernel/audit.h
> @@ -100,6 +100,7 @@ struct audit_context {
> int dummy; /* must be the first element */
> int in_syscall; /* 1 if task is in a syscall */
> bool local; /* local context needed */
> + bool lsmdone; /* multiple security reported */
"lsmdone" doesn't seem consistent with the comment, how about
"lsm_multi" or something similar?
> enum audit_state state, current_state;
> unsigned int serial; /* serial number for record */
> int major; /* syscall number */
> diff --git a/kernel/auditsc.c b/kernel/auditsc.c
> index d4e061f95da8..55509faf5341 100644
> --- a/kernel/auditsc.c
> +++ b/kernel/auditsc.c
> @@ -1013,6 +1013,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);
We don't need this function, just use audit_free_context(). A "local
context" is the same as a non-local context; what makes a context
"local" is the scope of the audit context (local function scope vs
syscall scope) and nothing else.
> @@ -1504,6 +1512,47 @@ static void audit_log_proctitle(void)
> audit_log_end(ab);
> }
>
> +void audit_log_lsm_common(struct audit_context *context)
> +{
> + struct audit_buffer *ab;
> + struct lsmcontext lsmdata;
> + bool sep = false;
> + int error;
> + int i;
> +
> + if (!lsm_multiple_contexts() || context == NULL ||
> + !lsmblob_is_set(&context->lsm))
> + return;
> +
> + ab = audit_log_start(context, GFP_ATOMIC, AUDIT_MAC_TASK_CONTEXTS);
> + if (!ab)
> + return; /* audit_panic or being filtered */
We should be consistent with our use of audit_panic() when we bail on
error; we use it below, but not here - why?
> + for (i = 0; i < LSMBLOB_ENTRIES; i++) {
> + if (context->lsm.secid[i] == 0)
> + continue;
> + error = security_secid_to_secctx(&context->lsm, &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;
> + security_release_secctx(&lsmdata);
> + }
> + audit_log_end(ab);
> + context->lsmdone = true;
Maybe I missed it, but why do we need this flag?
> +}
> +
> +void audit_log_lsm(struct audit_context *context)
> +{
> + if (!context->lsmdone)
> + audit_log_lsm_common(context);
> +}
I think I was distracted with the local context issue and I've lost
track of the details here, perhaps it's best to fix the local context
issue first (that should be a big change to this patch) and then we
can take another look.
--
paul moore
www.paul-moore.com