On Mon, Apr 06, 2020 at 05:01:49PM -0700, Casey Schaufler wrote:
> Create a new entry "display" in the procfs attr directory for
> controlling which LSM security information is displayed for a
> process. A process can only read or write its own display value.
> 
> The name of an active LSM that supplies hooks for
> human readable data may be written to "display" to set the
> value. The name of the LSM currently in use can be read from
> "display". At this point there can only be one LSM capable
> of display active. A helper function lsm_task_display() is
> provided to get the display slot for a task_struct.
> 
> Setting the "display" requires that all security modules using
> setprocattr hooks allow the action. Each security module is
> responsible for defining its policy.
> 
> AppArmor hook provided by John Johansen <[email protected]>
> SELinux hook provided by Stephen Smalley <[email protected]>
> 
> Acked-by: Stephen Smalley <[email protected]>
> Acked-by: Paul Moore <[email protected]>
> Signed-off-by: Casey Schaufler <[email protected]>

Reviewed-by: Kees Cook <[email protected]>

-Kees

> ---
>  fs/proc/base.c                       |   1 +
>  include/linux/lsm_hooks.h            |  15 +++
>  security/apparmor/include/apparmor.h |   3 +-
>  security/apparmor/lsm.c              |  32 +++++
>  security/security.c                  | 167 ++++++++++++++++++++++++---
>  security/selinux/hooks.c             |  11 ++
>  security/selinux/include/classmap.h  |   2 +-
>  security/smack/smack_lsm.c           |   7 ++
>  8 files changed, 219 insertions(+), 19 deletions(-)
> 
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index c7c64272b0fa..505331ab7a14 100644
> --- a/fs/proc/base.c
> +++ b/fs/proc/base.c
> @@ -2743,6 +2743,7 @@ static const struct pid_entry attr_dir_stuff[] = {
>       ATTR(NULL, "fscreate",          0666),
>       ATTR(NULL, "keycreate",         0666),
>       ATTR(NULL, "sockcreate",        0666),
> +     ATTR(NULL, "display",           0666),
>  #ifdef CONFIG_SECURITY_SMACK
>       DIR("smack",                    0555,
>           proc_smack_attr_dir_inode_ops, proc_smack_attr_dir_ops),
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index 7eb808cde051..2bf82e1cf347 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -2186,4 +2186,19 @@ static inline void security_delete_hooks(struct 
> security_hook_list *hooks,
>  
>  extern int lsm_inode_alloc(struct inode *inode);
>  
> +/**
> + * lsm_task_display - the "display" LSM for this task
> + * @task: The task to report on
> + *
> + * Returns the task's display LSM slot.
> + */
> +static inline int lsm_task_display(struct task_struct *task)
> +{
> +     int *display = task->security;
> +
> +     if (display)
> +             return *display;
> +     return LSMBLOB_INVALID;
> +}
> +
>  #endif /* ! __LINUX_LSM_HOOKS_H */
> diff --git a/security/apparmor/include/apparmor.h 
> b/security/apparmor/include/apparmor.h
> index 1fbabdb565a8..b1622fcb4394 100644
> --- a/security/apparmor/include/apparmor.h
> +++ b/security/apparmor/include/apparmor.h
> @@ -28,8 +28,9 @@
>  #define AA_CLASS_SIGNAL              10
>  #define AA_CLASS_NET         14
>  #define AA_CLASS_LABEL               16
> +#define AA_CLASS_DISPLAY_LSM 17
>  
> -#define AA_CLASS_LAST                AA_CLASS_LABEL
> +#define AA_CLASS_LAST                AA_CLASS_DISPLAY_LSM
>  
>  /* Control parameters settable through module/boot flags */
>  extern enum audit_mode aa_g_audit;
> diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
> index 146d75e5e021..16b992235c11 100644
> --- a/security/apparmor/lsm.c
> +++ b/security/apparmor/lsm.c
> @@ -612,6 +612,25 @@ static int apparmor_getprocattr(struct task_struct 
> *task, char *name,
>       return error;
>  }
>  
> +
> +static int profile_display_lsm(struct aa_profile *profile,
> +                            struct common_audit_data *sa)
> +{
> +     struct aa_perms perms = { };
> +     unsigned int state;
> +
> +     state = PROFILE_MEDIATES(profile, AA_CLASS_DISPLAY_LSM);
> +     if (state) {
> +             aa_compute_perms(profile->policy.dfa, state, &perms);
> +             aa_apply_modes_to_perms(profile, &perms);
> +             aad(sa)->label = &profile->label;
> +
> +             return aa_check_perms(profile, &perms, AA_MAY_WRITE, sa, NULL);
> +     }
> +
> +     return 0;
> +}
> +
>  static int apparmor_setprocattr(const char *name, void *value,
>                               size_t size)
>  {
> @@ -623,6 +642,19 @@ static int apparmor_setprocattr(const char *name, void 
> *value,
>       if (size == 0)
>               return -EINVAL;
>  
> +     /* LSM infrastructure does actual setting of display if allowed */
> +     if (!strcmp(name, "display")) {
> +             struct aa_profile *profile;
> +             struct aa_label *label;
> +
> +             aad(&sa)->info = "set display lsm";
> +             label = begin_current_label_crit_section();
> +             error = fn_for_each_confined(label, profile,
> +                                          profile_display_lsm(profile, &sa));
> +             end_current_label_crit_section(label);
> +             return error;
> +     }
> +
>       /* AppArmor requires that the buffer must be null terminated atm */
>       if (args[size - 1] != '\0') {
>               /* null terminate */
> diff --git a/security/security.c b/security/security.c
> index bf71066ea19b..0208652ad53a 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -75,7 +75,14 @@ static struct kmem_cache *lsm_file_cache;
>  static struct kmem_cache *lsm_inode_cache;
>  
>  char *lsm_names;
> -static struct lsm_blob_sizes blob_sizes __lsm_ro_after_init;
> +
> +/*
> + * The task blob includes the "display" slot used for
> + * chosing which module presents contexts.
> + */
> +static struct lsm_blob_sizes blob_sizes __lsm_ro_after_init = {
> +     .lbs_task = sizeof(int),
> +};
>  
>  /* Boot-time LSM user choice */
>  static __initdata const char *chosen_lsm_order;
> @@ -470,8 +477,10 @@ static int lsm_append(const char *new, char **result)
>  
>  /*
>   * Current index to use while initializing the lsmblob secid list.
> + * Pointers to the LSM id structures for local use.
>   */
>  static int lsm_slot __lsm_ro_after_init;
> +static struct lsm_id *lsm_slotlist[LSMBLOB_ENTRIES];
>  
>  /**
>   * security_add_hooks - Add a modules hooks to the hook lists.
> @@ -491,6 +500,7 @@ void __init security_add_hooks(struct security_hook_list 
> *hooks, int count,
>       if (lsmid->slot == LSMBLOB_NEEDED) {
>               if (lsm_slot >= LSMBLOB_ENTRIES)
>                       panic("%s Too many LSMs registered.\n", __func__);
> +             lsm_slotlist[lsm_slot] = lsmid;
>               lsmid->slot = lsm_slot++;
>               init_debug("%s assigned lsmblob slot %d\n", lsmid->lsm,
>                          lsmid->slot);
> @@ -620,6 +630,8 @@ int lsm_inode_alloc(struct inode *inode)
>   */
>  static int lsm_task_alloc(struct task_struct *task)
>  {
> +     int *display;
> +
>       if (blob_sizes.lbs_task == 0) {
>               task->security = NULL;
>               return 0;
> @@ -628,6 +640,15 @@ static int lsm_task_alloc(struct task_struct *task)
>       task->security = kzalloc(blob_sizes.lbs_task, GFP_KERNEL);
>       if (task->security == NULL)
>               return -ENOMEM;
> +
> +     /*
> +      * The start of the task blob contains the "display" LSM slot number.
> +      * Start with it set to the invalid slot number, indicating that the
> +      * default first registered LSM be displayed.
> +      */
> +     display = task->security;
> +     *display = LSMBLOB_INVALID;
> +
>       return 0;
>  }
>  
> @@ -1583,14 +1604,26 @@ int security_file_open(struct file *file)
>  
>  int security_task_alloc(struct task_struct *task, unsigned long clone_flags)
>  {
> +     int *odisplay = current->security;
> +     int *ndisplay;
>       int rc = lsm_task_alloc(task);
>  
> -     if (rc)
> +     if (unlikely(rc))
>               return rc;
> +
>       rc = call_int_hook(task_alloc, 0, task, clone_flags);
> -     if (unlikely(rc))
> +     if (unlikely(rc)) {
>               security_task_free(task);
> -     return rc;
> +             return rc;
> +     }
> +
> +     if (odisplay) {
> +             ndisplay = task->security;
> +             if (ndisplay)
> +                     *ndisplay = *odisplay;
> +     }
> +
> +     return 0;
>  }
>  
>  void security_task_free(struct task_struct *task)
> @@ -1987,23 +2020,110 @@ int security_getprocattr(struct task_struct *p, 
> const char *lsm, char *name,
>                               char **value)
>  {
>       struct security_hook_list *hp;
> +     int display = lsm_task_display(current);
> +     int slot = 0;
> +
> +     if (!strcmp(name, "display")) {
> +             /*
> +              * lsm_slot will be 0 if there are no displaying modules.
> +              */
> +             if (lsm_slot == 0)
> +                     return -EINVAL;
> +
> +             /*
> +              * Only allow getting the current process' display.
> +              * There are too few reasons to get another process'
> +              * display and too many LSM policy issues.
> +              */
> +             if (current != p)
> +                     return -EINVAL;
> +
> +             display = lsm_task_display(p);
> +             if (display != LSMBLOB_INVALID)
> +                     slot = display;
> +             *value = kstrdup(lsm_slotlist[slot]->lsm, GFP_KERNEL);
> +             if (*value)
> +                     return strlen(*value);
> +             return -ENOMEM;
> +     }
>  
>       hlist_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
>               if (lsm != NULL && strcmp(lsm, hp->lsmid->lsm))
>                       continue;
> +             if (lsm == NULL && display != LSMBLOB_INVALID &&
> +                 display != hp->lsmid->slot)
> +                     continue;
>               return hp->hook.getprocattr(p, name, value);
>       }
>       return -EINVAL;
>  }
>  
> +/**
> + * security_setprocattr - Set process attributes via /proc
> + * @lsm: name of module involved, or NULL
> + * @name: name of the attribute
> + * @value: value to set the attribute to
> + * @size: size of the value
> + *
> + * Set the process attribute for the specified security module
> + * to the specified value. Note that this can only be used to set
> + * the process attributes for the current, or "self" process.
> + * The /proc code has already done this check.
> + *
> + * Returns 0 on success, an appropriate code otherwise.
> + */
>  int security_setprocattr(const char *lsm, const char *name, void *value,
>                        size_t size)
>  {
>       struct security_hook_list *hp;
> +     char *termed;
> +     char *copy;
> +     int *display = current->security;
> +     int rc = -EINVAL;
> +     int slot = 0;
> +
> +     if (!strcmp(name, "display")) {
> +             /*
> +              * Change the "display" value only if all the security
> +              * modules that support setting a procattr allow it.
> +              * It is assumed that all such security modules will be
> +              * cooperative.
> +              */
> +             if (size == 0)
> +                     return -EINVAL;
> +
> +             hlist_for_each_entry(hp, &security_hook_heads.setprocattr,
> +                                  list) {
> +                     rc = hp->hook.setprocattr(name, value, size);
> +                     if (rc < 0)
> +                             return rc;
> +             }
> +
> +             rc = -EINVAL;
> +
> +             copy = kmemdup_nul(value, size, GFP_KERNEL);
> +             if (copy == NULL)
> +                     return -ENOMEM;
> +
> +             termed = strsep(&copy, " \n");
> +
> +             for (slot = 0; slot < lsm_slot; slot++)
> +                     if (!strcmp(termed, lsm_slotlist[slot]->lsm)) {
> +                             *display = lsm_slotlist[slot]->slot;
> +                             rc = size;
> +                             break;
> +                     }
> +
> +             kfree(termed);
> +             return rc;
> +     }
>  
>       hlist_for_each_entry(hp, &security_hook_heads.setprocattr, list) {
>               if (lsm != NULL && strcmp(lsm, hp->lsmid->lsm))
>                       continue;
> +             if (lsm == NULL && *display != LSMBLOB_INVALID &&
> +                 *display != hp->lsmid->slot)
> +                     continue;
>               return hp->hook.setprocattr(name, value, size);
>       }
>       return -EINVAL;
> @@ -2023,15 +2143,15 @@ EXPORT_SYMBOL(security_ismaclabel);
>  int security_secid_to_secctx(struct lsmblob *blob, char **secdata, u32 
> *seclen)
>  {
>       struct security_hook_list *hp;
> -     int rc;
> +     int display = lsm_task_display(current);
>  
>       hlist_for_each_entry(hp, &security_hook_heads.secid_to_secctx, list) {
>               if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
>                       continue;
> -             rc = hp->hook.secid_to_secctx(blob->secid[hp->lsmid->slot],
> -                                           secdata, seclen);
> -             if (rc != 0)
> -                     return rc;
> +             if (display == LSMBLOB_INVALID || display == hp->lsmid->slot)
> +                     return hp->hook.secid_to_secctx(
> +                                     blob->secid[hp->lsmid->slot],
> +                                     secdata, seclen);
>       }
>       return 0;
>  }
> @@ -2041,16 +2161,15 @@ int security_secctx_to_secid(const char *secdata, u32 
> seclen,
>                            struct lsmblob *blob)
>  {
>       struct security_hook_list *hp;
> -     int rc;
> +     int display = lsm_task_display(current);
>  
>       lsmblob_init(blob, 0);
>       hlist_for_each_entry(hp, &security_hook_heads.secctx_to_secid, list) {
>               if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
>                       continue;
> -             rc = hp->hook.secctx_to_secid(secdata, seclen,
> -                                           &blob->secid[hp->lsmid->slot]);
> -             if (rc != 0)
> -                     return rc;
> +             if (display == LSMBLOB_INVALID || display == hp->lsmid->slot)
> +                     return hp->hook.secctx_to_secid(secdata, seclen,
> +                                             &blob->secid[hp->lsmid->slot]);
>       }
>       return 0;
>  }
> @@ -2058,7 +2177,14 @@ EXPORT_SYMBOL(security_secctx_to_secid);
>  
>  void security_release_secctx(char *secdata, u32 seclen)
>  {
> -     call_void_hook(release_secctx, secdata, seclen);
> +     struct security_hook_list *hp;
> +     int display = lsm_task_display(current);
> +
> +     hlist_for_each_entry(hp, &security_hook_heads.release_secctx, list)
> +             if (display == LSMBLOB_INVALID || display == hp->lsmid->slot) {
> +                     hp->hook.release_secctx(secdata, seclen);
> +                     return;
> +             }
>  }
>  EXPORT_SYMBOL(security_release_secctx);
>  
> @@ -2183,8 +2309,15 @@ EXPORT_SYMBOL(security_sock_rcv_skb);
>  int security_socket_getpeersec_stream(struct socket *sock, char __user 
> *optval,
>                                     int __user *optlen, unsigned len)
>  {
> -     return call_int_hook(socket_getpeersec_stream, -ENOPROTOOPT, sock,
> -                             optval, optlen, len);
> +     int display = lsm_task_display(current);
> +     struct security_hook_list *hp;
> +
> +     hlist_for_each_entry(hp, &security_hook_heads.socket_getpeersec_stream,
> +                          list)
> +             if (display == LSMBLOB_INVALID || display == hp->lsmid->slot)
> +                     return hp->hook.socket_getpeersec_stream(sock, optval,
> +                                                              optlen, len);
> +     return -ENOPROTOOPT;
>  }
>  
>  int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff 
> *skb,
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 84eef6af53e7..dc5e3bd2b770 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -6299,6 +6299,17 @@ static int selinux_setprocattr(const char *name, void 
> *value, size_t size)
>       /*
>        * Basic control over ability to set these attributes at all.
>        */
> +
> +     /*
> +      * For setting display, we only perform a permission check;
> +      * the actual update to the display value is handled by the
> +      * LSM framework.
> +      */
> +     if (!strcmp(name, "display"))
> +             return avc_has_perm(&selinux_state,
> +                                 mysid, mysid, SECCLASS_PROCESS2,
> +                                 PROCESS2__SETDISPLAY, NULL);
> +
>       if (!strcmp(name, "exec"))
>               error = avc_has_perm(&selinux_state,
>                                    mysid, mysid, SECCLASS_PROCESS,
> diff --git a/security/selinux/include/classmap.h 
> b/security/selinux/include/classmap.h
> index 986f3ac14282..ba36a55b5ea4 100644
> --- a/security/selinux/include/classmap.h
> +++ b/security/selinux/include/classmap.h
> @@ -52,7 +52,7 @@ struct security_class_mapping secclass_map[] = {
>           "execmem", "execstack", "execheap", "setkeycreate",
>           "setsockcreate", "getrlimit", NULL } },
>       { "process2",
> -       { "nnp_transition", "nosuid_transition", NULL } },
> +       { "nnp_transition", "nosuid_transition", "setdisplay", NULL } },
>       { "system",
>         { "ipc_info", "syslog_read", "syslog_mod",
>           "syslog_console", "module_request", "module_load", NULL } },
> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
> index 12e01d450dd4..00a304861657 100644
> --- a/security/smack/smack_lsm.c
> +++ b/security/smack/smack_lsm.c
> @@ -3510,6 +3510,13 @@ static int smack_setprocattr(const char *name, void 
> *value, size_t size)
>       struct smack_known_list_elem *sklep;
>       int rc;
>  
> +     /*
> +      * Allow the /proc/.../attr/current and SO_PEERSEC "display"
> +      * to be reset at will.
> +      */
> +     if (strcmp(name, "display") == 0)
> +             return 0;
> +
>       if (!smack_privileged(CAP_MAC_ADMIN) && list_empty(&tsp->smk_relabel))
>               return -EPERM;
>  
> -- 
> 2.24.1
> 

-- 
Kees Cook


--
Linux-audit mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/linux-audit

Reply via email to