diff options
Diffstat (limited to 'security/smack')
| -rw-r--r-- | security/smack/Kconfig | 41 | ||||
| -rw-r--r-- | security/smack/Makefile | 2 | ||||
| -rw-r--r-- | security/smack/smack.h | 319 | ||||
| -rw-r--r-- | security/smack/smack_access.c | 474 | ||||
| -rw-r--r-- | security/smack/smack_lsm.c | 4129 | ||||
| -rw-r--r-- | security/smack/smack_netfilter.c | 78 | ||||
| -rw-r--r-- | security/smack/smackfs.c | 1598 |
7 files changed, 4626 insertions, 2015 deletions
diff --git a/security/smack/Kconfig b/security/smack/Kconfig index e69de9c642b7..5a8dfad469c3 100644 --- a/security/smack/Kconfig +++ b/security/smack/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only config SECURITY_SMACK bool "Simplified Mandatory Access Control Kernel Support" depends on NET @@ -12,3 +13,43 @@ config SECURITY_SMACK of other mandatory security schemes. If you are unsure how to answer this question, answer N. +config SECURITY_SMACK_BRINGUP + bool "Reporting on access granted by Smack rules" + depends on SECURITY_SMACK + default n + help + Enable the bring-up ("b") access mode in Smack rules. + When access is granted by a rule with the "b" mode a + message about the access requested is generated. The + intention is that a process can be granted a wide set + of access initially with the bringup mode set on the + rules. The developer can use the information to + identify which rules are necessary and what accesses + may be inappropriate. The developer can reduce the + access rule set once the behavior is well understood. + This is a superior mechanism to the oft abused + "permissive" mode of other systems. + If you are unsure how to answer this question, answer N. + +config SECURITY_SMACK_NETFILTER + bool "Packet marking using secmarks for netfilter" + depends on SECURITY_SMACK + depends on NETWORK_SECMARK + depends on NETFILTER + default n + help + This enables security marking of network packets using + Smack labels. + If you are unsure how to answer this question, answer N. + +config SECURITY_SMACK_APPEND_SIGNALS + bool "Treat delivering signals as an append operation" + depends on SECURITY_SMACK + default n + help + Sending a signal has been treated as a write operation to the + receiving process. If this option is selected, the delivery + will be an append operation instead. This makes it possible + to differentiate between delivering a network packet and + delivering a signal in the Smack rules. + If you are unsure how to answer this question, answer N. diff --git a/security/smack/Makefile b/security/smack/Makefile index 67a63aaec827..6dbf6e22a68b 100644 --- a/security/smack/Makefile +++ b/security/smack/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only # # Makefile for the SMACK LSM # @@ -5,3 +6,4 @@ obj-$(CONFIG_SECURITY_SMACK) := smack.o smack-y := smack_lsm.o smack_access.o smackfs.o +smack-$(CONFIG_SECURITY_SMACK_NETFILTER) += smack_netfilter.o diff --git a/security/smack/smack.h b/security/smack/smack.h index 339614c76e63..9b9eb262fe33 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -1,13 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (C) 2007 Casey Schaufler <casey@schaufler-ca.com> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 2. - * * Author: * Casey Schaufler <casey@schaufler-ca.com> - * */ #ifndef _SECURITY_SMACK_H @@ -15,12 +11,28 @@ #include <linux/capability.h> #include <linux/spinlock.h> -#include <linux/security.h> +#include <linux/lsm_hooks.h> #include <linux/in.h> +#if IS_ENABLED(CONFIG_IPV6) +#include <linux/in6.h> +#endif /* CONFIG_IPV6 */ #include <net/netlabel.h> #include <linux/list.h> #include <linux/rculist.h> #include <linux/lsm_audit.h> +#include <linux/msg.h> + +/* + * Use IPv6 port labeling if IPv6 is enabled and secmarks + * are not being used. + */ +#if IS_ENABLED(CONFIG_IPV6) && !defined(CONFIG_SECURITY_SMACK_NETFILTER) +#define SMACK_IPV6_PORT_LABELING 1 +#endif + +#if IS_ENABLED(CONFIG_IPV6) && defined(CONFIG_SECURITY_SMACK_NETFILTER) +#define SMACK_IPV6_SECMARK_LABELING 1 +#endif /* * Smack labels were limited to 23 characters for a long time. @@ -30,7 +42,7 @@ /* * This is the repository for labels seen so that it is - * not necessary to keep allocating tiny chuncks of memory + * not necessary to keep allocating tiny chunks of memory * and so that they can be shared. * * Labels are never modified in place. Anytime a label @@ -53,6 +65,7 @@ */ struct smack_known { struct list_head list; + struct hlist_node smk_hashed; char *smk_known; u32 smk_secid; struct netlbl_lsm_secattr smk_netlabel; /* on wire labels */ @@ -70,40 +83,53 @@ struct smack_known { #define SMK_CIPSOLEN 24 struct superblock_smack { - char *smk_root; - char *smk_floor; - char *smk_hat; - char *smk_default; - int smk_initialized; + struct smack_known *smk_root; + struct smack_known *smk_floor; + struct smack_known *smk_hat; + struct smack_known *smk_default; + int smk_flags; }; +/* + * Superblock flags + */ +#define SMK_SB_INITIALIZED 0x01 +#define SMK_SB_UNTRUSTED 0x02 + struct socket_smack { struct smack_known *smk_out; /* outbound label */ - char *smk_in; /* inbound label */ - char *smk_packet; /* TCP peer label */ + struct smack_known *smk_in; /* inbound label */ + struct smack_known *smk_packet; /* TCP peer label */ + int smk_state; /* netlabel socket states */ }; +#define SMK_NETLBL_UNSET 0 +#define SMK_NETLBL_UNLABELED 1 +#define SMK_NETLBL_LABELED 2 +#define SMK_NETLBL_REQSKB 3 /* * Inode smack data */ struct inode_smack { - char *smk_inode; /* label of the fso */ + struct smack_known *smk_inode; /* label of the fso */ struct smack_known *smk_task; /* label of the task */ struct smack_known *smk_mmap; /* label of the mmap domain */ - struct mutex smk_lock; /* initialization lock */ int smk_flags; /* smack inode flags */ }; struct task_smack { struct smack_known *smk_task; /* label for access control */ struct smack_known *smk_forked; /* label when forked */ + struct smack_known *smk_transmuted;/* label when transmuted */ struct list_head smk_rules; /* per task access rules */ struct mutex smk_rules_lock; /* lock for the rules */ + struct list_head smk_relabel; /* transit allowed labels */ }; #define SMK_INODE_INSTANT 0x01 /* inode is instantiated */ #define SMK_INODE_TRANSMUTE 0x02 /* directory is transmuting */ -#define SMK_INODE_CHANGED 0x04 /* smack was transmuted */ +#define SMK_INODE_CHANGED 0x04 /* smack was transmuted (unused) */ +#define SMK_INODE_IMPURE 0x08 /* involved in an impure transaction */ /* * A label access rule. @@ -111,20 +137,35 @@ struct task_smack { struct smack_rule { struct list_head list; struct smack_known *smk_subject; - char *smk_object; + struct smack_known *smk_object; int smk_access; }; /* - * An entry in the table identifying hosts. + * An entry in the table identifying IPv4 hosts. */ -struct smk_netlbladdr { +struct smk_net4addr { struct list_head list; - struct sockaddr_in smk_host; /* network address */ + struct in_addr smk_host; /* network address */ struct in_addr smk_mask; /* network mask */ - char *smk_label; /* label */ + int smk_masks; /* mask size */ + struct smack_known *smk_label; /* label */ }; +#if IS_ENABLED(CONFIG_IPV6) +/* + * An entry in the table identifying IPv6 hosts. + */ +struct smk_net6addr { + struct list_head list; + struct in6_addr smk_host; /* network address */ + struct in6_addr smk_mask; /* network mask */ + int smk_masks; /* mask size */ + struct smack_known *smk_label; /* label */ +}; +#endif /* CONFIG_IPV6 */ + +#ifdef SMACK_IPV6_PORT_LABELING /* * An entry in the table identifying ports. */ @@ -132,33 +173,29 @@ struct smk_port_label { struct list_head list; struct sock *smk_sock; /* socket initialized on */ unsigned short smk_port; /* the port number */ - char *smk_in; /* incoming label */ + struct smack_known *smk_in; /* inbound label */ struct smack_known *smk_out; /* outgoing label */ + short smk_sock_type; /* Socket type */ + short smk_can_reuse; }; +#endif /* SMACK_IPV6_PORT_LABELING */ -/* - * Mount options - */ -#define SMK_FSDEFAULT "smackfsdef=" -#define SMK_FSFLOOR "smackfsfloor=" -#define SMK_FSHAT "smackfshat=" -#define SMK_FSROOT "smackfsroot=" -#define SMK_FSTRANS "smackfstransmute=" +struct smack_known_list_elem { + struct list_head list; + struct smack_known *smk_label; +}; -#define SMACK_CIPSO_OPTION "-CIPSO" +enum { + Opt_error = -1, + Opt_fsdefault = 0, + Opt_fsfloor = 1, + Opt_fshat = 2, + Opt_fsroot = 3, + Opt_fstransmute = 4, +}; -/* - * How communications on this socket are treated. - * Usually it's determined by the underlying netlabel code - * but there are certain cases, including single label hosts - * and potentially single label interfaces for which the - * treatment can not be known in advance. - * - * The possibility of additional labeling schemes being - * introduced in the future exists as well. - */ -#define SMACK_UNLABELED_SOCKET 0 -#define SMACK_CIPSO_SOCKET 1 +#define SMACK_DELETE_OPTION "-DELETE" +#define SMACK_CIPSO_OPTION "-CIPSO" /* * CIPSO defaults. @@ -167,14 +204,45 @@ struct smk_port_label { #define SMACK_CIPSO_DOI_INVALID -1 /* Not a DOI */ #define SMACK_CIPSO_DIRECT_DEFAULT 250 /* Arbitrary */ #define SMACK_CIPSO_MAPPED_DEFAULT 251 /* Also arbitrary */ -#define SMACK_CIPSO_MAXCATVAL 63 /* Bigger gets harder */ #define SMACK_CIPSO_MAXLEVEL 255 /* CIPSO 2.2 standard */ -#define SMACK_CIPSO_MAXCATNUM 239 /* CIPSO 2.2 standard */ +/* + * CIPSO 2.2 standard is 239, but Smack wants to use the + * categories in a structured way that limits the value to + * the bits in 23 bytes, hence the unusual number. + */ +#define SMACK_CIPSO_MAXCATNUM 184 /* 23 * 8 */ + +/* + * Ptrace rules + */ +#define SMACK_PTRACE_DEFAULT 0 +#define SMACK_PTRACE_EXACT 1 +#define SMACK_PTRACE_DRACONIAN 2 +#define SMACK_PTRACE_MAX SMACK_PTRACE_DRACONIAN /* - * Flag for transmute access + * Flags for untraditional access modes. + * It shouldn't be necessary to avoid conflicts with definitions + * in fs.h, but do so anyway. */ -#define MAY_TRANSMUTE 64 +#define MAY_TRANSMUTE 0x00001000 /* Controls directory labeling */ +#define MAY_LOCK 0x00002000 /* Locks should be writes, but ... */ +#define MAY_BRINGUP 0x00004000 /* Report use of this rule */ + +/* + * The policy for delivering signals is configurable. + * It is usually "write", but can be "append". + */ +#ifdef CONFIG_SECURITY_SMACK_APPEND_SIGNALS +#define MAY_DELIVER MAY_APPEND /* Signal delivery requires append */ +#else +#define MAY_DELIVER MAY_WRITE /* Signal delivery requires write */ +#endif + +#define SMACK_BRINGUP_ALLOW 1 /* Allow bringup mode */ +#define SMACK_UNCONFINED_SUBJECT 2 /* Allow unconfined label */ +#define SMACK_UNCONFINED_OBJECT 3 /* Allow unconfined label */ + /* * Just to make the common cases easier to deal with */ @@ -183,9 +251,9 @@ struct smk_port_label { #define MAY_NOT 0 /* - * Number of access types used by Smack (rwxat) + * Number of access types used by Smack (rwxatlb) */ -#define SMK_NUM_ACCESS_TYPE 5 +#define SMK_NUM_ACCESS_TYPE 7 /* SMACK data */ struct smack_audit_data { @@ -206,62 +274,138 @@ struct smk_audit_info { struct smack_audit_data sad; #endif }; + /* - * These functions are in smack_lsm.c + * Initialization */ -struct inode_smack *new_inode_smack(char *); +#if defined(CONFIG_SECURITY_SMACK_NETFILTER) +int smack_nf_ip_init(void); +#else +static inline int smack_nf_ip_init(void) +{ + return 0; +} +#endif +int init_smk_fs(void); +int smack_initcall(void); /* * These functions are in smack_access.c */ int smk_access_entry(char *, char *, struct list_head *); -int smk_access(struct smack_known *, char *, int, struct smk_audit_info *); -int smk_curacc(char *, u32, struct smk_audit_info *); +int smk_access(struct smack_known *, struct smack_known *, + int, struct smk_audit_info *); +int smk_tskacc(struct task_smack *, struct smack_known *, + u32, struct smk_audit_info *); +int smk_curacc(struct smack_known *, u32, struct smk_audit_info *); +int smack_str_from_perm(char *string, int access); struct smack_known *smack_from_secid(const u32); +int smk_parse_label_len(const char *string, int len); char *smk_parse_smack(const char *string, int len); int smk_netlbl_mls(int, char *, struct netlbl_lsm_secattr *, int); -char *smk_import(const char *, int); struct smack_known *smk_import_entry(const char *, int); +struct smack_known *smk_import_valid_label(const char *label, int label_len, + gfp_t gfp); +void smk_insert_entry(struct smack_known *skp); struct smack_known *smk_find_entry(const char *); -u32 smack_to_secid(const char *); +bool smack_privileged(int cap); +bool smack_privileged_cred(int cap, const struct cred *cred); +void smk_destroy_label_list(struct list_head *list); +int smack_populate_secattr(struct smack_known *skp); /* * Shared data. */ +extern int smack_enabled __initdata; extern int smack_cipso_direct; extern int smack_cipso_mapped; extern struct smack_known *smack_net_ambient; -extern char *smack_onlycap; -extern const char *smack_cipso_option; +extern struct smack_known *smack_syslog_label; +#ifdef CONFIG_SECURITY_SMACK_BRINGUP +extern struct smack_known *smack_unconfined; +#endif +extern int smack_ptrace_rule; +extern struct lsm_blob_sizes smack_blob_sizes; extern struct smack_known smack_known_floor; extern struct smack_known smack_known_hat; extern struct smack_known smack_known_huh; -extern struct smack_known smack_known_invalid; extern struct smack_known smack_known_star; extern struct smack_known smack_known_web; extern struct mutex smack_known_lock; extern struct list_head smack_known_list; -extern struct list_head smk_netlbladdr_list; +extern struct list_head smk_net4addr_list; +#if IS_ENABLED(CONFIG_IPV6) +extern struct list_head smk_net6addr_list; +#endif /* CONFIG_IPV6 */ + +extern struct mutex smack_onlycap_lock; +extern struct list_head smack_onlycap_list; + +#define SMACK_HASH_SLOTS 16 +extern struct hlist_head smack_known_hash[SMACK_HASH_SLOTS]; +extern struct kmem_cache *smack_rule_cache; + +static inline struct task_smack *smack_cred(const struct cred *cred) +{ + return cred->security + smack_blob_sizes.lbs_cred; +} + +static inline struct smack_known **smack_file(const struct file *file) +{ + return (struct smack_known **)(file->f_security + + smack_blob_sizes.lbs_file); +} + +static inline struct inode_smack *smack_inode(const struct inode *inode) +{ + return inode->i_security + smack_blob_sizes.lbs_inode; +} -extern struct security_operations smack_ops; +static inline struct smack_known **smack_msg_msg(const struct msg_msg *msg) +{ + return msg->security + smack_blob_sizes.lbs_msg_msg; +} + +static inline struct smack_known **smack_ipc(const struct kern_ipc_perm *ipc) +{ + return ipc->security + smack_blob_sizes.lbs_ipc; +} + +static inline struct superblock_smack *smack_superblock( + const struct super_block *superblock) +{ + return superblock->s_security + smack_blob_sizes.lbs_superblock; +} + +static inline struct socket_smack *smack_sock(const struct sock *sock) +{ + return sock->sk_security + smack_blob_sizes.lbs_sock; +} + +#ifdef CONFIG_KEYS +static inline struct smack_known **smack_key(const struct key *key) +{ + return key->security + smack_blob_sizes.lbs_key; +} +#endif /* CONFIG_KEYS */ /* * Is the directory transmuting? */ static inline int smk_inode_transmutable(const struct inode *isp) { - struct inode_smack *sip = isp->i_security; + struct inode_smack *sip = smack_inode(isp); return (sip->smk_flags & SMK_INODE_TRANSMUTE) != 0; } /* - * Present a pointer to the smack label in an inode blob. + * Present a pointer to the smack label entry in an inode blob. */ -static inline char *smk_of_inode(const struct inode *isp) +static inline struct smack_known *smk_of_inode(const struct inode *isp) { - struct inode_smack *sip = isp->i_security; + struct inode_smack *sip = smack_inode(isp); return sip->smk_inode; } @@ -273,6 +417,22 @@ static inline struct smack_known *smk_of_task(const struct task_smack *tsp) return tsp->smk_task; } +static inline struct smack_known *smk_of_task_struct_obj( + const struct task_struct *t) +{ + struct smack_known *skp; + const struct cred *cred; + + rcu_read_lock(); + + cred = __task_cred(t); + skp = smk_of_task(smack_cred(cred)); + + rcu_read_unlock(); + + return skp; +} + /* * Present a pointer to the forked smack label entry in an task blob. */ @@ -286,23 +446,14 @@ static inline struct smack_known *smk_of_forked(const struct task_smack *tsp) */ static inline struct smack_known *smk_of_current(void) { - return smk_of_task(current_security()); + return smk_of_task(smack_cred(current_cred())); } -/* - * Is the task privileged and allowed to be privileged - * by the onlycap rule. - */ -static inline int smack_privileged(int cap) -{ - struct smack_known *skp = smk_of_current(); +void smack_log(char *subject_label, char *object_label, + int request, + int result, struct smk_audit_info *auditdata); - if (!capable(cap)) - return 0; - if (smack_onlycap == NULL || smack_onlycap == skp->smk_known) - return 1; - return 0; -} +#ifdef CONFIG_AUDIT /* * logging functions @@ -311,12 +462,6 @@ static inline int smack_privileged(int cap) #define SMACK_AUDIT_ACCEPT 0x2 extern int log_policy; -void smack_log(char *subject_label, char *object_label, - int request, - int result, struct smk_audit_info *auditdata); - -#ifdef CONFIG_AUDIT - /* * some inline functions to set up audit data * they do nothing if CONFIG_AUDIT is not set @@ -379,10 +524,6 @@ static inline void smk_ad_setfield_u_fs_path_dentry(struct smk_audit_info *a, struct dentry *d) { } -static inline void smk_ad_setfield_u_fs_path_mnt(struct smk_audit_info *a, - struct vfsmount *m) -{ -} static inline void smk_ad_setfield_u_fs_inode(struct smk_audit_info *a, struct inode *i) { diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 6a0377f38620..fc507dcc7ea5 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -1,13 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2007 Casey Schaufler <casey@schaufler-ca.com> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 2. - * * Author: * Casey Schaufler <casey@schaufler-ca.com> - * */ #include <linux/types.h> @@ -36,11 +32,6 @@ struct smack_known smack_known_floor = { .smk_secid = 5, }; -struct smack_known smack_known_invalid = { - .smk_known = "", - .smk_secid = 6, -}; - struct smack_known smack_known_web = { .smk_known = "@", .smk_secid = 7, @@ -54,11 +45,13 @@ LIST_HEAD(smack_known_list); */ static u32 smack_next_secid = 10; +#ifdef CONFIG_AUDIT /* * what events do we log * can be overwritten at run-time by /smack/logging */ int log_policy = SMACK_AUDIT_DENIED; +#endif /* CONFIG_AUDIT */ /** * smk_access_entry - look up matching access rule @@ -84,28 +77,34 @@ int log_policy = SMACK_AUDIT_DENIED; * * Do the object check first because that is more * likely to differ. + * + * Allowing write access implies allowing locking. */ int smk_access_entry(char *subject_label, char *object_label, struct list_head *rule_list) { - int may = -ENOENT; struct smack_rule *srp; list_for_each_entry_rcu(srp, rule_list, list) { - if (srp->smk_object == object_label && + if (srp->smk_object->smk_known == object_label && srp->smk_subject->smk_known == subject_label) { - may = srp->smk_access; - break; + int may = srp->smk_access; + /* + * MAY_WRITE implies MAY_LOCK. + */ + if ((may & MAY_WRITE) == MAY_WRITE) + may |= MAY_LOCK; + return may; } } - return may; + return -ENOENT; } /** * smk_access - determine if a subject has a specific access to an object - * @subject_known: a pointer to the subject's Smack label entry - * @object_label: a pointer to the object's Smack label + * @subject: a pointer to the subject's Smack label entry + * @object: a pointer to the object's Smack label entry * @request: the access requested, in "MAY" format * @a : a pointer to the audit data * @@ -115,18 +114,19 @@ int smk_access_entry(char *subject_label, char *object_label, * * Smack labels are shared on smack_list */ -int smk_access(struct smack_known *subject_known, char *object_label, - int request, struct smk_audit_info *a) +int smk_access(struct smack_known *subject, struct smack_known *object, + int request, struct smk_audit_info *a) { int may = MAY_NOT; int rc = 0; /* * Hardcoded comparisons. - * + */ + /* * A star subject can't access any object. */ - if (subject_known == &smack_known_star) { + if (subject == &smack_known_star) { rc = -EACCES; goto out_audit; } @@ -135,28 +135,28 @@ int smk_access(struct smack_known *subject_known, char *object_label, * Tasks cannot be assigned the internet label. * An internet subject can access any object. */ - if (object_label == smack_known_web.smk_known || - subject_known == &smack_known_web) + if (object == &smack_known_web || subject == &smack_known_web) goto out_audit; /* * A star object can be accessed by any subject. */ - if (object_label == smack_known_star.smk_known) + if (object == &smack_known_star) goto out_audit; /* * An object can be accessed in any way by a subject * with the same label. */ - if (subject_known->smk_known == object_label) + if (subject->smk_known == object->smk_known) goto out_audit; /* - * A hat subject can read any object. - * A floor object can be read by any subject. + * A hat subject can read or lock any object. + * A floor object can be read or locked by any subject. */ - if ((request & MAY_ANYREAD) == request) { - if (object_label == smack_known_floor.smk_known) + if ((request & MAY_ANYREAD) == request || + (request & MAY_LOCK) == request) { + if (object == &smack_known_floor) goto out_audit; - if (subject_known == &smack_known_hat) + if (subject == &smack_known_hat) goto out_audit; } /* @@ -167,52 +167,75 @@ int smk_access(struct smack_known *subject_known, char *object_label, * indicates there is no entry for this pair. */ rcu_read_lock(); - may = smk_access_entry(subject_known->smk_known, object_label, - &subject_known->smk_rules); + may = smk_access_entry(subject->smk_known, object->smk_known, + &subject->smk_rules); rcu_read_unlock(); - if (may > 0 && (request & may) == request) + if (may <= 0 || (request & may) != request) { + rc = -EACCES; goto out_audit; + } +#ifdef CONFIG_SECURITY_SMACK_BRINGUP + /* + * Return a positive value if using bringup mode. + * This allows the hooks to identify checks that + * succeed because of "b" rules. + */ + if (may & MAY_BRINGUP) + rc = SMACK_BRINGUP_ALLOW; +#endif - rc = -EACCES; out_audit: + +#ifdef CONFIG_SECURITY_SMACK_BRINGUP + if (rc < 0) { + if (object == smack_unconfined) + rc = SMACK_UNCONFINED_OBJECT; + if (subject == smack_unconfined) + rc = SMACK_UNCONFINED_SUBJECT; + } +#endif + #ifdef CONFIG_AUDIT if (a) - smack_log(subject_known->smk_known, object_label, request, - rc, a); + smack_log(subject->smk_known, object->smk_known, + request, rc, a); #endif + return rc; } /** - * smk_curacc - determine if current has a specific access to an object - * @obj_label: a pointer to the object's Smack label + * smk_tskacc - determine if a task has a specific access to an object + * @tsp: a pointer to the subject's task + * @obj_known: a pointer to the object's label entry * @mode: the access requested, in "MAY" format * @a : common audit data * - * This function checks the current subject label/object label pair + * This function checks the subject task's label/object label pair * in the access rule list and returns 0 if the access is permitted, - * non zero otherwise. It allows that current may have the capability + * non zero otherwise. It allows that the task may have the capability * to override the rules. */ -int smk_curacc(char *obj_label, u32 mode, struct smk_audit_info *a) +int smk_tskacc(struct task_smack *tsp, struct smack_known *obj_known, + u32 mode, struct smk_audit_info *a) { - struct task_smack *tsp = current_security(); - struct smack_known *skp = smk_of_task(tsp); + struct smack_known *sbj_known = smk_of_task(tsp); int may; int rc; /* * Check the global rule list */ - rc = smk_access(skp, obj_label, mode, NULL); - if (rc == 0) { + rc = smk_access(sbj_known, obj_known, mode, NULL); + if (rc >= 0) { /* * If there is an entry in the task's rule list * it can further restrict access. */ - may = smk_access_entry(skp->smk_known, obj_label, - &tsp->smk_rules); + may = smk_access_entry(sbj_known->smk_known, + obj_known->smk_known, + &tsp->smk_rules); if (may < 0) goto out_audit; if ((mode & may) == mode) @@ -221,7 +244,7 @@ int smk_curacc(char *obj_label, u32 mode, struct smk_audit_info *a) } /* - * Allow for priviliged to override policy. + * Allow for privileged to override policy. */ if (rc != 0 && smack_privileged(CAP_MAC_OVERRIDE)) rc = 0; @@ -229,22 +252,42 @@ int smk_curacc(char *obj_label, u32 mode, struct smk_audit_info *a) out_audit: #ifdef CONFIG_AUDIT if (a) - smack_log(skp->smk_known, obj_label, mode, rc, a); + smack_log(sbj_known->smk_known, obj_known->smk_known, + mode, rc, a); #endif return rc; } -#ifdef CONFIG_AUDIT /** - * smack_str_from_perm : helper to transalate an int to a + * smk_curacc - determine if current has a specific access to an object + * @obj_known: a pointer to the object's Smack label entry + * @mode: the access requested, in "MAY" format + * @a : common audit data + * + * This function checks the current subject label/object label pair + * in the access rule list and returns 0 if the access is permitted, + * non zero otherwise. It allows that current may have the capability + * to override the rules. + */ +int smk_curacc(struct smack_known *obj_known, + u32 mode, struct smk_audit_info *a) +{ + struct task_smack *tsp = smack_cred(current_cred()); + + return smk_tskacc(tsp, obj_known, mode, a); +} + +/** + * smack_str_from_perm : helper to translate an int to a * readable string * @string : the string to fill * @access : the int * */ -static inline void smack_str_from_perm(char *string, int access) +int smack_str_from_perm(char *string, int access) { int i = 0; + if (access & MAY_READ) string[i++] = 'r'; if (access & MAY_WRITE) @@ -255,8 +298,17 @@ static inline void smack_str_from_perm(char *string, int access) string[i++] = 'a'; if (access & MAY_TRANSMUTE) string[i++] = 't'; + if (access & MAY_LOCK) + string[i++] = 'l'; + if (access & MAY_BRINGUP) + string[i++] = 'b'; + if (i == 0) + string[i++] = '-'; string[i] = '\0'; + return i; } + +#ifdef CONFIG_AUDIT /** * smack_log_callback - SMACK specific information * will be called by generic audit code @@ -275,7 +327,10 @@ static void smack_log_callback(struct audit_buffer *ab, void *a) audit_log_untrustedstring(ab, sad->subject); audit_log_format(ab, " object="); audit_log_untrustedstring(ab, sad->object); - audit_log_format(ab, " requested=%s", sad->request); + if (sad->request[0] == '\0') + audit_log_format(ab, " labels_differ"); + else + audit_log_format(ab, " requested=%s", sad->request); } /** @@ -284,7 +339,7 @@ static void smack_log_callback(struct audit_buffer *ab, void *a) * @object_label : smack label of the object being accessed * @request: requested permissions * @result: result from smk_access - * @a: auxiliary audit data + * @ad: auxiliary audit data * * Audit the granting or denial of permissions in accordance * with the policy. @@ -292,12 +347,16 @@ static void smack_log_callback(struct audit_buffer *ab, void *a) void smack_log(char *subject_label, char *object_label, int request, int result, struct smk_audit_info *ad) { +#ifdef CONFIG_SECURITY_SMACK_BRINGUP + char request_buffer[SMK_NUM_ACCESS_TYPE + 5]; +#else char request_buffer[SMK_NUM_ACCESS_TYPE + 1]; +#endif struct smack_audit_data *sad; struct common_audit_data *a = &ad->a; /* check if we have to log the current event */ - if (result != 0 && (log_policy & SMACK_AUDIT_DENIED) == 0) + if (result < 0 && (log_policy & SMACK_AUDIT_DENIED) == 0) return; if (result == 0 && (log_policy & SMACK_AUDIT_ACCEPT) == 0) return; @@ -311,6 +370,21 @@ void smack_log(char *subject_label, char *object_label, int request, smack_str_from_perm(request_buffer, request); sad->subject = subject_label; sad->object = object_label; +#ifdef CONFIG_SECURITY_SMACK_BRINGUP + /* + * The result may be positive in bringup mode. + * A positive result is an allow, but not for normal reasons. + * Mark it as successful, but don't filter it out even if + * the logging policy says to do so. + */ + if (result == SMACK_UNCONFINED_SUBJECT) + strcat(request_buffer, "(US)"); + else if (result == SMACK_UNCONFINED_OBJECT) + strcat(request_buffer, "(UO)"); + + if (result > 0) + result = 0; +#endif sad->request = request_buffer; sad->result = result; @@ -325,39 +399,63 @@ void smack_log(char *subject_label, char *object_label, int request, DEFINE_MUTEX(smack_known_lock); +struct hlist_head smack_known_hash[SMACK_HASH_SLOTS]; + +/** + * smk_insert_entry - insert a smack label into a hash map, + * @skp: smack label + * + * this function must be called under smack_known_lock + */ +void smk_insert_entry(struct smack_known *skp) +{ + unsigned int hash; + struct hlist_head *head; + + hash = full_name_hash(NULL, skp->smk_known, strlen(skp->smk_known)); + head = &smack_known_hash[hash & (SMACK_HASH_SLOTS - 1)]; + + hlist_add_head_rcu(&skp->smk_hashed, head); + list_add_rcu(&skp->list, &smack_known_list); +} + /** * smk_find_entry - find a label on the list, return the list entry * @string: a text string that might be a Smack label * * Returns a pointer to the entry in the label list that - * matches the passed string. + * matches the passed string or NULL if not found. */ struct smack_known *smk_find_entry(const char *string) { + unsigned int hash; + struct hlist_head *head; struct smack_known *skp; - list_for_each_entry_rcu(skp, &smack_known_list, list) { + hash = full_name_hash(NULL, string, strlen(string)); + head = &smack_known_hash[hash & (SMACK_HASH_SLOTS - 1)]; + + hlist_for_each_entry_rcu(skp, head, smk_hashed) if (strcmp(skp->smk_known, string) == 0) return skp; - } return NULL; } /** - * smk_parse_smack - parse smack label from a text string - * @string: a text string that might contain a Smack label - * @len: the maximum size, or zero if it is NULL terminated. + * smk_parse_label_len - calculate the length of the starting segment + * in the string that constitutes a valid smack label + * @string: a text string that might contain a Smack label at the beginning + * @len: the maximum size to look into, may be zero if string is null-terminated * - * Returns a pointer to the clean label, or NULL + * Returns the length of the segment (0 < L < SMK_LONGLABEL) or an error code. */ -char *smk_parse_smack(const char *string, int len) +int smk_parse_label_len(const char *string, int len) { - char *smack; int i; - if (len <= 0) - len = strlen(string) + 1; + if (len <= 0 || len > SMK_LONGLABEL) + len = SMK_LONGLABEL; /* * Reserve a leading '-' as an indicator that @@ -365,7 +463,7 @@ char *smk_parse_smack(const char *string, int len) * including /smack/cipso and /smack/cipso2 */ if (string[0] == '-') - return NULL; + return -EINVAL; for (i = 0; i < len; i++) if (string[i] > '~' || string[i] <= ' ' || string[i] == '/' || @@ -373,20 +471,39 @@ char *smk_parse_smack(const char *string, int len) break; if (i == 0 || i >= SMK_LONGLABEL) - return NULL; + return -EINVAL; - smack = kzalloc(i + 1, GFP_KERNEL); - if (smack != NULL) { - strncpy(smack, string, i + 1); - smack[i] = '\0'; - } + return i; +} + +/** + * smk_parse_smack - copy the starting segment in the string + * that constitutes a valid smack label + * @string: a text string that might contain a Smack label at the beginning + * @len: the maximum size to look into, may be zero if string is null-terminated + * + * Returns a pointer to the copy of the label or an error code. + */ +char *smk_parse_smack(const char *string, int len) +{ + char *smack; + int i = smk_parse_label_len(string, len); + + if (i < 0) + return ERR_PTR(-EINVAL); + + smack = kstrndup(string, i, GFP_NOFS); + if (!smack) + return ERR_PTR(-ENOMEM); return smack; } /** * smk_netlbl_mls - convert a catset to netlabel mls categories + * @level: MLS sensitivity level * @catset: the Smack categories * @sap: where to put the netlabel categories + * @len: number of bytes for the levels in a CIPSO IP option * * Allocates and fills attr.mls * Returns 0 on success, error code on failure. @@ -402,19 +519,16 @@ int smk_netlbl_mls(int level, char *catset, struct netlbl_lsm_secattr *sap, sap->flags |= NETLBL_SECATTR_MLS_CAT; sap->attr.mls.lvl = level; - sap->attr.mls.cat = netlbl_secattr_catmap_alloc(GFP_ATOMIC); - if (!sap->attr.mls.cat) - return -ENOMEM; - sap->attr.mls.cat->startbit = 0; + sap->attr.mls.cat = NULL; for (cat = 1, cp = catset, byte = 0; byte < len; cp++, byte++) for (m = 0x80; m != 0; m >>= 1, cat++) { if ((m & *cp) == 0) continue; - rc = netlbl_secattr_catmap_setbit(sap->attr.mls.cat, - cat, GFP_ATOMIC); + rc = netlbl_catmap_setbit(&sap->attr.mls.cat, + cat, GFP_NOFS); if (rc < 0) { - netlbl_secattr_catmap_free(sap->attr.mls.cat); + netlbl_catmap_free(sap->attr.mls.cat); return rc; } } @@ -423,23 +537,54 @@ int smk_netlbl_mls(int level, char *catset, struct netlbl_lsm_secattr *sap, } /** - * smk_import_entry - import a label, return the list entry - * @string: a text string that might be a Smack label - * @len: the maximum size, or zero if it is NULL terminated. + * smack_populate_secattr - fill in the smack_known netlabel information + * @skp: pointer to the structure to fill * - * Returns a pointer to the entry in the label list that - * matches the passed string, adding it if necessary. + * Populate the netlabel secattr structure for a Smack label. + * + * Returns 0 unless creating the category mapping fails */ -struct smack_known *smk_import_entry(const char *string, int len) +int smack_populate_secattr(struct smack_known *skp) { - struct smack_known *skp; - char *smack; int slen; - int rc; - smack = smk_parse_smack(string, len); - if (smack == NULL) - return NULL; + skp->smk_netlabel.attr.secid = skp->smk_secid; + skp->smk_netlabel.domain = skp->smk_known; + skp->smk_netlabel.cache = netlbl_secattr_cache_alloc(GFP_ATOMIC); + if (skp->smk_netlabel.cache != NULL) { + skp->smk_netlabel.flags |= NETLBL_SECATTR_CACHE; + skp->smk_netlabel.cache->free = NULL; + skp->smk_netlabel.cache->data = skp; + } + skp->smk_netlabel.flags |= NETLBL_SECATTR_SECID | + NETLBL_SECATTR_MLS_LVL | + NETLBL_SECATTR_DOMAIN; + /* + * If direct labeling works use it. + * Otherwise use mapped labeling. + */ + slen = strlen(skp->smk_known); + if (slen < SMK_CIPSOLEN) + return smk_netlbl_mls(smack_cipso_direct, skp->smk_known, + &skp->smk_netlabel, slen); + + return smk_netlbl_mls(smack_cipso_mapped, (char *)&skp->smk_secid, + &skp->smk_netlabel, sizeof(skp->smk_secid)); +} + +/** + * smk_import_valid_allocated_label - import a label, return the list entry + * @smack: a text string that is a valid Smack label and may be kfree()ed. + * It is consumed: either becomes a part of the entry or kfree'ed. + * @gfp: Allocation type + * + * Returns: see description of smk_import_entry() + */ +static struct smack_known * +smk_import_allocated_label(char *smack, gfp_t gfp) +{ + struct smack_known *skp; + int rc; mutex_lock(&smack_known_lock); @@ -447,27 +592,16 @@ struct smack_known *smk_import_entry(const char *string, int len) if (skp != NULL) goto freeout; - skp = kzalloc(sizeof(*skp), GFP_KERNEL); - if (skp == NULL) + skp = kzalloc(sizeof(*skp), gfp); + if (skp == NULL) { + skp = ERR_PTR(-ENOMEM); goto freeout; + } skp->smk_known = smack; skp->smk_secid = smack_next_secid++; - skp->smk_netlabel.domain = skp->smk_known; - skp->smk_netlabel.flags = - NETLBL_SECATTR_DOMAIN | NETLBL_SECATTR_MLS_LVL; - /* - * If direct labeling works use it. - * Otherwise use mapped labeling. - */ - slen = strlen(smack); - if (slen < SMK_CIPSOLEN) - rc = smk_netlbl_mls(smack_cipso_direct, skp->smk_known, - &skp->smk_netlabel, slen); - else - rc = smk_netlbl_mls(smack_cipso_mapped, (char *)&skp->smk_secid, - &skp->smk_netlabel, sizeof(skp->smk_secid)); + rc = smack_populate_secattr(skp); if (rc >= 0) { INIT_LIST_HEAD(&skp->smk_rules); mutex_init(&skp->smk_rules_lock); @@ -475,14 +609,11 @@ struct smack_known *smk_import_entry(const char *string, int len) * Make sure that the entry is actually * filled before putting it on the list. */ - list_add_rcu(&skp->list, &smack_known_list); + smk_insert_entry(skp); goto unlockout; } - /* - * smk_netlbl_mls failed. - */ kfree(skp); - skp = NULL; + skp = ERR_PTR(rc); freeout: kfree(smack); unlockout: @@ -492,24 +623,41 @@ unlockout: } /** - * smk_import - import a smack label - * @string: a text string that might be a Smack label - * @len: the maximum size, or zero if it is NULL terminated. + * smk_import_entry - import a label, return the list entry + * @string: a text string that might contain a Smack label at the beginning + * @len: the maximum size to look into, may be zero if string is null-terminated * - * Returns a pointer to the label in the label list that - * matches the passed string, adding it if necessary. + * Returns a pointer to the entry in the label list that + * matches the passed string, adding it if necessary, + * or an error code. */ -char *smk_import(const char *string, int len) +struct smack_known *smk_import_entry(const char *string, int len) { - struct smack_known *skp; + char *smack = smk_parse_smack(string, len); - /* labels cannot begin with a '-' */ - if (string[0] == '-') - return NULL; - skp = smk_import_entry(string, len); - if (skp == NULL) - return NULL; - return skp->smk_known; + if (IS_ERR(smack)) + return ERR_CAST(smack); + + return smk_import_allocated_label(smack, GFP_NOFS); +} + +/** + * smk_import_valid_label - import a label, return the list entry + * @label: a text string that is a valid Smack label, not null-terminated + * @label_len: the length of the text string in the @label + * @gfp: the GFP mask used for allocating memory for the @label text string copy + * + * Return: see description of smk_import_entry() + */ +struct smack_known * +smk_import_valid_label(const char *label, int label_len, gfp_t gfp) +{ + char *smack = kstrndup(label, label_len, gfp); + + if (!smack) + return ERR_PTR(-ENOMEM); + + return smk_import_allocated_label(smack, gfp); } /** @@ -536,21 +684,73 @@ struct smack_known *smack_from_secid(const u32 secid) * of a secid that is not on the list. */ rcu_read_unlock(); - return &smack_known_invalid; + return &smack_known_huh; +} + +/* + * Unless a process is running with one of these labels + * even having CAP_MAC_OVERRIDE isn't enough to grant + * privilege to violate MAC policy. If no labels are + * designated (the empty list case) capabilities apply to + * everyone. + */ +LIST_HEAD(smack_onlycap_list); +DEFINE_MUTEX(smack_onlycap_lock); + +/** + * smack_privileged_cred - are all privilege requirements met by cred + * @cap: The requested capability + * @cred: the credential to use + * + * Is the task privileged and allowed to be privileged + * by the onlycap rule. + * + * Returns true if the task is allowed to be privileged, false if it's not. + */ +bool smack_privileged_cred(int cap, const struct cred *cred) +{ + struct task_smack *tsp = smack_cred(cred); + struct smack_known *skp = tsp->smk_task; + struct smack_known_list_elem *sklep; + int rc; + + rc = cap_capable(cred, &init_user_ns, cap, CAP_OPT_NONE); + if (rc) + return false; + + rcu_read_lock(); + if (list_empty(&smack_onlycap_list)) { + rcu_read_unlock(); + return true; + } + + list_for_each_entry_rcu(sklep, &smack_onlycap_list, list) { + if (sklep->smk_label == skp) { + rcu_read_unlock(); + return true; + } + } + rcu_read_unlock(); + + return false; } /** - * smack_to_secid - find the secid associated with a Smack label - * @smack: the Smack label + * smack_privileged - are all privilege requirements met + * @cap: The requested capability + * + * Is the task privileged and allowed to be privileged + * by the onlycap rule. * - * Returns the appropriate secid if there is one, - * otherwise 0 + * Returns true if the task is allowed to be privileged, false if it's not. */ -u32 smack_to_secid(const char *smack) +bool smack_privileged(int cap) { - struct smack_known *skp = smk_find_entry(smack); + /* + * All kernel tasks are privileged + */ + if (unlikely(current->flags & PF_KTHREAD)) + return true; - if (skp == NULL) - return 0; - return skp->smk_secid; + return smack_privileged_cred(cap, current_cred()); } diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 3f7682a387b7..a0bd4919a9d9 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Simplified MAC Kernel (smack) security module * @@ -12,10 +13,6 @@ * Paul Moore <paul@paul-moore.com> * Copyright (C) 2010 Nokia Corporation * Copyright (C) 2011 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. */ #include <linux/xattr.h> @@ -27,10 +24,9 @@ #include <linux/ip.h> #include <linux/tcp.h> #include <linux/udp.h> -#include <linux/dccp.h> +#include <linux/icmpv6.h> #include <linux/slab.h> #include <linux/mutex.h> -#include <linux/pipe_fs_i.h> #include <net/cipso_ipv4.h> #include <net/ip.h> #include <net/ipv6.h> @@ -40,11 +36,16 @@ #include <linux/personality.h> #include <linux/msg.h> #include <linux/shm.h> +#include <uapi/linux/shm.h> #include <linux/binfmts.h> +#include <linux/parser.h> +#include <linux/fs_context.h> +#include <linux/fs_parser.h> +#include <linux/watch_queue.h> +#include <linux/io_uring/cmd.h> +#include <uapi/linux/lsm.h> #include "smack.h" -#define task_security(task) (task_cred_xxx((task), security)) - #define TRANS_TRUE "TRUE" #define TRANS_TRUE_SIZE 4 @@ -52,15 +53,224 @@ #define SMK_RECEIVING 1 #define SMK_SENDING 2 -LIST_HEAD(smk_ipv6_port_list); +/* + * Smack uses multiple xattrs. + * SMACK64 - for access control, + * SMACK64TRANSMUTE - label initialization, + * Not saved on files - SMACK64IPIN and SMACK64IPOUT, + * Must be set explicitly - SMACK64EXEC and SMACK64MMAP + */ +#define SMACK_INODE_INIT_XATTRS 2 + +#ifdef SMACK_IPV6_PORT_LABELING +static DEFINE_MUTEX(smack_ipv6_lock); +static LIST_HEAD(smk_ipv6_port_list); +#endif +struct kmem_cache *smack_rule_cache; +int smack_enabled __initdata; + +#define A(s) {"smack"#s, sizeof("smack"#s) - 1, Opt_##s} +static struct { + const char *name; + int len; + int opt; +} smk_mount_opts[] = { + {"smackfsdef", sizeof("smackfsdef") - 1, Opt_fsdefault}, + A(fsdefault), A(fsfloor), A(fshat), A(fsroot), A(fstransmute) +}; +#undef A + +static int match_opt_prefix(char *s, int l, char **arg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smk_mount_opts); i++) { + size_t len = smk_mount_opts[i].len; + if (len > l || memcmp(s, smk_mount_opts[i].name, len)) + continue; + if (len == l || s[len] != '=') + continue; + *arg = s + len + 1; + return smk_mount_opts[i].opt; + } + return Opt_error; +} + +#ifdef CONFIG_SECURITY_SMACK_BRINGUP +static char *smk_bu_mess[] = { + "Bringup Error", /* Unused */ + "Bringup", /* SMACK_BRINGUP_ALLOW */ + "Unconfined Subject", /* SMACK_UNCONFINED_SUBJECT */ + "Unconfined Object", /* SMACK_UNCONFINED_OBJECT */ +}; + +static void smk_bu_mode(int mode, char *s) +{ + smack_str_from_perm(s, mode); +} +#endif + +#ifdef CONFIG_SECURITY_SMACK_BRINGUP +static int smk_bu_note(char *note, struct smack_known *sskp, + struct smack_known *oskp, int mode, int rc) +{ + char acc[SMK_NUM_ACCESS_TYPE + 1]; + + if (rc <= 0) + return rc; + if (rc > SMACK_UNCONFINED_OBJECT) + rc = 0; + + smk_bu_mode(mode, acc); + pr_info("Smack %s: (%s %s %s) %s\n", smk_bu_mess[rc], + sskp->smk_known, oskp->smk_known, acc, note); + return 0; +} +#else +#define smk_bu_note(note, sskp, oskp, mode, RC) (RC) +#endif + +#ifdef CONFIG_SECURITY_SMACK_BRINGUP +static int smk_bu_current(char *note, struct smack_known *oskp, + int mode, int rc) +{ + struct task_smack *tsp = smack_cred(current_cred()); + char acc[SMK_NUM_ACCESS_TYPE + 1]; + + if (rc <= 0) + return rc; + if (rc > SMACK_UNCONFINED_OBJECT) + rc = 0; + + smk_bu_mode(mode, acc); + pr_info("Smack %s: (%s %s %s) %s %s\n", smk_bu_mess[rc], + tsp->smk_task->smk_known, oskp->smk_known, + acc, current->comm, note); + return 0; +} +#else +#define smk_bu_current(note, oskp, mode, RC) (RC) +#endif + +#ifdef CONFIG_SECURITY_SMACK_BRINGUP +static int smk_bu_task(struct task_struct *otp, int mode, int rc) +{ + struct task_smack *tsp = smack_cred(current_cred()); + struct smack_known *smk_task = smk_of_task_struct_obj(otp); + char acc[SMK_NUM_ACCESS_TYPE + 1]; + + if (rc <= 0) + return rc; + if (rc > SMACK_UNCONFINED_OBJECT) + rc = 0; + + smk_bu_mode(mode, acc); + pr_info("Smack %s: (%s %s %s) %s to %s\n", smk_bu_mess[rc], + tsp->smk_task->smk_known, smk_task->smk_known, acc, + current->comm, otp->comm); + return 0; +} +#else +#define smk_bu_task(otp, mode, RC) (RC) +#endif + +#ifdef CONFIG_SECURITY_SMACK_BRINGUP +static int smk_bu_inode(struct inode *inode, int mode, int rc) +{ + struct task_smack *tsp = smack_cred(current_cred()); + struct inode_smack *isp = smack_inode(inode); + char acc[SMK_NUM_ACCESS_TYPE + 1]; + + if (isp->smk_flags & SMK_INODE_IMPURE) + pr_info("Smack Unconfined Corruption: inode=(%s %ld) %s\n", + inode->i_sb->s_id, inode->i_ino, current->comm); + + if (rc <= 0) + return rc; + if (rc > SMACK_UNCONFINED_OBJECT) + rc = 0; + if (rc == SMACK_UNCONFINED_SUBJECT && + (mode & (MAY_WRITE | MAY_APPEND))) + isp->smk_flags |= SMK_INODE_IMPURE; + + smk_bu_mode(mode, acc); + + pr_info("Smack %s: (%s %s %s) inode=(%s %ld) %s\n", smk_bu_mess[rc], + tsp->smk_task->smk_known, isp->smk_inode->smk_known, acc, + inode->i_sb->s_id, inode->i_ino, current->comm); + return 0; +} +#else +#define smk_bu_inode(inode, mode, RC) (RC) +#endif + +#ifdef CONFIG_SECURITY_SMACK_BRINGUP +static int smk_bu_file(struct file *file, int mode, int rc) +{ + struct task_smack *tsp = smack_cred(current_cred()); + struct smack_known *sskp = tsp->smk_task; + struct inode *inode = file_inode(file); + struct inode_smack *isp = smack_inode(inode); + char acc[SMK_NUM_ACCESS_TYPE + 1]; + + if (isp->smk_flags & SMK_INODE_IMPURE) + pr_info("Smack Unconfined Corruption: inode=(%s %ld) %s\n", + inode->i_sb->s_id, inode->i_ino, current->comm); + + if (rc <= 0) + return rc; + if (rc > SMACK_UNCONFINED_OBJECT) + rc = 0; + + smk_bu_mode(mode, acc); + pr_info("Smack %s: (%s %s %s) file=(%s %ld %pD) %s\n", smk_bu_mess[rc], + sskp->smk_known, smk_of_inode(inode)->smk_known, acc, + inode->i_sb->s_id, inode->i_ino, file, + current->comm); + return 0; +} +#else +#define smk_bu_file(file, mode, RC) (RC) +#endif + +#ifdef CONFIG_SECURITY_SMACK_BRINGUP +static int smk_bu_credfile(const struct cred *cred, struct file *file, + int mode, int rc) +{ + struct task_smack *tsp = smack_cred(cred); + struct smack_known *sskp = tsp->smk_task; + struct inode *inode = file_inode(file); + struct inode_smack *isp = smack_inode(inode); + char acc[SMK_NUM_ACCESS_TYPE + 1]; + + if (isp->smk_flags & SMK_INODE_IMPURE) + pr_info("Smack Unconfined Corruption: inode=(%s %ld) %s\n", + inode->i_sb->s_id, inode->i_ino, current->comm); + + if (rc <= 0) + return rc; + if (rc > SMACK_UNCONFINED_OBJECT) + rc = 0; + + smk_bu_mode(mode, acc); + pr_info("Smack %s: (%s %s %s) file=(%s %ld %pD) %s\n", smk_bu_mess[rc], + sskp->smk_known, smk_of_inode(inode)->smk_known, acc, + inode->i_sb->s_id, inode->i_ino, file, + current->comm); + return 0; +} +#else +#define smk_bu_credfile(cred, file, mode, RC) (RC) +#endif /** * smk_fetch - Fetch the smack label from a file. + * @name: type of the label (attribute) * @ip: a pointer to the inode * @dp: a pointer to the dentry * - * Returns a pointer to the master list entry for the Smack label - * or NULL if there was no label to fetch. + * Returns a pointer to the master list entry for the Smack label, + * NULL if there was no label to fetch, or an error code. */ static struct smack_known *smk_fetch(const char *name, struct inode *ip, struct dentry *dp) @@ -69,15 +279,19 @@ static struct smack_known *smk_fetch(const char *name, struct inode *ip, char *buffer; struct smack_known *skp = NULL; - if (ip->i_op->getxattr == NULL) - return NULL; + if (!(ip->i_opflags & IOP_XATTR)) + return ERR_PTR(-EOPNOTSUPP); - buffer = kzalloc(SMK_LONGLABEL, GFP_KERNEL); + buffer = kzalloc(SMK_LONGLABEL, GFP_NOFS); if (buffer == NULL) - return NULL; + return ERR_PTR(-ENOMEM); - rc = ip->i_op->getxattr(dp, name, buffer, SMK_LONGLABEL); - if (rc > 0) + rc = __vfs_getxattr(dp, ip, name, buffer, SMK_LONGLABEL); + if (rc < 0) + skp = ERR_PTR(rc); + else if (rc == 0) + skp = NULL; + else skp = smk_import_entry(buffer, rc); kfree(buffer); @@ -86,53 +300,41 @@ static struct smack_known *smk_fetch(const char *name, struct inode *ip, } /** - * new_inode_smack - allocate an inode security blob - * @smack: a pointer to the Smack label to use in the blob + * init_inode_smack - initialize an inode security blob + * @inode: inode to extract the info from + * @skp: a pointer to the Smack label entry to use in the blob * - * Returns the new blob or NULL if there's no memory available */ -struct inode_smack *new_inode_smack(char *smack) +static void init_inode_smack(struct inode *inode, struct smack_known *skp) { - struct inode_smack *isp; - - isp = kzalloc(sizeof(struct inode_smack), GFP_NOFS); - if (isp == NULL) - return NULL; + struct inode_smack *isp = smack_inode(inode); - isp->smk_inode = smack; + isp->smk_inode = skp; isp->smk_flags = 0; - mutex_init(&isp->smk_lock); - - return isp; } /** - * new_task_smack - allocate a task security blob - * @smack: a pointer to the Smack label to use in the blob + * init_task_smack - initialize a task security blob + * @tsp: blob to initialize + * @task: a pointer to the Smack label for the running task + * @forked: a pointer to the Smack label for the forked task * - * Returns the new blob or NULL if there's no memory available */ -static struct task_smack *new_task_smack(struct smack_known *task, - struct smack_known *forked, gfp_t gfp) +static void init_task_smack(struct task_smack *tsp, struct smack_known *task, + struct smack_known *forked) { - struct task_smack *tsp; - - tsp = kzalloc(sizeof(struct task_smack), gfp); - if (tsp == NULL) - return NULL; - tsp->smk_task = task; tsp->smk_forked = forked; INIT_LIST_HEAD(&tsp->smk_rules); + INIT_LIST_HEAD(&tsp->smk_relabel); mutex_init(&tsp->smk_rules_lock); - - return tsp; } /** * smk_copy_rules - copy a rule set - * @nhead - new rules header pointer - * @ohead - old rules header pointer + * @nhead: new rules header pointer + * @ohead: old rules header pointer + * @gfp: type of the memory for the allocation * * Returns 0 on success, -ENOMEM on error */ @@ -143,10 +345,8 @@ static int smk_copy_rules(struct list_head *nhead, struct list_head *ohead, struct smack_rule *orp; int rc = 0; - INIT_LIST_HEAD(nhead); - list_for_each_entry_rcu(orp, ohead, list) { - nrp = kzalloc(sizeof(struct smack_rule), gfp); + nrp = kmem_cache_zalloc(smack_rule_cache, gfp); if (nrp == NULL) { rc = -ENOMEM; break; @@ -157,6 +357,107 @@ static int smk_copy_rules(struct list_head *nhead, struct list_head *ohead, return rc; } +/** + * smk_copy_relabel - copy smk_relabel labels list + * @nhead: new rules header pointer + * @ohead: old rules header pointer + * @gfp: type of the memory for the allocation + * + * Returns 0 on success, -ENOMEM on error + */ +static int smk_copy_relabel(struct list_head *nhead, struct list_head *ohead, + gfp_t gfp) +{ + struct smack_known_list_elem *nklep; + struct smack_known_list_elem *oklep; + + list_for_each_entry(oklep, ohead, list) { + nklep = kzalloc(sizeof(struct smack_known_list_elem), gfp); + if (nklep == NULL) { + smk_destroy_label_list(nhead); + return -ENOMEM; + } + nklep->smk_label = oklep->smk_label; + list_add(&nklep->list, nhead); + } + + return 0; +} + +/** + * smk_ptrace_mode - helper function for converting PTRACE_MODE_* into MAY_* + * @mode: input mode in form of PTRACE_MODE_* + * + * Returns a converted MAY_* mode usable by smack rules + */ +static inline unsigned int smk_ptrace_mode(unsigned int mode) +{ + if (mode & PTRACE_MODE_ATTACH) + return MAY_READWRITE; + if (mode & PTRACE_MODE_READ) + return MAY_READ; + + return 0; +} + +/** + * smk_ptrace_rule_check - helper for ptrace access + * @tracer: tracer process + * @tracee_known: label entry of the process that's about to be traced + * @mode: ptrace attachment mode (PTRACE_MODE_*) + * @func: name of the function that called us, used for audit + * + * Returns 0 on access granted, -error on error + */ +static int smk_ptrace_rule_check(struct task_struct *tracer, + struct smack_known *tracee_known, + unsigned int mode, const char *func) +{ + int rc; + struct smk_audit_info ad, *saip = NULL; + struct task_smack *tsp; + struct smack_known *tracer_known; + const struct cred *tracercred; + + if ((mode & PTRACE_MODE_NOAUDIT) == 0) { + smk_ad_init(&ad, func, LSM_AUDIT_DATA_TASK); + smk_ad_setfield_u_tsk(&ad, tracer); + saip = &ad; + } + + rcu_read_lock(); + tracercred = __task_cred(tracer); + tsp = smack_cred(tracercred); + tracer_known = smk_of_task(tsp); + + if ((mode & PTRACE_MODE_ATTACH) && + (smack_ptrace_rule == SMACK_PTRACE_EXACT || + smack_ptrace_rule == SMACK_PTRACE_DRACONIAN)) { + if (tracer_known->smk_known == tracee_known->smk_known) + rc = 0; + else if (smack_ptrace_rule == SMACK_PTRACE_DRACONIAN) + rc = -EACCES; + else if (smack_privileged_cred(CAP_SYS_PTRACE, tracercred)) + rc = 0; + else + rc = -EACCES; + + if (saip) + smack_log(tracer_known->smk_known, + tracee_known->smk_known, + 0, rc, saip); + + rcu_read_unlock(); + return rc; + } + + /* In case of rule==SMACK_PTRACE_DEFAULT or mode==PTRACE_MODE_READ */ + rc = smk_tskacc(tsp, tracee_known, smk_ptrace_mode(mode), saip); + + rcu_read_unlock(); + return rc; +} + /* * LSM hooks. * We he, that is fun! @@ -165,28 +466,19 @@ static int smk_copy_rules(struct list_head *nhead, struct list_head *ohead, /** * smack_ptrace_access_check - Smack approval on PTRACE_ATTACH * @ctp: child task pointer - * @mode: ptrace attachment mode + * @mode: ptrace attachment mode (PTRACE_MODE_*) * * Returns 0 if access is OK, an error code otherwise * - * Do the capability checks, and require read and write. + * Do the capability checks. */ static int smack_ptrace_access_check(struct task_struct *ctp, unsigned int mode) { - int rc; - struct smk_audit_info ad; struct smack_known *skp; - rc = cap_ptrace_access_check(ctp, mode); - if (rc != 0) - return rc; - - skp = smk_of_task(task_security(ctp)); - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); - smk_ad_setfield_u_tsk(&ad, ctp); + skp = smk_of_task_struct_obj(ctp); - rc = smk_curacc(skp->smk_known, MAY_READWRITE, &ad); - return rc; + return smk_ptrace_rule_check(current, skp, mode, __func__); } /** @@ -195,31 +487,20 @@ static int smack_ptrace_access_check(struct task_struct *ctp, unsigned int mode) * * Returns 0 if access is OK, an error code otherwise * - * Do the capability checks, and require read and write. + * Do the capability checks, and require PTRACE_MODE_ATTACH. */ static int smack_ptrace_traceme(struct task_struct *ptp) { - int rc; - struct smk_audit_info ad; struct smack_known *skp; - rc = cap_ptrace_traceme(ptp); - if (rc != 0) - return rc; - - skp = smk_of_task(task_security(ptp)); - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); - smk_ad_setfield_u_tsk(&ad, ptp); + skp = smk_of_task(smack_cred(current_cred())); - rc = smk_curacc(skp->smk_known, MAY_READWRITE, &ad); - return rc; + return smk_ptrace_rule_check(ptp, skp, PTRACE_MODE_ATTACH, __func__); } /** * smack_syslog - Smack approval on syslog - * @type: message type - * - * Require that the task has the floor label + * @typefrom_file: unused * * Returns 0 on success, error code otherwise. */ @@ -231,13 +512,12 @@ static int smack_syslog(int typefrom_file) if (smack_privileged(CAP_MAC_OVERRIDE)) return 0; - if (skp != &smack_known_floor) + if (smack_syslog_label != NULL && smack_syslog_label != skp) rc = -EACCES; return rc; } - /* * Superblock Hooks. */ @@ -250,155 +530,339 @@ static int smack_syslog(int typefrom_file) */ static int smack_sb_alloc_security(struct super_block *sb) { - struct superblock_smack *sbsp; + struct superblock_smack *sbsp = smack_superblock(sb); + + sbsp->smk_root = &smack_known_floor; + sbsp->smk_default = &smack_known_floor; + sbsp->smk_floor = &smack_known_floor; + sbsp->smk_hat = &smack_known_hat; + /* + * SMK_SB_INITIALIZED will be zero from kzalloc. + */ - sbsp = kzalloc(sizeof(struct superblock_smack), GFP_KERNEL); + return 0; +} + +struct smack_mnt_opts { + const char *fsdefault; + const char *fsfloor; + const char *fshat; + const char *fsroot; + const char *fstransmute; +}; + +static void smack_free_mnt_opts(void *mnt_opts) +{ + kfree(mnt_opts); +} + +static int smack_add_opt(int token, const char *s, void **mnt_opts) +{ + struct smack_mnt_opts *opts = *mnt_opts; + struct smack_known *skp; - if (sbsp == NULL) + if (!opts) { + opts = kzalloc(sizeof(struct smack_mnt_opts), GFP_KERNEL); + if (!opts) + return -ENOMEM; + *mnt_opts = opts; + } + if (!s) return -ENOMEM; - sbsp->smk_root = smack_known_floor.smk_known; - sbsp->smk_default = smack_known_floor.smk_known; - sbsp->smk_floor = smack_known_floor.smk_known; - sbsp->smk_hat = smack_known_hat.smk_known; - /* - * smk_initialized will be zero from kzalloc. - */ - sb->s_security = sbsp; + skp = smk_import_entry(s, 0); + if (IS_ERR(skp)) + return PTR_ERR(skp); + switch (token) { + case Opt_fsdefault: + if (opts->fsdefault) + goto out_opt_err; + opts->fsdefault = skp->smk_known; + break; + case Opt_fsfloor: + if (opts->fsfloor) + goto out_opt_err; + opts->fsfloor = skp->smk_known; + break; + case Opt_fshat: + if (opts->fshat) + goto out_opt_err; + opts->fshat = skp->smk_known; + break; + case Opt_fsroot: + if (opts->fsroot) + goto out_opt_err; + opts->fsroot = skp->smk_known; + break; + case Opt_fstransmute: + if (opts->fstransmute) + goto out_opt_err; + opts->fstransmute = skp->smk_known; + break; + } return 0; + +out_opt_err: + pr_warn("Smack: duplicate mount options\n"); + return -EINVAL; } /** - * smack_sb_free_security - free a superblock blob - * @sb: the superblock getting the blob + * smack_fs_context_submount - Initialise security data for a filesystem context + * @fc: The filesystem context. + * @reference: reference superblock * + * Returns 0 on success or -ENOMEM on error. */ -static void smack_sb_free_security(struct super_block *sb) +static int smack_fs_context_submount(struct fs_context *fc, + struct super_block *reference) { - kfree(sb->s_security); - sb->s_security = NULL; + struct superblock_smack *sbsp; + struct smack_mnt_opts *ctx; + struct inode_smack *isp; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + fc->security = ctx; + + sbsp = smack_superblock(reference); + isp = smack_inode(reference->s_root->d_inode); + + if (sbsp->smk_default) { + ctx->fsdefault = kstrdup(sbsp->smk_default->smk_known, GFP_KERNEL); + if (!ctx->fsdefault) + return -ENOMEM; + } + + if (sbsp->smk_floor) { + ctx->fsfloor = kstrdup(sbsp->smk_floor->smk_known, GFP_KERNEL); + if (!ctx->fsfloor) + return -ENOMEM; + } + + if (sbsp->smk_hat) { + ctx->fshat = kstrdup(sbsp->smk_hat->smk_known, GFP_KERNEL); + if (!ctx->fshat) + return -ENOMEM; + } + + if (isp->smk_flags & SMK_INODE_TRANSMUTE) { + if (sbsp->smk_root) { + ctx->fstransmute = kstrdup(sbsp->smk_root->smk_known, GFP_KERNEL); + if (!ctx->fstransmute) + return -ENOMEM; + } + } + return 0; } /** - * smack_sb_copy_data - copy mount options data for processing - * @orig: where to start - * @smackopts: mount options string + * smack_fs_context_dup - Duplicate the security data on fs_context duplication + * @fc: The new filesystem context. + * @src_fc: The source filesystem context being duplicated. * * Returns 0 on success or -ENOMEM on error. - * - * Copy the Smack specific mount options out of the mount - * options list. */ -static int smack_sb_copy_data(char *orig, char *smackopts) +static int smack_fs_context_dup(struct fs_context *fc, + struct fs_context *src_fc) { - char *cp, *commap, *otheropts, *dp; + struct smack_mnt_opts *dst, *src = src_fc->security; - otheropts = (char *)get_zeroed_page(GFP_KERNEL); - if (otheropts == NULL) + if (!src) + return 0; + + fc->security = kzalloc(sizeof(struct smack_mnt_opts), GFP_KERNEL); + if (!fc->security) return -ENOMEM; - for (cp = orig, commap = orig; commap != NULL; cp = commap + 1) { - if (strstr(cp, SMK_FSDEFAULT) == cp) - dp = smackopts; - else if (strstr(cp, SMK_FSFLOOR) == cp) - dp = smackopts; - else if (strstr(cp, SMK_FSHAT) == cp) - dp = smackopts; - else if (strstr(cp, SMK_FSROOT) == cp) - dp = smackopts; - else if (strstr(cp, SMK_FSTRANS) == cp) - dp = smackopts; - else - dp = otheropts; + dst = fc->security; + dst->fsdefault = src->fsdefault; + dst->fsfloor = src->fsfloor; + dst->fshat = src->fshat; + dst->fsroot = src->fsroot; + dst->fstransmute = src->fstransmute; + + return 0; +} - commap = strchr(cp, ','); - if (commap != NULL) - *commap = '\0'; +static const struct fs_parameter_spec smack_fs_parameters[] = { + fsparam_string("smackfsdef", Opt_fsdefault), + fsparam_string("smackfsdefault", Opt_fsdefault), + fsparam_string("smackfsfloor", Opt_fsfloor), + fsparam_string("smackfshat", Opt_fshat), + fsparam_string("smackfsroot", Opt_fsroot), + fsparam_string("smackfstransmute", Opt_fstransmute), + {} +}; - if (*dp != '\0') - strcat(dp, ","); - strcat(dp, cp); - } +/** + * smack_fs_context_parse_param - Parse a single mount parameter + * @fc: The new filesystem context being constructed. + * @param: The parameter. + * + * Returns 0 on success, -ENOPARAM to pass the parameter on or anything else on + * error. + */ +static int smack_fs_context_parse_param(struct fs_context *fc, + struct fs_parameter *param) +{ + struct fs_parse_result result; + int opt, rc; + + opt = fs_parse(fc, smack_fs_parameters, param, &result); + if (opt < 0) + return opt; - strcpy(orig, otheropts); - free_page((unsigned long)otheropts); + rc = smack_add_opt(opt, param->string, &fc->security); + if (!rc) + param->string = NULL; + return rc; +} +static int smack_sb_eat_lsm_opts(char *options, void **mnt_opts) +{ + char *from = options, *to = options; + bool first = true; + + while (1) { + char *next = strchr(from, ','); + int token, len, rc; + char *arg = NULL; + + if (next) + len = next - from; + else + len = strlen(from); + + token = match_opt_prefix(from, len, &arg); + if (token != Opt_error) { + arg = kmemdup_nul(arg, from + len - arg, GFP_KERNEL); + rc = smack_add_opt(token, arg, mnt_opts); + kfree(arg); + if (unlikely(rc)) { + if (*mnt_opts) + smack_free_mnt_opts(*mnt_opts); + *mnt_opts = NULL; + return rc; + } + } else { + if (!first) { // copy with preceding comma + from--; + len++; + } + if (to != from) + memmove(to, from, len); + to += len; + first = false; + } + if (!from[len]) + break; + from += len + 1; + } + *to = '\0'; return 0; } /** - * smack_sb_kern_mount - Smack specific mount processing + * smack_set_mnt_opts - set Smack specific mount options * @sb: the file system superblock - * @flags: the mount flags - * @data: the smack mount options + * @mnt_opts: Smack mount options + * @kern_flags: mount option from kernel space or user space + * @set_kern_flags: where to store converted mount opts * * Returns 0 on success, an error code on failure + * + * Allow filesystems with binary mount data to explicitly set Smack mount + * labels. */ -static int smack_sb_kern_mount(struct super_block *sb, int flags, void *data) +static int smack_set_mnt_opts(struct super_block *sb, + void *mnt_opts, + unsigned long kern_flags, + unsigned long *set_kern_flags) { struct dentry *root = sb->s_root; - struct inode *inode = root->d_inode; - struct superblock_smack *sp = sb->s_security; + struct inode *inode = d_backing_inode(root); + struct superblock_smack *sp = smack_superblock(sb); struct inode_smack *isp; - char *op; - char *commap; - char *nsp; - int transmute = 0; + struct smack_known *skp; + struct smack_mnt_opts *opts = mnt_opts; + bool transmute = false; - if (sp->smk_initialized) + if (sp->smk_flags & SMK_SB_INITIALIZED) return 0; - sp->smk_initialized = 1; - - for (op = data; op != NULL; op = commap) { - commap = strchr(op, ','); - if (commap != NULL) - *commap++ = '\0'; - - if (strncmp(op, SMK_FSHAT, strlen(SMK_FSHAT)) == 0) { - op += strlen(SMK_FSHAT); - nsp = smk_import(op, 0); - if (nsp != NULL) - sp->smk_hat = nsp; - } else if (strncmp(op, SMK_FSFLOOR, strlen(SMK_FSFLOOR)) == 0) { - op += strlen(SMK_FSFLOOR); - nsp = smk_import(op, 0); - if (nsp != NULL) - sp->smk_floor = nsp; - } else if (strncmp(op, SMK_FSDEFAULT, - strlen(SMK_FSDEFAULT)) == 0) { - op += strlen(SMK_FSDEFAULT); - nsp = smk_import(op, 0); - if (nsp != NULL) - sp->smk_default = nsp; - } else if (strncmp(op, SMK_FSROOT, strlen(SMK_FSROOT)) == 0) { - op += strlen(SMK_FSROOT); - nsp = smk_import(op, 0); - if (nsp != NULL) - sp->smk_root = nsp; - } else if (strncmp(op, SMK_FSTRANS, strlen(SMK_FSTRANS)) == 0) { - op += strlen(SMK_FSTRANS); - nsp = smk_import(op, 0); - if (nsp != NULL) { - sp->smk_root = nsp; - transmute = 1; - } + if (!smack_privileged(CAP_MAC_ADMIN)) { + /* + * Unprivileged mounts don't get to specify Smack values. + */ + if (opts) + return -EPERM; + /* + * Unprivileged mounts get root and default from the caller. + */ + skp = smk_of_current(); + sp->smk_root = skp; + sp->smk_default = skp; + /* + * For a handful of fs types with no user-controlled + * backing store it's okay to trust security labels + * in the filesystem. The rest are untrusted. + */ + if (sb->s_user_ns != &init_user_ns && + sb->s_magic != SYSFS_MAGIC && sb->s_magic != TMPFS_MAGIC && + sb->s_magic != RAMFS_MAGIC) { + transmute = true; + sp->smk_flags |= SMK_SB_UNTRUSTED; + } + } + + sp->smk_flags |= SMK_SB_INITIALIZED; + + if (opts) { + if (opts->fsdefault) { + skp = smk_import_entry(opts->fsdefault, 0); + if (IS_ERR(skp)) + return PTR_ERR(skp); + sp->smk_default = skp; + } + if (opts->fsfloor) { + skp = smk_import_entry(opts->fsfloor, 0); + if (IS_ERR(skp)) + return PTR_ERR(skp); + sp->smk_floor = skp; + } + if (opts->fshat) { + skp = smk_import_entry(opts->fshat, 0); + if (IS_ERR(skp)) + return PTR_ERR(skp); + sp->smk_hat = skp; + } + if (opts->fsroot) { + skp = smk_import_entry(opts->fsroot, 0); + if (IS_ERR(skp)) + return PTR_ERR(skp); + sp->smk_root = skp; + } + if (opts->fstransmute) { + skp = smk_import_entry(opts->fstransmute, 0); + if (IS_ERR(skp)) + return PTR_ERR(skp); + sp->smk_root = skp; + transmute = true; } } /* * Initialize the root inode. */ - isp = inode->i_security; - if (inode->i_security == NULL) { - inode->i_security = new_inode_smack(sp->smk_root); - isp = inode->i_security; - } else - isp->smk_inode = sp->smk_root; + init_inode_smack(inode, sp->smk_root); - if (transmute) + if (transmute) { + isp = smack_inode(inode); isp->smk_flags |= SMK_INODE_TRANSMUTE; + } return 0; } @@ -412,7 +876,7 @@ static int smack_sb_kern_mount(struct super_block *sb, int flags, void *data) */ static int smack_sb_statfs(struct dentry *dentry) { - struct superblock_smack *sbp = dentry->d_sb->s_security; + struct superblock_smack *sbp = smack_superblock(dentry->d_sb); int rc; struct smk_audit_info ad; @@ -420,122 +884,64 @@ static int smack_sb_statfs(struct dentry *dentry) smk_ad_setfield_u_fs_path_dentry(&ad, dentry); rc = smk_curacc(sbp->smk_floor, MAY_READ, &ad); + rc = smk_bu_current("statfs", sbp->smk_floor, MAY_READ, rc); return rc; } -/** - * smack_sb_mount - Smack check for mounting - * @dev_name: unused - * @path: mount point - * @type: unused - * @flags: unused - * @data: unused - * - * Returns 0 if current can write the floor of the filesystem - * being mounted on, an error code otherwise. - */ -static int smack_sb_mount(const char *dev_name, struct path *path, - const char *type, unsigned long flags, void *data) -{ - struct superblock_smack *sbp = path->dentry->d_sb->s_security; - struct smk_audit_info ad; - - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); - smk_ad_setfield_u_fs_path(&ad, *path); - - return smk_curacc(sbp->smk_floor, MAY_WRITE, &ad); -} - -/** - * smack_sb_umount - Smack check for unmounting - * @mnt: file system to unmount - * @flags: unused - * - * Returns 0 if current can write the floor of the filesystem - * being unmounted, an error code otherwise. - */ -static int smack_sb_umount(struct vfsmount *mnt, int flags) -{ - struct superblock_smack *sbp; - struct smk_audit_info ad; - struct path path; - - path.dentry = mnt->mnt_root; - path.mnt = mnt; - - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); - smk_ad_setfield_u_fs_path(&ad, path); - - sbp = path.dentry->d_sb->s_security; - return smk_curacc(sbp->smk_floor, MAY_WRITE, &ad); -} - /* * BPRM hooks */ /** - * smack_bprm_set_creds - set creds for exec + * smack_bprm_creds_for_exec - Update bprm->cred if needed for exec * @bprm: the exec information * - * Returns 0 if it gets a blob, -ENOMEM otherwise + * Returns 0 if it gets a blob, -EPERM if exec forbidden and -ENOMEM otherwise */ -static int smack_bprm_set_creds(struct linux_binprm *bprm) +static int smack_bprm_creds_for_exec(struct linux_binprm *bprm) { struct inode *inode = file_inode(bprm->file); - struct task_smack *bsp = bprm->cred->security; + struct task_smack *bsp = smack_cred(bprm->cred); struct inode_smack *isp; + struct superblock_smack *sbsp; int rc; - rc = cap_bprm_set_creds(bprm); - if (rc != 0) - return rc; - - if (bprm->cred_prepared) + isp = smack_inode(inode); + if (isp->smk_task == NULL || isp->smk_task == bsp->smk_task) return 0; - isp = inode->i_security; - if (isp->smk_task == NULL || isp->smk_task == bsp->smk_task) + sbsp = smack_superblock(inode->i_sb); + if ((sbsp->smk_flags & SMK_SB_UNTRUSTED) && + isp->smk_task != sbsp->smk_root) return 0; - if (bprm->unsafe) + if (bprm->unsafe & LSM_UNSAFE_PTRACE) { + struct task_struct *tracer; + rc = 0; + + rcu_read_lock(); + tracer = ptrace_parent(current); + if (likely(tracer != NULL)) + rc = smk_ptrace_rule_check(tracer, + isp->smk_task, + PTRACE_MODE_ATTACH, + __func__); + rcu_read_unlock(); + + if (rc != 0) + return rc; + } + if (bprm->unsafe & ~LSM_UNSAFE_PTRACE) return -EPERM; bsp->smk_task = isp->smk_task; bprm->per_clear |= PER_CLEAR_ON_SETID; - return 0; -} - -/** - * smack_bprm_committing_creds - Prepare to install the new credentials - * from bprm. - * - * @bprm: binprm for exec - */ -static void smack_bprm_committing_creds(struct linux_binprm *bprm) -{ - struct task_smack *bsp = bprm->cred->security; - + /* Decide if this is a secure exec. */ if (bsp->smk_task != bsp->smk_forked) - current->pdeath_signal = 0; -} - -/** - * smack_bprm_secureexec - Return the decision to use secureexec. - * @bprm: binprm for exec - * - * Returns 0 on success. - */ -static int smack_bprm_secureexec(struct linux_binprm *bprm) -{ - struct task_smack *tsp = current_security(); - int ret = cap_bprm_secureexec(bprm); - - if (!ret && (tsp->smk_task != tsp->smk_forked)) - ret = 1; + bprm->secureexec = 1; - return ret; + return 0; } /* @@ -546,83 +952,130 @@ static int smack_bprm_secureexec(struct linux_binprm *bprm) * smack_inode_alloc_security - allocate an inode blob * @inode: the inode in need of a blob * - * Returns 0 if it gets a blob, -ENOMEM otherwise + * Returns 0 */ static int smack_inode_alloc_security(struct inode *inode) { struct smack_known *skp = smk_of_current(); - inode->i_security = new_inode_smack(skp->smk_known); - if (inode->i_security == NULL) - return -ENOMEM; + init_inode_smack(inode, skp); return 0; } /** - * smack_inode_free_security - free an inode blob - * @inode: the inode with a blob - * - * Clears the blob pointer in inode + * smk_rule_transmutes - does access rule for (subject,object) contain 't'? + * @subject: a pointer to the subject's Smack label entry + * @object: a pointer to the object's Smack label entry */ -static void smack_inode_free_security(struct inode *inode) +static bool +smk_rule_transmutes(struct smack_known *subject, + const struct smack_known *object) { - kfree(inode->i_security); - inode->i_security = NULL; + int may; + + rcu_read_lock(); + may = smk_access_entry(subject->smk_known, object->smk_known, + &subject->smk_rules); + rcu_read_unlock(); + return (may > 0) && (may & MAY_TRANSMUTE); +} + +static int +xattr_dupval(struct xattr *xattrs, int *xattr_count, + const char *name, const void *value, unsigned int vallen) +{ + struct xattr * const xattr = lsm_get_xattr_slot(xattrs, xattr_count); + + if (!xattr) + return 0; + + xattr->value = kmemdup(value, vallen, GFP_NOFS); + if (!xattr->value) + return -ENOMEM; + + xattr->value_len = vallen; + xattr->name = name; + return 0; } /** * smack_inode_init_security - copy out the smack from an inode - * @inode: the inode - * @dir: unused + * @inode: the newly created inode + * @dir: containing directory object * @qstr: unused - * @name: where to put the attribute name - * @value: where to put the attribute value - * @len: where to put the length of the attribute + * @xattrs: where to put the attributes + * @xattr_count: current number of LSM-provided xattrs (updated) * * Returns 0 if it all works out, -ENOMEM if there's no memory */ static int smack_inode_init_security(struct inode *inode, struct inode *dir, - const struct qstr *qstr, char **name, - void **value, size_t *len) + const struct qstr *qstr, + struct xattr *xattrs, int *xattr_count) { - struct inode_smack *issp = inode->i_security; - struct smack_known *skp = smk_of_current(); - char *isp = smk_of_inode(inode); - char *dsp = smk_of_inode(dir); - int may; + struct task_smack *tsp = smack_cred(current_cred()); + struct inode_smack * const issp = smack_inode(inode); + struct smack_known *dsp = smk_of_inode(dir); + int rc = 0; + int transflag = 0; + bool trans_cred; + bool trans_rule; - if (name) { - *name = kstrdup(XATTR_SMACK_SUFFIX, GFP_NOFS); - if (*name == NULL) - return -ENOMEM; + /* + * UNIX domain sockets use lower level socket data. Let + * UDS inode have fixed * label to keep smack_inode_permission() calm + * when called from unix_find_bsd() + */ + if (S_ISSOCK(inode->i_mode)) { + /* forced label, no need to save to xattrs */ + issp->smk_inode = &smack_known_star; + goto instant_inode; } + /* + * If equal, transmuting already occurred in + * smack_dentry_create_files_as(). No need to check again. + */ + trans_cred = (tsp->smk_task == tsp->smk_transmuted); + if (!trans_cred) + trans_rule = smk_rule_transmutes(smk_of_task(tsp), dsp); - if (value) { - rcu_read_lock(); - may = smk_access_entry(skp->smk_known, dsp, &skp->smk_rules); - rcu_read_unlock(); - + /* + * In addition to having smk_task equal to smk_transmuted, + * if the access rule allows transmutation and the directory + * requests transmutation then by all means transmute. + * Mark the inode as changed. + */ + if (trans_cred || (trans_rule && smk_inode_transmutable(dir))) { /* - * If the access rule allows transmutation and - * the directory requests transmutation then - * by all means transmute. - * Mark the inode as changed. + * The caller of smack_dentry_create_files_as() + * should have overridden the current cred, so the + * inode label was already set correctly in + * smack_inode_alloc_security(). */ - if (may > 0 && ((may & MAY_TRANSMUTE) != 0) && - smk_inode_transmutable(dir)) { - isp = dsp; - issp->smk_flags |= SMK_INODE_CHANGED; - } + if (!trans_cred) + issp->smk_inode = dsp; - *value = kstrdup(isp, GFP_NOFS); - if (*value == NULL) - return -ENOMEM; + if (S_ISDIR(inode->i_mode)) { + transflag = SMK_INODE_TRANSMUTE; + + if (xattr_dupval(xattrs, xattr_count, + XATTR_SMACK_TRANSMUTE, + TRANS_TRUE, + TRANS_TRUE_SIZE + )) + rc = -ENOMEM; + } } - if (len) - *len = strlen(isp) + 1; - - return 0; + if (rc == 0) + if (xattr_dupval(xattrs, xattr_count, + XATTR_SMACK_SUFFIX, + issp->smk_inode->smk_known, + strlen(issp->smk_inode->smk_known) + )) + rc = -ENOMEM; +instant_inode: + issp->smk_flags |= (SMK_INODE_INSTANT | transflag); + return rc; } /** @@ -636,20 +1089,22 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, static int smack_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { - char *isp; + struct smack_known *isp; struct smk_audit_info ad; int rc; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, old_dentry); - isp = smk_of_inode(old_dentry->d_inode); + isp = smk_of_inode(d_backing_inode(old_dentry)); rc = smk_curacc(isp, MAY_WRITE, &ad); + rc = smk_bu_inode(d_backing_inode(old_dentry), MAY_WRITE, rc); - if (rc == 0 && new_dentry->d_inode != NULL) { - isp = smk_of_inode(new_dentry->d_inode); + if (rc == 0 && d_is_positive(new_dentry)) { + isp = smk_of_inode(d_backing_inode(new_dentry)); smk_ad_setfield_u_fs_path_dentry(&ad, new_dentry); rc = smk_curacc(isp, MAY_WRITE, &ad); + rc = smk_bu_inode(d_backing_inode(new_dentry), MAY_WRITE, rc); } return rc; @@ -665,7 +1120,7 @@ static int smack_inode_link(struct dentry *old_dentry, struct inode *dir, */ static int smack_inode_unlink(struct inode *dir, struct dentry *dentry) { - struct inode *ip = dentry->d_inode; + struct inode *ip = d_backing_inode(dentry); struct smk_audit_info ad; int rc; @@ -676,6 +1131,7 @@ static int smack_inode_unlink(struct inode *dir, struct dentry *dentry) * You need write access to the thing you're unlinking */ rc = smk_curacc(smk_of_inode(ip), MAY_WRITE, &ad); + rc = smk_bu_inode(ip, MAY_WRITE, rc); if (rc == 0) { /* * You also need write access to the containing directory @@ -683,6 +1139,7 @@ static int smack_inode_unlink(struct inode *dir, struct dentry *dentry) smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_INODE); smk_ad_setfield_u_fs_inode(&ad, dir); rc = smk_curacc(smk_of_inode(dir), MAY_WRITE, &ad); + rc = smk_bu_inode(dir, MAY_WRITE, rc); } return rc; } @@ -706,7 +1163,8 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry) /* * You need write access to the thing you're removing */ - rc = smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad); + rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc); if (rc == 0) { /* * You also need write access to the containing directory @@ -714,6 +1172,7 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry) smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_INODE); smk_ad_setfield_u_fs_inode(&ad, dir); rc = smk_curacc(smk_of_inode(dir), MAY_WRITE, &ad); + rc = smk_bu_inode(dir, MAY_WRITE, rc); } return rc; @@ -721,10 +1180,10 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry) /** * smack_inode_rename - Smack check on rename - * @old_inode: the old directory - * @old_dentry: unused - * @new_inode: the new directory - * @new_dentry: unused + * @old_inode: unused + * @old_dentry: the old object + * @new_inode: unused + * @new_dentry: the new object * * Read and write access is required on both the old and * new directories. @@ -737,19 +1196,21 @@ static int smack_inode_rename(struct inode *old_inode, struct dentry *new_dentry) { int rc; - char *isp; + struct smack_known *isp; struct smk_audit_info ad; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, old_dentry); - isp = smk_of_inode(old_dentry->d_inode); + isp = smk_of_inode(d_backing_inode(old_dentry)); rc = smk_curacc(isp, MAY_READWRITE, &ad); + rc = smk_bu_inode(d_backing_inode(old_dentry), MAY_READWRITE, rc); - if (rc == 0 && new_dentry->d_inode != NULL) { - isp = smk_of_inode(new_dentry->d_inode); + if (rc == 0 && d_is_positive(new_dentry)) { + isp = smk_of_inode(d_backing_inode(new_dentry)); smk_ad_setfield_u_fs_path_dentry(&ad, new_dentry); rc = smk_curacc(isp, MAY_READWRITE, &ad); + rc = smk_bu_inode(d_backing_inode(new_dentry), MAY_READWRITE, rc); } return rc; } @@ -761,12 +1222,14 @@ static int smack_inode_rename(struct inode *old_inode, * * This is the important Smack hook. * - * Returns 0 if access is permitted, -EACCES otherwise + * Returns 0 if access is permitted, an error code otherwise */ static int smack_inode_permission(struct inode *inode, int mask) { + struct superblock_smack *sbsp = smack_superblock(inode->i_sb); struct smk_audit_info ad; int no_block = mask & MAY_NOT_BLOCK; + int rc; mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND); /* @@ -775,24 +1238,35 @@ static int smack_inode_permission(struct inode *inode, int mask) if (mask == 0) return 0; + if (sbsp->smk_flags & SMK_SB_UNTRUSTED) { + if (smk_of_inode(inode) != sbsp->smk_root) + return -EACCES; + } + /* May be droppable after audit */ if (no_block) return -ECHILD; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_INODE); smk_ad_setfield_u_fs_inode(&ad, inode); - return smk_curacc(smk_of_inode(inode), mask, &ad); + rc = smk_curacc(smk_of_inode(inode), mask, &ad); + rc = smk_bu_inode(inode, mask, rc); + return rc; } /** * smack_inode_setattr - Smack check for setting attributes + * @idmap: idmap of the mount * @dentry: the object * @iattr: for the force flag * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_setattr(struct dentry *dentry, struct iattr *iattr) +static int smack_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *iattr) { struct smk_audit_info ad; + int rc; + /* * Need to allow for clearing the setuid bit. */ @@ -801,75 +1275,131 @@ static int smack_inode_setattr(struct dentry *dentry, struct iattr *iattr) smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); - return smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad); + rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc); + return rc; } /** * smack_inode_getattr - Smack check for getting attributes - * @mnt: unused - * @dentry: the object + * @path: path to extract the info from * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) +static int smack_inode_getattr(const struct path *path) { struct smk_audit_info ad; - struct path path; - - path.dentry = dentry; - path.mnt = mnt; + struct inode *inode = d_backing_inode(path->dentry); + int rc; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); - smk_ad_setfield_u_fs_path(&ad, path); - return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ, &ad); + smk_ad_setfield_u_fs_path(&ad, *path); + rc = smk_curacc(smk_of_inode(inode), MAY_READ, &ad); + rc = smk_bu_inode(inode, MAY_READ, rc); + return rc; +} + +/** + * smack_inode_xattr_skipcap - Skip the xattr capability checks? + * @name: name of the xattr + * + * Returns 1 to indicate that Smack "owns" the access control rights to xattrs + * named @name; the LSM layer should avoid enforcing any traditional + * capability based access controls on this xattr. Returns 0 to indicate that + * Smack does not "own" the access control rights to xattrs named @name and is + * deferring to the LSM layer for further access controls, including capability + * based controls. + */ +static int smack_inode_xattr_skipcap(const char *name) +{ + if (strncmp(name, XATTR_SMACK_SUFFIX, strlen(XATTR_SMACK_SUFFIX))) + return 0; + + if (strcmp(name, XATTR_NAME_SMACK) == 0 || + strcmp(name, XATTR_NAME_SMACKIPIN) == 0 || + strcmp(name, XATTR_NAME_SMACKIPOUT) == 0 || + strcmp(name, XATTR_NAME_SMACKEXEC) == 0 || + strcmp(name, XATTR_NAME_SMACKMMAP) == 0 || + strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0) + return 1; + + return 0; } /** * smack_inode_setxattr - Smack check for setting xattrs + * @idmap: idmap of the mount * @dentry: the object * @name: name of the attribute - * @value: unused - * @size: unused + * @value: value of the attribute + * @size: size of the value * @flags: unused * * This protects the Smack attribute explicitly. * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_setxattr(struct dentry *dentry, const char *name, +static int smack_inode_setxattr(struct mnt_idmap *idmap, + struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { struct smk_audit_info ad; + struct smack_known *skp; + int check_priv = 0; + int check_import = 0; + int check_star = 0; int rc = 0; + umode_t const i_mode = d_backing_inode(dentry)->i_mode; - if (strcmp(name, XATTR_NAME_SMACK) == 0 || - strcmp(name, XATTR_NAME_SMACKIPIN) == 0 || - strcmp(name, XATTR_NAME_SMACKIPOUT) == 0 || - strcmp(name, XATTR_NAME_SMACKEXEC) == 0 || - strcmp(name, XATTR_NAME_SMACKMMAP) == 0) { - if (!smack_privileged(CAP_MAC_ADMIN)) - rc = -EPERM; + /* + * Check label validity here so import won't fail in post_setxattr + */ + if (strcmp(name, XATTR_NAME_SMACK) == 0) { /* - * check label validity here so import wont fail on - * post_setxattr + * UDS inode has fixed label */ - if (size == 0 || size >= SMK_LONGLABEL || - smk_import(value, size) == NULL) + if (S_ISSOCK(i_mode)) { rc = -EINVAL; + } else { + check_priv = 1; + check_import = 1; + } + } else if (strcmp(name, XATTR_NAME_SMACKIPIN) == 0 || + strcmp(name, XATTR_NAME_SMACKIPOUT) == 0) { + check_priv = 1; + check_import = 1; + } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0 || + strcmp(name, XATTR_NAME_SMACKMMAP) == 0) { + check_priv = 1; + check_import = 1; + check_star = 1; } else if (strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0) { - if (!smack_privileged(CAP_MAC_ADMIN)) - rc = -EPERM; - if (size != TRANS_TRUE_SIZE || + check_priv = 1; + if (!S_ISDIR(i_mode) || + size != TRANS_TRUE_SIZE || strncmp(value, TRANS_TRUE, TRANS_TRUE_SIZE) != 0) rc = -EINVAL; - } else - rc = cap_inode_setxattr(dentry, name, value, size, flags); + } + + if (check_priv && !smack_privileged(CAP_MAC_ADMIN)) + rc = -EPERM; + + if (rc == 0 && check_import) { + skp = size ? smk_import_entry(value, size) : NULL; + if (IS_ERR(skp)) + rc = PTR_ERR(skp); + else if (skp == NULL || (check_star && + (skp == &smack_known_star || skp == &smack_known_web))) + rc = -EINVAL; + } smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); - if (rc == 0) - rc = smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); + if (rc == 0) { + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad); + rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc); + } return rc; } @@ -889,29 +1419,25 @@ static void smack_inode_post_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { struct smack_known *skp; - struct inode_smack *isp = dentry->d_inode->i_security; + struct inode_smack *isp = smack_inode(d_backing_inode(dentry)); if (strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0) { isp->smk_flags |= SMK_INODE_TRANSMUTE; return; } - skp = smk_import_entry(value, size); if (strcmp(name, XATTR_NAME_SMACK) == 0) { - if (skp != NULL) - isp->smk_inode = skp->smk_known; - else - isp->smk_inode = smack_known_invalid.smk_known; + skp = smk_import_entry(value, size); + if (!IS_ERR(skp)) + isp->smk_inode = skp; } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0) { - if (skp != NULL) + skp = smk_import_entry(value, size); + if (!IS_ERR(skp)) isp->smk_task = skp; - else - isp->smk_task = &smack_known_invalid; } else if (strcmp(name, XATTR_NAME_SMACKMMAP) == 0) { - if (skp != NULL) + skp = smk_import_entry(value, size); + if (!IS_ERR(skp)) isp->smk_mmap = skp; - else - isp->smk_mmap = &smack_known_invalid; } return; @@ -927,15 +1453,19 @@ static void smack_inode_post_setxattr(struct dentry *dentry, const char *name, static int smack_inode_getxattr(struct dentry *dentry, const char *name) { struct smk_audit_info ad; + int rc; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); - return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ, &ad); + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_READ, &ad); + rc = smk_bu_inode(d_backing_inode(dentry), MAY_READ, rc); + return rc; } /** * smack_inode_removexattr - Smack check on removexattr + * @idmap: idmap of the mount * @dentry: the object * @name: name of the attribute * @@ -943,7 +1473,8 @@ static int smack_inode_getxattr(struct dentry *dentry, const char *name) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_removexattr(struct dentry *dentry, const char *name) +static int smack_inode_removexattr(struct mnt_idmap *idmap, + struct dentry *dentry, const char *name) { struct inode_smack *isp; struct smk_audit_info ad; @@ -954,81 +1485,179 @@ static int smack_inode_removexattr(struct dentry *dentry, const char *name) strcmp(name, XATTR_NAME_SMACKIPOUT) == 0 || strcmp(name, XATTR_NAME_SMACKEXEC) == 0 || strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0 || - strcmp(name, XATTR_NAME_SMACKMMAP)) { + strcmp(name, XATTR_NAME_SMACKMMAP) == 0) { if (!smack_privileged(CAP_MAC_ADMIN)) rc = -EPERM; - } else - rc = cap_inode_removexattr(dentry, name); + } + + if (rc != 0) + return rc; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); - if (rc == 0) - rc = smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); - if (rc == 0) { - isp = dentry->d_inode->i_security; + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad); + rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc); + if (rc != 0) + return rc; + + isp = smack_inode(d_backing_inode(dentry)); + /* + * Don't do anything special for these. + * XATTR_NAME_SMACKIPIN + * XATTR_NAME_SMACKIPOUT + * XATTR_NAME_SMACK if S_ISSOCK (UDS inode has fixed label) + */ + if (strcmp(name, XATTR_NAME_SMACK) == 0) { + if (!S_ISSOCK(d_backing_inode(dentry)->i_mode)) { + struct super_block *sbp = dentry->d_sb; + struct superblock_smack *sbsp = smack_superblock(sbp); + + isp->smk_inode = sbsp->smk_default; + } + } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0) isp->smk_task = NULL; + else if (strcmp(name, XATTR_NAME_SMACKMMAP) == 0) isp->smk_mmap = NULL; - } + else if (strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0) + isp->smk_flags &= ~SMK_INODE_TRANSMUTE; + return 0; +} + +/** + * smack_inode_set_acl - Smack check for setting posix acls + * @idmap: idmap of the mnt this request came from + * @dentry: the object + * @acl_name: name of the posix acl + * @kacl: the posix acls + * + * Returns 0 if access is permitted, an error code otherwise + */ +static int smack_inode_set_acl(struct mnt_idmap *idmap, + struct dentry *dentry, const char *acl_name, + struct posix_acl *kacl) +{ + struct smk_audit_info ad; + int rc; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, dentry); + + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad); + rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc); + return rc; +} + +/** + * smack_inode_get_acl - Smack check for getting posix acls + * @idmap: idmap of the mnt this request came from + * @dentry: the object + * @acl_name: name of the posix acl + * + * Returns 0 if access is permitted, an error code otherwise + */ +static int smack_inode_get_acl(struct mnt_idmap *idmap, + struct dentry *dentry, const char *acl_name) +{ + struct smk_audit_info ad; + int rc; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, dentry); + + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_READ, &ad); + rc = smk_bu_inode(d_backing_inode(dentry), MAY_READ, rc); + return rc; +} + +/** + * smack_inode_remove_acl - Smack check for getting posix acls + * @idmap: idmap of the mnt this request came from + * @dentry: the object + * @acl_name: name of the posix acl + * + * Returns 0 if access is permitted, an error code otherwise + */ +static int smack_inode_remove_acl(struct mnt_idmap *idmap, + struct dentry *dentry, const char *acl_name) +{ + struct smk_audit_info ad; + int rc; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); + smk_ad_setfield_u_fs_path_dentry(&ad, dentry); + + rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad); + rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc); return rc; } /** * smack_inode_getsecurity - get smack xattrs + * @idmap: idmap of the mount * @inode: the object * @name: attribute name * @buffer: where to put the result - * @alloc: unused + * @alloc: duplicate memory * * Returns the size of the attribute or an error code */ -static int smack_inode_getsecurity(const struct inode *inode, - const char *name, void **buffer, - bool alloc) +static int smack_inode_getsecurity(struct mnt_idmap *idmap, + struct inode *inode, const char *name, + void **buffer, bool alloc) { struct socket_smack *ssp; struct socket *sock; struct super_block *sbp; - struct inode *ip = (struct inode *)inode; - char *isp; - int ilen; - int rc = 0; + struct inode *ip = inode; + struct smack_known *isp; + struct inode_smack *ispp; + size_t label_len; + char *label = NULL; if (strcmp(name, XATTR_SMACK_SUFFIX) == 0) { isp = smk_of_inode(inode); - ilen = strlen(isp) + 1; - *buffer = isp; - return ilen; - } + } else if (strcmp(name, XATTR_SMACK_TRANSMUTE) == 0) { + ispp = smack_inode(inode); + if (ispp->smk_flags & SMK_INODE_TRANSMUTE) + label = TRANS_TRUE; + else + label = ""; + } else { + /* + * The rest of the Smack xattrs are only on sockets. + */ + sbp = ip->i_sb; + if (sbp->s_magic != SOCKFS_MAGIC) + return -EOPNOTSUPP; - /* - * The rest of the Smack xattrs are only on sockets. - */ - sbp = ip->i_sb; - if (sbp->s_magic != SOCKFS_MAGIC) - return -EOPNOTSUPP; + sock = SOCKET_I(ip); + if (sock == NULL || sock->sk == NULL) + return -EOPNOTSUPP; - sock = SOCKET_I(ip); - if (sock == NULL || sock->sk == NULL) - return -EOPNOTSUPP; + ssp = smack_sock(sock->sk); - ssp = sock->sk->sk_security; + if (strcmp(name, XATTR_SMACK_IPIN) == 0) + isp = ssp->smk_in; + else if (strcmp(name, XATTR_SMACK_IPOUT) == 0) + isp = ssp->smk_out; + else + return -EOPNOTSUPP; + } - if (strcmp(name, XATTR_SMACK_IPIN) == 0) - isp = ssp->smk_in; - else if (strcmp(name, XATTR_SMACK_IPOUT) == 0) - isp = ssp->smk_out->smk_known; - else - return -EOPNOTSUPP; + if (!label) + label = isp->smk_known; - ilen = strlen(isp) + 1; - if (rc == 0) { - *buffer = isp; - rc = ilen; + label_len = strlen(label); + + if (alloc) { + *buffer = kstrdup(label, GFP_KERNEL); + if (*buffer == NULL) + return -ENOMEM; } - return rc; + return label_len; } @@ -1037,43 +1666,34 @@ static int smack_inode_getsecurity(const struct inode *inode, * @inode: the object * @buffer: where they go * @buffer_size: size of buffer - * - * Returns 0 on success, -EINVAL otherwise */ static int smack_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size) { - int len = strlen(XATTR_NAME_SMACK); + int len = sizeof(XATTR_NAME_SMACK); - if (buffer != NULL && len <= buffer_size) { + if (buffer != NULL && len <= buffer_size) memcpy(buffer, XATTR_NAME_SMACK, len); - return len; - } - return -EINVAL; + + return len; } /** - * smack_inode_getsecid - Extract inode's security id + * smack_inode_getlsmprop - Extract inode's security id * @inode: inode to extract the info from - * @secid: where result will be saved + * @prop: where result will be saved */ -static void smack_inode_getsecid(const struct inode *inode, u32 *secid) +static void smack_inode_getlsmprop(struct inode *inode, struct lsm_prop *prop) { - struct inode_smack *isp = inode->i_security; - - *secid = smack_to_secid(isp->smk_inode); + prop->smack.skp = smk_of_inode(inode); } /* * File Hooks */ -/** - * smack_file_permission - Smack check on file operations - * @file: unused - * @mask: unused - * - * Returns 0 +/* + * There is no smack_file_permission hook * * Should access checks be done on each read or write? * UNICOS and SELinux say yes. @@ -1082,10 +1702,6 @@ static void smack_inode_getsecid(const struct inode *inode, u32 *secid) * I'll say no for now. Smack does not do the frequent * label changing that SELinux does. */ -static int smack_file_permission(struct file *file, int mask) -{ - return 0; -} /** * smack_file_alloc_security - assign a file security blob @@ -1094,29 +1710,20 @@ static int smack_file_permission(struct file *file, int mask) * The security blob for a file is a pointer to the master * label list, so no allocation is done. * + * f_security is the owner security information. It + * isn't used on file access checks, it's for send_sigio. + * * Returns 0 */ static int smack_file_alloc_security(struct file *file) { - struct smack_known *skp = smk_of_current(); + struct smack_known **blob = smack_file(file); - file->f_security = skp->smk_known; + *blob = smk_of_current(); return 0; } /** - * smack_file_free_security - clear a file security blob - * @file: the object - * - * The security blob for a file is a pointer to the master - * label list, so no memory is freed. - */ -static void smack_file_free_security(struct file *file) -{ - file->f_security = NULL; -} - -/** * smack_file_ioctl - Smack check on ioctls * @file: the object * @cmd: what to do @@ -1131,15 +1738,23 @@ static int smack_file_ioctl(struct file *file, unsigned int cmd, { int rc = 0; struct smk_audit_info ad; + struct inode *inode = file_inode(file); + + if (unlikely(IS_PRIVATE(inode))) + return 0; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path(&ad, file->f_path); - if (_IOC_DIR(cmd) & _IOC_WRITE) - rc = smk_curacc(file->f_security, MAY_WRITE, &ad); + if (_IOC_DIR(cmd) & _IOC_WRITE) { + rc = smk_curacc(smk_of_inode(inode), MAY_WRITE, &ad); + rc = smk_bu_file(file, MAY_WRITE, rc); + } - if (rc == 0 && (_IOC_DIR(cmd) & _IOC_READ)) - rc = smk_curacc(file->f_security, MAY_READ, &ad); + if (rc == 0 && (_IOC_DIR(cmd) & _IOC_READ)) { + rc = smk_curacc(smk_of_inode(inode), MAY_READ, &ad); + rc = smk_bu_file(file, MAY_READ, rc); + } return rc; } @@ -1149,15 +1764,22 @@ static int smack_file_ioctl(struct file *file, unsigned int cmd, * @file: the object * @cmd: unused * - * Returns 0 if current has write access, error code otherwise + * Returns 0 if current has lock access, error code otherwise */ static int smack_file_lock(struct file *file, unsigned int cmd) { struct smk_audit_info ad; + int rc; + struct inode *inode = file_inode(file); + + if (unlikely(IS_PRIVATE(inode))) + return 0; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path(&ad, file->f_path); - return smk_curacc(file->f_security, MAY_WRITE, &ad); + rc = smk_curacc(smk_of_inode(inode), MAY_LOCK, &ad); + rc = smk_bu_file(file, MAY_LOCK, rc); + return rc; } /** @@ -1177,17 +1799,27 @@ static int smack_file_fcntl(struct file *file, unsigned int cmd, { struct smk_audit_info ad; int rc = 0; + struct inode *inode = file_inode(file); + if (unlikely(IS_PRIVATE(inode))) + return 0; switch (cmd) { case F_GETLK: + break; case F_SETLK: case F_SETLKW: + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, file->f_path); + rc = smk_curacc(smk_of_inode(inode), MAY_LOCK, &ad); + rc = smk_bu_file(file, MAY_LOCK, rc); + break; case F_SETOWN: case F_SETSIG: smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path(&ad, file->f_path); - rc = smk_curacc(file->f_security, MAY_WRITE, &ad); + rc = smk_curacc(smk_of_inode(inode), MAY_WRITE, &ad); + rc = smk_bu_file(file, MAY_WRITE, rc); break; default: break; @@ -1197,13 +1829,14 @@ static int smack_file_fcntl(struct file *file, unsigned int cmd, } /** - * smack_mmap_file : - * Check permissions for a mmap operation. The @file may be NULL, e.g. - * if mapping anonymous memory. - * @file contains the file structure for file to map (may be NULL). - * @reqprot contains the protection requested by the application. - * @prot contains the protection that will be applied by the kernel. - * @flags contains the operational flags. + * smack_mmap_file - Check permissions for a mmap operation. + * @file: contains the file structure for file to map (may be NULL). + * @reqprot: contains the protection requested by the application. + * @prot: contains the protection that will be applied by the kernel. + * @flags: contains the operational flags. + * + * The @file may be NULL, e.g. if mapping anonymous memory. + * * Return 0 if permission is granted. */ static int smack_mmap_file(struct file *file, @@ -1214,8 +1847,9 @@ static int smack_mmap_file(struct file *file, struct smack_known *mkp; struct smack_rule *srp; struct task_smack *tsp; - char *osmack; + struct smack_known *okp; struct inode_smack *isp; + struct superblock_smack *sbsp; int may; int mmay; int tmay; @@ -1224,12 +1858,19 @@ static int smack_mmap_file(struct file *file, if (file == NULL) return 0; - isp = file_inode(file)->i_security; + if (unlikely(IS_PRIVATE(file_inode(file)))) + return 0; + + isp = smack_inode(file_inode(file)); if (isp->smk_mmap == NULL) return 0; + sbsp = smack_superblock(file_inode(file)->i_sb); + if (sbsp->smk_flags & SMK_SB_UNTRUSTED && + isp->smk_mmap != sbsp->smk_root) + return -EACCES; mkp = isp->smk_mmap; - tsp = current_security(); + tsp = smack_cred(current_cred()); skp = smk_of_current(); rc = 0; @@ -1240,18 +1881,19 @@ static int smack_mmap_file(struct file *file, * to that rule's object label. */ list_for_each_entry_rcu(srp, &skp->smk_rules, list) { - osmack = srp->smk_object; + okp = srp->smk_object; /* * Matching labels always allows access. */ - if (mkp->smk_known == osmack) + if (mkp->smk_known == okp->smk_known) continue; /* * If there is a matching local rule take * that into account as well. */ - may = smk_access_entry(srp->smk_subject->smk_known, osmack, - &tsp->smk_rules); + may = smk_access_entry(srp->smk_subject->smk_known, + okp->smk_known, + &tsp->smk_rules); if (may == -ENOENT) may = srp->smk_access; else @@ -1268,8 +1910,8 @@ static int smack_mmap_file(struct file *file, * If there isn't one a SMACK64MMAP subject * can't have as much access as current. */ - mmay = smk_access_entry(mkp->smk_known, osmack, - &mkp->smk_rules); + mmay = smk_access_entry(mkp->smk_known, okp->smk_known, + &mkp->smk_rules); if (mmay == -ENOENT) { rc = -EACCES; break; @@ -1278,8 +1920,8 @@ static int smack_mmap_file(struct file *file, * If there is a local entry it modifies the * potential access, too. */ - tmay = smk_access_entry(mkp->smk_known, osmack, - &tsp->smk_rules); + tmay = smk_access_entry(mkp->smk_known, okp->smk_known, + &tsp->smk_rules); if (tmay != -ENOENT) mmay &= tmay; @@ -1303,15 +1945,12 @@ static int smack_mmap_file(struct file *file, * smack_file_set_fowner - set the file security blob value * @file: object in question * - * Returns 0 - * Further research may be required on this one. */ -static int smack_file_set_fowner(struct file *file) +static void smack_file_set_fowner(struct file *file) { - struct smack_known *skp = smk_of_current(); + struct smack_known **blob = smack_file(file); - file->f_security = skp->smk_known; - return 0; + *blob = smk_of_current(); } /** @@ -1328,8 +1967,10 @@ static int smack_file_set_fowner(struct file *file) static int smack_file_send_sigiotask(struct task_struct *tsk, struct fown_struct *fown, int signum) { + struct smack_known **blob; struct smack_known *skp; - struct smack_known *tkp = smk_of_task(tsk->cred->security); + struct smack_known *tkp = smk_of_task(smack_cred(tsk->cred)); + const struct cred *tcred; struct file *file; int rc; struct smk_audit_info ad; @@ -1337,17 +1978,23 @@ static int smack_file_send_sigiotask(struct task_struct *tsk, /* * struct fown_struct is never outside the context of a struct file */ - file = container_of(fown, struct file, f_owner); + file = fown->file; + + /* we don't log here as rc can be overridden */ + blob = smack_file(file); + skp = *blob; + rc = smk_access(skp, tkp, MAY_DELIVER, NULL); + rc = smk_bu_note("sigiotask", skp, tkp, MAY_DELIVER, rc); - /* we don't log here as rc can be overriden */ - skp = smk_find_entry(file->f_security); - rc = smk_access(skp, tkp->smk_known, MAY_WRITE, NULL); - if (rc != 0 && has_capability(tsk, CAP_MAC_OVERRIDE)) + rcu_read_lock(); + tcred = __task_cred(tsk); + if (rc != 0 && smack_privileged_cred(CAP_MAC_OVERRIDE, tcred)) rc = 0; + rcu_read_unlock(); smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); smk_ad_setfield_u_tsk(&ad, tsk); - smack_log(file->f_security, tkp->smk_known, MAY_WRITE, rc, &ad); + smack_log(skp->smk_known, tkp->smk_known, MAY_DELIVER, rc, &ad); return rc; } @@ -1359,11 +2006,38 @@ static int smack_file_send_sigiotask(struct task_struct *tsk, */ static int smack_file_receive(struct file *file) { + int rc; int may = 0; struct smk_audit_info ad; + struct inode *inode = file_inode(file); + struct socket *sock; + struct task_smack *tsp; + struct socket_smack *ssp; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); + if (unlikely(IS_PRIVATE(inode))) + return 0; + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path(&ad, file->f_path); + + if (inode->i_sb->s_magic == SOCKFS_MAGIC) { + sock = SOCKET_I(inode); + ssp = smack_sock(sock->sk); + tsp = smack_cred(current_cred()); + /* + * If the receiving process can't write to the + * passed socket or if the passed socket can't + * write to the receiving process don't accept + * the passed socket. + */ + rc = smk_access(tsp->smk_task, ssp->smk_out, MAY_WRITE, &ad); + rc = smk_bu_file(file, may, rc); + if (rc < 0) + return rc; + rc = smk_access(ssp->smk_in, tsp->smk_task, MAY_WRITE, &ad); + rc = smk_bu_file(file, may, rc); + return rc; + } /* * This code relies on bitmasks. */ @@ -1372,25 +2046,35 @@ static int smack_file_receive(struct file *file) if (file->f_mode & FMODE_WRITE) may |= MAY_WRITE; - return smk_curacc(file->f_security, may, &ad); + rc = smk_curacc(smk_of_inode(inode), may, &ad); + rc = smk_bu_file(file, may, rc); + return rc; } /** * smack_file_open - Smack dentry open processing * @file: the object - * @cred: unused * * Set the security blob in the file structure. + * Allow the open only if the task has read access. There are + * many read operations (e.g. fstat) that you can do with an + * fd even if you have the file open write-only. * - * Returns 0 + * Returns 0 if current has access, error code otherwise */ -static int smack_file_open(struct file *file, const struct cred *cred) +static int smack_file_open(struct file *file) { - struct inode_smack *isp = file_inode(file)->i_security; + struct task_smack *tsp = smack_cred(file->f_cred); + struct inode *inode = file_inode(file); + struct smk_audit_info ad; + int rc; - file->f_security = isp->smk_inode; + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, file->f_path); + rc = smk_tskacc(tsp, smk_of_inode(inode), MAY_READ, &ad); + rc = smk_bu_credfile(file->f_cred, file, MAY_READ, rc); - return 0; + return rc; } /* @@ -1399,7 +2083,7 @@ static int smack_file_open(struct file *file, const struct cred *cred) /** * smack_cred_alloc_blank - "allocate" blank task-level security credentials - * @new: the new credentials + * @cred: the new credentials * @gfp: the atomicity of any memory allocations * * Prepare a blank set of credentials for modification. This must allocate all @@ -1408,14 +2092,7 @@ static int smack_file_open(struct file *file, const struct cred *cred) */ static int smack_cred_alloc_blank(struct cred *cred, gfp_t gfp) { - struct task_smack *tsp; - - tsp = new_task_smack(NULL, NULL, gfp); - if (tsp == NULL) - return -ENOMEM; - - cred->security = tsp; - + init_task_smack(smack_cred(cred), NULL, NULL); return 0; } @@ -1427,21 +2104,18 @@ static int smack_cred_alloc_blank(struct cred *cred, gfp_t gfp) */ static void smack_cred_free(struct cred *cred) { - struct task_smack *tsp = cred->security; + struct task_smack *tsp = smack_cred(cred); struct smack_rule *rp; struct list_head *l; struct list_head *n; - if (tsp == NULL) - return; - cred->security = NULL; + smk_destroy_label_list(&tsp->smk_relabel); list_for_each_safe(l, n, &tsp->smk_rules) { rp = list_entry(l, struct smack_rule, list); list_del(&rp->list); - kfree(rp); + kmem_cache_free(smack_rule_cache, rp); } - kfree(tsp); } /** @@ -1455,20 +2129,19 @@ static void smack_cred_free(struct cred *cred) static int smack_cred_prepare(struct cred *new, const struct cred *old, gfp_t gfp) { - struct task_smack *old_tsp = old->security; - struct task_smack *new_tsp; + struct task_smack *old_tsp = smack_cred(old); + struct task_smack *new_tsp = smack_cred(new); int rc; - new_tsp = new_task_smack(old_tsp->smk_task, old_tsp->smk_task, gfp); - if (new_tsp == NULL) - return -ENOMEM; + init_task_smack(new_tsp, old_tsp->smk_task, old_tsp->smk_task); rc = smk_copy_rules(&new_tsp->smk_rules, &old_tsp->smk_rules, gfp); if (rc != 0) return rc; - new->security = new_tsp; - return 0; + rc = smk_copy_relabel(&new_tsp->smk_relabel, &old_tsp->smk_relabel, + gfp); + return rc; } /** @@ -1480,16 +2153,42 @@ static int smack_cred_prepare(struct cred *new, const struct cred *old, */ static void smack_cred_transfer(struct cred *new, const struct cred *old) { - struct task_smack *old_tsp = old->security; - struct task_smack *new_tsp = new->security; + struct task_smack *old_tsp = smack_cred(old); + struct task_smack *new_tsp = smack_cred(new); - new_tsp->smk_task = old_tsp->smk_task; - new_tsp->smk_forked = old_tsp->smk_task; - mutex_init(&new_tsp->smk_rules_lock); - INIT_LIST_HEAD(&new_tsp->smk_rules); + init_task_smack(new_tsp, old_tsp->smk_task, old_tsp->smk_task); +} +/** + * smack_cred_getsecid - get the secid corresponding to a creds structure + * @cred: the object creds + * @secid: where to put the result + * + * Sets the secid to contain a u32 version of the smack label. + */ +static void smack_cred_getsecid(const struct cred *cred, u32 *secid) +{ + struct smack_known *skp; - /* cbs copy rule list */ + rcu_read_lock(); + skp = smk_of_task(smack_cred(cred)); + *secid = skp->smk_secid; + rcu_read_unlock(); +} + +/** + * smack_cred_getlsmprop - get the Smack label for a creds structure + * @cred: the object creds + * @prop: where to put the data + * + * Sets the Smack part of the ref + */ +static void smack_cred_getlsmprop(const struct cred *cred, + struct lsm_prop *prop) +{ + rcu_read_lock(); + prop->smack.skp = smk_of_task(smack_cred(cred)); + rcu_read_unlock(); } /** @@ -1501,13 +2200,9 @@ static void smack_cred_transfer(struct cred *new, const struct cred *old) */ static int smack_kernel_act_as(struct cred *new, u32 secid) { - struct task_smack *new_tsp = new->security; - struct smack_known *skp = smack_from_secid(secid); + struct task_smack *new_tsp = smack_cred(new); - if (skp == NULL) - return -EINVAL; - - new_tsp->smk_task = skp; + new_tsp->smk_task = smack_from_secid(secid); return 0; } @@ -1522,10 +2217,10 @@ static int smack_kernel_act_as(struct cred *new, u32 secid) static int smack_kernel_create_files_as(struct cred *new, struct inode *inode) { - struct inode_smack *isp = inode->i_security; - struct task_smack *tsp = new->security; + struct inode_smack *isp = smack_inode(inode); + struct task_smack *tsp = smack_cred(new); - tsp->smk_forked = smk_find_entry(isp->smk_inode); + tsp->smk_forked = isp->smk_inode; tsp->smk_task = tsp->smk_forked; return 0; } @@ -1542,11 +2237,14 @@ static int smk_curacc_on_task(struct task_struct *p, int access, const char *caller) { struct smk_audit_info ad; - struct smack_known *skp = smk_of_task(task_security(p)); + struct smack_known *skp = smk_of_task_struct_obj(p); + int rc; smk_ad_init(&ad, caller, LSM_AUDIT_DATA_TASK); smk_ad_setfield_u_tsk(&ad, p); - return smk_curacc(skp->smk_known, access, &ad); + rc = smk_curacc(skp, access, &ad); + rc = smk_bu_task(p, access, rc); + return rc; } /** @@ -1584,17 +2282,27 @@ static int smack_task_getsid(struct task_struct *p) } /** - * smack_task_getsecid - get the secid of the task - * @p: the object task - * @secid: where to put the result + * smack_current_getlsmprop_subj - get the subjective secid of the current task + * @prop: where to put the result * - * Sets the secid to contain a u32 version of the smack label. + * Sets the secid to contain a u32 version of the task's subjective smack label. */ -static void smack_task_getsecid(struct task_struct *p, u32 *secid) +static void smack_current_getlsmprop_subj(struct lsm_prop *prop) { - struct smack_known *skp = smk_of_task(task_security(p)); + prop->smack.skp = smk_of_current(); +} - *secid = skp->smk_secid; +/** + * smack_task_getlsmprop_obj - get the objective data of the task + * @p: the task + * @prop: where to put the result + * + * Sets the secid to contain a u32 version of the task's objective smack label. + */ +static void smack_task_getlsmprop_obj(struct task_struct *p, + struct lsm_prop *prop) +{ + prop->smack.skp = smk_of_task_struct_obj(p); } /** @@ -1606,12 +2314,7 @@ static void smack_task_getsecid(struct task_struct *p, u32 *secid) */ static int smack_task_setnice(struct task_struct *p, int nice) { - int rc; - - rc = cap_task_setnice(p, nice); - if (rc == 0) - rc = smk_curacc_on_task(p, MAY_WRITE, __func__); - return rc; + return smk_curacc_on_task(p, MAY_WRITE, __func__); } /** @@ -1623,12 +2326,7 @@ static int smack_task_setnice(struct task_struct *p, int nice) */ static int smack_task_setioprio(struct task_struct *p, int ioprio) { - int rc; - - rc = cap_task_setioprio(p, ioprio); - if (rc == 0) - rc = smk_curacc_on_task(p, MAY_WRITE, __func__); - return rc; + return smk_curacc_on_task(p, MAY_WRITE, __func__); } /** @@ -1645,19 +2343,12 @@ static int smack_task_getioprio(struct task_struct *p) /** * smack_task_setscheduler - Smack check on setting scheduler * @p: the task object - * @policy: unused - * @lp: unused * * Return 0 if read access is permitted */ static int smack_task_setscheduler(struct task_struct *p) { - int rc; - - rc = cap_task_setscheduler(p); - if (rc == 0) - rc = smk_curacc_on_task(p, MAY_WRITE, __func__); - return rc; + return smk_curacc_on_task(p, MAY_WRITE, __func__); } /** @@ -1687,19 +2378,21 @@ static int smack_task_movememory(struct task_struct *p) * @p: the task object * @info: unused * @sig: unused - * @secid: identifies the smack to use in lieu of current's + * @cred: identifies the cred to use in lieu of current's * * Return 0 if write access is permitted * - * The secid behavior is an artifact of an SELinux hack - * in the USB code. Someday it may go away. */ -static int smack_task_kill(struct task_struct *p, struct siginfo *info, - int sig, u32 secid) +static int smack_task_kill(struct task_struct *p, struct kernel_siginfo *info, + int sig, const struct cred *cred) { struct smk_audit_info ad; struct smack_known *skp; - struct smack_known *tkp = smk_of_task(task_security(p)); + struct smack_known *tkp = smk_of_task_struct_obj(p); + int rc; + + if (!sig) + return 0; /* null signal; existence test */ smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); smk_ad_setfield_u_tsk(&ad, p); @@ -1707,34 +2400,20 @@ static int smack_task_kill(struct task_struct *p, struct siginfo *info, * Sending a signal requires that the sender * can write the receiver. */ - if (secid == 0) - return smk_curacc(tkp->smk_known, MAY_WRITE, &ad); + if (cred == NULL) { + rc = smk_curacc(tkp, MAY_DELIVER, &ad); + rc = smk_bu_task(p, MAY_DELIVER, rc); + return rc; + } /* - * If the secid isn't 0 we're dealing with some USB IO + * If the cred isn't NULL we're dealing with some USB IO * specific behavior. This is not clean. For one thing * we can't take privilege into account. */ - skp = smack_from_secid(secid); - return smk_access(skp, tkp->smk_known, MAY_WRITE, &ad); -} - -/** - * smack_task_wait - Smack access check for waiting - * @p: task to wait for - * - * Returns 0 - */ -static int smack_task_wait(struct task_struct *p) -{ - /* - * Allow the operation to succeed. - * Zombies are bad. - * In userless environments (e.g. phones) programs - * get marked with SMACK64EXEC and even if the parent - * and child shouldn't be talking the parent still - * may expect to know when the child exits. - */ - return 0; + skp = smk_of_task(smack_cred(cred)); + rc = smk_access(skp, tkp, MAY_DELIVER, &ad); + rc = smk_bu_note("USB signal", skp, tkp, MAY_DELIVER, rc); + return rc; } /** @@ -1746,10 +2425,11 @@ static int smack_task_wait(struct task_struct *p) */ static void smack_task_to_inode(struct task_struct *p, struct inode *inode) { - struct inode_smack *isp = inode->i_security; - struct smack_known *skp = smk_of_task(task_security(p)); + struct inode_smack *isp = smack_inode(inode); + struct smack_known *skp = smk_of_task_struct_obj(p); - isp->smk_inode = skp->smk_known; + isp->smk_inode = skp; + isp->smk_flags |= SMK_INODE_INSTANT; } /* @@ -1769,21 +2449,24 @@ static void smack_task_to_inode(struct task_struct *p, struct inode *inode) static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags) { struct smack_known *skp = smk_of_current(); - struct socket_smack *ssp; - - ssp = kzalloc(sizeof(struct socket_smack), gfp_flags); - if (ssp == NULL) - return -ENOMEM; + struct socket_smack *ssp = smack_sock(sk); - ssp->smk_in = skp->smk_known; - ssp->smk_out = skp; + /* + * Sockets created by kernel threads receive web label. + */ + if (unlikely(current->flags & PF_KTHREAD)) { + ssp->smk_in = &smack_known_web; + ssp->smk_out = &smack_known_web; + } else { + ssp->smk_in = skp; + ssp->smk_out = skp; + } ssp->smk_packet = NULL; - sk->sk_security = ssp; - return 0; } +#ifdef SMACK_IPV6_PORT_LABELING /** * smack_sk_free_security - Free a socket blob * @sk: the socket @@ -1792,11 +2475,38 @@ static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags) */ static void smack_sk_free_security(struct sock *sk) { - kfree(sk->sk_security); + struct smk_port_label *spp; + + if (sk->sk_family == PF_INET6) { + rcu_read_lock(); + list_for_each_entry_rcu(spp, &smk_ipv6_port_list, list) { + if (spp->smk_sock != sk) + continue; + spp->smk_can_reuse = 1; + break; + } + rcu_read_unlock(); + } } +#endif /** -* smack_host_label - check host based restrictions + * smack_sk_clone_security - Copy security context + * @sk: the old socket + * @newsk: the new socket + * + * Copy the security context of the old socket pointer to the cloned + */ +static void smack_sk_clone_security(const struct sock *sk, struct sock *newsk) +{ + struct socket_smack *ssp_old = smack_sock(sk); + struct socket_smack *ssp_new = smack_sock(newsk); + + *ssp_new = *ssp_old; +} + +/** +* smack_ipv4host_label - check host based restrictions * @sip: the object end * * looks for host based access restrictions @@ -1807,64 +2517,124 @@ static void smack_sk_free_security(struct sock *sk) * * Returns the label of the far end or NULL if it's not special. */ -static char *smack_host_label(struct sockaddr_in *sip) +static struct smack_known *smack_ipv4host_label(struct sockaddr_in *sip) { - struct smk_netlbladdr *snp; + struct smk_net4addr *snp; struct in_addr *siap = &sip->sin_addr; if (siap->s_addr == 0) return NULL; - list_for_each_entry_rcu(snp, &smk_netlbladdr_list, list) + list_for_each_entry_rcu(snp, &smk_net4addr_list, list) + /* + * we break after finding the first match because + * the list is sorted from longest to shortest mask + * so we have found the most specific match + */ + if (snp->smk_host.s_addr == + (siap->s_addr & snp->smk_mask.s_addr)) + return snp->smk_label; + + return NULL; +} + +#if IS_ENABLED(CONFIG_IPV6) +/* + * smk_ipv6_localhost - Check for local ipv6 host address + * @sip: the address + * + * Returns boolean true if this is the localhost address + */ +static bool smk_ipv6_localhost(struct sockaddr_in6 *sip) +{ + __be16 *be16p = (__be16 *)&sip->sin6_addr; + __be32 *be32p = (__be32 *)&sip->sin6_addr; + + if (be32p[0] == 0 && be32p[1] == 0 && be32p[2] == 0 && be16p[6] == 0 && + ntohs(be16p[7]) == 1) + return true; + return false; +} + +/** +* smack_ipv6host_label - check host based restrictions +* @sip: the object end +* +* looks for host based access restrictions +* +* This version will only be appropriate for really small sets of single label +* hosts. The caller is responsible for ensuring that the RCU read lock is +* taken before calling this function. +* +* Returns the label of the far end or NULL if it's not special. +*/ +static struct smack_known *smack_ipv6host_label(struct sockaddr_in6 *sip) +{ + struct smk_net6addr *snp; + struct in6_addr *sap = &sip->sin6_addr; + int i; + int found = 0; + + /* + * It's local. Don't look for a host label. + */ + if (smk_ipv6_localhost(sip)) + return NULL; + + list_for_each_entry_rcu(snp, &smk_net6addr_list, list) { + /* + * If the label is NULL the entry has + * been renounced. Ignore it. + */ + if (snp->smk_label == NULL) + continue; /* * we break after finding the first match because * the list is sorted from longest to shortest mask * so we have found the most specific match */ - if ((&snp->smk_host.sin_addr)->s_addr == - (siap->s_addr & (&snp->smk_mask)->s_addr)) { - /* we have found the special CIPSO option */ - if (snp->smk_label == smack_cipso_option) - return NULL; - return snp->smk_label; + for (found = 1, i = 0; i < 8; i++) { + if ((sap->s6_addr16[i] & snp->smk_mask.s6_addr16[i]) != + snp->smk_host.s6_addr16[i]) { + found = 0; + break; + } } + if (found) + return snp->smk_label; + } return NULL; } +#endif /* CONFIG_IPV6 */ /** - * smack_netlabel - Set the secattr on a socket + * smack_netlbl_add - Set the secattr on a socket * @sk: the socket - * @labeled: socket label scheme * - * Convert the outbound smack value (smk_out) to a - * secattr and attach it to the socket. + * Attach the outbound smack value (smk_out) to the socket. * * Returns 0 on success or an error code */ -static int smack_netlabel(struct sock *sk, int labeled) +static int smack_netlbl_add(struct sock *sk) { - struct smack_known *skp; - struct socket_smack *ssp = sk->sk_security; - int rc = 0; + struct socket_smack *ssp = smack_sock(sk); + struct smack_known *skp = ssp->smk_out; + int rc; - /* - * Usually the netlabel code will handle changing the - * packet labeling based on the label. - * The case of a single label host is different, because - * a single label host should never get a labeled packet - * even though the label is usually associated with a packet - * label. - */ local_bh_disable(); bh_lock_sock_nested(sk); - if (ssp->smk_out == smack_net_ambient || - labeled == SMACK_UNLABELED_SOCKET) - netlbl_sock_delattr(sk); - else { - skp = ssp->smk_out; - rc = netlbl_sock_setattr(sk, sk->sk_family, &skp->smk_netlabel); + rc = netlbl_sock_setattr(sk, sk->sk_family, &skp->smk_netlabel, + netlbl_sk_lock_check(sk)); + switch (rc) { + case 0: + ssp->smk_state = SMK_NETLBL_LABELED; + break; + case -EDESTADDRREQ: + ssp->smk_state = SMK_NETLBL_REQSKB; + rc = 0; + break; } bh_unlock_sock(sk); @@ -1874,7 +2644,31 @@ static int smack_netlabel(struct sock *sk, int labeled) } /** - * smack_netlbel_send - Set the secattr on a socket and perform access checks + * smack_netlbl_delete - Remove the secattr from a socket + * @sk: the socket + * + * Remove the outbound smack value from a socket + */ +static void smack_netlbl_delete(struct sock *sk) +{ + struct socket_smack *ssp = smack_sock(sk); + + /* + * Take the label off the socket if one is set. + */ + if (ssp->smk_state != SMK_NETLBL_LABELED) + return; + + local_bh_disable(); + bh_lock_sock_nested(sk); + netlbl_sock_delattr(sk); + bh_unlock_sock(sk); + local_bh_enable(); + ssp->smk_state = SMK_NETLBL_UNLABELED; +} + +/** + * smk_ipv4_check - Perform IPv4 host access checks * @sk: the socket * @sap: the destination address * @@ -1884,18 +2678,17 @@ static int smack_netlabel(struct sock *sk, int labeled) * Returns 0 on success or an error code. * */ -static int smack_netlabel_send(struct sock *sk, struct sockaddr_in *sap) +static int smk_ipv4_check(struct sock *sk, struct sockaddr_in *sap) { struct smack_known *skp; - int rc; - int sk_lbl; - char *hostsp; - struct socket_smack *ssp = sk->sk_security; + int rc = 0; + struct smack_known *hkp; + struct socket_smack *ssp = smack_sock(sk); struct smk_audit_info ad; rcu_read_lock(); - hostsp = smack_host_label(sap); - if (hostsp != NULL) { + hkp = smack_ipv4host_label(sap); + if (hkp != NULL) { #ifdef CONFIG_AUDIT struct lsm_network_audit net; @@ -1904,20 +2697,56 @@ static int smack_netlabel_send(struct sock *sk, struct sockaddr_in *sap) ad.a.u.net->dport = sap->sin_port; ad.a.u.net->v4info.daddr = sap->sin_addr.s_addr; #endif - sk_lbl = SMACK_UNLABELED_SOCKET; skp = ssp->smk_out; - rc = smk_access(skp, hostsp, MAY_WRITE, &ad); - } else { - sk_lbl = SMACK_CIPSO_SOCKET; - rc = 0; + rc = smk_access(skp, hkp, MAY_WRITE, &ad); + rc = smk_bu_note("IPv4 host check", skp, hkp, MAY_WRITE, rc); + /* + * Clear the socket netlabel if it's set. + */ + if (!rc) + smack_netlbl_delete(sk); } rcu_read_unlock(); - if (rc != 0) - return rc; - return smack_netlabel(sk, sk_lbl); + return rc; +} + +#if IS_ENABLED(CONFIG_IPV6) +/** + * smk_ipv6_check - check Smack access + * @subject: subject Smack label + * @object: object Smack label + * @address: address + * @act: the action being taken + * + * Check an IPv6 access + */ +static int smk_ipv6_check(struct smack_known *subject, + struct smack_known *object, + struct sockaddr_in6 *address, int act) +{ +#ifdef CONFIG_AUDIT + struct lsm_network_audit net; +#endif + struct smk_audit_info ad; + int rc; + +#ifdef CONFIG_AUDIT + smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); + ad.a.u.net->family = PF_INET6; + ad.a.u.net->dport = address->sin6_port; + if (act == SMK_RECEIVING) + ad.a.u.net->v6info.saddr = address->sin6_addr; + else + ad.a.u.net->v6info.daddr = address->sin6_addr; +#endif + rc = smk_access(subject, object, MAY_WRITE, &ad); + rc = smk_bu_note("IPv6 check", subject, object, MAY_WRITE, rc); + return rc; } +#endif /* CONFIG_IPV6 */ +#ifdef SMACK_IPV6_PORT_LABELING /** * smk_ipv6_port_label - Smack port access table management * @sock: socket @@ -1929,7 +2758,7 @@ static void smk_ipv6_port_label(struct socket *sock, struct sockaddr *address) { struct sock *sk = sock->sk; struct sockaddr_in6 *addr6; - struct socket_smack *ssp = sock->sk->sk_security; + struct socket_smack *ssp = smack_sock(sock->sk); struct smk_port_label *spp; unsigned short port = 0; @@ -1939,17 +2768,20 @@ static void smk_ipv6_port_label(struct socket *sock, struct sockaddr *address) * on the bound socket. Take the changes to the port * as well. */ - list_for_each_entry(spp, &smk_ipv6_port_list, list) { + rcu_read_lock(); + list_for_each_entry_rcu(spp, &smk_ipv6_port_list, list) { if (sk != spp->smk_sock) continue; spp->smk_in = ssp->smk_in; spp->smk_out = ssp->smk_out; + rcu_read_unlock(); return; } /* * A NULL address is only used for updating existing * bound entries. If there isn't one, it's OK. */ + rcu_read_unlock(); return; } @@ -1965,16 +2797,23 @@ static void smk_ipv6_port_label(struct socket *sock, struct sockaddr *address) * Look for an existing port list entry. * This is an indication that a port is getting reused. */ - list_for_each_entry(spp, &smk_ipv6_port_list, list) { - if (spp->smk_port != port) + rcu_read_lock(); + list_for_each_entry_rcu(spp, &smk_ipv6_port_list, list) { + if (spp->smk_port != port || spp->smk_sock_type != sock->type) continue; + if (spp->smk_can_reuse != 1) { + rcu_read_unlock(); + return; + } spp->smk_port = port; spp->smk_sock = sk; spp->smk_in = ssp->smk_in; spp->smk_out = ssp->smk_out; + spp->smk_can_reuse = 0; + rcu_read_unlock(); return; } - + rcu_read_unlock(); /* * A new port entry is required. */ @@ -1986,86 +2825,77 @@ static void smk_ipv6_port_label(struct socket *sock, struct sockaddr *address) spp->smk_sock = sk; spp->smk_in = ssp->smk_in; spp->smk_out = ssp->smk_out; + spp->smk_sock_type = sock->type; + spp->smk_can_reuse = 0; - list_add(&spp->list, &smk_ipv6_port_list); + mutex_lock(&smack_ipv6_lock); + list_add_rcu(&spp->list, &smk_ipv6_port_list); + mutex_unlock(&smack_ipv6_lock); return; } /** * smk_ipv6_port_check - check Smack port access - * @sock: socket + * @sk: socket * @address: address + * @act: the action being taken * * Create or update the port list entry */ -static int smk_ipv6_port_check(struct sock *sk, struct sockaddr *address, +static int smk_ipv6_port_check(struct sock *sk, struct sockaddr_in6 *address, int act) { - __be16 *bep; - __be32 *be32p; - struct sockaddr_in6 *addr6; struct smk_port_label *spp; - struct socket_smack *ssp = sk->sk_security; - struct smack_known *skp; - unsigned short port = 0; - char *object; - struct smk_audit_info ad; -#ifdef CONFIG_AUDIT - struct lsm_network_audit net; -#endif + struct socket_smack *ssp = smack_sock(sk); + struct smack_known *skp = NULL; + unsigned short port; + struct smack_known *object; if (act == SMK_RECEIVING) { - skp = smack_net_ambient; + skp = smack_ipv6host_label(address); object = ssp->smk_in; } else { skp = ssp->smk_out; - object = smack_net_ambient->smk_known; + object = smack_ipv6host_label(address); } /* - * Get the IP address and port from the address. + * The other end is a single label host. */ - addr6 = (struct sockaddr_in6 *)address; - port = ntohs(addr6->sin6_port); - bep = (__be16 *)(&addr6->sin6_addr); - be32p = (__be32 *)(&addr6->sin6_addr); + if (skp != NULL && object != NULL) + return smk_ipv6_check(skp, object, address, act); + if (skp == NULL) + skp = smack_net_ambient; + if (object == NULL) + object = smack_net_ambient; /* * It's remote, so port lookup does no good. */ - if (be32p[0] || be32p[1] || be32p[2] || bep[6] || ntohs(bep[7]) != 1) - goto auditout; + if (!smk_ipv6_localhost(address)) + return smk_ipv6_check(skp, object, address, act); /* * It's local so the send check has to have passed. */ - if (act == SMK_RECEIVING) { - skp = &smack_known_web; - goto auditout; - } + if (act == SMK_RECEIVING) + return 0; - list_for_each_entry(spp, &smk_ipv6_port_list, list) { - if (spp->smk_port != port) + port = ntohs(address->sin6_port); + rcu_read_lock(); + list_for_each_entry_rcu(spp, &smk_ipv6_port_list, list) { + if (spp->smk_port != port || spp->smk_sock_type != sk->sk_type) continue; object = spp->smk_in; if (act == SMK_CONNECTING) - ssp->smk_packet = spp->smk_out->smk_known; + ssp->smk_packet = spp->smk_out; break; } + rcu_read_unlock(); -auditout: - -#ifdef CONFIG_AUDIT - smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); - ad.a.u.net->family = sk->sk_family; - ad.a.u.net->dport = port; - if (act == SMK_RECEIVING) - ad.a.u.net->v6info.saddr = addr6->sin6_addr; - else - ad.a.u.net->v6info.daddr = addr6->sin6_addr; -#endif - return smk_access(skp, object, MAY_WRITE, &ad); + return smk_ipv6_check(skp, object, address, act); } +#endif /** * smack_inode_setsecurity - set smack xattrs @@ -2083,20 +2913,29 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags) { struct smack_known *skp; - struct inode_smack *nsp = inode->i_security; + struct inode_smack *nsp = smack_inode(inode); struct socket_smack *ssp; struct socket *sock; int rc = 0; if (value == NULL || size > SMK_LONGLABEL || size == 0) - return -EACCES; + return -EINVAL; + + if (strcmp(name, XATTR_SMACK_TRANSMUTE) == 0) { + if (!S_ISDIR(inode->i_mode) || size != TRANS_TRUE_SIZE || + strncmp(value, TRANS_TRUE, TRANS_TRUE_SIZE) != 0) + return -EINVAL; + + nsp->smk_flags |= SMK_INODE_TRANSMUTE; + return 0; + } skp = smk_import_entry(value, size); - if (skp == NULL) - return -EINVAL; + if (IS_ERR(skp)) + return PTR_ERR(skp); if (strcmp(name, XATTR_SMACK_SUFFIX) == 0) { - nsp->smk_inode = skp->smk_known; + nsp->smk_inode = skp; nsp->smk_flags |= SMK_INODE_INSTANT; return 0; } @@ -2110,14 +2949,14 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name, if (sock == NULL || sock->sk == NULL) return -EOPNOTSUPP; - ssp = sock->sk->sk_security; + ssp = smack_sock(sock->sk); if (strcmp(name, XATTR_SMACK_IPIN) == 0) - ssp->smk_in = skp->smk_known; + ssp->smk_in = skp; else if (strcmp(name, XATTR_SMACK_IPOUT) == 0) { ssp->smk_out = skp; if (sock->sk->sk_family == PF_INET) { - rc = smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); + rc = smack_netlbl_add(sock->sk); if (rc != 0) printk(KERN_WARNING "Smack: \"%s\" netlbl error %d.\n", @@ -2126,8 +2965,10 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name, } else return -EOPNOTSUPP; +#ifdef SMACK_IPV6_PORT_LABELING if (sock->sk->sk_family == PF_INET6) smk_ipv6_port_label(sock, NULL); +#endif return 0; } @@ -2147,15 +2988,51 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name, static int smack_socket_post_create(struct socket *sock, int family, int type, int protocol, int kern) { - if (family != PF_INET || sock->sk == NULL) + struct socket_smack *ssp; + + if (sock->sk == NULL) + return 0; + + /* + * Sockets created by kernel threads receive web label. + */ + if (unlikely(current->flags & PF_KTHREAD)) { + ssp = smack_sock(sock->sk); + ssp->smk_in = &smack_known_web; + ssp->smk_out = &smack_known_web; + } + + if (family != PF_INET) return 0; /* * Set the outbound netlbl. */ - return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); + return smack_netlbl_add(sock->sk); } /** + * smack_socket_socketpair - create socket pair + * @socka: one socket + * @sockb: another socket + * + * Cross reference the peer labels for SO_PEERSEC + * + * Returns 0 + */ +static int smack_socket_socketpair(struct socket *socka, + struct socket *sockb) +{ + struct socket_smack *asp = smack_sock(socka->sk); + struct socket_smack *bsp = smack_sock(sockb->sk); + + asp->smk_packet = bsp->smk_out; + bsp->smk_packet = asp->smk_out; + + return 0; +} + +#ifdef SMACK_IPV6_PORT_LABELING +/** * smack_socket_bind - record port binding information. * @sock: the socket * @address: the port address @@ -2163,16 +3040,20 @@ static int smack_socket_post_create(struct socket *sock, int family, * * Records the label bound to a port. * - * Returns 0 + * Returns 0 on success, and error code otherwise */ static int smack_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) { - if (sock->sk != NULL && sock->sk->sk_family == PF_INET6) + if (sock->sk != NULL && sock->sk->sk_family == PF_INET6) { + if (addrlen < SIN6_LEN_RFC2133 || + address->sa_family != AF_INET6) + return -EINVAL; smk_ipv6_port_label(sock, address); - + } return 0; } +#endif /* SMACK_IPV6_PORT_LABELING */ /** * smack_socket_connect - connect access check @@ -2191,19 +3072,38 @@ static int smack_socket_connect(struct socket *sock, struct sockaddr *sap, if (sock->sk == NULL) return 0; + if (sock->sk->sk_family != PF_INET && + (!IS_ENABLED(CONFIG_IPV6) || sock->sk->sk_family != PF_INET6)) + return 0; + if (addrlen < offsetofend(struct sockaddr, sa_family)) + return 0; - switch (sock->sk->sk_family) { - case PF_INET: - if (addrlen < sizeof(struct sockaddr_in)) - return -EINVAL; - rc = smack_netlabel_send(sock->sk, (struct sockaddr_in *)sap); - break; - case PF_INET6: - if (addrlen < sizeof(struct sockaddr_in6)) - return -EINVAL; - rc = smk_ipv6_port_check(sock->sk, sap, SMK_CONNECTING); - break; +#if IS_ENABLED(CONFIG_IPV6) + if (sap->sa_family == AF_INET6) { + struct sockaddr_in6 *sip = (struct sockaddr_in6 *)sap; + struct smack_known *rsp = NULL; + + if (addrlen < SIN6_LEN_RFC2133) + return 0; + if (__is_defined(SMACK_IPV6_SECMARK_LABELING)) + rsp = smack_ipv6host_label(sip); + if (rsp != NULL) { + struct socket_smack *ssp = smack_sock(sock->sk); + + rc = smk_ipv6_check(ssp->smk_out, rsp, sip, + SMK_CONNECTING); + } +#ifdef SMACK_IPV6_PORT_LABELING + rc = smk_ipv6_port_check(sock->sk, sip, SMK_CONNECTING); +#endif + + return rc; } +#endif /* CONFIG_IPV6 */ + + if (sap->sa_family != AF_INET || addrlen < sizeof(struct sockaddr_in)) + return 0; + rc = smk_ipv4_check(sock->sk, (struct sockaddr_in *)sap); return rc; } @@ -2235,110 +3135,91 @@ static int smack_flags_to_may(int flags) */ static int smack_msg_msg_alloc_security(struct msg_msg *msg) { - struct smack_known *skp = smk_of_current(); + struct smack_known **blob = smack_msg_msg(msg); - msg->security = skp->smk_known; + *blob = smk_of_current(); return 0; } /** - * smack_msg_msg_free_security - Clear the security blob for msg_msg - * @msg: the object - * - * Clears the blob pointer - */ -static void smack_msg_msg_free_security(struct msg_msg *msg) -{ - msg->security = NULL; -} - -/** - * smack_of_shm - the smack pointer for the shm - * @shp: the object + * smack_of_ipc - the smack pointer for the ipc + * @isp: the object * * Returns a pointer to the smack value */ -static char *smack_of_shm(struct shmid_kernel *shp) +static struct smack_known *smack_of_ipc(struct kern_ipc_perm *isp) { - return (char *)shp->shm_perm.security; + struct smack_known **blob = smack_ipc(isp); + + return *blob; } /** - * smack_shm_alloc_security - Set the security blob for shm - * @shp: the object + * smack_ipc_alloc_security - Set the security blob for ipc + * @isp: the object * * Returns 0 */ -static int smack_shm_alloc_security(struct shmid_kernel *shp) +static int smack_ipc_alloc_security(struct kern_ipc_perm *isp) { - struct kern_ipc_perm *isp = &shp->shm_perm; - struct smack_known *skp = smk_of_current(); + struct smack_known **blob = smack_ipc(isp); - isp->security = skp->smk_known; + *blob = smk_of_current(); return 0; } /** - * smack_shm_free_security - Clear the security blob for shm - * @shp: the object - * - * Clears the blob pointer - */ -static void smack_shm_free_security(struct shmid_kernel *shp) -{ - struct kern_ipc_perm *isp = &shp->shm_perm; - - isp->security = NULL; -} - -/** * smk_curacc_shm : check if current has access on shm - * @shp : the object + * @isp : the object * @access : access requested * * Returns 0 if current has the requested access, error code otherwise */ -static int smk_curacc_shm(struct shmid_kernel *shp, int access) +static int smk_curacc_shm(struct kern_ipc_perm *isp, int access) { - char *ssp = smack_of_shm(shp); + struct smack_known *ssp = smack_of_ipc(isp); struct smk_audit_info ad; + int rc; #ifdef CONFIG_AUDIT smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_IPC); - ad.a.u.ipc_id = shp->shm_perm.id; + ad.a.u.ipc_id = isp->id; #endif - return smk_curacc(ssp, access, &ad); + rc = smk_curacc(ssp, access, &ad); + rc = smk_bu_current("shm", ssp, access, rc); + return rc; } /** * smack_shm_associate - Smack access check for shm - * @shp: the object + * @isp: the object * @shmflg: access requested * * Returns 0 if current has the requested access, error code otherwise */ -static int smack_shm_associate(struct shmid_kernel *shp, int shmflg) +static int smack_shm_associate(struct kern_ipc_perm *isp, int shmflg) { int may; may = smack_flags_to_may(shmflg); - return smk_curacc_shm(shp, may); + return smk_curacc_shm(isp, may); } /** * smack_shm_shmctl - Smack access check for shm - * @shp: the object + * @isp: the object * @cmd: what it wants to do * * Returns 0 if current has the requested access, error code otherwise */ -static int smack_shm_shmctl(struct shmid_kernel *shp, int cmd) +static int smack_shm_shmctl(struct kern_ipc_perm *isp, int cmd) { int may; switch (cmd) { case IPC_STAT: case SHM_STAT: + case SHM_STAT_ANY: may = MAY_READ; break; case IPC_SET: @@ -2356,107 +3237,71 @@ static int smack_shm_shmctl(struct shmid_kernel *shp, int cmd) default: return -EINVAL; } - return smk_curacc_shm(shp, may); + return smk_curacc_shm(isp, may); } /** * smack_shm_shmat - Smack access for shmat - * @shp: the object + * @isp: the object * @shmaddr: unused * @shmflg: access requested * * Returns 0 if current has the requested access, error code otherwise */ -static int smack_shm_shmat(struct shmid_kernel *shp, char __user *shmaddr, +static int smack_shm_shmat(struct kern_ipc_perm *isp, char __user *shmaddr, int shmflg) { int may; may = smack_flags_to_may(shmflg); - return smk_curacc_shm(shp, may); -} - -/** - * smack_of_sem - the smack pointer for the sem - * @sma: the object - * - * Returns a pointer to the smack value - */ -static char *smack_of_sem(struct sem_array *sma) -{ - return (char *)sma->sem_perm.security; -} - -/** - * smack_sem_alloc_security - Set the security blob for sem - * @sma: the object - * - * Returns 0 - */ -static int smack_sem_alloc_security(struct sem_array *sma) -{ - struct kern_ipc_perm *isp = &sma->sem_perm; - struct smack_known *skp = smk_of_current(); - - isp->security = skp->smk_known; - return 0; -} - -/** - * smack_sem_free_security - Clear the security blob for sem - * @sma: the object - * - * Clears the blob pointer - */ -static void smack_sem_free_security(struct sem_array *sma) -{ - struct kern_ipc_perm *isp = &sma->sem_perm; - - isp->security = NULL; + return smk_curacc_shm(isp, may); } /** * smk_curacc_sem : check if current has access on sem - * @sma : the object + * @isp : the object * @access : access requested * * Returns 0 if current has the requested access, error code otherwise */ -static int smk_curacc_sem(struct sem_array *sma, int access) +static int smk_curacc_sem(struct kern_ipc_perm *isp, int access) { - char *ssp = smack_of_sem(sma); + struct smack_known *ssp = smack_of_ipc(isp); struct smk_audit_info ad; + int rc; #ifdef CONFIG_AUDIT smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_IPC); - ad.a.u.ipc_id = sma->sem_perm.id; + ad.a.u.ipc_id = isp->id; #endif - return smk_curacc(ssp, access, &ad); + rc = smk_curacc(ssp, access, &ad); + rc = smk_bu_current("sem", ssp, access, rc); + return rc; } /** * smack_sem_associate - Smack access check for sem - * @sma: the object + * @isp: the object * @semflg: access requested * * Returns 0 if current has the requested access, error code otherwise */ -static int smack_sem_associate(struct sem_array *sma, int semflg) +static int smack_sem_associate(struct kern_ipc_perm *isp, int semflg) { int may; may = smack_flags_to_may(semflg); - return smk_curacc_sem(sma, may); + return smk_curacc_sem(isp, may); } /** - * smack_sem_shmctl - Smack access check for sem - * @sma: the object + * smack_sem_semctl - Smack access check for sem + * @isp: the object * @cmd: what it wants to do * * Returns 0 if current has the requested access, error code otherwise */ -static int smack_sem_semctl(struct sem_array *sma, int cmd) +static int smack_sem_semctl(struct kern_ipc_perm *isp, int cmd) { int may; @@ -2468,6 +3313,7 @@ static int smack_sem_semctl(struct sem_array *sma, int cmd) case GETALL: case IPC_STAT: case SEM_STAT: + case SEM_STAT_ANY: may = MAY_READ; break; case SETVAL: @@ -2486,12 +3332,12 @@ static int smack_sem_semctl(struct sem_array *sma, int cmd) return -EINVAL; } - return smk_curacc_sem(sma, may); + return smk_curacc_sem(isp, may); } /** * smack_sem_semop - Smack checks of semaphore operations - * @sma: the object + * @isp: the object * @sops: unused * @nsops: unused * @alter: unused @@ -2500,99 +3346,64 @@ static int smack_sem_semctl(struct sem_array *sma, int cmd) * * Returns 0 if access is allowed, error code otherwise */ -static int smack_sem_semop(struct sem_array *sma, struct sembuf *sops, +static int smack_sem_semop(struct kern_ipc_perm *isp, struct sembuf *sops, unsigned nsops, int alter) { - return smk_curacc_sem(sma, MAY_READWRITE); -} - -/** - * smack_msg_alloc_security - Set the security blob for msg - * @msq: the object - * - * Returns 0 - */ -static int smack_msg_queue_alloc_security(struct msg_queue *msq) -{ - struct kern_ipc_perm *kisp = &msq->q_perm; - struct smack_known *skp = smk_of_current(); - - kisp->security = skp->smk_known; - return 0; -} - -/** - * smack_msg_free_security - Clear the security blob for msg - * @msq: the object - * - * Clears the blob pointer - */ -static void smack_msg_queue_free_security(struct msg_queue *msq) -{ - struct kern_ipc_perm *kisp = &msq->q_perm; - - kisp->security = NULL; -} - -/** - * smack_of_msq - the smack pointer for the msq - * @msq: the object - * - * Returns a pointer to the smack value - */ -static char *smack_of_msq(struct msg_queue *msq) -{ - return (char *)msq->q_perm.security; + return smk_curacc_sem(isp, MAY_READWRITE); } /** * smk_curacc_msq : helper to check if current has access on msq - * @msq : the msq + * @isp : the msq * @access : access requested * * return 0 if current has access, error otherwise */ -static int smk_curacc_msq(struct msg_queue *msq, int access) +static int smk_curacc_msq(struct kern_ipc_perm *isp, int access) { - char *msp = smack_of_msq(msq); + struct smack_known *msp = smack_of_ipc(isp); struct smk_audit_info ad; + int rc; #ifdef CONFIG_AUDIT smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_IPC); - ad.a.u.ipc_id = msq->q_perm.id; + ad.a.u.ipc_id = isp->id; #endif - return smk_curacc(msp, access, &ad); + rc = smk_curacc(msp, access, &ad); + rc = smk_bu_current("msq", msp, access, rc); + return rc; } /** * smack_msg_queue_associate - Smack access check for msg_queue - * @msq: the object + * @isp: the object * @msqflg: access requested * * Returns 0 if current has the requested access, error code otherwise */ -static int smack_msg_queue_associate(struct msg_queue *msq, int msqflg) +static int smack_msg_queue_associate(struct kern_ipc_perm *isp, int msqflg) { int may; may = smack_flags_to_may(msqflg); - return smk_curacc_msq(msq, may); + return smk_curacc_msq(isp, may); } /** * smack_msg_queue_msgctl - Smack access check for msg_queue - * @msq: the object + * @isp: the object * @cmd: what it wants to do * * Returns 0 if current has the requested access, error code otherwise */ -static int smack_msg_queue_msgctl(struct msg_queue *msq, int cmd) +static int smack_msg_queue_msgctl(struct kern_ipc_perm *isp, int cmd) { int may; switch (cmd) { case IPC_STAT: case MSG_STAT: + case MSG_STAT_ANY: may = MAY_READ; break; case IPC_SET: @@ -2609,29 +3420,29 @@ static int smack_msg_queue_msgctl(struct msg_queue *msq, int cmd) return -EINVAL; } - return smk_curacc_msq(msq, may); + return smk_curacc_msq(isp, may); } /** * smack_msg_queue_msgsnd - Smack access check for msg_queue - * @msq: the object + * @isp: the object * @msg: unused * @msqflg: access requested * * Returns 0 if current has the requested access, error code otherwise */ -static int smack_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg, +static int smack_msg_queue_msgsnd(struct kern_ipc_perm *isp, struct msg_msg *msg, int msqflg) { int may; may = smack_flags_to_may(msqflg); - return smk_curacc_msq(msq, may); + return smk_curacc_msq(isp, may); } /** - * smack_msg_queue_msgsnd - Smack access check for msg_queue - * @msq: the object + * smack_msg_queue_msgrcv - Smack access check for msg_queue + * @isp: the object * @msg: unused * @target: unused * @type: unused @@ -2639,10 +3450,12 @@ static int smack_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg, * * Returns 0 if current has read and write access, error code otherwise */ -static int smack_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg, - struct task_struct *target, long type, int mode) +static int smack_msg_queue_msgrcv(struct kern_ipc_perm *isp, + struct msg_msg *msg, + struct task_struct *target, long type, + int mode) { - return smk_curacc_msq(msq, MAY_READWRITE); + return smk_curacc_msq(isp, MAY_READWRITE); } /** @@ -2654,27 +3467,31 @@ static int smack_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg, */ static int smack_ipc_permission(struct kern_ipc_perm *ipp, short flag) { - char *isp = ipp->security; + struct smack_known **blob = smack_ipc(ipp); + struct smack_known *iskp = *blob; int may = smack_flags_to_may(flag); struct smk_audit_info ad; + int rc; #ifdef CONFIG_AUDIT smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_IPC); ad.a.u.ipc_id = ipp->id; #endif - return smk_curacc(isp, may, &ad); + rc = smk_curacc(iskp, may, &ad); + rc = smk_bu_current("svipc", iskp, may, rc); + return rc; } /** - * smack_ipc_getsecid - Extract smack security id + * smack_ipc_getlsmprop - Extract smack security data * @ipp: the object permissions - * @secid: where result will be saved + * @prop: where result will be saved */ -static void smack_ipc_getsecid(struct kern_ipc_perm *ipp, u32 *secid) +static void smack_ipc_getlsmprop(struct kern_ipc_perm *ipp, struct lsm_prop *prop) { - char *smack = ipp->security; + struct smack_known **iskpp = smack_ipc(ipp); - *secid = smack_to_secid(smack); + prop->smack.skp = *iskpp; } /** @@ -2691,7 +3508,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) struct inode_smack *isp; struct smack_known *skp; struct smack_known *ckp = smk_of_current(); - char *final; + struct smack_known *final; char trattr[TRANS_TRUE_SIZE]; int transflag = 0; int rc; @@ -2700,18 +3517,17 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) if (inode == NULL) return; - isp = inode->i_security; + isp = smack_inode(inode); - mutex_lock(&isp->smk_lock); /* * If the inode is already instantiated * take the quick way out */ if (isp->smk_flags & SMK_INODE_INSTANT) - goto unlockandout; + return; sbp = inode->i_sb; - sbsp = sbp->s_security; + sbsp = smack_superblock(sbp); /* * We're going to use the superblock default label * if there's no label on the file. @@ -2725,9 +3541,41 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) * of the superblock. */ if (opt_dentry->d_parent == opt_dentry) { - isp->smk_inode = sbsp->smk_root; + switch (sbp->s_magic) { + case CGROUP_SUPER_MAGIC: + case CGROUP2_SUPER_MAGIC: + /* + * The cgroup filesystem is never mounted, + * so there's no opportunity to set the mount + * options. + */ + sbsp->smk_root = &smack_known_star; + sbsp->smk_default = &smack_known_star; + isp->smk_inode = sbsp->smk_root; + break; + case TMPFS_MAGIC: + /* + * What about shmem/tmpfs anonymous files with dentry + * obtained from d_alloc_pseudo()? + */ + isp->smk_inode = smk_of_current(); + break; + case PIPEFS_MAGIC: + isp->smk_inode = smk_of_current(); + break; + case SOCKFS_MAGIC: + /* + * Socket access is controlled by the socket + * structures associated with the task involved. + */ + isp->smk_inode = &smack_known_star; + break; + default: + isp->smk_inode = sbsp->smk_root; + break; + } isp->smk_flags |= SMK_INODE_INSTANT; - goto unlockandout; + return; } /* @@ -2738,18 +3586,16 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) */ switch (sbp->s_magic) { case SMACK_MAGIC: + case CGROUP_SUPER_MAGIC: + case CGROUP2_SUPER_MAGIC: /* * Casey says that it's a little embarrassing * that the smack file system doesn't do * extended attributes. + * + * Cgroupfs is special */ - final = smack_known_star.smk_known; - break; - case PIPEFS_MAGIC: - /* - * Casey says pipes are easy (?) - */ - final = smack_known_star.smk_known; + final = &smack_known_star; break; case DEVPTS_SUPER_MAGIC: /* @@ -2757,14 +3603,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) * Programs that change smack have to treat the * pty with respect. */ - final = ckp->smk_known; - break; - case SOCKFS_MAGIC: - /* - * Socket access is controlled by the socket - * structures associated with the task involved. - */ - final = smack_known_star.smk_known; + final = ckp; break; case PROC_SUPER_MAGIC: /* @@ -2778,15 +3617,14 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) * but watch out, because they're volitile, * getting recreated on every reboot. */ - final = smack_known_star.smk_known; + final = &smack_known_star; /* - * No break. - * * If a smack value has been set we want to use it, * but since tmpfs isn't giving us the opportunity * to set mount options simulate setting the * superblock default. */ + fallthrough; default: /* * This isn't an understood special case. @@ -2794,10 +3632,10 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) */ /* - * UNIX domain sockets use lower level socket data. + * UDS inode has fixed label (*) */ if (S_ISSOCK(inode->i_mode)) { - final = smack_known_star.smk_known; + final = &smack_known_star; break; } /* @@ -2806,15 +3644,15 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) * It would be curious if the label of the task * does not match that assigned. */ - if (inode->i_op->getxattr == NULL) - break; + if (!(inode->i_opflags & IOP_XATTR)) + break; /* * Get the dentry for xattr. */ dp = dget(opt_dentry); skp = smk_fetch(XATTR_NAME_SMACK, inode, dp); - if (skp != NULL) - final = skp->smk_known; + if (!IS_ERR_OR_NULL(skp)) + final = skp; /* * Transmuting directory @@ -2829,43 +3667,74 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) * If there is a transmute attribute on the * directory mark the inode. */ - if (isp->smk_flags & SMK_INODE_CHANGED) { - isp->smk_flags &= ~SMK_INODE_CHANGED; - rc = inode->i_op->setxattr(dp, - XATTR_NAME_SMACKTRANSMUTE, - TRANS_TRUE, TRANS_TRUE_SIZE, - 0); - } else { - rc = inode->i_op->getxattr(dp, - XATTR_NAME_SMACKTRANSMUTE, trattr, - TRANS_TRUE_SIZE); - if (rc >= 0 && strncmp(trattr, TRANS_TRUE, - TRANS_TRUE_SIZE) != 0) - rc = -EINVAL; - } + rc = __vfs_getxattr(dp, inode, + XATTR_NAME_SMACKTRANSMUTE, trattr, + TRANS_TRUE_SIZE); + if (rc >= 0 && strncmp(trattr, TRANS_TRUE, + TRANS_TRUE_SIZE) != 0) + rc = -EINVAL; if (rc >= 0) transflag = SMK_INODE_TRANSMUTE; } - isp->smk_task = smk_fetch(XATTR_NAME_SMACKEXEC, inode, dp); - isp->smk_mmap = smk_fetch(XATTR_NAME_SMACKMMAP, inode, dp); + /* + * Don't let the exec or mmap label be "*" or "@". + */ + skp = smk_fetch(XATTR_NAME_SMACKEXEC, inode, dp); + if (IS_ERR(skp) || skp == &smack_known_star || + skp == &smack_known_web) + skp = NULL; + isp->smk_task = skp; + + skp = smk_fetch(XATTR_NAME_SMACKMMAP, inode, dp); + if (IS_ERR(skp) || skp == &smack_known_star || + skp == &smack_known_web) + skp = NULL; + isp->smk_mmap = skp; dput(dp); break; } if (final == NULL) - isp->smk_inode = ckp->smk_known; + isp->smk_inode = ckp; else isp->smk_inode = final; isp->smk_flags |= (SMK_INODE_INSTANT | transflag); -unlockandout: - mutex_unlock(&isp->smk_lock); return; } /** + * smack_getselfattr - Smack current process attribute + * @attr: which attribute to fetch + * @ctx: buffer to receive the result + * @size: available size in, actual size out + * @flags: reserved, currently zero + * + * Fill the passed user space @ctx with the details of the requested + * attribute. + * + * Returns the number of attributes on success, an error code otherwise. + * There will only ever be one attribute. + */ +static int smack_getselfattr(unsigned int attr, struct lsm_ctx __user *ctx, + u32 *size, u32 flags) +{ + int rc; + struct smack_known *skp; + + if (attr != LSM_ATTR_CURRENT) + return -EOPNOTSUPP; + + skp = smk_of_current(); + rc = lsm_fill_user_ctx(ctx, size, + skp->smk_known, strlen(skp->smk_known) + 1, + LSM_ID_SMACK, 0); + return (!rc ? 1 : rc); +} + +/** * smack_getprocattr - Smack process attribute access * @p: the object task * @name: the name of the attribute in /proc/.../attr @@ -2875,9 +3744,9 @@ unlockandout: * * Returns the length of the smack label or an error code */ -static int smack_getprocattr(struct task_struct *p, char *name, char **value) +static int smack_getprocattr(struct task_struct *p, const char *name, char **value) { - struct smack_known *skp = smk_of_task(task_security(p)); + struct smack_known *skp = smk_of_task_struct_obj(p); char *cp; int slen; @@ -2894,59 +3763,157 @@ static int smack_getprocattr(struct task_struct *p, char *name, char **value) } /** - * smack_setprocattr - Smack process attribute setting - * @p: the object task - * @name: the name of the attribute in /proc/.../attr + * do_setattr - Smack process attribute setting + * @attr: the ID of the attribute * @value: the value to set * @size: the size of the value * * Sets the Smack value of the task. Only setting self * is permitted and only with privilege * - * Returns the length of the smack label or an error code + * Returns zero on success or an error code */ -static int smack_setprocattr(struct task_struct *p, char *name, - void *value, size_t size) +static int do_setattr(unsigned int attr, void *value, size_t size) { - struct task_smack *tsp; + struct task_smack *tsp = smack_cred(current_cred()); struct cred *new; struct smack_known *skp; + int label_len; /* - * Changing another process' Smack value is too dangerous - * and supports no sane use case. + * let unprivileged user validate input, check permissions later */ - if (p != current) - return -EPERM; - - if (!smack_privileged(CAP_MAC_ADMIN)) - return -EPERM; - if (value == NULL || size == 0 || size >= SMK_LONGLABEL) return -EINVAL; - if (strcmp(name, "current") != 0) - return -EINVAL; - - skp = smk_import_entry(value, size); - if (skp == NULL) + label_len = smk_parse_label_len(value, size); + if (label_len < 0 || label_len != size) return -EINVAL; /* - * No process is ever allowed the web ("@") label. + * No process is ever allowed the web ("@") label + * and the star ("*") label. */ - if (skp == &smack_known_web) + if (label_len == 1 /* '@', '*' */) { + const char c = *(const char *)value; + + if (c == *smack_known_web.smk_known || + c == *smack_known_star.smk_known) + return -EPERM; + } + + if (!smack_privileged(CAP_MAC_ADMIN)) { + const struct smack_known_list_elem *sklep; + list_for_each_entry(sklep, &tsp->smk_relabel, list) { + const char *cp = sklep->smk_label->smk_known; + + if (strlen(cp) == label_len && + strncmp(cp, value, label_len) == 0) + goto in_relabel; + } return -EPERM; +in_relabel: + ; + } + + skp = smk_import_valid_label(value, label_len, GFP_KERNEL); + if (IS_ERR(skp)) + return PTR_ERR(skp); new = prepare_creds(); if (new == NULL) return -ENOMEM; - tsp = new->security; + tsp = smack_cred(new); tsp->smk_task = skp; + /* + * process can change its label only once + */ + smk_destroy_label_list(&tsp->smk_relabel); commit_creds(new); - return size; + return 0; +} + +/** + * smack_setselfattr - Set a Smack process attribute + * @attr: which attribute to set + * @ctx: buffer containing the data + * @size: size of @ctx + * @flags: reserved, must be zero + * + * Fill the passed user space @ctx with the details of the requested + * attribute. + * + * Returns 0 on success, an error code otherwise. + */ +static int smack_setselfattr(unsigned int attr, struct lsm_ctx *ctx, + u32 size, u32 flags) +{ + if (attr != LSM_ATTR_CURRENT) + return -EOPNOTSUPP; + + if (ctx->flags) + return -EINVAL; + /* + * string must have \0 terminator, included in ctx->ctx + * (see description of struct lsm_ctx) + */ + if (ctx->ctx_len == 0) + return -EINVAL; + + if (ctx->ctx[ctx->ctx_len - 1] != '\0') + return -EINVAL; + /* + * other do_setattr() caller, smack_setprocattr(), + * does not count \0 into size, so + * decreasing length by 1 to accommodate the divergence. + */ + return do_setattr(attr, ctx->ctx, ctx->ctx_len - 1); +} + +/** + * smack_setprocattr - Smack process attribute setting + * @name: the name of the attribute in /proc/.../attr + * @value: the value to set + * @size: the size of the value + * + * Sets the Smack value of the task. Only setting self + * is permitted and only with privilege + * + * Returns the size of the input value or an error code + */ +static int smack_setprocattr(const char *name, void *value, size_t size) +{ + size_t realsize = size; + unsigned int attr = lsm_name_to_attr(name); + + switch (attr) { + case LSM_ATTR_UNDEF: return -EINVAL; + default: return -EOPNOTSUPP; + case LSM_ATTR_CURRENT: + ; + } + + /* + * The value for the "current" attribute is the label + * followed by one of the 4 trailers: none, \0, \n, \n\0 + * + * I.e. following inputs are accepted as 3-characters long label "foo": + * + * "foo" (3 characters) + * "foo\0" (4 characters) + * "foo\n" (4 characters) + * "foo\n\0" (5 characters) + */ + + if (realsize && (((const char *)value)[realsize - 1] == '\0')) + --realsize; + + if (realsize && (((const char *)value)[realsize - 1] == '\n')) + --realsize; + + return do_setattr(attr, value, realsize) ? : size; } /** @@ -2962,30 +3929,46 @@ static int smack_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk) { struct smack_known *skp; - struct socket_smack *ssp = sock->sk_security; - struct socket_smack *osp = other->sk_security; - struct socket_smack *nsp = newsk->sk_security; + struct smack_known *okp; + struct socket_smack *ssp = smack_sock(sock); + struct socket_smack *osp = smack_sock(other); + struct socket_smack *nsp = smack_sock(newsk); struct smk_audit_info ad; int rc = 0; - #ifdef CONFIG_AUDIT struct lsm_network_audit net; - - smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); - smk_ad_setfield_u_net_sk(&ad, other); #endif if (!smack_privileged(CAP_MAC_OVERRIDE)) { skp = ssp->smk_out; - rc = smk_access(skp, osp->smk_in, MAY_WRITE, &ad); + okp = osp->smk_in; +#ifdef CONFIG_AUDIT + smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); + smk_ad_setfield_u_net_sk(&ad, other); +#endif + rc = smk_access(skp, okp, MAY_WRITE, &ad); + rc = smk_bu_note("UDS connect", skp, okp, MAY_WRITE, rc); + if (rc == 0) { + okp = osp->smk_out; + skp = ssp->smk_in; + rc = smk_access(okp, skp, MAY_WRITE, &ad); + rc = smk_bu_note("UDS connect", okp, skp, + MAY_WRITE, rc); + } } - /* - * Cross reference the peer labels for SO_PEERSEC. - */ if (rc == 0) { - nsp->smk_packet = ssp->smk_out->smk_known; - ssp->smk_packet = osp->smk_out->smk_known; + /* + * Cross reference the peer labels for SO_PEERSEC. + */ + nsp->smk_packet = ssp->smk_out; + ssp->smk_packet = osp->smk_out; + + /* + * new/child/established socket must inherit listening socket labels + */ + nsp->smk_out = osp->smk_out; + nsp->smk_in = osp->smk_in; } return rc; @@ -3001,10 +3984,10 @@ static int smack_unix_stream_connect(struct sock *sock, */ static int smack_unix_may_send(struct socket *sock, struct socket *other) { - struct socket_smack *ssp = sock->sk->sk_security; - struct socket_smack *osp = other->sk->sk_security; - struct smack_known *skp; + struct socket_smack *ssp = smack_sock(sock->sk); + struct socket_smack *osp = smack_sock(other->sk); struct smk_audit_info ad; + int rc; #ifdef CONFIG_AUDIT struct lsm_network_audit net; @@ -3016,8 +3999,9 @@ static int smack_unix_may_send(struct socket *sock, struct socket *other) if (smack_privileged(CAP_MAC_OVERRIDE)) return 0; - skp = ssp->smk_out; - return smk_access(skp, osp->smk_in, MAY_WRITE, &ad); + rc = smk_access(ssp->smk_out, osp->smk_in, MAY_WRITE, &ad); + rc = smk_bu_note("UDS send", ssp->smk_out, osp->smk_in, MAY_WRITE, rc); + return rc; } /** @@ -3034,7 +4018,13 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { struct sockaddr_in *sip = (struct sockaddr_in *) msg->msg_name; - struct sockaddr *sap = (struct sockaddr *) msg->msg_name; +#if IS_ENABLED(CONFIG_IPV6) + struct sockaddr_in6 *sap = (struct sockaddr_in6 *) msg->msg_name; +#endif +#ifdef SMACK_IPV6_SECMARK_LABELING + struct socket_smack *ssp = smack_sock(sock->sk); + struct smack_known *rsp; +#endif int rc = 0; /* @@ -3043,12 +4033,28 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, if (sip == NULL) return 0; - switch (sip->sin_family) { + switch (sock->sk->sk_family) { case AF_INET: - rc = smack_netlabel_send(sock->sk, sip); + if (msg->msg_namelen < sizeof(struct sockaddr_in) || + sip->sin_family != AF_INET) + return -EINVAL; + rc = smk_ipv4_check(sock->sk, sip); break; +#if IS_ENABLED(CONFIG_IPV6) case AF_INET6: + if (msg->msg_namelen < SIN6_LEN_RFC2133 || + sap->sin6_family != AF_INET6) + return -EINVAL; +#ifdef SMACK_IPV6_SECMARK_LABELING + rsp = smack_ipv6host_label(sap); + if (rsp != NULL) + rc = smk_ipv6_check(ssp->smk_out, rsp, sap, + SMK_CONNECTING); +#endif +#ifdef SMACK_IPV6_PORT_LABELING rc = smk_ipv6_port_check(sock->sk, sap, SMK_SENDING); +#endif +#endif /* IS_ENABLED(CONFIG_IPV6) */ break; } return rc; @@ -3066,6 +4072,20 @@ static struct smack_known *smack_from_secattr(struct netlbl_lsm_secattr *sap, { struct smack_known *skp; int found = 0; + int acat; + int kcat; + + /* + * Netlabel found it in the cache. + */ + if ((sap->flags & NETLBL_SECATTR_CACHE) != 0) + return (struct smack_known *)sap->cache->data; + + if ((sap->flags & NETLBL_SECATTR_SECID) != 0) + /* + * Looks like a fallback, which gives us a secid. + */ + return smack_from_secid(sap->attr.secid); if ((sap->flags & NETLBL_SECATTR_MLS_LVL) != 0) { /* @@ -3079,40 +4099,41 @@ static struct smack_known *smack_from_secattr(struct netlbl_lsm_secattr *sap, * ambient value. */ rcu_read_lock(); - list_for_each_entry(skp, &smack_known_list, list) { + list_for_each_entry_rcu(skp, &smack_known_list, list) { if (sap->attr.mls.lvl != skp->smk_netlabel.attr.mls.lvl) continue; - if (memcmp(sap->attr.mls.cat, - skp->smk_netlabel.attr.mls.cat, - SMK_CIPSOLEN) != 0) - continue; - found = 1; - break; + /* + * Compare the catsets. Use the netlbl APIs. + */ + if ((sap->flags & NETLBL_SECATTR_MLS_CAT) == 0) { + if ((skp->smk_netlabel.flags & + NETLBL_SECATTR_MLS_CAT) == 0) + found = 1; + break; + } + for (acat = -1, kcat = -1; acat == kcat; ) { + acat = netlbl_catmap_walk(sap->attr.mls.cat, + acat + 1); + kcat = netlbl_catmap_walk( + skp->smk_netlabel.attr.mls.cat, + kcat + 1); + if (acat < 0 || kcat < 0) + break; + } + if (acat == kcat) { + found = 1; + break; + } } rcu_read_unlock(); if (found) return skp; - if (ssp != NULL && ssp->smk_in == smack_known_star.smk_known) + if (ssp != NULL && ssp->smk_in == &smack_known_star) return &smack_known_web; return &smack_known_star; } - if ((sap->flags & NETLBL_SECATTR_SECID) != 0) { - /* - * Looks like a fallback, which gives us a secid. - */ - skp = smack_from_secid(sap->attr.secid); - /* - * This has got to be a bug because it is - * impossible to specify a fallback without - * specifying the label, which will ensure - * it has a secid, and the only way to get a - * secid is from a fallback. - */ - BUG_ON(skp == NULL); - return skp; - } /* * Without guidance regarding the smack value * for the packet fall back on the network @@ -3121,9 +4142,9 @@ static struct smack_known *smack_from_secattr(struct netlbl_lsm_secattr *sap, return smack_net_ambient; } -static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr *sap) +#if IS_ENABLED(CONFIG_IPV6) +static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr_in6 *sip) { - struct sockaddr_in6 *sip = (struct sockaddr_in6 *)sap; u8 nexthdr; int offset; int proto = -EINVAL; @@ -3132,7 +4153,6 @@ static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr *sap) __be16 frag_off; struct tcphdr _tcph, *th; struct udphdr _udph, *uh; - struct dccp_hdr _dccph, *dh; sip->sin6_port = 0; @@ -3156,18 +4176,70 @@ static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr *sap) sip->sin6_port = th->source; break; case IPPROTO_UDP: + case IPPROTO_UDPLITE: uh = skb_header_pointer(skb, offset, sizeof(_udph), &_udph); if (uh != NULL) sip->sin6_port = uh->source; break; - case IPPROTO_DCCP: - dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); - if (dh != NULL) - sip->sin6_port = dh->dccph_sport; - break; } return proto; } +#endif /* CONFIG_IPV6 */ + +/** + * smack_from_skb - Smack data from the secmark in an skb + * @skb: packet + * + * Returns smack_known of the secmark or NULL if that won't work. + */ +#ifdef CONFIG_NETWORK_SECMARK +static struct smack_known *smack_from_skb(struct sk_buff *skb) +{ + if (skb == NULL || skb->secmark == 0) + return NULL; + + return smack_from_secid(skb->secmark); +} +#else +static inline struct smack_known *smack_from_skb(struct sk_buff *skb) +{ + return NULL; +} +#endif + +/** + * smack_from_netlbl - Smack data from the IP options in an skb + * @sk: socket data came in on + * @family: address family + * @skb: packet + * + * Find the Smack label in the IP options. If it hasn't been + * added to the netlabel cache, add it here. + * + * Returns smack_known of the IP options or NULL if that won't work. + */ +static struct smack_known *smack_from_netlbl(const struct sock *sk, u16 family, + struct sk_buff *skb) +{ + struct netlbl_lsm_secattr secattr; + struct socket_smack *ssp = NULL; + struct smack_known *skp = NULL; + + netlbl_secattr_init(&secattr); + + if (sk) + ssp = smack_sock(sk); + + if (netlbl_skbuff_getattr(skb, family, &secattr) == 0) { + skp = smack_from_secattr(&secattr, ssp); + if (secattr.flags & NETLBL_SECATTR_CACHEABLE) + netlbl_cache_add(skb, family, &skp->smk_netlabel); + } + + netlbl_secattr_destroy(&secattr); + + return skp; +} /** * smack_socket_sock_rcv_skb - Smack packet delivery access check @@ -3178,54 +4250,89 @@ static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr *sap) */ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) { - struct netlbl_lsm_secattr secattr; - struct socket_smack *ssp = sk->sk_security; - struct smack_known *skp; - struct sockaddr sadd; + struct socket_smack *ssp = smack_sock(sk); + struct smack_known *skp = NULL; int rc = 0; struct smk_audit_info ad; + u16 family = sk->sk_family; #ifdef CONFIG_AUDIT struct lsm_network_audit net; #endif - switch (sk->sk_family) { +#if IS_ENABLED(CONFIG_IPV6) + struct sockaddr_in6 sadd; + int proto; + + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; +#endif /* CONFIG_IPV6 */ + + switch (family) { case PF_INET: /* - * Translate what netlabel gave us. + * If there is a secmark use it rather than the CIPSO label. + * If there is no secmark fall back to CIPSO. + * The secmark is assumed to reflect policy better. */ - netlbl_secattr_init(&secattr); - - rc = netlbl_skbuff_getattr(skb, sk->sk_family, &secattr); - if (rc == 0) - skp = smack_from_secattr(&secattr, ssp); - else - skp = smack_net_ambient; - - netlbl_secattr_destroy(&secattr); + skp = smack_from_skb(skb); + if (skp == NULL) { + skp = smack_from_netlbl(sk, family, skb); + if (skp == NULL) + skp = smack_net_ambient; + } #ifdef CONFIG_AUDIT smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); - ad.a.u.net->family = sk->sk_family; + ad.a.u.net->family = family; ad.a.u.net->netif = skb->skb_iif; ipv4_skb_to_auditdata(skb, &ad.a, NULL); #endif /* * Receiving a packet requires that the other end * be able to write here. Read access is not required. - * This is the simplist possible security model + * This is the simplest possible security model * for networking. */ rc = smk_access(skp, ssp->smk_in, MAY_WRITE, &ad); + rc = smk_bu_note("IPv4 delivery", skp, ssp->smk_in, + MAY_WRITE, rc); if (rc != 0) - netlbl_skbuff_err(skb, rc, 0); + netlbl_skbuff_err(skb, family, rc, 0); break; +#if IS_ENABLED(CONFIG_IPV6) case PF_INET6: - rc = smk_skb_to_addr_ipv6(skb, &sadd); - if (rc == IPPROTO_UDP || rc == IPPROTO_TCP) - rc = smk_ipv6_port_check(sk, &sadd, SMK_RECEIVING); - else - rc = 0; + proto = smk_skb_to_addr_ipv6(skb, &sadd); + if (proto != IPPROTO_UDP && proto != IPPROTO_UDPLITE && + proto != IPPROTO_TCP) + break; +#ifdef SMACK_IPV6_SECMARK_LABELING + skp = smack_from_skb(skb); + if (skp == NULL) { + if (smk_ipv6_localhost(&sadd)) + break; + skp = smack_ipv6host_label(&sadd); + if (skp == NULL) + skp = smack_net_ambient; + } +#ifdef CONFIG_AUDIT + smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); + ad.a.u.net->family = family; + ad.a.u.net->netif = skb->skb_iif; + ipv6_skb_to_auditdata(skb, &ad.a, NULL); +#endif /* CONFIG_AUDIT */ + rc = smk_access(skp, ssp->smk_in, MAY_WRITE, &ad); + rc = smk_bu_note("IPv6 delivery", skp, ssp->smk_in, + MAY_WRITE, rc); +#endif /* SMACK_IPV6_SECMARK_LABELING */ +#ifdef SMACK_IPV6_PORT_LABELING + rc = smk_ipv6_port_check(sk, &sadd, SMK_RECEIVING); +#endif /* SMACK_IPV6_PORT_LABELING */ + if (rc != 0) + icmpv6_send(skb, ICMPV6_DEST_UNREACH, + ICMPV6_ADM_PROHIBITED, 0); break; +#endif /* CONFIG_IPV6 */ } + return rc; } @@ -3239,28 +4346,29 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) * returns zero on success, an error code otherwise */ static int smack_socket_getpeersec_stream(struct socket *sock, - char __user *optval, - int __user *optlen, unsigned len) + sockptr_t optval, sockptr_t optlen, + unsigned int len) { struct socket_smack *ssp; char *rcp = ""; - int slen = 1; + u32 slen = 1; int rc = 0; - ssp = sock->sk->sk_security; + ssp = smack_sock(sock->sk); if (ssp->smk_packet != NULL) { - rcp = ssp->smk_packet; + rcp = ssp->smk_packet->smk_known; slen = strlen(rcp) + 1; } - - if (slen > len) + if (slen > len) { rc = -ERANGE; - else if (copy_to_user(optval, rcp, slen) != 0) - rc = -EFAULT; + goto out_len; + } - if (put_user(slen, optlen) != 0) + if (copy_to_sockptr(optval, rcp, slen)) + rc = -EFAULT; +out_len: + if (copy_to_sockptr(optlen, &slen, sizeof(slen))) rc = -EFAULT; - return rc; } @@ -3277,38 +4385,50 @@ static int smack_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid) { - struct netlbl_lsm_secattr secattr; struct socket_smack *ssp = NULL; struct smack_known *skp; + struct sock *sk = NULL; int family = PF_UNSPEC; u32 s = 0; /* 0 is the invalid secid */ - int rc; if (skb != NULL) { if (skb->protocol == htons(ETH_P_IP)) family = PF_INET; +#if IS_ENABLED(CONFIG_IPV6) else if (skb->protocol == htons(ETH_P_IPV6)) family = PF_INET6; +#endif /* CONFIG_IPV6 */ } if (family == PF_UNSPEC && sock != NULL) family = sock->sk->sk_family; - if (family == PF_UNIX) { - ssp = sock->sk->sk_security; + switch (family) { + case PF_UNIX: + ssp = smack_sock(sock->sk); s = ssp->smk_out->smk_secid; - } else if (family == PF_INET || family == PF_INET6) { + break; + case PF_INET: + skp = smack_from_skb(skb); + if (skp) { + s = skp->smk_secid; + break; + } /* * Translate what netlabel gave us. */ - if (sock != NULL && sock->sk != NULL) - ssp = sock->sk->sk_security; - netlbl_secattr_init(&secattr); - rc = netlbl_skbuff_getattr(skb, family, &secattr); - if (rc == 0) { - skp = smack_from_secattr(&secattr, ssp); + if (sock != NULL) + sk = sock->sk; + skp = smack_from_netlbl(sk, family, skb); + if (skp != NULL) s = skp->smk_secid; - } - netlbl_secattr_destroy(&secattr); + break; + case PF_INET6: +#ifdef SMACK_IPV6_SECMARK_LABELING + skp = smack_from_skb(skb); + if (skp) + s = skp->smk_secid; +#endif + break; } *secid = s; if (s == 0) @@ -3317,29 +4437,6 @@ static int smack_socket_getpeersec_dgram(struct socket *sock, } /** - * smack_sock_graft - Initialize a newly created socket with an existing sock - * @sk: child sock - * @parent: parent socket - * - * Set the smk_{in,out} state of an existing sock based on the process that - * is creating the new socket. - */ -static void smack_sock_graft(struct sock *sk, struct socket *parent) -{ - struct socket_smack *ssp; - struct smack_known *skp = smk_of_current(); - - if (sk == NULL || - (sk->sk_family != PF_INET && sk->sk_family != PF_INET6)) - return; - - ssp = sk->sk_security; - ssp->smk_in = skp->smk_known; - ssp->smk_out = skp; - /* cssp->smk_packet is already set in smack_inet_csk_clone() */ -} - -/** * smack_inet_conn_request - Smack access check on connect * @sk: socket involved * @skb: packet @@ -3348,22 +4445,22 @@ static void smack_sock_graft(struct sock *sk, struct socket *parent) * Returns 0 if a task with the packet label could write to * the socket, otherwise an error code */ -static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, +static int smack_inet_conn_request(const struct sock *sk, struct sk_buff *skb, struct request_sock *req) { u16 family = sk->sk_family; struct smack_known *skp; - struct socket_smack *ssp = sk->sk_security; - struct netlbl_lsm_secattr secattr; + struct socket_smack *ssp = smack_sock(sk); struct sockaddr_in addr; struct iphdr *hdr; - char *hsp; + struct smack_known *hskp; int rc; struct smk_audit_info ad; #ifdef CONFIG_AUDIT struct lsm_network_audit net; #endif +#if IS_ENABLED(CONFIG_IPV6) if (family == PF_INET6) { /* * Handle mapped IPv4 packets arriving @@ -3375,14 +4472,19 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, else return 0; } +#endif /* CONFIG_IPV6 */ - netlbl_secattr_init(&secattr); - rc = netlbl_skbuff_getattr(skb, family, &secattr); - if (rc == 0) - skp = smack_from_secattr(&secattr, ssp); - else - skp = &smack_known_huh; - netlbl_secattr_destroy(&secattr); + /* + * If there is a secmark use it rather than the CIPSO label. + * If there is no secmark fall back to CIPSO. + * The secmark is assumed to reflect policy better. + */ + skp = smack_from_skb(skb); + if (skp == NULL) { + skp = smack_from_netlbl(sk, family, skb); + if (skp == NULL) + skp = &smack_known_huh; + } #ifdef CONFIG_AUDIT smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); @@ -3395,6 +4497,7 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, * here. Read access is not required. */ rc = smk_access(skp, ssp->smk_in, MAY_WRITE, &ad); + rc = smk_bu_note("IPv4 connect", skp, ssp->smk_in, MAY_WRITE, rc); if (rc != 0) return rc; @@ -3412,11 +4515,11 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, hdr = ip_hdr(skb); addr.sin_addr.s_addr = hdr->saddr; rcu_read_lock(); - hsp = smack_host_label(&addr); + hskp = smack_ipv4host_label(&addr); rcu_read_unlock(); - if (hsp == NULL) - rc = netlbl_req_setattr(req, &skp->smk_netlabel); + if (hskp == NULL) + rc = netlbl_req_setattr(req, &ssp->smk_out->smk_netlabel); else netlbl_req_delattr(req); @@ -3433,12 +4536,12 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, static void smack_inet_csk_clone(struct sock *sk, const struct request_sock *req) { - struct socket_smack *ssp = sk->sk_security; + struct socket_smack *ssp = smack_sock(sk); struct smack_known *skp; if (req->peer_secid != 0) { skp = smack_from_secid(req->peer_secid); - ssp->smk_packet = skp->smk_known; + ssp->smk_packet = skp; } else ssp->smk_packet = NULL; } @@ -3465,38 +4568,57 @@ static void smack_inet_csk_clone(struct sock *sk, static int smack_key_alloc(struct key *key, const struct cred *cred, unsigned long flags) { - struct smack_known *skp = smk_of_task(cred->security); + struct smack_known **blob = smack_key(key); + struct smack_known *skp = smk_of_task(smack_cred(cred)); - key->security = skp->smk_known; + *blob = skp; return 0; } /** - * smack_key_free - Clear the key security blob - * @key: the object - * - * Clear the blob pointer - */ -static void smack_key_free(struct key *key) -{ - key->security = NULL; -} - -/* * smack_key_permission - Smack access on a key * @key_ref: gets to the object * @cred: the credentials to use - * @perm: unused + * @need_perm: requested key permission * * Return 0 if the task has read and write to the object, * an error code otherwise */ static int smack_key_permission(key_ref_t key_ref, - const struct cred *cred, key_perm_t perm) + const struct cred *cred, + enum key_need_perm need_perm) { + struct smack_known **blob; + struct smack_known *skp; struct key *keyp; struct smk_audit_info ad; - struct smack_known *tkp = smk_of_task(cred->security); + struct smack_known *tkp = smk_of_task(smack_cred(cred)); + int request = 0; + int rc; + + /* + * Validate requested permissions + */ + switch (need_perm) { + case KEY_NEED_READ: + case KEY_NEED_SEARCH: + case KEY_NEED_VIEW: + request |= MAY_READ; + break; + case KEY_NEED_WRITE: + case KEY_NEED_LINK: + case KEY_NEED_SETATTR: + request |= MAY_WRITE; + break; + case KEY_NEED_UNSPECIFIED: + case KEY_NEED_UNLINK: + case KEY_SYSADMIN_OVERRIDE: + case KEY_AUTHTOKEN_OVERRIDE: + case KEY_DEFER_PERM_CHECK: + return 0; + default: + return -EINVAL; + } keyp = key_ref_to_ptr(key_ref); if (keyp == NULL) @@ -3505,22 +4627,127 @@ static int smack_key_permission(key_ref_t key_ref, * If the key hasn't been initialized give it access so that * it may do so. */ - if (keyp->security == NULL) + blob = smack_key(keyp); + skp = *blob; + if (skp == NULL) return 0; /* * This should not occur */ if (tkp == NULL) return -EACCES; + + if (smack_privileged(CAP_MAC_OVERRIDE)) + return 0; + #ifdef CONFIG_AUDIT smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_KEY); ad.a.u.key_struct.key = keyp->serial; ad.a.u.key_struct.key_desc = keyp->description; #endif - return smk_access(tkp, keyp->security, MAY_READWRITE, &ad); + rc = smk_access(tkp, skp, request, &ad); + rc = smk_bu_note("key access", tkp, skp, request, rc); + return rc; } + +/* + * smack_key_getsecurity - Smack label tagging the key + * @key points to the key to be queried + * @_buffer points to a pointer that should be set to point to the + * resulting string (if no label or an error occurs). + * Return the length of the string (including terminating NUL) or -ve if + * an error. + * May also return 0 (and a NULL buffer pointer) if there is no label. + */ +static int smack_key_getsecurity(struct key *key, char **_buffer) +{ + struct smack_known **blob = smack_key(key); + struct smack_known *skp = *blob; + size_t length; + char *copy; + + if (skp == NULL) { + *_buffer = NULL; + return 0; + } + + copy = kstrdup(skp->smk_known, GFP_KERNEL); + if (copy == NULL) + return -ENOMEM; + length = strlen(copy) + 1; + + *_buffer = copy; + return length; +} + + +#ifdef CONFIG_KEY_NOTIFICATIONS +/** + * smack_watch_key - Smack access to watch a key for notifications. + * @key: The key to be watched + * + * Return 0 if the @watch->cred has permission to read from the key object and + * an error otherwise. + */ +static int smack_watch_key(struct key *key) +{ + struct smk_audit_info ad; + struct smack_known *tkp = smk_of_current(); + struct smack_known **blob = smack_key(key); + int rc; + + /* + * This should not occur + */ + if (tkp == NULL) + return -EACCES; + + if (smack_privileged_cred(CAP_MAC_OVERRIDE, current_cred())) + return 0; + +#ifdef CONFIG_AUDIT + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_KEY); + ad.a.u.key_struct.key = key->serial; + ad.a.u.key_struct.key_desc = key->description; +#endif + rc = smk_access(tkp, *blob, MAY_READ, &ad); + rc = smk_bu_note("key watch", tkp, *blob, MAY_READ, rc); + return rc; +} +#endif /* CONFIG_KEY_NOTIFICATIONS */ #endif /* CONFIG_KEYS */ +#ifdef CONFIG_WATCH_QUEUE +/** + * smack_post_notification - Smack access to post a notification to a queue + * @w_cred: The credentials of the watcher. + * @cred: The credentials of the event source (may be NULL). + * @n: The notification message to be posted. + */ +static int smack_post_notification(const struct cred *w_cred, + const struct cred *cred, + struct watch_notification *n) +{ + struct smk_audit_info ad; + struct smack_known *subj, *obj; + int rc; + + /* Always let maintenance notifications through. */ + if (n->type == WATCH_TYPE_META) + return 0; + + if (!cred) + return 0; + subj = smk_of_task(smack_cred(cred)); + obj = smk_of_task(smack_cred(w_cred)); + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NOTIFICATION); + rc = smk_access(subj, obj, MAY_WRITE, &ad); + rc = smk_bu_note("notification", subj, obj, MAY_WRITE, rc); + return rc; +} +#endif /* CONFIG_WATCH_QUEUE */ + /* * Smack Audit hooks * @@ -3542,12 +4769,15 @@ static int smack_key_permission(key_ref_t key_ref, * @op: required testing operator (=, !=, >, <, ...) * @rulestr: smack label to be audited * @vrule: pointer to save our own audit rule representation + * @gfp: type of the memory for the allocation * * Prepare to audit cases where (@field @op @rulestr) is true. - * The label to be audited is created if necessay. + * The label to be audited is created if necessary. */ -static int smack_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) +static int smack_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule, + gfp_t gfp) { + struct smack_known *skp; char **rule = (char **)vrule; *rule = NULL; @@ -3557,7 +4787,11 @@ static int smack_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) if (op != Audit_equal && op != Audit_not_equal) return -EINVAL; - *rule = smk_import(rulestr, 0); + skp = smk_import_entry(rulestr, 0); + if (IS_ERR(skp)) + return PTR_ERR(skp); + + *rule = skp->smk_known; return 0; } @@ -3587,32 +4821,28 @@ static int smack_audit_rule_known(struct audit_krule *krule) /** * smack_audit_rule_match - Audit given object ? - * @secid: security id for identifying the object to test + * @prop: security id for identifying the object to test * @field: audit rule flags given from user-space * @op: required testing operator * @vrule: smack internal rule presentation - * @actx: audit context associated with the check * * The core Audit hook. It's used to take the decision of * whether to audit or not to audit a given object. */ -static int smack_audit_rule_match(u32 secid, u32 field, u32 op, void *vrule, - struct audit_context *actx) +static int smack_audit_rule_match(struct lsm_prop *prop, u32 field, u32 op, + void *vrule) { - struct smack_known *skp; + struct smack_known *skp = prop->smack.skp; char *rule = vrule; - if (!rule) { - audit_log(actx, GFP_ATOMIC, AUDIT_SELINUX_ERR, - "Smack: missing rule\n"); + if (unlikely(!rule)) { + WARN_ONCE(1, "Smack: missing rule\n"); return -ENOENT; } if (field != AUDIT_SUBJ_USER && field != AUDIT_OBJ_USER) return 0; - skp = smack_from_secid(secid); - /* * No need to do string comparisons. If a match occurs, * both pointers will point to the same smack_known @@ -3626,16 +4856,10 @@ static int smack_audit_rule_match(u32 secid, u32 field, u32 op, void *vrule, return 0; } -/** - * smack_audit_rule_free - free smack rule representation - * @vrule: rule to be freed. - * +/* + * There is no need for a smack_audit_rule_free hook. * No memory was allocated. */ -static void smack_audit_rule_free(void *vrule) -{ - /* No-op */ -} #endif /* CONFIG_AUDIT */ @@ -3648,23 +4872,48 @@ static int smack_ismaclabel(const char *name) return (strcmp(name, XATTR_SMACK_SUFFIX) == 0); } +/** + * smack_to_secctx - fill a lsm_context + * @skp: Smack label + * @cp: destination + * + * Fill the passed @cp and return the length of the string + */ +static int smack_to_secctx(struct smack_known *skp, struct lsm_context *cp) +{ + int len = strlen(skp->smk_known); + + if (cp) { + cp->context = skp->smk_known; + cp->len = len; + cp->id = LSM_ID_SMACK; + } + return len; +} /** * smack_secid_to_secctx - return the smack label for a secid * @secid: incoming integer - * @secdata: destination - * @seclen: how long it is + * @cp: destination * * Exists for networking code. */ -static int smack_secid_to_secctx(u32 secid, char **secdata, u32 *seclen) +static int smack_secid_to_secctx(u32 secid, struct lsm_context *cp) { - struct smack_known *skp = smack_from_secid(secid); + return smack_to_secctx(smack_from_secid(secid), cp); +} - if (secdata) - *secdata = skp->smk_known; - *seclen = strlen(skp->smk_known); - return 0; +/** + * smack_lsmprop_to_secctx - return the smack label + * @prop: includes incoming Smack data + * @cp: destination + * + * Exists for audit code. + */ +static int smack_lsmprop_to_secctx(struct lsm_prop *prop, + struct lsm_context *cp) +{ + return smack_to_secctx(prop->smack.skp, cp); } /** @@ -3677,182 +4926,367 @@ static int smack_secid_to_secctx(u32 secid, char **secdata, u32 *seclen) */ static int smack_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid) { - *secid = smack_to_secid(secdata); + struct smack_known *skp = smk_find_entry(secdata); + + if (skp) + *secid = skp->smk_secid; + else + *secid = 0; return 0; } -/** - * smack_release_secctx - don't do anything. - * @secdata: unused - * @seclen: unused - * - * Exists to make sure nothing gets done, and properly +/* + * There used to be a smack_release_secctx hook + * that did nothing back when hooks were in a vector. + * Now that there's a list such a hook adds cost. */ -static void smack_release_secctx(char *secdata, u32 seclen) -{ -} static int smack_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen) { - return smack_inode_setsecurity(inode, XATTR_SMACK_SUFFIX, ctx, ctxlen, 0); + /* + * UDS inode has fixed label. Ignore nfs label. + */ + if (S_ISSOCK(inode->i_mode)) + return 0; + return smack_inode_setsecurity(inode, XATTR_SMACK_SUFFIX, ctx, + ctxlen, 0); } static int smack_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen) { - return __vfs_setxattr_noperm(dentry, XATTR_NAME_SMACK, ctx, ctxlen, 0); + return __vfs_setxattr_locked(&nop_mnt_idmap, dentry, XATTR_NAME_SMACK, + ctx, ctxlen, 0, NULL); +} + +static int smack_inode_getsecctx(struct inode *inode, struct lsm_context *cp) +{ + struct smack_known *skp = smk_of_inode(inode); + + cp->context = skp->smk_known; + cp->len = strlen(skp->smk_known); + cp->id = LSM_ID_SMACK; + return 0; +} + +static int smack_inode_copy_up(struct dentry *dentry, struct cred **new) +{ + + struct task_smack *tsp; + struct smack_known *skp; + struct inode_smack *isp; + struct cred *new_creds = *new; + + if (new_creds == NULL) { + new_creds = prepare_creds(); + if (new_creds == NULL) + return -ENOMEM; + } + + tsp = smack_cred(new_creds); + + /* + * Get label from overlay inode and set it in create_sid + */ + isp = smack_inode(d_inode(dentry)); + skp = isp->smk_inode; + tsp->smk_task = skp; + *new = new_creds; + return 0; +} + +static int smack_inode_copy_up_xattr(struct dentry *src, const char *name) +{ + /* + * Return -ECANCELED if this is the smack access Smack attribute. + */ + if (!strcmp(name, XATTR_NAME_SMACK)) + return -ECANCELED; + + return -EOPNOTSUPP; } -static int smack_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) +static int smack_dentry_create_files_as(struct dentry *dentry, int mode, + const struct qstr *name, + const struct cred *old, + struct cred *new) { - int len = 0; - len = smack_inode_getsecurity(inode, XATTR_SMACK_SUFFIX, ctx, true); + struct task_smack *otsp = smack_cred(old); + struct task_smack *ntsp = smack_cred(new); + struct inode_smack *isp; + + /* + * Use the process credential unless all of + * the transmuting criteria are met + */ + ntsp->smk_task = otsp->smk_task; + + /* + * the attribute of the containing directory + */ + isp = smack_inode(d_inode(dentry->d_parent)); - if (len < 0) - return len; - *ctxlen = len; + if (isp->smk_flags & SMK_INODE_TRANSMUTE) { + /* + * If the directory is transmuting and the rule + * providing access is transmuting use the containing + * directory label instead of the process label. + */ + if (smk_rule_transmutes(otsp->smk_task, isp->smk_inode)) { + ntsp->smk_task = isp->smk_inode; + ntsp->smk_transmuted = ntsp->smk_task; + } + } return 0; } -struct security_operations smack_ops = { - .name = "smack", - - .ptrace_access_check = smack_ptrace_access_check, - .ptrace_traceme = smack_ptrace_traceme, - .syslog = smack_syslog, - - .sb_alloc_security = smack_sb_alloc_security, - .sb_free_security = smack_sb_free_security, - .sb_copy_data = smack_sb_copy_data, - .sb_kern_mount = smack_sb_kern_mount, - .sb_statfs = smack_sb_statfs, - .sb_mount = smack_sb_mount, - .sb_umount = smack_sb_umount, - - .bprm_set_creds = smack_bprm_set_creds, - .bprm_committing_creds = smack_bprm_committing_creds, - .bprm_secureexec = smack_bprm_secureexec, - - .inode_alloc_security = smack_inode_alloc_security, - .inode_free_security = smack_inode_free_security, - .inode_init_security = smack_inode_init_security, - .inode_link = smack_inode_link, - .inode_unlink = smack_inode_unlink, - .inode_rmdir = smack_inode_rmdir, - .inode_rename = smack_inode_rename, - .inode_permission = smack_inode_permission, - .inode_setattr = smack_inode_setattr, - .inode_getattr = smack_inode_getattr, - .inode_setxattr = smack_inode_setxattr, - .inode_post_setxattr = smack_inode_post_setxattr, - .inode_getxattr = smack_inode_getxattr, - .inode_removexattr = smack_inode_removexattr, - .inode_getsecurity = smack_inode_getsecurity, - .inode_setsecurity = smack_inode_setsecurity, - .inode_listsecurity = smack_inode_listsecurity, - .inode_getsecid = smack_inode_getsecid, - - .file_permission = smack_file_permission, - .file_alloc_security = smack_file_alloc_security, - .file_free_security = smack_file_free_security, - .file_ioctl = smack_file_ioctl, - .file_lock = smack_file_lock, - .file_fcntl = smack_file_fcntl, - .mmap_file = smack_mmap_file, - .mmap_addr = cap_mmap_addr, - .file_set_fowner = smack_file_set_fowner, - .file_send_sigiotask = smack_file_send_sigiotask, - .file_receive = smack_file_receive, - - .file_open = smack_file_open, - - .cred_alloc_blank = smack_cred_alloc_blank, - .cred_free = smack_cred_free, - .cred_prepare = smack_cred_prepare, - .cred_transfer = smack_cred_transfer, - .kernel_act_as = smack_kernel_act_as, - .kernel_create_files_as = smack_kernel_create_files_as, - .task_setpgid = smack_task_setpgid, - .task_getpgid = smack_task_getpgid, - .task_getsid = smack_task_getsid, - .task_getsecid = smack_task_getsecid, - .task_setnice = smack_task_setnice, - .task_setioprio = smack_task_setioprio, - .task_getioprio = smack_task_getioprio, - .task_setscheduler = smack_task_setscheduler, - .task_getscheduler = smack_task_getscheduler, - .task_movememory = smack_task_movememory, - .task_kill = smack_task_kill, - .task_wait = smack_task_wait, - .task_to_inode = smack_task_to_inode, - - .ipc_permission = smack_ipc_permission, - .ipc_getsecid = smack_ipc_getsecid, - - .msg_msg_alloc_security = smack_msg_msg_alloc_security, - .msg_msg_free_security = smack_msg_msg_free_security, - - .msg_queue_alloc_security = smack_msg_queue_alloc_security, - .msg_queue_free_security = smack_msg_queue_free_security, - .msg_queue_associate = smack_msg_queue_associate, - .msg_queue_msgctl = smack_msg_queue_msgctl, - .msg_queue_msgsnd = smack_msg_queue_msgsnd, - .msg_queue_msgrcv = smack_msg_queue_msgrcv, - - .shm_alloc_security = smack_shm_alloc_security, - .shm_free_security = smack_shm_free_security, - .shm_associate = smack_shm_associate, - .shm_shmctl = smack_shm_shmctl, - .shm_shmat = smack_shm_shmat, - - .sem_alloc_security = smack_sem_alloc_security, - .sem_free_security = smack_sem_free_security, - .sem_associate = smack_sem_associate, - .sem_semctl = smack_sem_semctl, - .sem_semop = smack_sem_semop, - - .d_instantiate = smack_d_instantiate, - - .getprocattr = smack_getprocattr, - .setprocattr = smack_setprocattr, - - .unix_stream_connect = smack_unix_stream_connect, - .unix_may_send = smack_unix_may_send, - - .socket_post_create = smack_socket_post_create, - .socket_bind = smack_socket_bind, - .socket_connect = smack_socket_connect, - .socket_sendmsg = smack_socket_sendmsg, - .socket_sock_rcv_skb = smack_socket_sock_rcv_skb, - .socket_getpeersec_stream = smack_socket_getpeersec_stream, - .socket_getpeersec_dgram = smack_socket_getpeersec_dgram, - .sk_alloc_security = smack_sk_alloc_security, - .sk_free_security = smack_sk_free_security, - .sock_graft = smack_sock_graft, - .inet_conn_request = smack_inet_conn_request, - .inet_csk_clone = smack_inet_csk_clone, +#ifdef CONFIG_IO_URING +/** + * smack_uring_override_creds - Is io_uring cred override allowed? + * @new: the target creds + * + * Check to see if the current task is allowed to override it's credentials + * to service an io_uring operation. + */ +static int smack_uring_override_creds(const struct cred *new) +{ + struct task_smack *tsp = smack_cred(current_cred()); + struct task_smack *nsp = smack_cred(new); + + /* + * Allow the degenerate case where the new Smack value is + * the same as the current Smack value. + */ + if (tsp->smk_task == nsp->smk_task) + return 0; + + if (smack_privileged_cred(CAP_MAC_OVERRIDE, current_cred())) + return 0; + + return -EPERM; +} + +/** + * smack_uring_sqpoll - check if a io_uring polling thread can be created + * + * Check to see if the current task is allowed to create a new io_uring + * kernel polling thread. + */ +static int smack_uring_sqpoll(void) +{ + if (smack_privileged_cred(CAP_MAC_ADMIN, current_cred())) + return 0; + + return -EPERM; +} + +/** + * smack_uring_cmd - check on file operations for io_uring + * @ioucmd: the command in question + * + * Make a best guess about whether a io_uring "command" should + * be allowed. Use the same logic used for determining if the + * file could be opened for read in the absence of better criteria. + */ +static int smack_uring_cmd(struct io_uring_cmd *ioucmd) +{ + struct file *file = ioucmd->file; + struct smk_audit_info ad; + struct task_smack *tsp; + struct inode *inode; + int rc; + + if (!file) + return -EINVAL; + + tsp = smack_cred(file->f_cred); + inode = file_inode(file); + + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, file->f_path); + rc = smk_tskacc(tsp, smk_of_inode(inode), MAY_READ, &ad); + rc = smk_bu_credfile(file->f_cred, file, MAY_READ, rc); + + return rc; +} + +#endif /* CONFIG_IO_URING */ + +struct lsm_blob_sizes smack_blob_sizes __ro_after_init = { + .lbs_cred = sizeof(struct task_smack), + .lbs_file = sizeof(struct smack_known *), + .lbs_inode = sizeof(struct inode_smack), + .lbs_ipc = sizeof(struct smack_known *), + .lbs_key = sizeof(struct smack_known *), + .lbs_msg_msg = sizeof(struct smack_known *), + .lbs_sock = sizeof(struct socket_smack), + .lbs_superblock = sizeof(struct superblock_smack), + .lbs_xattr_count = SMACK_INODE_INIT_XATTRS, +}; + +static const struct lsm_id smack_lsmid = { + .name = "smack", + .id = LSM_ID_SMACK, +}; + +static struct security_hook_list smack_hooks[] __ro_after_init = { + LSM_HOOK_INIT(ptrace_access_check, smack_ptrace_access_check), + LSM_HOOK_INIT(ptrace_traceme, smack_ptrace_traceme), + LSM_HOOK_INIT(syslog, smack_syslog), + + LSM_HOOK_INIT(fs_context_submount, smack_fs_context_submount), + LSM_HOOK_INIT(fs_context_dup, smack_fs_context_dup), + LSM_HOOK_INIT(fs_context_parse_param, smack_fs_context_parse_param), + + LSM_HOOK_INIT(sb_alloc_security, smack_sb_alloc_security), + LSM_HOOK_INIT(sb_free_mnt_opts, smack_free_mnt_opts), + LSM_HOOK_INIT(sb_eat_lsm_opts, smack_sb_eat_lsm_opts), + LSM_HOOK_INIT(sb_statfs, smack_sb_statfs), + LSM_HOOK_INIT(sb_set_mnt_opts, smack_set_mnt_opts), + + LSM_HOOK_INIT(bprm_creds_for_exec, smack_bprm_creds_for_exec), + + LSM_HOOK_INIT(inode_alloc_security, smack_inode_alloc_security), + LSM_HOOK_INIT(inode_init_security, smack_inode_init_security), + LSM_HOOK_INIT(inode_link, smack_inode_link), + LSM_HOOK_INIT(inode_unlink, smack_inode_unlink), + LSM_HOOK_INIT(inode_rmdir, smack_inode_rmdir), + LSM_HOOK_INIT(inode_rename, smack_inode_rename), + LSM_HOOK_INIT(inode_permission, smack_inode_permission), + LSM_HOOK_INIT(inode_setattr, smack_inode_setattr), + LSM_HOOK_INIT(inode_getattr, smack_inode_getattr), + LSM_HOOK_INIT(inode_xattr_skipcap, smack_inode_xattr_skipcap), + LSM_HOOK_INIT(inode_setxattr, smack_inode_setxattr), + LSM_HOOK_INIT(inode_post_setxattr, smack_inode_post_setxattr), + LSM_HOOK_INIT(inode_getxattr, smack_inode_getxattr), + LSM_HOOK_INIT(inode_removexattr, smack_inode_removexattr), + LSM_HOOK_INIT(inode_set_acl, smack_inode_set_acl), + LSM_HOOK_INIT(inode_get_acl, smack_inode_get_acl), + LSM_HOOK_INIT(inode_remove_acl, smack_inode_remove_acl), + LSM_HOOK_INIT(inode_getsecurity, smack_inode_getsecurity), + LSM_HOOK_INIT(inode_setsecurity, smack_inode_setsecurity), + LSM_HOOK_INIT(inode_listsecurity, smack_inode_listsecurity), + LSM_HOOK_INIT(inode_getlsmprop, smack_inode_getlsmprop), + + LSM_HOOK_INIT(file_alloc_security, smack_file_alloc_security), + LSM_HOOK_INIT(file_ioctl, smack_file_ioctl), + LSM_HOOK_INIT(file_ioctl_compat, smack_file_ioctl), + LSM_HOOK_INIT(file_lock, smack_file_lock), + LSM_HOOK_INIT(file_fcntl, smack_file_fcntl), + LSM_HOOK_INIT(mmap_file, smack_mmap_file), + LSM_HOOK_INIT(mmap_addr, cap_mmap_addr), + LSM_HOOK_INIT(file_set_fowner, smack_file_set_fowner), + LSM_HOOK_INIT(file_send_sigiotask, smack_file_send_sigiotask), + LSM_HOOK_INIT(file_receive, smack_file_receive), + + LSM_HOOK_INIT(file_open, smack_file_open), + + LSM_HOOK_INIT(cred_alloc_blank, smack_cred_alloc_blank), + LSM_HOOK_INIT(cred_free, smack_cred_free), + LSM_HOOK_INIT(cred_prepare, smack_cred_prepare), + LSM_HOOK_INIT(cred_transfer, smack_cred_transfer), + LSM_HOOK_INIT(cred_getsecid, smack_cred_getsecid), + LSM_HOOK_INIT(cred_getlsmprop, smack_cred_getlsmprop), + LSM_HOOK_INIT(kernel_act_as, smack_kernel_act_as), + LSM_HOOK_INIT(kernel_create_files_as, smack_kernel_create_files_as), + LSM_HOOK_INIT(task_setpgid, smack_task_setpgid), + LSM_HOOK_INIT(task_getpgid, smack_task_getpgid), + LSM_HOOK_INIT(task_getsid, smack_task_getsid), + LSM_HOOK_INIT(current_getlsmprop_subj, smack_current_getlsmprop_subj), + LSM_HOOK_INIT(task_getlsmprop_obj, smack_task_getlsmprop_obj), + LSM_HOOK_INIT(task_setnice, smack_task_setnice), + LSM_HOOK_INIT(task_setioprio, smack_task_setioprio), + LSM_HOOK_INIT(task_getioprio, smack_task_getioprio), + LSM_HOOK_INIT(task_setscheduler, smack_task_setscheduler), + LSM_HOOK_INIT(task_getscheduler, smack_task_getscheduler), + LSM_HOOK_INIT(task_movememory, smack_task_movememory), + LSM_HOOK_INIT(task_kill, smack_task_kill), + LSM_HOOK_INIT(task_to_inode, smack_task_to_inode), + + LSM_HOOK_INIT(ipc_permission, smack_ipc_permission), + LSM_HOOK_INIT(ipc_getlsmprop, smack_ipc_getlsmprop), + + LSM_HOOK_INIT(msg_msg_alloc_security, smack_msg_msg_alloc_security), + + LSM_HOOK_INIT(msg_queue_alloc_security, smack_ipc_alloc_security), + LSM_HOOK_INIT(msg_queue_associate, smack_msg_queue_associate), + LSM_HOOK_INIT(msg_queue_msgctl, smack_msg_queue_msgctl), + LSM_HOOK_INIT(msg_queue_msgsnd, smack_msg_queue_msgsnd), + LSM_HOOK_INIT(msg_queue_msgrcv, smack_msg_queue_msgrcv), + + LSM_HOOK_INIT(shm_alloc_security, smack_ipc_alloc_security), + LSM_HOOK_INIT(shm_associate, smack_shm_associate), + LSM_HOOK_INIT(shm_shmctl, smack_shm_shmctl), + LSM_HOOK_INIT(shm_shmat, smack_shm_shmat), + + LSM_HOOK_INIT(sem_alloc_security, smack_ipc_alloc_security), + LSM_HOOK_INIT(sem_associate, smack_sem_associate), + LSM_HOOK_INIT(sem_semctl, smack_sem_semctl), + LSM_HOOK_INIT(sem_semop, smack_sem_semop), + + LSM_HOOK_INIT(d_instantiate, smack_d_instantiate), + + LSM_HOOK_INIT(getselfattr, smack_getselfattr), + LSM_HOOK_INIT(setselfattr, smack_setselfattr), + LSM_HOOK_INIT(getprocattr, smack_getprocattr), + LSM_HOOK_INIT(setprocattr, smack_setprocattr), + + LSM_HOOK_INIT(unix_stream_connect, smack_unix_stream_connect), + LSM_HOOK_INIT(unix_may_send, smack_unix_may_send), + + LSM_HOOK_INIT(socket_post_create, smack_socket_post_create), + LSM_HOOK_INIT(socket_socketpair, smack_socket_socketpair), +#ifdef SMACK_IPV6_PORT_LABELING + LSM_HOOK_INIT(socket_bind, smack_socket_bind), +#endif + LSM_HOOK_INIT(socket_connect, smack_socket_connect), + LSM_HOOK_INIT(socket_sendmsg, smack_socket_sendmsg), + LSM_HOOK_INIT(socket_sock_rcv_skb, smack_socket_sock_rcv_skb), + LSM_HOOK_INIT(socket_getpeersec_stream, smack_socket_getpeersec_stream), + LSM_HOOK_INIT(socket_getpeersec_dgram, smack_socket_getpeersec_dgram), + LSM_HOOK_INIT(sk_alloc_security, smack_sk_alloc_security), +#ifdef SMACK_IPV6_PORT_LABELING + LSM_HOOK_INIT(sk_free_security, smack_sk_free_security), +#endif + LSM_HOOK_INIT(sk_clone_security, smack_sk_clone_security), + LSM_HOOK_INIT(inet_conn_request, smack_inet_conn_request), + LSM_HOOK_INIT(inet_csk_clone, smack_inet_csk_clone), /* key management security hooks */ #ifdef CONFIG_KEYS - .key_alloc = smack_key_alloc, - .key_free = smack_key_free, - .key_permission = smack_key_permission, + LSM_HOOK_INIT(key_alloc, smack_key_alloc), + LSM_HOOK_INIT(key_permission, smack_key_permission), + LSM_HOOK_INIT(key_getsecurity, smack_key_getsecurity), +#ifdef CONFIG_KEY_NOTIFICATIONS + LSM_HOOK_INIT(watch_key, smack_watch_key), +#endif #endif /* CONFIG_KEYS */ +#ifdef CONFIG_WATCH_QUEUE + LSM_HOOK_INIT(post_notification, smack_post_notification), +#endif + /* Audit hooks */ #ifdef CONFIG_AUDIT - .audit_rule_init = smack_audit_rule_init, - .audit_rule_known = smack_audit_rule_known, - .audit_rule_match = smack_audit_rule_match, - .audit_rule_free = smack_audit_rule_free, + LSM_HOOK_INIT(audit_rule_init, smack_audit_rule_init), + LSM_HOOK_INIT(audit_rule_known, smack_audit_rule_known), + LSM_HOOK_INIT(audit_rule_match, smack_audit_rule_match), #endif /* CONFIG_AUDIT */ - .ismaclabel = smack_ismaclabel, - .secid_to_secctx = smack_secid_to_secctx, - .secctx_to_secid = smack_secctx_to_secid, - .release_secctx = smack_release_secctx, - .inode_notifysecctx = smack_inode_notifysecctx, - .inode_setsecctx = smack_inode_setsecctx, - .inode_getsecctx = smack_inode_getsecctx, + LSM_HOOK_INIT(ismaclabel, smack_ismaclabel), + LSM_HOOK_INIT(secid_to_secctx, smack_secid_to_secctx), + LSM_HOOK_INIT(lsmprop_to_secctx, smack_lsmprop_to_secctx), + LSM_HOOK_INIT(secctx_to_secid, smack_secctx_to_secid), + LSM_HOOK_INIT(inode_notifysecctx, smack_inode_notifysecctx), + LSM_HOOK_INIT(inode_setsecctx, smack_inode_setsecctx), + LSM_HOOK_INIT(inode_getsecctx, smack_inode_getsecctx), + LSM_HOOK_INIT(inode_copy_up, smack_inode_copy_up), + LSM_HOOK_INIT(inode_copy_up_xattr, smack_inode_copy_up_xattr), + LSM_HOOK_INIT(dentry_create_files_as, smack_dentry_create_files_as), +#ifdef CONFIG_IO_URING + LSM_HOOK_INIT(uring_override_creds, smack_uring_override_creds), + LSM_HOOK_INIT(uring_sqpoll, smack_uring_sqpoll), + LSM_HOOK_INIT(uring_cmd, smack_uring_cmd), +#endif }; @@ -3865,7 +5299,6 @@ static __init void init_smack_known_list(void) mutex_init(&smack_known_hat.smk_rules_lock); mutex_init(&smack_known_floor.smk_rules_lock); mutex_init(&smack_known_star.smk_rules_lock); - mutex_init(&smack_known_invalid.smk_rules_lock); mutex_init(&smack_known_web.smk_rules_lock); /* * Initialize rule lists @@ -3874,59 +5307,81 @@ static __init void init_smack_known_list(void) INIT_LIST_HEAD(&smack_known_hat.smk_rules); INIT_LIST_HEAD(&smack_known_star.smk_rules); INIT_LIST_HEAD(&smack_known_floor.smk_rules); - INIT_LIST_HEAD(&smack_known_invalid.smk_rules); INIT_LIST_HEAD(&smack_known_web.smk_rules); /* * Create the known labels list */ - list_add(&smack_known_huh.list, &smack_known_list); - list_add(&smack_known_hat.list, &smack_known_list); - list_add(&smack_known_star.list, &smack_known_list); - list_add(&smack_known_floor.list, &smack_known_list); - list_add(&smack_known_invalid.list, &smack_known_list); - list_add(&smack_known_web.list, &smack_known_list); + smk_insert_entry(&smack_known_huh); + smk_insert_entry(&smack_known_hat); + smk_insert_entry(&smack_known_star); + smk_insert_entry(&smack_known_floor); + smk_insert_entry(&smack_known_web); } /** * smack_init - initialize the smack system * - * Returns 0 + * Returns 0 on success, -ENOMEM is there's no memory */ static __init int smack_init(void) { - struct cred *cred; + struct cred *cred = (struct cred *) current->cred; struct task_smack *tsp; - if (!security_module_enable(&smack_ops)) - return 0; - - tsp = new_task_smack(&smack_known_floor, &smack_known_floor, - GFP_KERNEL); - if (tsp == NULL) + smack_rule_cache = KMEM_CACHE(smack_rule, 0); + if (!smack_rule_cache) return -ENOMEM; - printk(KERN_INFO "Smack: Initializing.\n"); - /* * Set the security state for the initial task. */ - cred = (struct cred *) current->cred; - cred->security = tsp; - - /* initialize the smack_known_list */ - init_smack_known_list(); + tsp = smack_cred(cred); + init_task_smack(tsp, &smack_known_floor, &smack_known_floor); /* * Register with LSM */ - if (register_security(&smack_ops)) - panic("smack: Unable to register with kernel.\n"); + security_add_hooks(smack_hooks, ARRAY_SIZE(smack_hooks), &smack_lsmid); + smack_enabled = 1; + + pr_info("Smack: Initializing.\n"); +#ifdef CONFIG_SECURITY_SMACK_NETFILTER + pr_info("Smack: Netfilter enabled.\n"); +#endif +#ifdef SMACK_IPV6_PORT_LABELING + pr_info("Smack: IPv6 port labeling enabled.\n"); +#endif +#ifdef SMACK_IPV6_SECMARK_LABELING + pr_info("Smack: IPv6 Netfilter enabled.\n"); +#endif + + /* initialize the smack_known_list */ + init_smack_known_list(); + + /* Inform the audit system that secctx is used */ + audit_cfg_lsm(&smack_lsmid, + AUDIT_CFG_LSM_SECCTX_SUBJECT | + AUDIT_CFG_LSM_SECCTX_OBJECT); return 0; } +int __init smack_initcall(void) +{ + int rc_fs = init_smk_fs(); + int rc_nf = smack_nf_ip_init(); + + return rc_fs ? rc_fs : rc_nf; +} + /* * Smack requires early initialization in order to label * all processes and objects when they are created. */ -security_initcall(smack_init); +DEFINE_LSM(smack) = { + .id = &smack_lsmid, + .flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE, + .blobs = &smack_blob_sizes, + .init = smack_init, + .initcall_device = smack_initcall, +}; diff --git a/security/smack/smack_netfilter.c b/security/smack/smack_netfilter.c new file mode 100644 index 000000000000..17ba578b1308 --- /dev/null +++ b/security/smack/smack_netfilter.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Simplified MAC Kernel (smack) security module + * + * This file contains the Smack netfilter implementation + * + * Author: + * Casey Schaufler <casey@schaufler-ca.com> + * + * Copyright (C) 2014 Casey Schaufler <casey@schaufler-ca.com> + * Copyright (C) 2014 Intel Corporation. + */ + +#include <linux/netfilter_ipv4.h> +#include <linux/netfilter_ipv6.h> +#include <linux/netdevice.h> +#include <net/inet_sock.h> +#include <net/net_namespace.h> +#include "smack.h" + +static unsigned int smack_ip_output(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + struct sock *sk = skb_to_full_sk(skb); + struct socket_smack *ssp; + struct smack_known *skp; + + if (sk) { + ssp = smack_sock(sk); + skp = ssp->smk_out; + skb->secmark = skp->smk_secid; + } + + return NF_ACCEPT; +} + +static const struct nf_hook_ops smack_nf_ops[] = { + { + .hook = smack_ip_output, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_LOCAL_OUT, + .priority = NF_IP_PRI_SELINUX_FIRST, + }, +#if IS_ENABLED(CONFIG_IPV6) + { + .hook = smack_ip_output, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_LOCAL_OUT, + .priority = NF_IP6_PRI_SELINUX_FIRST, + }, +#endif /* IPV6 */ +}; + +static int __net_init smack_nf_register(struct net *net) +{ + return nf_register_net_hooks(net, smack_nf_ops, + ARRAY_SIZE(smack_nf_ops)); +} + +static void __net_exit smack_nf_unregister(struct net *net) +{ + nf_unregister_net_hooks(net, smack_nf_ops, ARRAY_SIZE(smack_nf_ops)); +} + +static struct pernet_operations smack_net_ops = { + .init = smack_nf_register, + .exit = smack_nf_unregister, +}; + +int __init smack_nf_ip_init(void) +{ + if (smack_enabled == 0) + return 0; + + printk(KERN_DEBUG "Smack: Registering netfilter hooks\n"); + return register_pernet_subsys(&smack_net_ops); +} diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index ab167037b2dd..2a9d3f2ebbe1 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -1,10 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2007 Casey Schaufler <casey@schaufler-ca.com> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 2. - * * Authors: * Casey Schaufler <casey@schaufler-ca.com> * Ahmed S. Darwish <darwish.07@gmail.com> @@ -13,7 +10,6 @@ * * Karl MacMillan <kmacmillan@tresys.com> * James Morris <jmorris@redhat.com> - * */ #include <linux/kernel.h> @@ -27,8 +23,11 @@ #include <linux/ctype.h> #include <linux/audit.h> #include <linux/magic.h> +#include <linux/mount.h> +#include <linux/fs_context.h> #include "smack.h" +#define BEBITS (sizeof(__be32) * 8) /* * smackfs pseudo filesystem. */ @@ -40,9 +39,11 @@ enum smk_inos { SMK_DOI = 5, /* CIPSO DOI */ SMK_DIRECT = 6, /* CIPSO level indicating direct label */ SMK_AMBIENT = 7, /* internet ambient label */ - SMK_NETLBLADDR = 8, /* single label hosts */ + SMK_NET4ADDR = 8, /* single label hosts */ SMK_ONLYCAP = 9, /* the only "capable" label */ +#ifdef CONFIG_AUDIT SMK_LOGGING = 10, /* logging */ +#endif /* CONFIG_AUDIT */ SMK_LOAD_SELF = 11, /* task specific rules */ SMK_ACCESSES = 12, /* access policy */ SMK_MAPPED = 13, /* CIPSO level indicating mapped label */ @@ -52,6 +53,15 @@ enum smk_inos { SMK_CIPSO2 = 17, /* load long label -> CIPSO mapping */ SMK_REVOKE_SUBJ = 18, /* set rules with subject label to '-' */ SMK_CHANGE_RULE = 19, /* change or add rules (long labels) */ + SMK_SYSLOG = 20, /* change syslog label) */ + SMK_PTRACE = 21, /* set ptrace rule */ +#ifdef CONFIG_SECURITY_SMACK_BRINGUP + SMK_UNCONFINED = 22, /* define an unconfined label */ +#endif +#if IS_ENABLED(CONFIG_IPV6) + SMK_NET6ADDR = 23, /* single label IPv6 hosts */ +#endif /* CONFIG_IPV6 */ + SMK_RELABEL_SELF = 24, /* relabel possible without CAP_MAC_ADMIN */ }; /* @@ -59,7 +69,10 @@ enum smk_inos { */ static DEFINE_MUTEX(smack_cipso_lock); static DEFINE_MUTEX(smack_ambient_lock); -static DEFINE_MUTEX(smk_netlbladdr_lock); +static DEFINE_MUTEX(smk_net4addr_lock); +#if IS_ENABLED(CONFIG_IPV6) +static DEFINE_MUTEX(smk_net6addr_lock); +#endif /* CONFIG_IPV6 */ /* * This is the "ambient" label for network traffic. @@ -82,15 +95,30 @@ int smack_cipso_direct = SMACK_CIPSO_DIRECT_DEFAULT; */ int smack_cipso_mapped = SMACK_CIPSO_MAPPED_DEFAULT; +#ifdef CONFIG_SECURITY_SMACK_BRINGUP +/* + * Allow one label to be unconfined. This is for + * debugging and application bring-up purposes only. + * It is bad and wrong, but everyone seems to expect + * to have it. + */ +struct smack_known *smack_unconfined; +#endif + /* - * Unless a process is running with this label even - * having CAP_MAC_OVERRIDE isn't enough to grant - * privilege to violate MAC policy. If no label is - * designated (the NULL case) capabilities apply to - * everyone. It is expected that the hat (^) label - * will be used if any label is used. + * If this value is set restrict syslog use to the label specified. + * It can be reset via smackfs/syslog */ -char *smack_onlycap; +struct smack_known *smack_syslog_label; + +/* + * Ptrace current rule + * SMACK_PTRACE_DEFAULT regular smack ptrace rules (/proc based) + * SMACK_PTRACE_EXACT labels must match, but can be overriden with + * CAP_SYS_PTRACE + * SMACK_PTRACE_DRACONIAN labels must match, CAP_SYS_PTRACE has no effect + */ +int smack_ptrace_rule = SMACK_PTRACE_DEFAULT; /* * Certain IP addresses may be designated as single label hosts. @@ -98,30 +126,23 @@ char *smack_onlycap; * can write to the specified label. */ -LIST_HEAD(smk_netlbladdr_list); +LIST_HEAD(smk_net4addr_list); +#if IS_ENABLED(CONFIG_IPV6) +LIST_HEAD(smk_net6addr_list); +#endif /* CONFIG_IPV6 */ /* * Rule lists are maintained for each label. - * This master list is just for reading /smack/load and /smack/load2. */ -struct smack_master_list { - struct list_head list; - struct smack_rule *smk_rule; -}; - -LIST_HEAD(smack_rule_list); - struct smack_parsed_rule { struct smack_known *smk_subject; - char *smk_object; + struct smack_known *smk_object; int smk_access1; int smk_access2; }; static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; -const char *smack_cipso_option = SMACK_CIPSO_OPTION; - /* * Values for parsing cipso rules * SMK_DIGITLEN: Length of a digit field in a rule. @@ -139,14 +160,14 @@ const char *smack_cipso_option = SMACK_CIPSO_OPTION; * SMK_LOADLEN: Smack rule length */ #define SMK_OACCESS "rwxa" -#define SMK_ACCESS "rwxat" +#define SMK_ACCESS "rwxatl" #define SMK_OACCESSLEN (sizeof(SMK_OACCESS) - 1) #define SMK_ACCESSLEN (sizeof(SMK_ACCESS) - 1) #define SMK_OLOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_OACCESSLEN) #define SMK_LOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_ACCESSLEN) /* - * Stricly for CIPSO level manipulation. + * Strictly for CIPSO level manipulation. * Set the category bit number in a smack label sized buffer. */ static inline void smack_catset_bit(unsigned int cat, char *catsetp) @@ -163,11 +184,9 @@ static inline void smack_catset_bit(unsigned int cat, char *catsetp) */ static void smk_netlabel_audit_set(struct netlbl_audit *nap) { - struct smack_known *skp = smk_of_current(); - nap->loginuid = audit_get_loginuid(current); nap->sessionid = audit_get_sessionid(current); - nap->secid = skp->smk_secid; + nap->prop.smack.skp = smk_of_current(); } /* @@ -181,7 +200,6 @@ static void smk_netlabel_audit_set(struct netlbl_audit *nap) * @srp: the rule to add or replace * @rule_list: the list of rules * @rule_lock: the rule list lock - * @global: if non-zero, indicates a global rule * * Looks through the current subject/object/access list for * the subject/object pair and replaces the access that was @@ -193,10 +211,9 @@ static void smk_netlabel_audit_set(struct netlbl_audit *nap) */ static int smk_set_access(struct smack_parsed_rule *srp, struct list_head *rule_list, - struct mutex *rule_lock, int global) + struct mutex *rule_lock) { struct smack_rule *sp; - struct smack_master_list *smlp; int found = 0; int rc = 0; @@ -217,7 +234,7 @@ static int smk_set_access(struct smack_parsed_rule *srp, } if (found == 0) { - sp = kzalloc(sizeof(*sp), GFP_KERNEL); + sp = kmem_cache_zalloc(smack_rule_cache, GFP_KERNEL); if (sp == NULL) { rc = -ENOMEM; goto out; @@ -228,18 +245,6 @@ static int smk_set_access(struct smack_parsed_rule *srp, sp->smk_access = srp->smk_access1 & ~srp->smk_access2; list_add_rcu(&sp->list, rule_list); - /* - * If this is a global as opposed to self and a new rule - * it needs to get added for reporting. - */ - if (global) { - smlp = kzalloc(sizeof(*smlp), GFP_KERNEL); - if (smlp != NULL) { - smlp->smk_rule = sp; - list_add_rcu(&smlp->list, &smack_rule_list); - } else - rc = -ENOMEM; - } } out: @@ -282,6 +287,14 @@ static int smk_perm_from_str(const char *string) case 'T': perm |= MAY_TRANSMUTE; break; + case 'l': + case 'L': + perm |= MAY_LOCK; + break; + case 'b': + case 'B': + perm |= MAY_BRINGUP; + break; default: return perm; } @@ -297,7 +310,7 @@ static int smk_perm_from_str(const char *string) * @import: if non-zero, import labels * @len: label length limit * - * Returns 0 on success, -1 on failure + * Returns 0 on success, appropriate error code on failure. */ static int smk_fill_rule(const char *subject, const char *object, const char *access1, const char *access2, @@ -309,30 +322,30 @@ static int smk_fill_rule(const char *subject, const char *object, if (import) { rule->smk_subject = smk_import_entry(subject, len); - if (rule->smk_subject == NULL) - return -1; + if (IS_ERR(rule->smk_subject)) + return PTR_ERR(rule->smk_subject); - rule->smk_object = smk_import(object, len); - if (rule->smk_object == NULL) - return -1; + rule->smk_object = smk_import_entry(object, len); + if (IS_ERR(rule->smk_object)) + return PTR_ERR(rule->smk_object); } else { cp = smk_parse_smack(subject, len); - if (cp == NULL) - return -1; + if (IS_ERR(cp)) + return PTR_ERR(cp); skp = smk_find_entry(cp); kfree(cp); if (skp == NULL) - return -1; + return -ENOENT; rule->smk_subject = skp; cp = smk_parse_smack(object, len); - if (cp == NULL) - return -1; + if (IS_ERR(cp)) + return PTR_ERR(cp); skp = smk_find_entry(cp); kfree(cp); if (skp == NULL) - return -1; - rule->smk_object = skp->smk_known; + return -ENOENT; + rule->smk_object = skp; } rule->smk_access1 = smk_perm_from_str(access1); @@ -368,56 +381,42 @@ static int smk_parse_rule(const char *data, struct smack_parsed_rule *rule, * @data: string to be parsed, null terminated * @rule: Will be filled with Smack parsed rule * @import: if non-zero, import labels - * @change: if non-zero, data is from /smack/change-rule + * @tokens: number of substrings expected in data * - * Returns 0 on success, -1 on failure - */ -static int smk_parse_long_rule(const char *data, struct smack_parsed_rule *rule, - int import, int change) -{ - char *subject; - char *object; - char *access1; - char *access2; - int datalen; - int rc = -1; - - /* This is inefficient */ - datalen = strlen(data); - - /* Our first element can be 64 + \0 with no spaces */ - subject = kzalloc(datalen + 1, GFP_KERNEL); - if (subject == NULL) - return -1; - object = kzalloc(datalen, GFP_KERNEL); - if (object == NULL) - goto free_out_s; - access1 = kzalloc(datalen, GFP_KERNEL); - if (access1 == NULL) - goto free_out_o; - access2 = kzalloc(datalen, GFP_KERNEL); - if (access2 == NULL) - goto free_out_a; - - if (change) { - if (sscanf(data, "%s %s %s %s", - subject, object, access1, access2) == 4) - rc = smk_fill_rule(subject, object, access1, access2, - rule, import, 0); - } else { - if (sscanf(data, "%s %s %s", subject, object, access1) == 3) - rc = smk_fill_rule(subject, object, access1, NULL, - rule, import, 0); + * Returns number of processed bytes on success, -ERRNO on failure. + */ +static ssize_t smk_parse_long_rule(char *data, struct smack_parsed_rule *rule, + int import, int tokens) +{ + ssize_t cnt = 0; + char *tok[4]; + int rc; + int i; + + /* + * Parsing the rule in-place, filling all white-spaces with '\0' + */ + for (i = 0; i < tokens; ++i) { + while (isspace(data[cnt])) + data[cnt++] = '\0'; + + if (data[cnt] == '\0') + /* Unexpected end of data */ + return -EINVAL; + + tok[i] = data + cnt; + + while (data[cnt] && !isspace(data[cnt])) + ++cnt; } + while (isspace(data[cnt])) + data[cnt++] = '\0'; - kfree(access2); -free_out_a: - kfree(access1); -free_out_o: - kfree(object); -free_out_s: - kfree(subject); - return rc; + while (i < 4) + tok[i++] = NULL; + + rc = smk_fill_rule(tok[0], tok[1], tok[2], tok[3], rule, import, 0); + return rc == 0 ? cnt : rc; } #define SMK_FIXED24_FMT 0 /* Fixed 24byte label format */ @@ -447,11 +446,12 @@ static ssize_t smk_write_rules_list(struct file *file, const char __user *buf, struct list_head *rule_list, struct mutex *rule_lock, int format) { - struct smack_parsed_rule *rule; + struct smack_parsed_rule rule; char *data; - int datalen; - int rc = -EINVAL; - int load = 0; + int rc; + int trunc = 0; + int tokens; + ssize_t cnt = 0; /* * No partial writes. @@ -464,62 +464,62 @@ static ssize_t smk_write_rules_list(struct file *file, const char __user *buf, /* * Minor hack for backward compatibility */ - if (count != SMK_OLOADLEN && count != SMK_LOADLEN) + if (count < SMK_OLOADLEN || count > SMK_LOADLEN) return -EINVAL; - datalen = SMK_LOADLEN; - } else - datalen = count + 1; - - data = kzalloc(datalen, GFP_KERNEL); - if (data == NULL) - return -ENOMEM; - - if (copy_from_user(data, buf, count) != 0) { - rc = -EFAULT; - goto out; + } else { + if (count >= PAGE_SIZE) { + count = PAGE_SIZE - 1; + trunc = 1; + } } - rule = kzalloc(sizeof(*rule), GFP_KERNEL); - if (rule == NULL) { - rc = -ENOMEM; - goto out; - } + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); - if (format == SMK_LONG_FMT) { - /* - * Be sure the data string is terminated. - */ - data[count] = '\0'; - if (smk_parse_long_rule(data, rule, 1, 0)) - goto out_free_rule; - } else if (format == SMK_CHANGE_FMT) { - data[count] = '\0'; - if (smk_parse_long_rule(data, rule, 1, 1)) - goto out_free_rule; - } else { - /* - * More on the minor hack for backward compatibility - */ - if (count == (SMK_OLOADLEN)) - data[SMK_OLOADLEN] = '-'; - if (smk_parse_rule(data, rule, 1)) - goto out_free_rule; + /* + * In case of parsing only part of user buf, + * avoid having partial rule at the data buffer + */ + if (trunc) { + while (count > 0 && (data[count - 1] != '\n')) + --count; + if (count == 0) { + rc = -EINVAL; + goto out; + } } - if (rule_list == NULL) { - load = 1; - rule_list = &rule->smk_subject->smk_rules; - rule_lock = &rule->smk_subject->smk_rules_lock; - } + data[count] = '\0'; + tokens = (format == SMK_CHANGE_FMT ? 4 : 3); + while (cnt < count) { + if (format == SMK_FIXED24_FMT) { + rc = smk_parse_rule(data, &rule, 1); + if (rc < 0) + goto out; + cnt = count; + } else { + rc = smk_parse_long_rule(data + cnt, &rule, 1, tokens); + if (rc < 0) + goto out; + if (rc == 0) { + rc = -EINVAL; + goto out; + } + cnt += rc; + } - rc = smk_set_access(rule, rule_list, rule_lock, load); - if (rc == 0) { - rc = count; - goto out; + if (rule_list == NULL) + rc = smk_set_access(&rule, &rule.smk_subject->smk_rules, + &rule.smk_subject->smk_rules_lock); + else + rc = smk_set_access(&rule, rule_list, rule_lock); + + if (rc) + goto out; } -out_free_rule: - kfree(rule); + rc = cnt; out: kfree(data); return rc; @@ -533,23 +533,17 @@ static void *smk_seq_start(struct seq_file *s, loff_t *pos, struct list_head *head) { struct list_head *list; + int i = *pos; + + rcu_read_lock(); + for (list = rcu_dereference(list_next_rcu(head)); + list != head; + list = rcu_dereference(list_next_rcu(list))) { + if (i-- == 0) + return list; + } - /* - * This is 0 the first time through. - */ - if (s->index == 0) - s->private = head; - - if (s->private == NULL) - return NULL; - - list = s->private; - if (list_empty(list)) - return NULL; - - if (s->index == 0) - return list->next; - return list; + return NULL; } static void *smk_seq_next(struct seq_file *s, void *v, loff_t *pos, @@ -557,21 +551,20 @@ static void *smk_seq_next(struct seq_file *s, void *v, loff_t *pos, { struct list_head *list = v; - if (list_is_last(list, head)) { - s->private = NULL; - return NULL; - } - s->private = list->next; - return list->next; + ++*pos; + list = rcu_dereference(list_next_rcu(list)); + + return (list == head) ? NULL : list; } static void smk_seq_stop(struct seq_file *s, void *v) { - /* No-op */ + rcu_read_unlock(); } static void smk_rule_show(struct seq_file *s, struct smack_rule *srp, int max) { + char acc[SMK_NUM_ACCESS_TYPE + 1]; /* * Don't show any rules with label names too long for * interface file (/smack/load or /smack/load2) @@ -579,28 +572,17 @@ static void smk_rule_show(struct seq_file *s, struct smack_rule *srp, int max) * anything you read back. */ if (strlen(srp->smk_subject->smk_known) >= max || - strlen(srp->smk_object) >= max) + strlen(srp->smk_object->smk_known) >= max) return; if (srp->smk_access == 0) return; - seq_printf(s, "%s %s", srp->smk_subject->smk_known, srp->smk_object); - - seq_putc(s, ' '); - - if (srp->smk_access & MAY_READ) - seq_putc(s, 'r'); - if (srp->smk_access & MAY_WRITE) - seq_putc(s, 'w'); - if (srp->smk_access & MAY_EXEC) - seq_putc(s, 'x'); - if (srp->smk_access & MAY_APPEND) - seq_putc(s, 'a'); - if (srp->smk_access & MAY_TRANSMUTE) - seq_putc(s, 't'); - - seq_putc(s, '\n'); + smack_str_from_perm(acc, srp->smk_access); + seq_printf(s, "%s %s %s\n", + srp->smk_subject->smk_known, + srp->smk_object->smk_known, + acc); } /* @@ -609,21 +591,23 @@ static void smk_rule_show(struct seq_file *s, struct smack_rule *srp, int max) static void *load2_seq_start(struct seq_file *s, loff_t *pos) { - return smk_seq_start(s, pos, &smack_rule_list); + return smk_seq_start(s, pos, &smack_known_list); } static void *load2_seq_next(struct seq_file *s, void *v, loff_t *pos) { - return smk_seq_next(s, v, pos, &smack_rule_list); + return smk_seq_next(s, v, pos, &smack_known_list); } static int load_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; - struct smack_master_list *smlp = - list_entry(list, struct smack_master_list, list); + struct smack_rule *srp; + struct smack_known *skp = + list_entry_rcu(list, struct smack_known, list); - smk_rule_show(s, smlp->smk_rule, SMK_LABELLEN); + list_for_each_entry_rcu(srp, &skp->smk_rules, list) + smk_rule_show(s, srp, SMK_LABELLEN); return 0; } @@ -694,9 +678,7 @@ static void smk_cipso_doi(void) printk(KERN_WARNING "%s:%d remove rc = %d\n", __func__, __LINE__, rc); - doip = kmalloc(sizeof(struct cipso_v4_doi), GFP_KERNEL); - if (doip == NULL) - panic("smack: Failed to initialize cipso DOI.\n"); + doip = kmalloc(sizeof(struct cipso_v4_doi), GFP_KERNEL | __GFP_NOFAIL); doip->map.std = NULL; doip->doi = smk_cipso_doi_value; doip->type = CIPSO_V4_MAP_PASS; @@ -715,7 +697,7 @@ static void smk_cipso_doi(void) if (rc != 0) { printk(KERN_WARNING "%s:%d map add rc = %d\n", __func__, __LINE__, rc); - kfree(doip); + netlbl_cfg_cipsov4_del(doip->doi, &nai); return; } } @@ -769,8 +751,8 @@ static int cipso_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smack_known *skp = - list_entry(list, struct smack_known, list); - struct netlbl_lsm_secattr_catmap *cmp = skp->smk_netlabel.attr.mls.cat; + list_entry_rcu(list, struct smack_known, list); + struct netlbl_lsm_catmap *cmp = skp->smk_netlabel.attr.mls.cat; char sep = '/'; int i; @@ -787,8 +769,8 @@ static int cipso_seq_show(struct seq_file *s, void *v) seq_printf(s, "%s %3d", skp->smk_known, skp->smk_netlabel.attr.mls.lvl); - for (i = netlbl_secattr_catmap_walk(cmp, 0); i >= 0; - i = netlbl_secattr_catmap_walk(cmp, i + 1)) { + for (i = netlbl_catmap_walk(cmp, 0); i >= 0; + i = netlbl_catmap_walk(cmp, i + 1)) { seq_printf(s, "%c%d", sep, i); sep = ','; } @@ -832,6 +814,7 @@ static int smk_open_cipso(struct inode *inode, struct file *file) static ssize_t smk_set_cipso(struct file *file, const char __user *buf, size_t count, loff_t *ppos, int format) { + struct netlbl_lsm_catmap *old_cat; struct smack_known *skp; struct netlbl_lsm_secattr ncats; char mapcatset[SMK_CIPSOLEN]; @@ -856,17 +839,13 @@ static ssize_t smk_set_cipso(struct file *file, const char __user *buf, if (format == SMK_FIXED24_FMT && (count < SMK_CIPSOMIN || count > SMK_CIPSOMAX)) return -EINVAL; + if (count > PAGE_SIZE) + return -EINVAL; - data = kzalloc(count + 1, GFP_KERNEL); - if (data == NULL) - return -ENOMEM; - - if (copy_from_user(data, buf, count) != 0) { - rc = -EFAULT; - goto unlockedout; - } + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); - data[count] = '\0'; rule = data; /* * Only allow one writer at a time. Writes should be @@ -875,21 +854,33 @@ static ssize_t smk_set_cipso(struct file *file, const char __user *buf, mutex_lock(&smack_cipso_lock); skp = smk_import_entry(rule, 0); - if (skp == NULL) + if (IS_ERR(skp)) { + rc = PTR_ERR(skp); goto out; + } if (format == SMK_FIXED24_FMT) rule += SMK_LABELLEN; else rule += strlen(skp->smk_known) + 1; + if (rule > data + count) { + rc = -EOVERFLOW; + goto out; + } + ret = sscanf(rule, "%d", &maplevel); - if (ret != 1 || maplevel > SMACK_CIPSO_MAXLEVEL) + if (ret != 1 || maplevel < 0 || maplevel > SMACK_CIPSO_MAXLEVEL) goto out; rule += SMK_DIGITLEN; + if (rule > data + count) { + rc = -EOVERFLOW; + goto out; + } + ret = sscanf(rule, "%d", &catlen); - if (ret != 1 || catlen > SMACK_CIPSO_MAXCATNUM) + if (ret != 1 || catlen < 0 || catlen > SMACK_CIPSO_MAXCATNUM) goto out; if (format == SMK_FIXED24_FMT && @@ -900,8 +891,12 @@ static ssize_t smk_set_cipso(struct file *file, const char __user *buf, for (i = 0; i < catlen; i++) { rule += SMK_DIGITLEN; + if (rule > data + count) { + rc = -EOVERFLOW; + goto out; + } ret = sscanf(rule, "%u", &cat); - if (ret != 1 || cat > SMACK_CIPSO_MAXCATVAL) + if (ret != 1 || cat > SMACK_CIPSO_MAXCATNUM) goto out; smack_catset_bit(cat, mapcatset); @@ -909,15 +904,24 @@ static ssize_t smk_set_cipso(struct file *file, const char __user *buf, rc = smk_netlbl_mls(maplevel, mapcatset, &ncats, SMK_CIPSOLEN); if (rc >= 0) { - netlbl_secattr_catmap_free(skp->smk_netlabel.attr.mls.cat); - skp->smk_netlabel.attr.mls.cat = ncats.attr.mls.cat; + old_cat = skp->smk_netlabel.attr.mls.cat; + rcu_assign_pointer(skp->smk_netlabel.attr.mls.cat, ncats.attr.mls.cat); + if (ncats.attr.mls.cat) + skp->smk_netlabel.flags |= NETLBL_SECATTR_MLS_CAT; + else + skp->smk_netlabel.flags &= ~(u32)NETLBL_SECATTR_MLS_CAT; skp->smk_netlabel.attr.mls.lvl = ncats.attr.mls.lvl; + synchronize_rcu(); + netlbl_catmap_free(old_cat); rc = count; + /* + * This mapping may have been cached, so clear the cache. + */ + netlbl_cache_invalidate(); } out: mutex_unlock(&smack_cipso_lock); -unlockedout: kfree(data); return rc; } @@ -958,15 +962,15 @@ static int cipso2_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smack_known *skp = - list_entry(list, struct smack_known, list); - struct netlbl_lsm_secattr_catmap *cmp = skp->smk_netlabel.attr.mls.cat; + list_entry_rcu(list, struct smack_known, list); + struct netlbl_lsm_catmap *cmp = skp->smk_netlabel.attr.mls.cat; char sep = '/'; int i; seq_printf(s, "%s %3d", skp->smk_known, skp->smk_netlabel.attr.mls.lvl); - for (i = netlbl_secattr_catmap_walk(cmp, 0); i >= 0; - i = netlbl_secattr_catmap_walk(cmp, i + 1)) { + for (i = netlbl_catmap_walk(cmp, 0); i >= 0; + i = netlbl_catmap_walk(cmp, i + 1)) { seq_printf(s, "%c%d", sep, i); sep = ','; } @@ -1024,92 +1028,89 @@ static const struct file_operations smk_cipso2_ops = { * Seq_file read operations for /smack/netlabel */ -static void *netlbladdr_seq_start(struct seq_file *s, loff_t *pos) +static void *net4addr_seq_start(struct seq_file *s, loff_t *pos) { - return smk_seq_start(s, pos, &smk_netlbladdr_list); + return smk_seq_start(s, pos, &smk_net4addr_list); } -static void *netlbladdr_seq_next(struct seq_file *s, void *v, loff_t *pos) +static void *net4addr_seq_next(struct seq_file *s, void *v, loff_t *pos) { - return smk_seq_next(s, v, pos, &smk_netlbladdr_list); + return smk_seq_next(s, v, pos, &smk_net4addr_list); } -#define BEBITS (sizeof(__be32) * 8) /* * Print host/label pairs */ -static int netlbladdr_seq_show(struct seq_file *s, void *v) +static int net4addr_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; - struct smk_netlbladdr *skp = - list_entry(list, struct smk_netlbladdr, list); - unsigned char *hp = (char *) &skp->smk_host.sin_addr.s_addr; - int maskn; - u32 temp_mask = be32_to_cpu(skp->smk_mask.s_addr); - - for (maskn = 0; temp_mask; temp_mask <<= 1, maskn++); + struct smk_net4addr *skp = + list_entry_rcu(list, struct smk_net4addr, list); + char *kp = SMACK_CIPSO_OPTION; - seq_printf(s, "%u.%u.%u.%u/%d %s\n", - hp[0], hp[1], hp[2], hp[3], maskn, skp->smk_label); + if (skp->smk_label != NULL) + kp = skp->smk_label->smk_known; + seq_printf(s, "%pI4/%d %s\n", &skp->smk_host.s_addr, + skp->smk_masks, kp); return 0; } -static const struct seq_operations netlbladdr_seq_ops = { - .start = netlbladdr_seq_start, - .next = netlbladdr_seq_next, - .show = netlbladdr_seq_show, +static const struct seq_operations net4addr_seq_ops = { + .start = net4addr_seq_start, + .next = net4addr_seq_next, + .show = net4addr_seq_show, .stop = smk_seq_stop, }; /** - * smk_open_netlbladdr - open() for /smack/netlabel + * smk_open_net4addr - open() for /smack/netlabel * @inode: inode structure representing file * @file: "netlabel" file pointer * - * Connect our netlbladdr_seq_* operations with /smack/netlabel + * Connect our net4addr_seq_* operations with /smack/netlabel * file_operations */ -static int smk_open_netlbladdr(struct inode *inode, struct file *file) +static int smk_open_net4addr(struct inode *inode, struct file *file) { - return seq_open(file, &netlbladdr_seq_ops); + return seq_open(file, &net4addr_seq_ops); } /** - * smk_netlbladdr_insert + * smk_net4addr_insert - insert a new entry into the net4addrs list * @new : netlabel to insert * - * This helper insert netlabel in the smack_netlbladdrs list + * This helper inserts netlabel in the smack_net4addrs list * sorted by netmask length (longest to smallest) - * locked by &smk_netlbladdr_lock in smk_write_netlbladdr - * + * locked by &smk_net4addr_lock in smk_write_net4addr. */ -static void smk_netlbladdr_insert(struct smk_netlbladdr *new) +static void smk_net4addr_insert(struct smk_net4addr *new) { - struct smk_netlbladdr *m, *m_next; + struct smk_net4addr *m; + struct smk_net4addr *m_next; - if (list_empty(&smk_netlbladdr_list)) { - list_add_rcu(&new->list, &smk_netlbladdr_list); + if (list_empty(&smk_net4addr_list)) { + list_add_rcu(&new->list, &smk_net4addr_list); return; } - m = list_entry_rcu(smk_netlbladdr_list.next, - struct smk_netlbladdr, list); + m = list_entry_rcu(smk_net4addr_list.next, + struct smk_net4addr, list); /* the comparison '>' is a bit hacky, but works */ - if (new->smk_mask.s_addr > m->smk_mask.s_addr) { - list_add_rcu(&new->list, &smk_netlbladdr_list); + if (new->smk_masks > m->smk_masks) { + list_add_rcu(&new->list, &smk_net4addr_list); return; } - list_for_each_entry_rcu(m, &smk_netlbladdr_list, list) { - if (list_is_last(&m->list, &smk_netlbladdr_list)) { + list_for_each_entry_rcu(m, &smk_net4addr_list, list) { + if (list_is_last(&m->list, &smk_net4addr_list)) { list_add_rcu(&new->list, &m->list); return; } m_next = list_entry_rcu(m->list.next, - struct smk_netlbladdr, list); - if (new->smk_mask.s_addr > m_next->smk_mask.s_addr) { + struct smk_net4addr, list); + if (new->smk_masks > m_next->smk_masks) { list_add_rcu(&new->list, &m->list); return; } @@ -1118,28 +1119,29 @@ static void smk_netlbladdr_insert(struct smk_netlbladdr *new) /** - * smk_write_netlbladdr - write() for /smack/netlabel + * smk_write_net4addr - write() for /smack/netlabel * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent * @ppos: where to start * - * Accepts only one netlbladdr per write call. + * Accepts only one net4addr per write call. * Returns number of bytes written or error code, as appropriate */ -static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, +static ssize_t smk_write_net4addr(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - struct smk_netlbladdr *skp; + struct smk_net4addr *snp; struct sockaddr_in newname; char *smack; - char *sp; + struct smack_known *skp = NULL; char *data; char *host = (char *)&newname.sin_addr.s_addr; int rc; struct netlbl_audit audit_info; struct in_addr mask; unsigned int m; + unsigned int masks; int found; u32 mask_bits = (1<<31); __be32 nsa; @@ -1156,17 +1158,12 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, return -EPERM; if (*ppos != 0) return -EINVAL; - if (count < SMK_NETLBLADDRMIN) + if (count < SMK_NETLBLADDRMIN || count > PAGE_SIZE - 1) return -EINVAL; - data = kzalloc(count + 1, GFP_KERNEL); - if (data == NULL) - return -ENOMEM; - - if (copy_from_user(data, buf, count) != 0) { - rc = -EFAULT; - goto free_data_out; - } + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); smack = kzalloc(count + 1, GFP_KERNEL); if (smack == NULL) { @@ -1174,10 +1171,8 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, goto free_data_out; } - data[count] = '\0'; - - rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd/%d %s", - &host[0], &host[1], &host[2], &host[3], &m, smack); + rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd/%u %s", + &host[0], &host[1], &host[2], &host[3], &masks, smack); if (rc != 6) { rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd %s", &host[0], &host[1], &host[2], &host[3], smack); @@ -1185,9 +1180,9 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, rc = -EINVAL; goto free_out; } - m = BEBITS; + masks = 32; } - if (m > BEBITS) { + if (masks > BEBITS) { rc = -EINVAL; goto free_out; } @@ -1196,22 +1191,22 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, * If smack begins with '-', it is an option, don't import it */ if (smack[0] != '-') { - sp = smk_import(smack, 0); - if (sp == NULL) { - rc = -EINVAL; + skp = smk_import_entry(smack, 0); + if (IS_ERR(skp)) { + rc = PTR_ERR(skp); goto free_out; } } else { - /* check known options */ - if (strcmp(smack, smack_cipso_option) == 0) - sp = (char *)smack_cipso_option; - else { + /* + * Only the -CIPSO option is supported for IPv4 + */ + if (strcmp(smack, SMACK_CIPSO_OPTION) != 0) { rc = -EINVAL; goto free_out; } } - for (temp_mask = 0; m > 0; m--) { + for (m = masks, temp_mask = 0; m > 0; m--) { temp_mask |= mask_bits; mask_bits >>= 1; } @@ -1222,14 +1217,13 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, * Only allow one writer at a time. Writes should be * quite rare and small in any case. */ - mutex_lock(&smk_netlbladdr_lock); + mutex_lock(&smk_net4addr_lock); nsa = newname.sin_addr.s_addr; /* try to find if the prefix is already in the list */ found = 0; - list_for_each_entry_rcu(skp, &smk_netlbladdr_list, list) { - if (skp->smk_host.sin_addr.s_addr == nsa && - skp->smk_mask.s_addr == mask.s_addr) { + list_for_each_entry_rcu(snp, &smk_net4addr_list, list) { + if (snp->smk_host.s_addr == nsa && snp->smk_masks == masks) { found = 1; break; } @@ -1237,26 +1231,29 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, smk_netlabel_audit_set(&audit_info); if (found == 0) { - skp = kzalloc(sizeof(*skp), GFP_KERNEL); - if (skp == NULL) + snp = kzalloc(sizeof(*snp), GFP_KERNEL); + if (snp == NULL) rc = -ENOMEM; else { rc = 0; - skp->smk_host.sin_addr.s_addr = newname.sin_addr.s_addr; - skp->smk_mask.s_addr = mask.s_addr; - skp->smk_label = sp; - smk_netlbladdr_insert(skp); + snp->smk_host.s_addr = newname.sin_addr.s_addr; + snp->smk_mask.s_addr = mask.s_addr; + snp->smk_label = skp; + snp->smk_masks = masks; + smk_net4addr_insert(snp); } } else { - /* we delete the unlabeled entry, only if the previous label - * wasn't the special CIPSO option */ - if (skp->smk_label != smack_cipso_option) + /* + * Delete the unlabeled entry, only if the previous label + * wasn't the special CIPSO option + */ + if (snp->smk_label != NULL) rc = netlbl_cfg_unlbl_static_del(&init_net, NULL, - &skp->smk_host.sin_addr, &skp->smk_mask, + &snp->smk_host, &snp->smk_mask, PF_INET, &audit_info); else rc = 0; - skp->smk_label = sp; + snp->smk_label = skp; } /* @@ -1264,15 +1261,15 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, * this host so that incoming packets get labeled. * but only if we didn't get the special CIPSO option */ - if (rc == 0 && sp != smack_cipso_option) + if (rc == 0 && skp != NULL) rc = netlbl_cfg_unlbl_static_add(&init_net, NULL, - &skp->smk_host.sin_addr, &skp->smk_mask, PF_INET, - smack_to_secid(skp->smk_label), &audit_info); + &snp->smk_host, &snp->smk_mask, PF_INET, + snp->smk_label->smk_secid, &audit_info); if (rc == 0) rc = count; - mutex_unlock(&smk_netlbladdr_lock); + mutex_unlock(&smk_net4addr_lock); free_out: kfree(smack); @@ -1282,14 +1279,271 @@ free_data_out: return rc; } -static const struct file_operations smk_netlbladdr_ops = { - .open = smk_open_netlbladdr, +static const struct file_operations smk_net4addr_ops = { + .open = smk_open_net4addr, .read = seq_read, .llseek = seq_lseek, - .write = smk_write_netlbladdr, + .write = smk_write_net4addr, .release = seq_release, }; +#if IS_ENABLED(CONFIG_IPV6) +/* + * Seq_file read operations for /smack/netlabel6 + */ + +static void *net6addr_seq_start(struct seq_file *s, loff_t *pos) +{ + return smk_seq_start(s, pos, &smk_net6addr_list); +} + +static void *net6addr_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + return smk_seq_next(s, v, pos, &smk_net6addr_list); +} + +/* + * Print host/label pairs + */ +static int net6addr_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smk_net6addr *skp = + list_entry(list, struct smk_net6addr, list); + + if (skp->smk_label != NULL) + seq_printf(s, "%pI6/%d %s\n", &skp->smk_host, skp->smk_masks, + skp->smk_label->smk_known); + + return 0; +} + +static const struct seq_operations net6addr_seq_ops = { + .start = net6addr_seq_start, + .next = net6addr_seq_next, + .show = net6addr_seq_show, + .stop = smk_seq_stop, +}; + +/** + * smk_open_net6addr - open() for /smack/netlabel + * @inode: inode structure representing file + * @file: "netlabel" file pointer + * + * Connect our net6addr_seq_* operations with /smack/netlabel + * file_operations + */ +static int smk_open_net6addr(struct inode *inode, struct file *file) +{ + return seq_open(file, &net6addr_seq_ops); +} + +/** + * smk_net6addr_insert - insert a new entry into the net6addrs list + * @new : entry to insert + * + * This inserts an entry in the smack_net6addrs list + * sorted by netmask length (longest to smallest) + * locked by &smk_net6addr_lock in smk_write_net6addr. + */ +static void smk_net6addr_insert(struct smk_net6addr *new) +{ + struct smk_net6addr *m_next; + struct smk_net6addr *m; + + if (list_empty(&smk_net6addr_list)) { + list_add_rcu(&new->list, &smk_net6addr_list); + return; + } + + m = list_entry_rcu(smk_net6addr_list.next, + struct smk_net6addr, list); + + if (new->smk_masks > m->smk_masks) { + list_add_rcu(&new->list, &smk_net6addr_list); + return; + } + + list_for_each_entry_rcu(m, &smk_net6addr_list, list) { + if (list_is_last(&m->list, &smk_net6addr_list)) { + list_add_rcu(&new->list, &m->list); + return; + } + m_next = list_entry_rcu(m->list.next, + struct smk_net6addr, list); + if (new->smk_masks > m_next->smk_masks) { + list_add_rcu(&new->list, &m->list); + return; + } + } +} + + +/** + * smk_write_net6addr - write() for /smack/netlabel + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Accepts only one net6addr per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_net6addr(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct smk_net6addr *snp; + struct in6_addr newname; + struct in6_addr fullmask; + struct smack_known *skp = NULL; + char *smack; + char *data; + int rc = 0; + int found = 0; + int i; + unsigned int scanned[8]; + unsigned int m; + unsigned int mask = 128; + + /* + * Must have privilege. + * No partial writes. + * Enough data must be present. + * "<addr/mask, as a:b:c:d:e:f:g:h/e><space><label>" + * "<addr, as a:b:c:d:e:f:g:h><space><label>" + */ + if (!smack_privileged(CAP_MAC_ADMIN)) + return -EPERM; + if (*ppos != 0) + return -EINVAL; + if (count < SMK_NETLBLADDRMIN || count > PAGE_SIZE - 1) + return -EINVAL; + + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); + + smack = kzalloc(count + 1, GFP_KERNEL); + if (smack == NULL) { + rc = -ENOMEM; + goto free_data_out; + } + + i = sscanf(data, "%x:%x:%x:%x:%x:%x:%x:%x/%u %s", + &scanned[0], &scanned[1], &scanned[2], &scanned[3], + &scanned[4], &scanned[5], &scanned[6], &scanned[7], + &mask, smack); + if (i != 10) { + i = sscanf(data, "%x:%x:%x:%x:%x:%x:%x:%x %s", + &scanned[0], &scanned[1], &scanned[2], + &scanned[3], &scanned[4], &scanned[5], + &scanned[6], &scanned[7], smack); + if (i != 9) { + rc = -EINVAL; + goto free_out; + } + } + if (mask > 128) { + rc = -EINVAL; + goto free_out; + } + for (i = 0; i < 8; i++) { + if (scanned[i] > 0xffff) { + rc = -EINVAL; + goto free_out; + } + newname.s6_addr16[i] = htons(scanned[i]); + } + + /* + * If smack begins with '-', it is an option, don't import it + */ + if (smack[0] != '-') { + skp = smk_import_entry(smack, 0); + if (IS_ERR(skp)) { + rc = PTR_ERR(skp); + goto free_out; + } + } else { + /* + * Only -DELETE is supported for IPv6 + */ + if (strcmp(smack, SMACK_DELETE_OPTION) != 0) { + rc = -EINVAL; + goto free_out; + } + } + + for (i = 0, m = mask; i < 8; i++) { + if (m >= 16) { + fullmask.s6_addr16[i] = 0xffff; + m -= 16; + } else if (m > 0) { + fullmask.s6_addr16[i] = (1 << m) - 1; + m = 0; + } else + fullmask.s6_addr16[i] = 0; + newname.s6_addr16[i] &= fullmask.s6_addr16[i]; + } + + /* + * Only allow one writer at a time. Writes should be + * quite rare and small in any case. + */ + mutex_lock(&smk_net6addr_lock); + /* + * Try to find the prefix in the list + */ + list_for_each_entry_rcu(snp, &smk_net6addr_list, list) { + if (mask != snp->smk_masks) + continue; + for (found = 1, i = 0; i < 8; i++) { + if (newname.s6_addr16[i] != + snp->smk_host.s6_addr16[i]) { + found = 0; + break; + } + } + if (found == 1) + break; + } + if (found == 0) { + snp = kzalloc(sizeof(*snp), GFP_KERNEL); + if (snp == NULL) + rc = -ENOMEM; + else { + snp->smk_host = newname; + snp->smk_mask = fullmask; + snp->smk_masks = mask; + snp->smk_label = skp; + smk_net6addr_insert(snp); + } + } else { + snp->smk_label = skp; + } + + if (rc == 0) + rc = count; + + mutex_unlock(&smk_net6addr_lock); + +free_out: + kfree(smack); +free_data_out: + kfree(data); + + return rc; +} + +static const struct file_operations smk_net6addr_ops = { + .open = smk_open_net6addr, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_net6addr, + .release = seq_release, +}; +#endif /* CONFIG_IPV6 */ + /** * smk_read_doi - read() for /smack/doi * @filp: file pointer, not actually used @@ -1569,18 +1823,17 @@ static ssize_t smk_write_ambient(struct file *file, const char __user *buf, if (!smack_privileged(CAP_MAC_ADMIN)) return -EPERM; - data = kzalloc(count + 1, GFP_KERNEL); - if (data == NULL) - return -ENOMEM; + /* Enough data must be present */ + if (count == 0 || count > PAGE_SIZE) + return -EINVAL; - if (copy_from_user(data, buf, count) != 0) { - rc = -EFAULT; - goto out; - } + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); skp = smk_import_entry(data, count); - if (skp == NULL) { - rc = -EINVAL; + if (IS_ERR(skp)) { + rc = PTR_ERR(skp); goto out; } @@ -1603,8 +1856,191 @@ static const struct file_operations smk_ambient_ops = { .llseek = default_llseek, }; +/* + * Seq_file operations for /smack/onlycap + */ +static void *onlycap_seq_start(struct seq_file *s, loff_t *pos) +{ + return smk_seq_start(s, pos, &smack_onlycap_list); +} + +static void *onlycap_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + return smk_seq_next(s, v, pos, &smack_onlycap_list); +} + +static int onlycap_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smack_known_list_elem *sklep = + list_entry_rcu(list, struct smack_known_list_elem, list); + + seq_puts(s, sklep->smk_label->smk_known); + seq_putc(s, ' '); + + return 0; +} + +static const struct seq_operations onlycap_seq_ops = { + .start = onlycap_seq_start, + .next = onlycap_seq_next, + .show = onlycap_seq_show, + .stop = smk_seq_stop, +}; + +static int smk_open_onlycap(struct inode *inode, struct file *file) +{ + return seq_open(file, &onlycap_seq_ops); +} + +/** + * smk_list_swap_rcu - swap public list with a private one in RCU-safe way + * The caller must hold appropriate mutex to prevent concurrent modifications + * to the public list. + * Private list is assumed to be not accessible to other threads yet. + * + * @public: public list + * @private: private list + */ +static void smk_list_swap_rcu(struct list_head *public, + struct list_head *private) +{ + struct list_head *first, *last; + + if (list_empty(public)) { + list_splice_init_rcu(private, public, synchronize_rcu); + } else { + /* Remember public list before replacing it */ + first = public->next; + last = public->prev; + + /* Publish private list in place of public in RCU-safe way */ + private->prev->next = public; + private->next->prev = public; + rcu_assign_pointer(public->next, private->next); + public->prev = private->prev; + + synchronize_rcu(); + + /* When all readers are done with the old public list, + * attach it in place of private */ + private->next = first; + private->prev = last; + first->prev = private; + last->next = private; + } +} + +/** + * smk_parse_label_list - parse list of Smack labels, separated by spaces + * + * @data: the string to parse + * @list: destination list + * + * Returns zero on success or error code, as appropriate + */ +static int smk_parse_label_list(char *data, struct list_head *list) +{ + char *tok; + struct smack_known *skp; + struct smack_known_list_elem *sklep; + + while ((tok = strsep(&data, " ")) != NULL) { + if (!*tok) + continue; + + skp = smk_import_entry(tok, 0); + if (IS_ERR(skp)) + return PTR_ERR(skp); + + sklep = kzalloc(sizeof(*sklep), GFP_KERNEL); + if (sklep == NULL) + return -ENOMEM; + + sklep->smk_label = skp; + list_add(&sklep->list, list); + } + + return 0; +} + +/** + * smk_destroy_label_list - destroy a list of smack_known_list_elem + * @list: header pointer of the list to destroy + */ +void smk_destroy_label_list(struct list_head *list) +{ + struct smack_known_list_elem *sklep; + struct smack_known_list_elem *sklep2; + + list_for_each_entry_safe(sklep, sklep2, list, list) + kfree(sklep); + + INIT_LIST_HEAD(list); +} + +/** + * smk_write_onlycap - write() for smackfs/onlycap + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_onlycap(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char *data; + LIST_HEAD(list_tmp); + int rc; + + if (!smack_privileged(CAP_MAC_ADMIN)) + return -EPERM; + + if (count > PAGE_SIZE) + return -EINVAL; + + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); + + rc = smk_parse_label_list(data, &list_tmp); + kfree(data); + + /* + * Clear the smack_onlycap on invalid label errors. This means + * that we can pass a null string to unset the onlycap value. + * + * Importing will also reject a label beginning with '-', + * so "-usecapabilities" will also work. + * + * But do so only on invalid label, not on system errors. + * The invalid label must be first to count as clearing attempt. + */ + if (!rc || (rc == -EINVAL && list_empty(&list_tmp))) { + mutex_lock(&smack_onlycap_lock); + smk_list_swap_rcu(&smack_onlycap_list, &list_tmp); + mutex_unlock(&smack_onlycap_lock); + rc = count; + } + + smk_destroy_label_list(&list_tmp); + + return rc; +} + +static const struct file_operations smk_onlycap_ops = { + .open = smk_open_onlycap, + .read = seq_read, + .write = smk_write_onlycap, + .llseek = seq_lseek, + .release = seq_release, +}; + +#ifdef CONFIG_SECURITY_SMACK_BRINGUP /** - * smk_read_onlycap - read() for /smack/onlycap + * smk_read_unconfined - read() for smackfs/unconfined * @filp: file pointer, not actually used * @buf: where to put the result * @cn: maximum to send along @@ -1612,8 +2048,8 @@ static const struct file_operations smk_ambient_ops = { * * Returns number of bytes read or error code, as appropriate */ -static ssize_t smk_read_onlycap(struct file *filp, char __user *buf, - size_t cn, loff_t *ppos) +static ssize_t smk_read_unconfined(struct file *filp, char __user *buf, + size_t cn, loff_t *ppos) { char *smack = ""; ssize_t rc = -EINVAL; @@ -1622,8 +2058,8 @@ static ssize_t smk_read_onlycap(struct file *filp, char __user *buf, if (*ppos != 0) return 0; - if (smack_onlycap != NULL) - smack = smack_onlycap; + if (smack_unconfined != NULL) + smack = smack_unconfined->smk_known; asize = strlen(smack) + 1; @@ -1634,7 +2070,7 @@ static ssize_t smk_read_onlycap(struct file *filp, char __user *buf, } /** - * smk_write_onlycap - write() for /smack/onlycap + * smk_write_unconfined - write() for smackfs/unconfined * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent @@ -1642,58 +2078,60 @@ static ssize_t smk_read_onlycap(struct file *filp, char __user *buf, * * Returns number of bytes written or error code, as appropriate */ -static ssize_t smk_write_onlycap(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) +static ssize_t smk_write_unconfined(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) { char *data; - struct smack_known *skp = smk_of_task(current->cred->security); + struct smack_known *skp; int rc = count; if (!smack_privileged(CAP_MAC_ADMIN)) return -EPERM; - /* - * This can be done using smk_access() but is done - * explicitly for clarity. The smk_access() implementation - * would use smk_access(smack_onlycap, MAY_WRITE) - */ - if (smack_onlycap != NULL && smack_onlycap != skp->smk_known) - return -EPERM; + if (count > PAGE_SIZE) + return -EINVAL; - data = kzalloc(count, GFP_KERNEL); - if (data == NULL) - return -ENOMEM; + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); /* - * Should the null string be passed in unset the onlycap value. - * This seems like something to be careful with as usually - * smk_import only expects to return NULL for errors. It - * is usually the case that a nullstring or "\n" would be - * bad to pass to smk_import but in fact this is useful here. + * Clear the smack_unconfined on invalid label errors. This means + * that we can pass a null string to unset the unconfined value. * - * smk_import will also reject a label beginning with '-', - * so "-usecapabilities" will also work. + * Importing will also reject a label beginning with '-', + * so "-confine" will also work. + * + * But do so only on invalid label, not on system errors. */ - if (copy_from_user(data, buf, count) != 0) - rc = -EFAULT; - else - smack_onlycap = smk_import(data, count); + skp = smk_import_entry(data, count); + if (PTR_ERR(skp) == -EINVAL) + skp = NULL; + else if (IS_ERR(skp)) { + rc = PTR_ERR(skp); + goto freeout; + } + smack_unconfined = skp; + +freeout: kfree(data); return rc; } -static const struct file_operations smk_onlycap_ops = { - .read = smk_read_onlycap, - .write = smk_write_onlycap, +static const struct file_operations smk_unconfined_ops = { + .read = smk_read_unconfined, + .write = smk_write_unconfined, .llseek = default_llseek, }; +#endif /* CONFIG_SECURITY_SMACK_BRINGUP */ +#ifdef CONFIG_AUDIT /** * smk_read_logging - read() for /smack/logging * @filp: file pointer, not actually used * @buf: where to put the result - * @cn: maximum to send along + * @count: maximum to send along * @ppos: where to start * * Returns number of bytes read or error code, as appropriate @@ -1753,6 +2191,7 @@ static const struct file_operations smk_logging_ops = { .write = smk_write_logging, .llseek = default_llseek, }; +#endif /* CONFIG_AUDIT */ /* * Seq_file read operations for /smack/load-self @@ -1760,14 +2199,14 @@ static const struct file_operations smk_logging_ops = { static void *load_self_seq_start(struct seq_file *s, loff_t *pos) { - struct task_smack *tsp = current_security(); + struct task_smack *tsp = smack_cred(current_cred()); return smk_seq_start(s, pos, &tsp->smk_rules); } static void *load_self_seq_next(struct seq_file *s, void *v, loff_t *pos) { - struct task_smack *tsp = current_security(); + struct task_smack *tsp = smack_cred(current_cred()); return smk_seq_next(s, v, pos, &tsp->smk_rules); } @@ -1776,7 +2215,7 @@ static int load_self_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smack_rule *srp = - list_entry(list, struct smack_rule, list); + list_entry_rcu(list, struct smack_rule, list); smk_rule_show(s, srp, SMK_LABELLEN); @@ -1814,7 +2253,7 @@ static int smk_open_load_self(struct inode *inode, struct file *file) static ssize_t smk_write_load_self(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - struct task_smack *tsp = current_security(); + struct task_smack *tsp = smack_cred(current_cred()); return smk_write_rules_list(file, buf, count, ppos, &tsp->smk_rules, &tsp->smk_rules_lock, SMK_FIXED24_FMT); @@ -1834,13 +2273,13 @@ static const struct file_operations smk_load_self_ops = { * @buf: data from user space * @count: bytes sent * @ppos: where to start - must be 0 + * @format: /smack/load or /smack/load2 or /smack/change-rule format. */ static ssize_t smk_user_access(struct file *file, const char __user *buf, size_t count, loff_t *ppos, int format) { struct smack_parsed_rule rule; char *data; - char *cod; int res; data = simple_transaction_get(file, buf, count); @@ -1853,23 +2292,21 @@ static ssize_t smk_user_access(struct file *file, const char __user *buf, res = smk_parse_rule(data, &rule, 0); } else { /* - * Copy the data to make sure the string is terminated. + * simple_transaction_get() returns null-terminated data */ - cod = kzalloc(count + 1, GFP_KERNEL); - if (cod == NULL) - return -ENOMEM; - memcpy(cod, data, count); - cod[count] = '\0'; - res = smk_parse_long_rule(cod, &rule, 0, 0); - kfree(cod); + res = smk_parse_long_rule(data, &rule, 0, 3); } - if (res) - return -EINVAL; + if (res >= 0) + res = smk_access(rule.smk_subject, rule.smk_object, + rule.smk_access1, NULL); + else if (res != -ENOENT) + return res; - res = smk_access(rule.smk_subject, rule.smk_object, - rule.smk_access1, NULL); - data[0] = res == 0 ? '1' : '0'; + /* + * smk_access() can return a value > 0 in the "bringup" case. + */ + data[0] = res >= 0 ? '1' : '0'; data[1] = '\0'; simple_transaction_set(file, 2); @@ -1907,10 +2344,12 @@ static const struct file_operations smk_access_ops = { static int load2_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; - struct smack_master_list *smlp = - list_entry(list, struct smack_master_list, list); + struct smack_rule *srp; + struct smack_known *skp = + list_entry_rcu(list, struct smack_known, list); - smk_rule_show(s, smlp->smk_rule, SMK_LONGLABEL); + list_for_each_entry_rcu(srp, &skp->smk_rules, list) + smk_rule_show(s, srp, SMK_LONGLABEL); return 0; } @@ -1969,14 +2408,14 @@ static const struct file_operations smk_load2_ops = { static void *load_self2_seq_start(struct seq_file *s, loff_t *pos) { - struct task_smack *tsp = current_security(); + struct task_smack *tsp = smack_cred(current_cred()); return smk_seq_start(s, pos, &tsp->smk_rules); } static void *load_self2_seq_next(struct seq_file *s, void *v, loff_t *pos) { - struct task_smack *tsp = current_security(); + struct task_smack *tsp = smack_cred(current_cred()); return smk_seq_next(s, v, pos, &tsp->smk_rules); } @@ -1985,7 +2424,7 @@ static int load_self2_seq_show(struct seq_file *s, void *v) { struct list_head *list = v; struct smack_rule *srp = - list_entry(list, struct smack_rule, list); + list_entry_rcu(list, struct smack_rule, list); smk_rule_show(s, srp, SMK_LONGLABEL); @@ -2022,7 +2461,7 @@ static int smk_open_load_self2(struct inode *inode, struct file *file) static ssize_t smk_write_load_self2(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - struct task_smack *tsp = current_security(); + struct task_smack *tsp = smack_cred(current_cred()); return smk_write_rules_list(file, buf, count, ppos, &tsp->smk_rules, &tsp->smk_rules_lock, SMK_LONG_FMT); @@ -2066,8 +2505,8 @@ static const struct file_operations smk_access2_ops = { static ssize_t smk_write_revoke_subj(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - char *data = NULL; - const char *cp = NULL; + char *data; + const char *cp; struct smack_known *skp; struct smack_rule *sp; struct list_head *rule_list; @@ -2083,24 +2522,19 @@ static ssize_t smk_write_revoke_subj(struct file *file, const char __user *buf, if (count == 0 || count > SMK_LONGLABEL) return -EINVAL; - data = kzalloc(count, GFP_KERNEL); - if (data == NULL) - return -ENOMEM; - - if (copy_from_user(data, buf, count) != 0) { - rc = -EFAULT; - goto free_out; - } + data = memdup_user(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); cp = smk_parse_smack(data, count); - if (cp == NULL) { - rc = -EINVAL; - goto free_out; + if (IS_ERR(cp)) { + rc = PTR_ERR(cp); + goto out_data; } skp = smk_find_entry(cp); if (skp == NULL) - goto free_out; + goto out_cp; rule_list = &skp->smk_rules; rule_lock = &skp->smk_rules_lock; @@ -2112,9 +2546,11 @@ static ssize_t smk_write_revoke_subj(struct file *file, const char __user *buf, mutex_unlock(rule_lock); -free_out: - kfree(data); +out_cp: kfree(cp); +out_data: + kfree(data); + return rc; } @@ -2125,17 +2561,13 @@ static const struct file_operations smk_revoke_subj_ops = { .llseek = generic_file_llseek, }; -static struct kset *smackfs_kset; /** * smk_init_sysfs - initialize /sys/fs/smackfs * */ static int smk_init_sysfs(void) { - smackfs_kset = kset_create_and_add("smackfs", NULL, fs_kobj); - if (!smackfs_kset) - return -ENOMEM; - return 0; + return sysfs_create_mount_point(fs_kobj, "smackfs"); } /** @@ -2151,7 +2583,7 @@ static ssize_t smk_write_change_rule(struct file *file, const char __user *buf, /* * Must have privilege. */ - if (!capable(CAP_MAC_ADMIN)) + if (!smack_privileged(CAP_MAC_ADMIN)) return -EPERM; return smk_write_rules_list(file, buf, count, ppos, NULL, NULL, @@ -2166,21 +2598,272 @@ static const struct file_operations smk_change_rule_ops = { }; /** - * smk_fill_super - fill the /smackfs superblock + * smk_read_syslog - read() for smackfs/syslog + * @filp: file pointer, not actually used + * @buf: where to put the result + * @cn: maximum to send along + * @ppos: where to start + * + * Returns number of bytes read or error code, as appropriate + */ +static ssize_t smk_read_syslog(struct file *filp, char __user *buf, + size_t cn, loff_t *ppos) +{ + struct smack_known *skp; + ssize_t rc = -EINVAL; + int asize; + + if (*ppos != 0) + return 0; + + if (smack_syslog_label == NULL) + skp = &smack_known_star; + else + skp = smack_syslog_label; + + asize = strlen(skp->smk_known) + 1; + + if (cn >= asize) + rc = simple_read_from_buffer(buf, cn, ppos, skp->smk_known, + asize); + + return rc; +} + +/** + * smk_write_syslog - write() for smackfs/syslog + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_syslog(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char *data; + struct smack_known *skp; + int rc = count; + + if (!smack_privileged(CAP_MAC_ADMIN)) + return -EPERM; + + /* Enough data must be present */ + if (count == 0 || count > PAGE_SIZE) + return -EINVAL; + + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); + + skp = smk_import_entry(data, count); + if (IS_ERR(skp)) + rc = PTR_ERR(skp); + else + smack_syslog_label = skp; + + kfree(data); + return rc; +} + +static const struct file_operations smk_syslog_ops = { + .read = smk_read_syslog, + .write = smk_write_syslog, + .llseek = default_llseek, +}; + +/* + * Seq_file read operations for /smack/relabel-self + */ + +static void *relabel_self_seq_start(struct seq_file *s, loff_t *pos) +{ + struct task_smack *tsp = smack_cred(current_cred()); + + return smk_seq_start(s, pos, &tsp->smk_relabel); +} + +static void *relabel_self_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct task_smack *tsp = smack_cred(current_cred()); + + return smk_seq_next(s, v, pos, &tsp->smk_relabel); +} + +static int relabel_self_seq_show(struct seq_file *s, void *v) +{ + struct list_head *list = v; + struct smack_known_list_elem *sklep = + list_entry(list, struct smack_known_list_elem, list); + + seq_puts(s, sklep->smk_label->smk_known); + seq_putc(s, ' '); + + return 0; +} + +static const struct seq_operations relabel_self_seq_ops = { + .start = relabel_self_seq_start, + .next = relabel_self_seq_next, + .show = relabel_self_seq_show, + .stop = smk_seq_stop, +}; + +/** + * smk_open_relabel_self - open() for /smack/relabel-self + * @inode: inode structure representing file + * @file: "relabel-self" file pointer + * + * Connect our relabel_self_seq_* operations with /smack/relabel-self + * file_operations + */ +static int smk_open_relabel_self(struct inode *inode, struct file *file) +{ + return seq_open(file, &relabel_self_seq_ops); +} + +/** + * smk_write_relabel_self - write() for /smack/relabel-self + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start - must be 0 + * + */ +static ssize_t smk_write_relabel_self(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char *data; + int rc; + LIST_HEAD(list_tmp); + + /* + * Must have privilege. + */ + if (!smack_privileged(CAP_MAC_ADMIN)) + return -EPERM; + + /* + * No partial write. + * Enough data must be present. + */ + if (*ppos != 0) + return -EINVAL; + if (count == 0 || count > PAGE_SIZE) + return -EINVAL; + + data = memdup_user_nul(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); + + rc = smk_parse_label_list(data, &list_tmp); + kfree(data); + + if (!rc || (rc == -EINVAL && list_empty(&list_tmp))) { + struct cred *new; + struct task_smack *tsp; + + new = prepare_creds(); + if (!new) { + rc = -ENOMEM; + goto out; + } + tsp = smack_cred(new); + smk_destroy_label_list(&tsp->smk_relabel); + list_splice(&list_tmp, &tsp->smk_relabel); + commit_creds(new); + return count; + } +out: + smk_destroy_label_list(&list_tmp); + return rc; +} + +static const struct file_operations smk_relabel_self_ops = { + .open = smk_open_relabel_self, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_relabel_self, + .release = seq_release, +}; + +/** + * smk_read_ptrace - read() for /smack/ptrace + * @filp: file pointer, not actually used + * @buf: where to put the result + * @count: maximum to send along + * @ppos: where to start + * + * Returns number of bytes read or error code, as appropriate + */ +static ssize_t smk_read_ptrace(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[32]; + ssize_t rc; + + if (*ppos != 0) + return 0; + + sprintf(temp, "%d\n", smack_ptrace_rule); + rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); + return rc; +} + +/** + * smk_write_ptrace - write() for /smack/ptrace + * @file: file pointer + * @buf: data from user space + * @count: bytes sent + * @ppos: where to start - must be 0 + */ +static ssize_t smk_write_ptrace(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[32]; + int i; + + if (!smack_privileged(CAP_MAC_ADMIN)) + return -EPERM; + + if (*ppos != 0 || count >= sizeof(temp) || count == 0) + return -EINVAL; + + if (copy_from_user(temp, buf, count) != 0) + return -EFAULT; + + temp[count] = '\0'; + + if (sscanf(temp, "%d", &i) != 1) + return -EINVAL; + if (i < SMACK_PTRACE_DEFAULT || i > SMACK_PTRACE_MAX) + return -EINVAL; + smack_ptrace_rule = i; + + return count; +} + +static const struct file_operations smk_ptrace_ops = { + .write = smk_write_ptrace, + .read = smk_read_ptrace, + .llseek = default_llseek, +}; + +/** + * smk_fill_super - fill the smackfs superblock * @sb: the empty superblock - * @data: unused - * @silent: unused + * @fc: unused * - * Fill in the well known entries for /smack + * Fill in the well known entries for the smack filesystem * * Returns 0 on success, an error code on failure */ -static int smk_fill_super(struct super_block *sb, void *data, int silent) +static int smk_fill_super(struct super_block *sb, struct fs_context *fc) { int rc; - struct inode *root_inode; - static struct tree_descr smack_files[] = { + static const struct tree_descr smack_files[] = { [SMK_LOAD] = { "load", &smk_load_ops, S_IRUGO|S_IWUSR}, [SMK_CIPSO] = { @@ -2191,12 +2874,14 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent) "direct", &smk_direct_ops, S_IRUGO|S_IWUSR}, [SMK_AMBIENT] = { "ambient", &smk_ambient_ops, S_IRUGO|S_IWUSR}, - [SMK_NETLBLADDR] = { - "netlabel", &smk_netlbladdr_ops, S_IRUGO|S_IWUSR}, + [SMK_NET4ADDR] = { + "netlabel", &smk_net4addr_ops, S_IRUGO|S_IWUSR}, [SMK_ONLYCAP] = { "onlycap", &smk_onlycap_ops, S_IRUGO|S_IWUSR}, +#ifdef CONFIG_AUDIT [SMK_LOGGING] = { "logging", &smk_logging_ops, S_IRUGO|S_IWUSR}, +#endif /* CONFIG_AUDIT */ [SMK_LOAD_SELF] = { "load-self", &smk_load_self_ops, S_IRUGO|S_IWUGO}, [SMK_ACCESSES] = { @@ -2216,6 +2901,21 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent) S_IRUGO|S_IWUSR}, [SMK_CHANGE_RULE] = { "change-rule", &smk_change_rule_ops, S_IRUGO|S_IWUSR}, + [SMK_SYSLOG] = { + "syslog", &smk_syslog_ops, S_IRUGO|S_IWUSR}, + [SMK_PTRACE] = { + "ptrace", &smk_ptrace_ops, S_IRUGO|S_IWUSR}, +#ifdef CONFIG_SECURITY_SMACK_BRINGUP + [SMK_UNCONFINED] = { + "unconfined", &smk_unconfined_ops, S_IRUGO|S_IWUSR}, +#endif +#if IS_ENABLED(CONFIG_IPV6) + [SMK_NET6ADDR] = { + "ipv6host", &smk_net6addr_ops, S_IRUGO|S_IWUSR}, +#endif /* CONFIG_IPV6 */ + [SMK_RELABEL_SELF] = { + "relabel-self", &smk_relabel_self_ops, + S_IRUGO|S_IWUGO}, /* last one */ {""} }; @@ -2227,45 +2927,44 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent) return rc; } - root_inode = sb->s_root->d_inode; - return 0; } /** - * smk_mount - get the smackfs superblock - * @fs_type: passed along without comment - * @flags: passed along without comment - * @dev_name: passed along without comment - * @data: passed along without comment + * smk_get_tree - get the smackfs superblock + * @fc: The mount context, including any options * * Just passes everything along. * * Returns what the lower level code does. */ -static struct dentry *smk_mount(struct file_system_type *fs_type, - int flags, const char *dev_name, void *data) +static int smk_get_tree(struct fs_context *fc) { - return mount_single(fs_type, flags, data, smk_fill_super); + return get_tree_single(fc, smk_fill_super); +} + +static const struct fs_context_operations smk_context_ops = { + .get_tree = smk_get_tree, +}; + +/** + * smk_init_fs_context - Initialise a filesystem context for smackfs + * @fc: The blank mount context + */ +static int smk_init_fs_context(struct fs_context *fc) +{ + fc->ops = &smk_context_ops; + return 0; } static struct file_system_type smk_fs_type = { .name = "smackfs", - .mount = smk_mount, - .kill_sb = kill_litter_super, + .init_fs_context = smk_init_fs_context, + .kill_sb = kill_anon_super, }; static struct vfsmount *smackfs_mount; -static int __init smk_preset_netlabel(struct smack_known *skp) -{ - skp->smk_netlabel.domain = skp->smk_known; - skp->smk_netlabel.flags = - NETLBL_SECATTR_DOMAIN | NETLBL_SECATTR_MLS_LVL; - return smk_netlbl_mls(smack_cipso_direct, skp->smk_known, - &skp->smk_netlabel, strlen(skp->smk_known)); -} - /** * init_smk_fs - get the smackfs superblock * @@ -2279,12 +2978,12 @@ static int __init smk_preset_netlabel(struct smack_known *skp) * Returns true if we were not chosen on boot or if * we were chosen and filesystem registration succeeded. */ -static int __init init_smk_fs(void) +int __init init_smk_fs(void) { int err; int rc; - if (!security_module_enable(&smack_ops)) + if (smack_enabled == 0) return 0; err = smk_init_sysfs(); @@ -2304,26 +3003,21 @@ static int __init init_smk_fs(void) smk_cipso_doi(); smk_unlbl_ambient(NULL); - rc = smk_preset_netlabel(&smack_known_floor); - if (err == 0 && rc < 0) - err = rc; - rc = smk_preset_netlabel(&smack_known_hat); + rc = smack_populate_secattr(&smack_known_floor); if (err == 0 && rc < 0) err = rc; - rc = smk_preset_netlabel(&smack_known_huh); + rc = smack_populate_secattr(&smack_known_hat); if (err == 0 && rc < 0) err = rc; - rc = smk_preset_netlabel(&smack_known_invalid); + rc = smack_populate_secattr(&smack_known_huh); if (err == 0 && rc < 0) err = rc; - rc = smk_preset_netlabel(&smack_known_star); + rc = smack_populate_secattr(&smack_known_star); if (err == 0 && rc < 0) err = rc; - rc = smk_preset_netlabel(&smack_known_web); + rc = smack_populate_secattr(&smack_known_web); if (err == 0 && rc < 0) err = rc; return err; } - -__initcall(init_smk_fs); |
