[apparmor] [RFC, PATCH 1/3] apparmor: add a cache entry expiration time aging out capability audit cache

Ryan Lee ryan.lee at canonical.com
Wed Sep 25 16:32:37 UTC 2024


Noting for additional clarity that I have withdrawn this RFC patchset
in favor of a later PATCH v2 with the same subject line.

On Fri, Sep 13, 2024 at 4:21 PM Ryan Lee <ryan.lee at canonical.com> wrote:
>
> When auditing capabilities, AppArmor uses a per-CPU, per-profile cache
> such that the same capability for the same profile doesn't get repeatedly
> audited, with the original goal of reducing audit logspam. However, this
> cache does not have an expiration time, resulting in confusion when a
> profile is shared across binaries (for example) and an expected DENIED
> audit entry doesn't appear, despite the cache entry having been populated
> much longer ago. This confusion was exacerbated by the per-CPU nature of
> the cache resulting in the expected entries sporadically appearing when
> the later denial+audit occurred on a different CPU.
>
> To resolve this, record the last time a capability was audited for a
> profile and add a timestamp expiration check before doing the audit. This
> first patch hardcodes a small duration for the timeout period.
>
> Signed-off-by: Ryan Lee <ryan.lee at canonical.com>
> ---
>  security/apparmor/capability.c | 10 +++++++++-
>  1 file changed, 9 insertions(+), 1 deletion(-)
>
> diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c
> index 7c0f66f1b297..64005b3d0fcc 100644
> --- a/security/apparmor/capability.c
> +++ b/security/apparmor/capability.c
> @@ -12,6 +12,7 @@
>  #include <linux/errno.h>
>  #include <linux/gfp.h>
>  #include <linux/security.h>
> +#include <linux/timekeeping.h>
>
>  #include "include/apparmor.h"
>  #include "include/capability.h"
> @@ -33,6 +34,8 @@ struct aa_sfs_entry aa_sfs_entry_caps[] = {
>  struct audit_cache {
>         struct aa_profile *profile;
>         kernel_cap_t caps;
> +       /* Capabilities go from 0 to CAP_LAST_CAP */
> +       u64 ktime_ns_last_audited[CAP_LAST_CAP+1];
>  };
>
>  static DEFINE_PER_CPU(struct audit_cache, audit_cache);
> @@ -65,6 +68,8 @@ static void audit_cb(struct audit_buffer *ab, void *va)
>  static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile,
>                       int cap, int error)
>  {
> +       const u64 AUDIT_CACHE_TIMEOUT_NS = 100*1000; /* 100 us */
> +       u64 audit_cache_expiration;
>         struct aa_ruleset *rules = list_first_entry(&profile->rules,
>                                                     typeof(*rules), list);
>         struct audit_cache *ent;
> @@ -90,7 +95,9 @@ static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile
>
>         /* Do simple duplicate message elimination */
>         ent = &get_cpu_var(audit_cache);
> -       if (profile == ent->profile && cap_raised(ent->caps, cap)) {
> +       audit_cache_expiration = ent->ktime_ns_last_audited[cap] + AUDIT_CACHE_TIMEOUT_NS;
> +       if (profile == ent->profile && cap_raised(ent->caps, cap)
> +                       && ktime_get_ns() <= audit_cache_expiration) {
>                 put_cpu_var(audit_cache);
>                 if (COMPLAIN_MODE(profile))
>                         return complain_error(error);
> @@ -99,6 +106,7 @@ static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile
>                 aa_put_profile(ent->profile);
>                 ent->profile = aa_get_profile(profile);
>                 cap_raise(ent->caps, cap);
> +               ent->ktime_ns_last_audited[cap] = ktime_get_ns();
>         }
>         put_cpu_var(audit_cache);
>
> --
> Major items I'm seeking input on (reason for RFC designation):
> - Whether storing a timestamp per capability is the best approach or whether we should do something else
> - Whether to hardcode the expiration offset or whether to expose it as a sysctl (see PATCH 3/3 of this series)
>



More information about the AppArmor mailing list