diff options
Diffstat (limited to 'security')
75 files changed, 3154 insertions, 1525 deletions
diff --git a/security/Kconfig b/security/Kconfig index 51bd5a0b69ae..e9c6ac724fef 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -4,73 +4,7 @@ menu "Security options" -config KEYS - bool "Enable access key retention support" - help - This option provides support for retaining authentication tokens and - access keys in the kernel. - - It also includes provision of methods by which such keys might be - associated with a process so that network filesystems, encryption - support and the like can find them. - - Furthermore, a special type of key is available that acts as keyring: - a searchable sequence of keys. Each process is equipped with access - to five standard keyrings: UID-specific, GID-specific, session, - process and thread. - - If you are unsure as to whether this is required, answer N. - -config TRUSTED_KEYS - tristate "TRUSTED KEYS" - depends on KEYS && TCG_TPM - select CRYPTO - select CRYPTO_HMAC - select CRYPTO_SHA1 - help - This option provides support for creating, sealing, and unsealing - keys in the kernel. Trusted keys are random number symmetric keys, - generated and RSA-sealed by the TPM. The TPM only unseals the keys, - if the boot PCRs and other criteria match. Userspace will only ever - see encrypted blobs. - - If you are unsure as to whether this is required, answer N. - -config ENCRYPTED_KEYS - tristate "ENCRYPTED KEYS" - depends on KEYS - select CRYPTO - select CRYPTO_HMAC - select CRYPTO_AES - select CRYPTO_CBC - select CRYPTO_SHA256 - select CRYPTO_RNG - help - This option provides support for create/encrypting/decrypting keys - in the kernel. Encrypted keys are kernel generated random numbers, - which are encrypted/decrypted with a 'master' symmetric key. The - 'master' key can be either a trusted-key or user-key type. - Userspace only ever sees/stores encrypted blobs. - - If you are unsure as to whether this is required, answer N. - -config KEYS_DEBUG_PROC_KEYS - bool "Enable the /proc/keys file by which keys may be viewed" - depends on KEYS - help - This option turns on support for the /proc/keys file - through which - can be listed all the keys on the system that are viewable by the - reading process. - - The only keys included in the list are those that grant View - permission to the reading process whether or not it possesses them. - Note that LSM security checks are still performed, and may further - filter out keys that the current process is not authorised to view. - - Only key attributes are listed here; key payloads are not included in - the resulting table. - - If you are unsure as to whether this is required, answer N. +source security/keys/Kconfig config SECURITY_DMESG_RESTRICT bool "Restrict unprivileged access to the kernel syslog" @@ -187,6 +121,7 @@ source security/selinux/Kconfig source security/smack/Kconfig source security/tomoyo/Kconfig source security/apparmor/Kconfig +source security/yama/Kconfig source security/integrity/Kconfig @@ -196,6 +131,7 @@ choice default DEFAULT_SECURITY_SMACK if SECURITY_SMACK default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR + default DEFAULT_SECURITY_YAMA if SECURITY_YAMA default DEFAULT_SECURITY_DAC help @@ -214,6 +150,9 @@ choice config DEFAULT_SECURITY_APPARMOR bool "AppArmor" if SECURITY_APPARMOR=y + config DEFAULT_SECURITY_YAMA + bool "Yama" if SECURITY_YAMA=y + config DEFAULT_SECURITY_DAC bool "Unix Discretionary Access Controls" @@ -225,6 +164,7 @@ config DEFAULT_SECURITY default "smack" if DEFAULT_SECURITY_SMACK default "tomoyo" if DEFAULT_SECURITY_TOMOYO default "apparmor" if DEFAULT_SECURITY_APPARMOR + default "yama" if DEFAULT_SECURITY_YAMA default "" if DEFAULT_SECURITY_DAC endmenu diff --git a/security/Makefile b/security/Makefile index a5e502f8a05b..c26c81e92571 100644 --- a/security/Makefile +++ b/security/Makefile @@ -7,6 +7,7 @@ subdir-$(CONFIG_SECURITY_SELINUX) += selinux subdir-$(CONFIG_SECURITY_SMACK) += smack subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor +subdir-$(CONFIG_SECURITY_YAMA) += yama # always enable default capabilities obj-y += commoncap.o @@ -21,6 +22,7 @@ obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o obj-$(CONFIG_AUDIT) += lsm_audit.o obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/built-in.o +obj-$(CONFIG_SECURITY_YAMA) += yama/built-in.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o # Object integrity file lists diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 2dafe50a2e25..806bd19af7f2 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -15,7 +15,7 @@ clean-files := capability_names.h rlim_names.h # to # [1] = "dac_override", quiet_cmd_make-caps = GEN $@ -cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ;\ +cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ @@ -28,25 +28,38 @@ cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ;\ # [RLIMIT_STACK] = "stack", # # and build a second integer table (with the second sed cmd), that maps -# RLIMIT defines to the order defined in asm-generic/resource.h Thi is +# RLIMIT defines to the order defined in asm-generic/resource.h This is # required by policy load to map policy ordering of RLIMITs to internal # ordering for architectures that redefine an RLIMIT. # Transforms lines from # #define RLIMIT_STACK 3 /* max stack size */ # to # RLIMIT_STACK, +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define RLIMIT_FSIZE 1 /* Maximum filesize */ +# #define RLIMIT_STACK 3 /* max stack size */ +# to +# #define AA_FS_RLIMIT_MASK "fsize stack" quiet_cmd_make-rlim = GEN $@ -cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\ +cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ + > $@ ;\ sed $< >> $@ -r -n \ -e 's/^\# ?define[ \t]+(RLIMIT_([A-Z0-9_]+)).*/[\1] = "\L\2",/p';\ echo "};" >> $@ ;\ - echo "static const int rlim_map[] = {" >> $@ ;\ + echo "static const int rlim_map[RLIM_NLIMITS] = {" >> $@ ;\ sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\ - echo "};" >> $@ + echo "};" >> $@ ; \ + echo -n '\#define AA_FS_RLIMIT_MASK "' >> $@ ;\ + sed -r -n 's/^\# ?define[ \t]+RLIMIT_([A-Z0-9_]+).*/\L\1/p' $< | \ + tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h $(obj)/resource.o : $(obj)/rlim_names.h -$(obj)/capability_names.h : $(srctree)/include/linux/capability.h +$(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ + $(src)/Makefile $(call cmd,make-caps) -$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h +$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h \ + $(src)/Makefile $(call cmd,make-rlim) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index e39df6d43779..16c15ec6f670 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -18,12 +18,14 @@ #include <linux/seq_file.h> #include <linux/uaccess.h> #include <linux/namei.h> +#include <linux/capability.h> #include "include/apparmor.h" #include "include/apparmorfs.h" #include "include/audit.h" #include "include/context.h" #include "include/policy.h" +#include "include/resource.h" /** * aa_simple_write_to_buffer - common routine for getting policy from user @@ -142,38 +144,166 @@ static const struct file_operations aa_fs_profile_remove = { .llseek = default_llseek, }; -/** Base file system setup **/ +static int aa_fs_seq_show(struct seq_file *seq, void *v) +{ + struct aa_fs_entry *fs_file = seq->private; + + if (!fs_file) + return 0; -static struct dentry *aa_fs_dentry __initdata; + switch (fs_file->v_type) { + case AA_FS_TYPE_BOOLEAN: + seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no"); + break; + case AA_FS_TYPE_STRING: + seq_printf(seq, "%s\n", fs_file->v.string); + break; + case AA_FS_TYPE_U64: + seq_printf(seq, "%#08lx\n", fs_file->v.u64); + break; + default: + /* Ignore unpritable entry types. */ + break; + } + + return 0; +} -static void __init aafs_remove(const char *name) +static int aa_fs_seq_open(struct inode *inode, struct file *file) { - struct dentry *dentry; + return single_open(file, aa_fs_seq_show, inode->i_private); +} + +const struct file_operations aa_fs_seq_file_ops = { + .owner = THIS_MODULE, + .open = aa_fs_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** Base file system setup **/ + +static struct aa_fs_entry aa_fs_entry_file[] = { + AA_FS_FILE_STRING("mask", "create read write exec append mmap_exec " \ + "link lock"), + { } +}; - dentry = lookup_one_len(name, aa_fs_dentry, strlen(name)); - if (!IS_ERR(dentry)) { - securityfs_remove(dentry); - dput(dentry); +static struct aa_fs_entry aa_fs_entry_domain[] = { + AA_FS_FILE_BOOLEAN("change_hat", 1), + AA_FS_FILE_BOOLEAN("change_hatv", 1), + AA_FS_FILE_BOOLEAN("change_onexec", 1), + AA_FS_FILE_BOOLEAN("change_profile", 1), + { } +}; + +static struct aa_fs_entry aa_fs_entry_features[] = { + AA_FS_DIR("domain", aa_fs_entry_domain), + AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), + AA_FS_DIR("rlimit", aa_fs_entry_rlimit), + { } +}; + +static struct aa_fs_entry aa_fs_entry_apparmor[] = { + AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), + AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), + AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), + AA_FS_DIR("features", aa_fs_entry_features), + { } +}; + +static struct aa_fs_entry aa_fs_entry = + AA_FS_DIR("apparmor", aa_fs_entry_apparmor); + +/** + * aafs_create_file - create a file entry in the apparmor securityfs + * @fs_file: aa_fs_entry to build an entry for (NOT NULL) + * @parent: the parent dentry in the securityfs + * + * Use aafs_remove_file to remove entries created with this fn. + */ +static int __init aafs_create_file(struct aa_fs_entry *fs_file, + struct dentry *parent) +{ + int error = 0; + + fs_file->dentry = securityfs_create_file(fs_file->name, + S_IFREG | fs_file->mode, + parent, fs_file, + fs_file->file_ops); + if (IS_ERR(fs_file->dentry)) { + error = PTR_ERR(fs_file->dentry); + fs_file->dentry = NULL; } + return error; } /** - * aafs_create - create an entry in the apparmor filesystem - * @name: name of the entry (NOT NULL) - * @mask: file permission mask of the file - * @fops: file operations for the file (NOT NULL) + * aafs_create_dir - recursively create a directory entry in the securityfs + * @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL) + * @parent: the parent dentry in the securityfs * - * Used aafs_remove to remove entries created with this fn. + * Use aafs_remove_dir to remove entries created with this fn. */ -static int __init aafs_create(const char *name, umode_t mask, - const struct file_operations *fops) +static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, + struct dentry *parent) { - struct dentry *dentry; + int error; + struct aa_fs_entry *fs_file; - dentry = securityfs_create_file(name, S_IFREG | mask, aa_fs_dentry, - NULL, fops); + fs_dir->dentry = securityfs_create_dir(fs_dir->name, parent); + if (IS_ERR(fs_dir->dentry)) { + error = PTR_ERR(fs_dir->dentry); + fs_dir->dentry = NULL; + goto failed; + } - return IS_ERR(dentry) ? PTR_ERR(dentry) : 0; + for (fs_file = fs_dir->v.files; fs_file->name; ++fs_file) { + if (fs_file->v_type == AA_FS_TYPE_DIR) + error = aafs_create_dir(fs_file, fs_dir->dentry); + else + error = aafs_create_file(fs_file, fs_dir->dentry); + if (error) + goto failed; + } + + return 0; + +failed: + return error; +} + +/** + * aafs_remove_file - drop a single file entry in the apparmor securityfs + * @fs_file: aa_fs_entry to detach from the securityfs (NOT NULL) + */ +static void __init aafs_remove_file(struct aa_fs_entry *fs_file) +{ + if (!fs_file->dentry) + return; + + securityfs_remove(fs_file->dentry); + fs_file->dentry = NULL; +} + +/** + * aafs_remove_dir - recursively drop a directory entry from the securityfs + * @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL) + */ +static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) +{ + struct aa_fs_entry *fs_file; + + for (fs_file = fs_dir->v.files; fs_file->name; ++fs_file) { + if (fs_file->v_type == AA_FS_TYPE_DIR) + aafs_remove_dir(fs_file); + else + aafs_remove_file(fs_file); + } + + aafs_remove_file(fs_dir); } /** @@ -183,14 +313,7 @@ static int __init aafs_create(const char *name, umode_t mask, */ void __init aa_destroy_aafs(void) { - if (aa_fs_dentry) { - aafs_remove(".remove"); - aafs_remove(".replace"); - aafs_remove(".load"); - - securityfs_remove(aa_fs_dentry); - aa_fs_dentry = NULL; - } + aafs_remove_dir(&aa_fs_entry); } /** @@ -207,25 +330,13 @@ static int __init aa_create_aafs(void) if (!apparmor_initialized) return 0; - if (aa_fs_dentry) { + if (aa_fs_entry.dentry) { AA_ERROR("%s: AppArmor securityfs already exists\n", __func__); return -EEXIST; } - aa_fs_dentry = securityfs_create_dir("apparmor", NULL); - if (IS_ERR(aa_fs_dentry)) { - error = PTR_ERR(aa_fs_dentry); - aa_fs_dentry = NULL; - goto error; - } - - error = aafs_create(".load", 0640, &aa_fs_profile_load); - if (error) - goto error; - error = aafs_create(".replace", 0640, &aa_fs_profile_replace); - if (error) - goto error; - error = aafs_create(".remove", 0640, &aa_fs_profile_remove); + /* Populate fs tree. */ + error = aafs_create_dir(&aa_fs_entry, NULL); if (error) goto error; diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index f3fafedd798a..3ae28db5a64f 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -19,7 +19,7 @@ #include "include/audit.h" #include "include/policy.h" -const char *op_table[] = { +const char *const op_table[] = { "null", "sysctl", @@ -73,7 +73,7 @@ const char *op_table[] = { "profile_remove" }; -const char *audit_mode_names[] = { +const char *const audit_mode_names[] = { "normal", "quiet_denied", "quiet", @@ -81,7 +81,7 @@ const char *audit_mode_names[] = { "all" }; -static char *aa_audit_type[] = { +static const char *const aa_audit_type[] = { "AUDIT", "ALLOWED", "DENIED", @@ -89,6 +89,7 @@ static char *aa_audit_type[] = { "STATUS", "ERROR", "KILLED" + "AUTO" }; /* @@ -110,27 +111,27 @@ static char *aa_audit_type[] = { static void audit_pre(struct audit_buffer *ab, void *ca) { struct common_audit_data *sa = ca; - struct task_struct *tsk = sa->tsk ? sa->tsk : current; + struct task_struct *tsk = sa->aad->tsk ? sa->aad->tsk : current; if (aa_g_audit_header) { audit_log_format(ab, "apparmor="); - audit_log_string(ab, aa_audit_type[sa->aad.type]); + audit_log_string(ab, aa_audit_type[sa->aad->type]); } - if (sa->aad.op) { + if (sa->aad->op) { audit_log_format(ab, " operation="); - audit_log_string(ab, op_table[sa->aad.op]); + audit_log_string(ab, op_table[sa->aad->op]); } - if (sa->aad.info) { + if (sa->aad->info) { audit_log_format(ab, " info="); - audit_log_string(ab, sa->aad.info); - if (sa->aad.error) - audit_log_format(ab, " error=%d", sa->aad.error); + audit_log_string(ab, sa->aad->info); + if (sa->aad->error) + audit_log_format(ab, " error=%d", sa->aad->error); } - if (sa->aad.profile) { - struct aa_profile *profile = sa->aad.profile; + if (sa->aad->profile) { + struct aa_profile *profile = sa->aad->profile; pid_t pid; rcu_read_lock(); pid = rcu_dereference(tsk->real_parent)->pid; @@ -144,10 +145,16 @@ static void audit_pre(struct audit_buffer *ab, void *ca) audit_log_untrustedstring(ab, profile->base.hname); } - if (sa->aad.name) { + if (sa->aad->name) { audit_log_format(ab, " name="); - audit_log_untrustedstring(ab, sa->aad.name); + audit_log_untrustedstring(ab, sa->aad->name); } + + if (sa->aad->tsk) { + audit_log_format(ab, " pid=%d comm=", tsk->pid); + audit_log_untrustedstring(ab, tsk->comm); + } + } /** @@ -158,10 +165,8 @@ static void audit_pre(struct audit_buffer *ab, void *ca) void aa_audit_msg(int type, struct common_audit_data *sa, void (*cb) (struct audit_buffer *, void *)) { - sa->aad.type = type; - sa->lsm_pre_audit = audit_pre; - sa->lsm_post_audit = cb; - common_lsm_audit(sa); + sa->aad->type = type; + common_lsm_audit(sa, audit_pre, cb); } /** @@ -183,7 +188,7 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, BUG_ON(!profile); if (type == AUDIT_APPARMOR_AUTO) { - if (likely(!sa->aad.error)) { + if (likely(!sa->aad->error)) { if (AUDIT_MODE(profile) != AUDIT_ALL) return 0; type = AUDIT_APPARMOR_AUDIT; @@ -195,21 +200,22 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, if (AUDIT_MODE(profile) == AUDIT_QUIET || (type == AUDIT_APPARMOR_DENIED && AUDIT_MODE(profile) == AUDIT_QUIET)) - return sa->aad.error; + return sa->aad->error; if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED) type = AUDIT_APPARMOR_KILL; if (!unconfined(profile)) - sa->aad.profile = profile; + sa->aad->profile = profile; aa_audit_msg(type, sa, cb); - if (sa->aad.type == AUDIT_APPARMOR_KILL) - (void)send_sig_info(SIGKILL, NULL, sa->tsk ? sa->tsk : current); + if (sa->aad->type == AUDIT_APPARMOR_KILL) + (void)send_sig_info(SIGKILL, NULL, + sa->aad->tsk ? sa->aad->tsk : current); - if (sa->aad.type == AUDIT_APPARMOR_ALLOWED) - return complain_error(sa->aad.error); + if (sa->aad->type == AUDIT_APPARMOR_ALLOWED) + return complain_error(sa->aad->error); - return sa->aad.error; + return sa->aad->error; } diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 9982c48def4e..887a5e948945 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -64,11 +64,13 @@ static int audit_caps(struct aa_profile *profile, struct task_struct *task, struct audit_cache *ent; int type = AUDIT_APPARMOR_AUTO; struct common_audit_data sa; - COMMON_AUDIT_DATA_INIT(&sa, CAP); - sa.tsk = task; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_CAP; + sa.aad = &aad; sa.u.cap = cap; - sa.aad.op = OP_CAPABLE; - sa.aad.error = error; + sa.aad->tsk = task; + sa.aad->op = OP_CAPABLE; + sa.aad->error = error; if (likely(!error)) { /* test if auditing is being forced */ diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index c1e18ba5bdc0..b81ea10a17a3 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -372,13 +372,12 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) state = profile->file.start; /* buffer freed below, name is pointer into buffer */ - error = aa_get_name(&bprm->file->f_path, profile->path_flags, &buffer, - &name); + error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer, + &name, &info); if (error) { if (profile->flags & (PFLAG_IX_ON_NAME_ERROR | PFLAG_UNCONFINED)) error = 0; - info = "Exec failed name resolution"; name = bprm->filename; goto audit; } @@ -395,6 +394,11 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) new_profile = find_attach(ns, &ns->base.profiles, name); if (!new_profile) goto cleanup; + /* + * NOTE: Domain transitions from unconfined are allowed + * even when no_new_privs is set because this aways results + * in a further reduction of permissions. + */ goto apply; } @@ -411,7 +415,8 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) * exec\0change_profile */ state = aa_dfa_null_transition(profile->file.dfa, state); - cp = change_profile_perms(profile, cxt->onexec->ns, name, + cp = change_profile_perms(profile, cxt->onexec->ns, + cxt->onexec->base.name, AA_MAY_ONEXEC, state); if (!(cp.allow & AA_MAY_ONEXEC)) @@ -455,6 +460,16 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) /* fail exec */ error = -EACCES; + /* + * Policy has specified a domain transition, if no_new_privs then + * fail the exec. + */ + if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) { + aa_put_profile(new_profile); + error = -EPERM; + goto cleanup; + } + if (!new_profile) goto audit; @@ -609,6 +624,14 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) const char *target = NULL, *info = NULL; int error = 0; + /* + * Fail explicitly requested domain transitions if no_new_privs. + * There is no exception for unconfined as change_hat is not + * available. + */ + if (current->no_new_privs) + return -EPERM; + /* released below */ cred = get_current_cred(); cxt = cred->security; @@ -750,6 +773,18 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec, cxt = cred->security; profile = aa_cred_profile(cred); + /* + * Fail explicitly requested domain transitions if no_new_privs + * and not unconfined. + * Domain transitions from unconfined are allowed even when + * no_new_privs is set because this aways results in a reduction + * of permissions. + */ + if (current->no_new_privs && !unconfined(profile)) { + put_cred(cred); + return -EPERM; + } + if (ns_name) { /* released below */ ns = aa_find_namespace(profile->ns, ns_name); diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 7312db741219..cf19d4093ca4 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -67,22 +67,22 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) struct common_audit_data *sa = va; uid_t fsuid = current_fsuid(); - if (sa->aad.fs.request & AA_AUDIT_FILE_MASK) { + if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) { audit_log_format(ab, " requested_mask="); - audit_file_mask(ab, sa->aad.fs.request); + audit_file_mask(ab, sa->aad->fs.request); } - if (sa->aad.fs.denied & AA_AUDIT_FILE_MASK) { + if (sa->aad->fs.denied & AA_AUDIT_FILE_MASK) { audit_log_format(ab, " denied_mask="); - audit_file_mask(ab, sa->aad.fs.denied); + audit_file_mask(ab, sa->aad->fs.denied); } - if (sa->aad.fs.request & AA_AUDIT_FILE_MASK) { + if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) { audit_log_format(ab, " fsuid=%d", fsuid); - audit_log_format(ab, " ouid=%d", sa->aad.fs.ouid); + audit_log_format(ab, " ouid=%d", sa->aad->fs.ouid); } - if (sa->aad.fs.target) { + if (sa->aad->fs.target) { audit_log_format(ab, " target="); - audit_log_untrustedstring(ab, sa->aad.fs.target); + audit_log_untrustedstring(ab, sa->aad->fs.target); } } @@ -107,45 +107,47 @@ int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, { int type = AUDIT_APPARMOR_AUTO; struct common_audit_data sa; - COMMON_AUDIT_DATA_INIT(&sa, NONE); - sa.aad.op = op, - sa.aad.fs.request = request; - sa.aad.name = name; - sa.aad.fs.target = target; - sa.aad.fs.ouid = ouid; - sa.aad.info = info; - sa.aad.error = error; - - if (likely(!sa.aad.error)) { + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.op = op, + aad.fs.request = request; + aad.name = name; + aad.fs.target = target; + aad.fs.ouid = ouid; + aad.info = info; + aad.error = error; + + if (likely(!sa.aad->error)) { u32 mask = perms->audit; if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) mask = 0xffff; /* mask off perms that are not being force audited */ - sa.aad.fs.request &= mask; + sa.aad->fs.request &= mask; - if (likely(!sa.aad.fs.request)) + if (likely(!sa.aad->fs.request)) return 0; type = AUDIT_APPARMOR_AUDIT; } else { /* only report permissions that were denied */ - sa.aad.fs.request = sa.aad.fs.request & ~perms->allow; + sa.aad->fs.request = sa.aad->fs.request & ~perms->allow; - if (sa.aad.fs.request & perms->kill) + if (sa.aad->fs.request & perms->kill) type = AUDIT_APPARMOR_KILL; /* quiet known rejects, assumes quiet and kill do not overlap */ - if ((sa.aad.fs.request & perms->quiet) && + if ((sa.aad->fs.request & perms->quiet) && AUDIT_MODE(profile) != AUDIT_NOQUIET && AUDIT_MODE(profile) != AUDIT_ALL) - sa.aad.fs.request &= ~perms->quiet; + sa.aad->fs.request &= ~perms->quiet; - if (!sa.aad.fs.request) - return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; + if (!sa.aad->fs.request) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; } - sa.aad.fs.denied = sa.aad.fs.request & ~perms->allow; + sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow; return aa_audit(type, profile, gfp, &sa, file_audit_cb); } @@ -173,8 +175,6 @@ static u32 map_old_perms(u32 old) if (old & 0x40) /* AA_EXEC_MMAP */ new |= AA_EXEC_MMAP; - new |= AA_MAY_META_READ; - return new; } @@ -212,10 +212,13 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); perms.xindex = dfa_other_xindex(dfa, state); } + perms.allow |= AA_MAY_META_READ; /* change_profile wasn't determined by ownership in old mapping */ if (ACCEPT_TABLE(dfa)[state] & 0x80000000) perms.allow |= AA_MAY_CHANGE_PROFILE; + if (ACCEPT_TABLE(dfa)[state] & 0x40000000) + perms.allow |= AA_MAY_ONEXEC; return perms; } @@ -279,22 +282,16 @@ int aa_path_perm(int op, struct aa_profile *profile, struct path *path, int error; flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); - error = aa_get_name(path, flags, &buffer, &name); + error = aa_path_name(path, flags, &buffer, &name, &info); if (error) { if (error == -ENOENT && is_deleted(path->dentry)) { /* Access to open files that are deleted are * give a pass (implicit delegation) */ error = 0; + info = NULL; perms.allow = request; - } else if (error == -ENOENT) - info = "Failed name lookup - deleted entry"; - else if (error == -ESTALE) - info = "Failed name lookup - disconnected path"; - else if (error == -ENAMETOOLONG) - info = "Failed name lookup - name too long"; - else - info = "Failed name lookup"; + } } else { aa_str_perms(profile->file.dfa, profile->file.start, name, cond, &perms); @@ -365,12 +362,14 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, lperms = nullperms; /* buffer freed below, lname is pointer in buffer */ - error = aa_get_name(&link, profile->path_flags, &buffer, &lname); + error = aa_path_name(&link, profile->path_flags, &buffer, &lname, + &info); if (error) goto audit; /* buffer2 freed below, tname is pointer in buffer2 */ - error = aa_get_name(&target, profile->path_flags, &buffer2, &tname); + error = aa_path_name(&target, profile->path_flags, &buffer2, &tname, + &info); if (error) goto audit; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index df3649560818..40aedd9f73ea 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -19,6 +19,19 @@ #include "match.h" +/* + * Class of mediation types in the AppArmor policy db + */ +#define AA_CLASS_ENTRY 0 +#define AA_CLASS_UNKNOWN 1 +#define AA_CLASS_FILE 2 +#define AA_CLASS_CAP 3 +#define AA_CLASS_NET 4 +#define AA_CLASS_RLIMITS 5 +#define AA_CLASS_DOMAIN 6 + +#define AA_CLASS_LAST AA_CLASS_DOMAIN + /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; extern bool aa_g_audit_header; @@ -81,7 +94,7 @@ static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start) { /* the null transition only needs the string's null terminator byte */ - return aa_dfa_match_len(dfa, start, "", 1); + return aa_dfa_next(dfa, start, 0); } static inline bool mediated_filesystem(struct inode *inode) diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index cb1e93a114d7..7ea4769fab3f 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -15,6 +15,50 @@ #ifndef __AA_APPARMORFS_H #define __AA_APPARMORFS_H +enum aa_fs_type { + AA_FS_TYPE_BOOLEAN, + AA_FS_TYPE_STRING, + AA_FS_TYPE_U64, + AA_FS_TYPE_FOPS, + AA_FS_TYPE_DIR, +}; + +struct aa_fs_entry; + +struct aa_fs_entry { + const char *name; + struct dentry *dentry; + umode_t mode; + enum aa_fs_type v_type; + union { + bool boolean; + char *string; + unsigned long u64; + struct aa_fs_entry *files; + } v; + const struct file_operations *file_ops; +}; + +extern const struct file_operations aa_fs_seq_file_ops; + +#define AA_FS_FILE_BOOLEAN(_name, _value) \ + { .name = (_name), .mode = 0444, \ + .v_type = AA_FS_TYPE_BOOLEAN, .v.boolean = (_value), \ + .file_ops = &aa_fs_seq_file_ops } +#define AA_FS_FILE_STRING(_name, _value) \ + { .name = (_name), .mode = 0444, \ + .v_type = AA_FS_TYPE_STRING, .v.string = (_value), \ + .file_ops = &aa_fs_seq_file_ops } +#define AA_FS_FILE_U64(_name, _value) \ + { .name = (_name), .mode = 0444, \ + .v_type = AA_FS_TYPE_U64, .v.u64 = (_value), \ + .file_ops = &aa_fs_seq_file_ops } +#define AA_FS_FILE_FOPS(_name, _mode, _fops) \ + { .name = (_name), .v_type = AA_FS_TYPE_FOPS, \ + .mode = (_mode), .file_ops = (_fops) } +#define AA_FS_DIR(_name, _value) \ + { .name = (_name), .v_type = AA_FS_TYPE_DIR, .v.files = (_value) } + extern void __init aa_destroy_aafs(void); #endif /* __AA_APPARMORFS_H */ diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 1951786d32e9..4b7e18951aea 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -25,11 +25,9 @@ struct aa_profile; -extern const char *audit_mode_names[]; +extern const char *const audit_mode_names[]; #define AUDIT_MAX_INDEX 5 -#define AUDIT_APPARMOR_AUTO 0 /* auto choose audit message type */ - enum audit_mode { AUDIT_NORMAL, /* follow normal auditing of accesses */ AUDIT_QUIET_DENIED, /* quiet all denied access messages */ @@ -45,10 +43,11 @@ enum audit_type { AUDIT_APPARMOR_HINT, AUDIT_APPARMOR_STATUS, AUDIT_APPARMOR_ERROR, - AUDIT_APPARMOR_KILL + AUDIT_APPARMOR_KILL, + AUDIT_APPARMOR_AUTO }; -extern const char *op_table[]; +extern const char *const op_table[]; enum aa_ops { OP_NULL, @@ -104,7 +103,34 @@ enum aa_ops { }; -/* define a short hand for apparmor_audit_data portion of common_audit_data */ +struct apparmor_audit_data { + int error; + int op; + int type; + void *profile; + const char *name; + const char *info; + struct task_struct *tsk; + union { + void *target; + struct { + long pos; + void *target; + } iface; + struct { + int rlim; + unsigned long max; + } rlim; + struct { + const char *target; + u32 request; + u32 denied; + uid_t ouid; + } fs; + }; +}; + +/* define a short hand for apparmor_audit_data structure */ #define aad apparmor_audit_data void aa_audit_msg(int type, struct common_audit_data *sa, diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index ab8c6d87f758..f98fd4701d80 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -117,7 +117,7 @@ static inline u16 dfa_map_xindex(u16 mask) index |= AA_X_NAME; } else if (old_index == 3) { index |= AA_X_NAME | AA_X_CHILD; - } else { + } else if (old_index) { index |= AA_X_TABLE; index |= old_index - 4; } diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index a4a863997bd5..775843e7f984 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -116,6 +116,9 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, const char *str, int len); unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, const char *str); +unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, + const char c); + void aa_dfa_free_kref(struct kref *kref); /** diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index 27b327a7fae5..286ac75dc88b 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -26,6 +26,7 @@ enum path_flags { PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */ }; -int aa_get_name(struct path *path, int flags, char **buffer, const char **name); +int aa_path_name(struct path *path, int flags, char **buffer, + const char **name, const char **info); #endif /* __AA_PATH_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index aeda5cf56904..bda4569fdd83 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -29,7 +29,7 @@ #include "file.h" #include "resource.h" -extern const char *profile_mode_names[]; +extern const char *const profile_mode_names[]; #define APPARMOR_NAMES_MAX_INDEX 3 #define COMPLAIN_MODE(_profile) \ @@ -129,6 +129,17 @@ struct aa_namespace { struct list_head sub_ns; }; +/* struct aa_policydb - match engine for a policy + * dfa: dfa pattern match + * start: set of start states for the different classes of data + */ +struct aa_policydb { + /* Generic policy DFA specific rule types will be subsections of it */ + struct aa_dfa *dfa; + unsigned int start[AA_CLASS_LAST + 1]; + +}; + /* struct aa_profile - basic confinement data * @base - base components of the profile (name, refcount, lists, lock ...) * @parent: parent of profile @@ -143,6 +154,7 @@ struct aa_namespace { * @flags: flags controlling profile behavior * @path_flags: flags controlling path generation behavior * @size: the memory consumed by this profiles rules + * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile * @rlimits: rlimits for the profile @@ -179,6 +191,7 @@ struct aa_profile { u32 path_flags; int size; + struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; struct aa_rlimit rlimits; diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h index 02baec732bb5..d3f4cf027957 100644 --- a/security/apparmor/include/resource.h +++ b/security/apparmor/include/resource.h @@ -18,6 +18,8 @@ #include <linux/resource.h> #include <linux/sched.h> +#include "apparmorfs.h" + struct aa_profile; /* struct aa_rlimit - rlimit settings for the profile @@ -32,6 +34,8 @@ struct aa_rlimit { struct rlimit limits[RLIM_NLIMITS]; }; +extern struct aa_fs_entry aa_fs_entry_rlimit[]; + int aa_map_resource(int resource); int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *, unsigned int resource, struct rlimit *new_rlim); diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 7ee05c6f3c64..cf1071b14232 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -26,7 +26,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; audit_log_format(ab, " target="); - audit_log_untrustedstring(ab, sa->aad.target); + audit_log_untrustedstring(ab, sa->aad->target); } /** @@ -41,10 +41,12 @@ static int aa_audit_ptrace(struct aa_profile *profile, struct aa_profile *target, int error) { struct common_audit_data sa; - COMMON_AUDIT_DATA_INIT(&sa, NONE); - sa.aad.op = OP_PTRACE; - sa.aad.target = target; - sa.aad.error = error; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.op = OP_PTRACE; + aad.target = target; + aad.error = error; return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa, audit_cb); diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 9516948041ad..7430298116d6 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -65,8 +65,10 @@ void aa_info_message(const char *str) { if (audit_enabled) { struct common_audit_data sa; - COMMON_AUDIT_DATA_INIT(&sa, NONE); - sa.aad.info = str; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.info = str; aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL); } printk(KERN_INFO "AppArmor: %s\n", str); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 97ce8fae49b3..032daab449b0 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -373,7 +373,7 @@ static int apparmor_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) AA_MAY_META_READ); } -static int apparmor_dentry_open(struct file *file, const struct cred *cred) +static int apparmor_file_open(struct file *file, const struct cred *cred) { struct aa_file_cxt *fcxt = file->f_security; struct aa_profile *profile; @@ -588,10 +588,12 @@ static int apparmor_setprocattr(struct task_struct *task, char *name, error = aa_setprocattr_permipc(args); } else { struct common_audit_data sa; - COMMON_AUDIT_DATA_INIT(&sa, NONE); - sa.aad.op = OP_SETPROCATTR; - sa.aad.info = name; - sa.aad.error = -EINVAL; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.op = OP_SETPROCATTR; + aad.info = name; + aad.error = -EINVAL; return aa_audit(AUDIT_APPARMOR_DENIED, __aa_current_profile(), GFP_KERNEL, &sa, NULL); @@ -638,9 +640,9 @@ static struct security_operations apparmor_ops = { .path_chmod = apparmor_path_chmod, .path_chown = apparmor_path_chown, .path_truncate = apparmor_path_truncate, - .dentry_open = apparmor_dentry_open, .inode_getattr = apparmor_inode_getattr, + .file_open = apparmor_file_open, .file_permission = apparmor_file_permission, .file_alloc_security = apparmor_file_alloc_security, .file_free_security = apparmor_file_free_security, diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 94de6b4907c8..90971a8c3789 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -335,12 +335,12 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, } /** - * aa_dfa_next_state - traverse @dfa to find state @str stops at + * aa_dfa_match - traverse @dfa to find state @str stops at * @dfa: the dfa to match @str against (NOT NULL) * @start: the state of the dfa to start matching in * @str: the null terminated string of bytes to match against the dfa (NOT NULL) * - * aa_dfa_next_state will match @str against the dfa and return the state it + * aa_dfa_match will match @str against the dfa and return the state it * finished matching in. The final state can be used to look up the accepting * label, or as the start state of a continuing match. * @@ -349,5 +349,79 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, const char *str) { - return aa_dfa_match_len(dfa, start, str, strlen(str)); + u16 *def = DEFAULT_TABLE(dfa); + u32 *base = BASE_TABLE(dfa); + u16 *next = NEXT_TABLE(dfa); + u16 *check = CHECK_TABLE(dfa); + unsigned int state = start, pos; + + if (state == 0) + return 0; + + /* current state is <state>, matching character *str */ + if (dfa->tables[YYTD_ID_EC]) { + /* Equivalence class table defined */ + u8 *equiv = EQUIV_TABLE(dfa); + /* default is direct to next state */ + while (*str) { + pos = base[state] + equiv[(u8) *str++]; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + } + } else { + /* default is direct to next state */ + while (*str) { + pos = base[state] + (u8) *str++; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + } + } + + return state; +} + +/** + * aa_dfa_next - step one character to the next state in the dfa + * @dfa: the dfa to tranverse (NOT NULL) + * @state: the state to start in + * @c: the input character to transition on + * + * aa_dfa_match will step through the dfa by one input character @c + * + * Returns: state reach after input @c + */ +unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, + const char c) +{ + u16 *def = DEFAULT_TABLE(dfa); + u32 *base = BASE_TABLE(dfa); + u16 *next = NEXT_TABLE(dfa); + u16 *check = CHECK_TABLE(dfa); + unsigned int pos; + + /* current state is <state>, matching character *str */ + if (dfa->tables[YYTD_ID_EC]) { + /* Equivalence class table defined */ + u8 *equiv = EQUIV_TABLE(dfa); + /* default is direct to next state */ + + pos = base[state] + equiv[(u8) c]; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + } else { + /* default is direct to next state */ + pos = base[state] + (u8) c; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + } + + return state; } diff --git a/security/apparmor/path.c b/security/apparmor/path.c index 9d070a7c3ffc..e91ffee80162 100644 --- a/security/apparmor/path.c +++ b/security/apparmor/path.c @@ -83,31 +83,31 @@ static int d_namespace_path(struct path *path, char *buf, int buflen, struct path root; get_fs_root(current->fs, &root); res = __d_path(path, &root, buf, buflen); - if (res && !IS_ERR(res)) { - /* everything's fine */ - *name = res; - path_put(&root); - goto ok; - } path_put(&root); - connected = 0; + } else { + res = d_absolute_path(path, buf, buflen); + if (!our_mnt(path->mnt)) + connected = 0; } - res = d_absolute_path(path, buf, buflen); - - *name = res; /* handle error conditions - and still allow a partial path to * be returned. */ - if (IS_ERR(res)) { - error = PTR_ERR(res); - *name = buf; - goto out; - } - if (!our_mnt(path->mnt)) + if (!res || IS_ERR(res)) { + if (PTR_ERR(res) == -ENAMETOOLONG) + return -ENAMETOOLONG; + connected = 0; + res = dentry_path_raw(path->dentry, buf, buflen); + if (IS_ERR(res)) { + error = PTR_ERR(res); + *name = buf; + goto out; + }; + } else if (!our_mnt(path->mnt)) connected = 0; -ok: + *name = res; + /* Handle two cases: * 1. A deleted dentry && profile is not allowing mediation of deleted * 2. On some filesystems, newly allocated dentries appear to the @@ -138,7 +138,7 @@ ok: /* disconnected path, don't return pathname starting * with '/' */ - error = -ESTALE; + error = -EACCES; if (*res == '/') *name = res + 1; } @@ -159,7 +159,7 @@ out: * Returns: %0 else error on failure */ static int get_name_to_buffer(struct path *path, int flags, char *buffer, - int size, char **name) + int size, char **name, const char **info) { int adjust = (flags & PATH_IS_DIR) ? 1 : 0; int error = d_namespace_path(path, buffer, size - adjust, name, flags); @@ -171,15 +171,27 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer, */ strcpy(&buffer[size - 2], "/"); + if (info && error) { + if (error == -ENOENT) + *info = "Failed name lookup - deleted entry"; + else if (error == -ESTALE) + *info = "Failed name lookup - disconnected path"; + else if (error == -ENAMETOOLONG) + *info = "Failed name lookup - name too long"; + else + *info = "Failed name lookup"; + } + return error; } /** - * aa_get_name - compute the pathname of a file + * aa_path_name - compute the pathname of a file * @path: path the file (NOT NULL) * @flags: flags controlling path name generation * @buffer: buffer that aa_get_name() allocated (NOT NULL) * @name: Returns - the generated path name if !error (NOT NULL) + * @info: Returns - information on why the path lookup failed (MAYBE NULL) * * @name is a pointer to the beginning of the pathname (which usually differs * from the beginning of the buffer), or NULL. If there is an error @name @@ -192,7 +204,8 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer, * * Returns: %0 else error code if could retrieve name */ -int aa_get_name(struct path *path, int flags, char **buffer, const char **name) +int aa_path_name(struct path *path, int flags, char **buffer, const char **name, + const char **info) { char *buf, *str = NULL; int size = 256; @@ -206,7 +219,7 @@ int aa_get_name(struct path *path, int flags, char **buffer, const char **name) if (!buf) return -ENOMEM; - error = get_name_to_buffer(path, flags, buf, size, &str); + error = get_name_to_buffer(path, flags, buf, size, &str, info); if (error != -ENAMETOOLONG) break; @@ -214,6 +227,7 @@ int aa_get_name(struct path *path, int flags, char **buffer, const char **name) size <<= 1; if (size > aa_g_path_max) return -ENAMETOOLONG; + *info = NULL; } *buffer = buf; *name = str; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 4f0eadee78b8..cf5fd220309b 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -93,7 +93,7 @@ /* root profile namespace */ struct aa_namespace *root_ns; -const char *profile_mode_names[] = { +const char *const profile_mode_names[] = { "enforce", "complain", "kill", @@ -749,6 +749,7 @@ static void free_profile(struct aa_profile *profile) aa_free_sid(profile->sid); aa_put_dfa(profile->xmatch); + aa_put_dfa(profile->policy.dfa); aa_put_profile(profile->replacedby); @@ -902,6 +903,10 @@ struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname) profile = aa_get_profile(__lookup_profile(&ns->base, hname)); read_unlock(&ns->lock); + /* the unconfined profile is not in the regular profile list */ + if (!profile && strcmp(hname, "unconfined") == 0) + profile = aa_get_profile(ns->unconfined); + /* refcount released by caller */ return profile; } @@ -963,11 +968,13 @@ static int audit_policy(int op, gfp_t gfp, const char *name, const char *info, int error) { struct common_audit_data sa; - COMMON_AUDIT_DATA_INIT(&sa, NONE); - sa.aad.op = op; - sa.aad.name = name; - sa.aad.info = info; - sa.aad.error = error; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.op = op; + aad.name = name; + aad.info = info; + aad.error = error; return aa_audit(AUDIT_APPARMOR_STATUS, __aa_current_profile(), gfp, &sa, NULL); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 741dd13e089b..329b1fd30749 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -70,13 +70,13 @@ struct aa_ext { static void audit_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; - if (sa->aad.iface.target) { - struct aa_profile *name = sa->aad.iface.target; + if (sa->aad->iface.target) { + struct aa_profile *name = sa->aad->iface.target; audit_log_format(ab, " name="); audit_log_untrustedstring(ab, name->base.hname); } - if (sa->aad.iface.pos) - audit_log_format(ab, " offset=%ld", sa->aad.iface.pos); + if (sa->aad->iface.pos) + audit_log_format(ab, " offset=%ld", sa->aad->iface.pos); } /** @@ -84,7 +84,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) * @new: profile if it has been allocated (MAYBE NULL) * @name: name of the profile being manipulated (MAYBE NULL) * @info: any extra info about the failure (MAYBE NULL) - * @e: buffer position info (NOT NULL) + * @e: buffer position info * @error: error code * * Returns: %0 or error @@ -94,12 +94,15 @@ static int audit_iface(struct aa_profile *new, const char *name, { struct aa_profile *profile = __aa_current_profile(); struct common_audit_data sa; - COMMON_AUDIT_DATA_INIT(&sa, NONE); - sa.aad.iface.pos = e->pos - e->start; - sa.aad.iface.target = new; - sa.aad.name = name; - sa.aad.info = info; - sa.aad.error = error; + struct apparmor_audit_data aad = {0,}; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + if (e) + aad.iface.pos = e->pos - e->start; + aad.iface.target = new; + aad.name = name; + aad.info = info; + aad.error = error; return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa, audit_cb); @@ -468,7 +471,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; - int error = -EPROTO; + int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -554,11 +557,35 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) goto fail; if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL)) goto fail; + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; } if (!unpack_rlimits(e, profile)) goto fail; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { + /* generic policy dfa - optional and may be NULL */ + profile->policy.dfa = unpack_dfa(e); + if (IS_ERR(profile->policy.dfa)) { + error = PTR_ERR(profile->policy.dfa); + profile->policy.dfa = NULL; + goto fail; + } + if (!unpack_u32(e, &profile->policy.start[0], "start")) + /* default start state */ + profile->policy.start[0] = DFA_START; + /* setup class index */ + for (i = AA_CLASS_FILE; i <= AA_CLASS_LAST; i++) { + profile->policy.start[i] = + aa_dfa_next(profile->policy.dfa, + profile->policy.start[0], + i); + } + if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + /* get file rules */ profile->file.dfa = unpack_dfa(e); if (IS_ERR(profile->file.dfa)) { diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index a4136c10b1c6..e1f3d7ef2c54 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -23,13 +23,18 @@ */ #include "rlim_names.h" +struct aa_fs_entry aa_fs_entry_rlimit[] = { + AA_FS_FILE_STRING("mask", AA_FS_RLIMIT_MASK), + { } +}; + /* audit callback for resource specific fields */ static void audit_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; audit_log_format(ab, " rlimit=%s value=%lu", - rlim_names[sa->aad.rlim.rlim], sa->aad.rlim.max); + rlim_names[sa->aad->rlim.rlim], sa->aad->rlim.max); } /** @@ -45,12 +50,14 @@ static int audit_resource(struct aa_profile *profile, unsigned int resource, unsigned long value, int error) { struct common_audit_data sa; + struct apparmor_audit_data aad = {0,}; - COMMON_AUDIT_DATA_INIT(&sa, NONE); - sa.aad.op = OP_SETRLIMIT, - sa.aad.rlim.rlim = resource; - sa.aad.rlim.max = value; - sa.aad.error = error; + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + aad.op = OP_SETRLIMIT, + aad.rlim.rlim = resource; + aad.rlim.max = value; + aad.error = error; return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_KERNEL, &sa, audit_cb); } diff --git a/security/capability.c b/security/capability.c index 2f680eb02b59..fca889676c5e 100644 --- a/security/capability.c +++ b/security/capability.c @@ -348,7 +348,7 @@ static int cap_file_receive(struct file *file) return 0; } -static int cap_dentry_open(struct file *file, const struct cred *cred) +static int cap_file_open(struct file *file, const struct cred *cred) { return 0; } @@ -358,6 +358,10 @@ static int cap_task_create(unsigned long clone_flags) return 0; } +static void cap_task_free(struct task_struct *task) +{ +} + static int cap_cred_alloc_blank(struct cred *cred, gfp_t gfp) { return 0; @@ -952,8 +956,9 @@ void __init security_fixup_ops(struct security_operations *ops) set_to_cap_if_null(ops, file_set_fowner); set_to_cap_if_null(ops, file_send_sigiotask); set_to_cap_if_null(ops, file_receive); - set_to_cap_if_null(ops, dentry_open); + set_to_cap_if_null(ops, file_open); set_to_cap_if_null(ops, task_create); + set_to_cap_if_null(ops, task_free); set_to_cap_if_null(ops, cred_alloc_blank); set_to_cap_if_null(ops, cred_free); set_to_cap_if_null(ops, cred_prepare); diff --git a/security/commoncap.c b/security/commoncap.c index 7ce191ea29a0..f80d11609391 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -28,6 +28,8 @@ #include <linux/prctl.h> #include <linux/securebits.h> #include <linux/user_namespace.h> +#include <linux/binfmts.h> +#include <linux/personality.h> /* * If a non-root user executes a setuid-root binary in @@ -504,15 +506,23 @@ int cap_bprm_set_creds(struct linux_binprm *bprm) } skip: + /* if we have fs caps, clear dangerous personality flags */ + if (!cap_issubset(new->cap_permitted, old->cap_permitted)) + bprm->per_clear |= PER_CLEAR_ON_SETID; + + /* Don't let someone trace a set[ug]id/setpcap binary with the revised - * credentials unless they have the appropriate permit + * credentials unless they have the appropriate permit. + * + * In addition, if NO_NEW_PRIVS, then ensure we get no new privs. */ if ((new->euid != old->uid || new->egid != old->gid || !cap_issubset(new->cap_permitted, old->cap_permitted)) && bprm->unsafe & ~LSM_UNSAFE_PTRACE_CAP) { /* downgrade; they get no more than they had, and maybe less */ - if (!capable(CAP_SETUID)) { + if (!capable(CAP_SETUID) || + (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS)) { new->euid = new->uid; new->egid = new->gid; } diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 8b5b5d8612c6..442204cc22d9 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -61,8 +61,8 @@ static inline struct dev_cgroup *task_devcgroup(struct task_struct *task) struct cgroup_subsys devices_subsys; -static int devcgroup_can_attach(struct cgroup_subsys *ss, - struct cgroup *new_cgrp, struct cgroup_taskset *set) +static int devcgroup_can_attach(struct cgroup *new_cgrp, + struct cgroup_taskset *set) { struct task_struct *task = cgroup_taskset_first(set); @@ -156,8 +156,7 @@ remove: /* * called from kernel/cgroup.c with cgroup_lock() held. */ -static struct cgroup_subsys_state *devcgroup_create(struct cgroup_subsys *ss, - struct cgroup *cgroup) +static struct cgroup_subsys_state *devcgroup_create(struct cgroup *cgroup) { struct dev_cgroup *dev_cgroup, *parent_dev_cgroup; struct cgroup *parent_cgroup; @@ -195,8 +194,7 @@ static struct cgroup_subsys_state *devcgroup_create(struct cgroup_subsys *ss, return &dev_cgroup->css; } -static void devcgroup_destroy(struct cgroup_subsys *ss, - struct cgroup *cgroup) +static void devcgroup_destroy(struct cgroup *cgroup) { struct dev_cgroup *dev_cgroup; struct dev_whitelist_item *wh, *tmp; @@ -449,22 +447,16 @@ static struct cftype dev_cgroup_files[] = { .read_seq_string = devcgroup_seq_read, .private = DEVCG_LIST, }, + { } /* terminate */ }; -static int devcgroup_populate(struct cgroup_subsys *ss, - struct cgroup *cgroup) -{ - return cgroup_add_files(cgroup, ss, dev_cgroup_files, - ARRAY_SIZE(dev_cgroup_files)); -} - struct cgroup_subsys devices_subsys = { .name = "devices", .can_attach = devcgroup_can_attach, .create = devcgroup_create, .destroy = devcgroup_destroy, - .populate = devcgroup_populate, .subsys_id = devices_subsys_id, + .base_cftypes = dev_cgroup_files, }; int __devcgroup_inode_permission(struct inode *inode, int mask) diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 4f554f20dc97..35664fe6daa1 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -9,8 +9,8 @@ config IMA select CRYPTO_HMAC select CRYPTO_MD5 select CRYPTO_SHA1 - select TCG_TPM if !S390 && !UML - select TCG_TIS if TCG_TPM + select TCG_TPM if HAS_IOMEM && !UML + select TCG_TIS if TCG_TPM && X86 help The Trusted Computing Group(TCG) runtime Integrity Measurement Architecture(IMA) maintains a list of hash diff --git a/security/integrity/ima/ima_audit.c b/security/integrity/ima/ima_audit.c index 2ad942fb1e23..21e96bf188df 100644 --- a/security/integrity/ima/ima_audit.c +++ b/security/integrity/ima/ima_audit.c @@ -61,6 +61,6 @@ void integrity_audit_msg(int audit_msgno, struct inode *inode, audit_log_untrustedstring(ab, inode->i_sb->s_id); audit_log_format(ab, " ino=%lu", inode->i_ino); } - audit_log_format(ab, " res=%d", !result ? 0 : 1); + audit_log_format(ab, " res=%d", !result); audit_log_end(ab); } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 1eff5cb001e5..b17be79b9cf2 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -194,7 +194,9 @@ int ima_bprm_check(struct linux_binprm *bprm) { int rc; - rc = process_measurement(bprm->file, bprm->filename, + rc = process_measurement(bprm->file, + (strcmp(bprm->filename, bprm->interp) == 0) ? + bprm->filename : bprm->interp, MAY_EXEC, BPRM_CHECK); return 0; } diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index d45061d02fee..d8edff209bf3 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -62,6 +62,7 @@ static struct ima_measure_rule_entry default_rules[] = { {.action = DONT_MEASURE,.fsmagic = SYSFS_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = DEBUGFS_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = TMPFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE,.fsmagic = RAMFS_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = SECURITYFS_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = SELINUX_MAGIC,.flags = IMA_FSMAGIC}, {.action = MEASURE,.func = FILE_MMAP,.mask = MAY_EXEC, @@ -417,7 +418,7 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) if (!result && (entry->action == UNKNOWN)) result = -EINVAL; - audit_log_format(ab, "res=%d", !!result); + audit_log_format(ab, "res=%d", !result); audit_log_end(ab); return result; } diff --git a/security/keys/Kconfig b/security/keys/Kconfig new file mode 100644 index 000000000000..a90d6d300dbd --- /dev/null +++ b/security/keys/Kconfig @@ -0,0 +1,71 @@ +# +# Key management configuration +# + +config KEYS + bool "Enable access key retention support" + help + This option provides support for retaining authentication tokens and + access keys in the kernel. + + It also includes provision of methods by which such keys might be + associated with a process so that network filesystems, encryption + support and the like can find them. + + Furthermore, a special type of key is available that acts as keyring: + a searchable sequence of keys. Each process is equipped with access + to five standard keyrings: UID-specific, GID-specific, session, + process and thread. + + If you are unsure as to whether this is required, answer N. + +config TRUSTED_KEYS + tristate "TRUSTED KEYS" + depends on KEYS && TCG_TPM + select CRYPTO + select CRYPTO_HMAC + select CRYPTO_SHA1 + help + This option provides support for creating, sealing, and unsealing + keys in the kernel. Trusted keys are random number symmetric keys, + generated and RSA-sealed by the TPM. The TPM only unseals the keys, + if the boot PCRs and other criteria match. Userspace will only ever + see encrypted blobs. + + If you are unsure as to whether this is required, answer N. + +config ENCRYPTED_KEYS + tristate "ENCRYPTED KEYS" + depends on KEYS + select CRYPTO + select CRYPTO_HMAC + select CRYPTO_AES + select CRYPTO_CBC + select CRYPTO_SHA256 + select CRYPTO_RNG + help + This option provides support for create/encrypting/decrypting keys + in the kernel. Encrypted keys are kernel generated random numbers, + which are encrypted/decrypted with a 'master' symmetric key. The + 'master' key can be either a trusted-key or user-key type. + Userspace only ever sees/stores encrypted blobs. + + If you are unsure as to whether this is required, answer N. + +config KEYS_DEBUG_PROC_KEYS + bool "Enable the /proc/keys file by which keys may be viewed" + depends on KEYS + help + This option turns on support for the /proc/keys file - through which + can be listed all the keys on the system that are viewable by the + reading process. + + The only keys included in the list are those that grant View + permission to the reading process whether or not it possesses them. + Note that LSM security checks are still performed, and may further + filter out keys that the current process is not authorised to view. + + Only key attributes are listed here; key payloads are not included in + the resulting table. + + If you are unsure as to whether this is required, answer N. diff --git a/security/keys/Makefile b/security/keys/Makefile index a56f1ffdc64d..504aaa008388 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -2,6 +2,9 @@ # Makefile for key management # +# +# Core +# obj-y := \ gc.o \ key.o \ @@ -12,9 +15,12 @@ obj-y := \ request_key.o \ request_key_auth.o \ user_defined.o - -obj-$(CONFIG_TRUSTED_KEYS) += trusted.o -obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys/ obj-$(CONFIG_KEYS_COMPAT) += compat.o obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_SYSCTL) += sysctl.o + +# +# Key types +# +obj-$(CONFIG_TRUSTED_KEYS) += trusted.o +obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys/ diff --git a/security/keys/compat.c b/security/keys/compat.c index 4c48e13448f8..fab4f8dda6c6 100644 --- a/security/keys/compat.c +++ b/security/keys/compat.c @@ -135,6 +135,9 @@ asmlinkage long compat_sys_keyctl(u32 option, return compat_keyctl_instantiate_key_iov( arg2, compat_ptr(arg3), arg4, arg5); + case KEYCTL_INVALIDATE: + return keyctl_invalidate_key(arg2); + default: return -EOPNOTSUPP; } diff --git a/security/keys/gc.c b/security/keys/gc.c index a42b45531aac..61ab7c82ebb1 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -72,6 +72,15 @@ void key_schedule_gc(time_t gc_at) } /* + * Schedule a dead links collection run. + */ +void key_schedule_gc_links(void) +{ + set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags); + queue_work(system_nrt_wq, &key_gc_work); +} + +/* * Some key's cleanup time was met after it expired, so we need to get the * reaper to go through a cycle finding expired keys. */ @@ -79,8 +88,7 @@ static void key_gc_timer_func(unsigned long data) { kenter(""); key_gc_next_run = LONG_MAX; - set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags); - queue_work(system_nrt_wq, &key_gc_work); + key_schedule_gc_links(); } /* @@ -131,12 +139,12 @@ void key_gc_keytype(struct key_type *ktype) static void key_gc_keyring(struct key *keyring, time_t limit) { struct keyring_list *klist; - struct key *key; int loop; kenter("%x", key_serial(keyring)); - if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + if (keyring->flags & ((1 << KEY_FLAG_INVALIDATED) | + (1 << KEY_FLAG_REVOKED))) goto dont_gc; /* scan the keyring looking for dead keys */ @@ -148,9 +156,8 @@ static void key_gc_keyring(struct key *keyring, time_t limit) loop = klist->nkeys; smp_rmb(); for (loop--; loop >= 0; loop--) { - key = klist->keys[loop]; - if (test_bit(KEY_FLAG_DEAD, &key->flags) || - (key->expiry > 0 && key->expiry <= limit)) + struct key *key = rcu_dereference(klist->keys[loop]); + if (key_is_dead(key, limit)) goto do_gc; } @@ -168,38 +175,45 @@ do_gc: } /* - * Garbage collect an unreferenced, detached key + * Garbage collect a list of unreferenced, detached keys */ -static noinline void key_gc_unused_key(struct key *key) +static noinline void key_gc_unused_keys(struct list_head *keys) { - key_check(key); - - security_key_free(key); - - /* deal with the user's key tracking and quota */ - if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { - spin_lock(&key->user->lock); - key->user->qnkeys--; - key->user->qnbytes -= key->quotalen; - spin_unlock(&key->user->lock); - } + while (!list_empty(keys)) { + struct key *key = + list_entry(keys->next, struct key, graveyard_link); + list_del(&key->graveyard_link); + + kdebug("- %u", key->serial); + key_check(key); + + security_key_free(key); + + /* deal with the user's key tracking and quota */ + if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } - atomic_dec(&key->user->nkeys); - if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) - atomic_dec(&key->user->nikeys); + atomic_dec(&key->user->nkeys); + if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) + atomic_dec(&key->user->nikeys); - key_user_put(key->user); + key_user_put(key->user); - /* now throw away the key memory */ - if (key->type->destroy) - key->type->destroy(key); + /* now throw away the key memory */ + if (key->type->destroy) + key->type->destroy(key); - kfree(key->description); + kfree(key->description); #ifdef KEY_DEBUGGING - key->magic = KEY_DEBUG_MAGIC_X; + key->magic = KEY_DEBUG_MAGIC_X; #endif - kmem_cache_free(key_jar, key); + kmem_cache_free(key_jar, key); + } } /* @@ -211,6 +225,7 @@ static noinline void key_gc_unused_key(struct key *key) */ static void key_garbage_collector(struct work_struct *work) { + static LIST_HEAD(graveyard); static u8 gc_state; /* Internal persistent state */ #define KEY_GC_REAP_AGAIN 0x01 /* - Need another cycle */ #define KEY_GC_REAPING_LINKS 0x02 /* - We need to reap links */ @@ -316,15 +331,22 @@ maybe_resched: key_schedule_gc(new_timer); } - if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) { - /* Make sure everyone revalidates their keys if we marked a - * bunch as being dead and make sure all keyring ex-payloads - * are destroyed. + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2) || + !list_empty(&graveyard)) { + /* Make sure that all pending keyring payload destructions are + * fulfilled and that people aren't now looking at dead or + * dying keys that they don't have a reference upon or a link + * to. */ - kdebug("dead sync"); + kdebug("gc sync"); synchronize_rcu(); } + if (!list_empty(&graveyard)) { + kdebug("gc keys"); + key_gc_unused_keys(&graveyard); + } + if (unlikely(gc_state & (KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2))) { if (!(gc_state & KEY_GC_FOUND_DEAD_KEY)) { @@ -359,7 +381,7 @@ found_unreferenced_key: rb_erase(&key->serial_node, &key_serial_tree); spin_unlock(&key_serial_lock); - key_gc_unused_key(key); + list_add_tail(&key->graveyard_link, &graveyard); gc_state |= KEY_GC_REAP_AGAIN; goto maybe_resched; diff --git a/security/keys/internal.h b/security/keys/internal.h index 65647f825584..f711b094ed41 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -152,7 +152,8 @@ extern long join_session_keyring(const char *name); extern struct work_struct key_gc_work; extern unsigned key_gc_delay; extern void keyring_gc(struct key *keyring, time_t limit); -extern void key_schedule_gc(time_t expiry_at); +extern void key_schedule_gc(time_t gc_at); +extern void key_schedule_gc_links(void); extern void key_gc_keytype(struct key_type *ktype); extern int key_task_permission(const key_ref_t key_ref, @@ -197,6 +198,17 @@ extern struct key *request_key_auth_new(struct key *target, extern struct key *key_get_instantiation_authkey(key_serial_t target_id); /* + * Determine whether a key is dead. + */ +static inline bool key_is_dead(struct key *key, time_t limit) +{ + return + key->flags & ((1 << KEY_FLAG_DEAD) | + (1 << KEY_FLAG_INVALIDATED)) || + (key->expiry > 0 && key->expiry <= limit); +} + +/* * keyctl() functions */ extern long keyctl_get_keyring_ID(key_serial_t, int); @@ -225,6 +237,7 @@ extern long keyctl_reject_key(key_serial_t, unsigned, unsigned, key_serial_t); extern long keyctl_instantiate_key_iov(key_serial_t, const struct iovec __user *, unsigned, key_serial_t); +extern long keyctl_invalidate_key(key_serial_t); extern long keyctl_instantiate_key_common(key_serial_t, const struct iovec __user *, diff --git a/security/keys/key.c b/security/keys/key.c index 7ada8019be1f..c9bf66ac36e0 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -671,6 +671,26 @@ found_kernel_type: return ktype; } +void key_set_timeout(struct key *key, unsigned timeout) +{ + struct timespec now; + time_t expiry = 0; + + /* make the changes with the locks held to prevent races */ + down_write(&key->sem); + + if (timeout > 0) { + now = current_kernel_time(); + expiry = now.tv_sec + timeout; + } + + key->expiry = expiry; + key_schedule_gc(key->expiry + key_gc_delay); + + up_write(&key->sem); +} +EXPORT_SYMBOL_GPL(key_set_timeout); + /* * Unlock a key type locked by key_type_lookup(). */ @@ -935,6 +955,28 @@ void key_revoke(struct key *key) EXPORT_SYMBOL(key_revoke); /** + * key_invalidate - Invalidate a key. + * @key: The key to be invalidated. + * + * Mark a key as being invalidated and have it cleaned up immediately. The key + * is ignored by all searches and other operations from this point. + */ +void key_invalidate(struct key *key) +{ + kenter("%d", key_serial(key)); + + key_check(key); + + if (!test_bit(KEY_FLAG_INVALIDATED, &key->flags)) { + down_write_nested(&key->sem, 1); + if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags)) + key_schedule_gc_links(); + up_write(&key->sem); + } +} +EXPORT_SYMBOL(key_invalidate); + +/** * register_key_type - Register a type of key. * @ktype: The new key type. * @@ -960,6 +1002,8 @@ int register_key_type(struct key_type *ktype) /* store the type */ list_add(&ktype->link, &key_types_list); + + pr_notice("Key type %s registered\n", ktype->name); ret = 0; out: @@ -982,6 +1026,7 @@ void unregister_key_type(struct key_type *ktype) list_del_init(&ktype->link); downgrade_write(&key_types_sem); key_gc_keytype(ktype); + pr_notice("Key type %s unregistered\n", ktype->name); up_read(&key_types_sem); } EXPORT_SYMBOL(unregister_key_type); diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 0b3f5d72af1c..ddb3e05bc5fc 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -14,6 +14,7 @@ #include <linux/sched.h> #include <linux/slab.h> #include <linux/syscalls.h> +#include <linux/key.h> #include <linux/keyctl.h> #include <linux/fs.h> #include <linux/capability.h> @@ -374,6 +375,37 @@ error: } /* + * Invalidate a key. + * + * The key must be grant the caller Invalidate permission for this to work. + * The key and any links to the key will be automatically garbage collected + * immediately. + * + * If successful, 0 is returned. + */ +long keyctl_invalidate_key(key_serial_t id) +{ + key_ref_t key_ref; + long ret; + + kenter("%d", id); + + key_ref = lookup_user_key(id, 0, KEY_SEARCH); + if (IS_ERR(key_ref)) { + ret = PTR_ERR(key_ref); + goto error; + } + + key_invalidate(key_ref_to_ptr(key_ref)); + ret = 0; + + key_ref_put(key_ref); +error: + kleave(" = %ld", ret); + return ret; +} + +/* * Clear the specified keyring, creating an empty process keyring if one of the * special keyring IDs is used. * @@ -388,11 +420,24 @@ long keyctl_keyring_clear(key_serial_t ringid) keyring_ref = lookup_user_key(ringid, KEY_LOOKUP_CREATE, KEY_WRITE); if (IS_ERR(keyring_ref)) { ret = PTR_ERR(keyring_ref); + + /* Root is permitted to invalidate certain special keyrings */ + if (capable(CAP_SYS_ADMIN)) { + keyring_ref = lookup_user_key(ringid, 0, 0); + if (IS_ERR(keyring_ref)) + goto error; + if (test_bit(KEY_FLAG_ROOT_CAN_CLEAR, + &key_ref_to_ptr(keyring_ref)->flags)) + goto clear; + goto error_put; + } + goto error; } +clear: ret = keyring_clear(key_ref_to_ptr(keyring_ref)); - +error_put: key_ref_put(keyring_ref); error: return ret; @@ -1244,10 +1289,8 @@ error: */ long keyctl_set_timeout(key_serial_t id, unsigned timeout) { - struct timespec now; struct key *key, *instkey; key_ref_t key_ref; - time_t expiry; long ret; key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL, @@ -1273,20 +1316,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout) okay: key = key_ref_to_ptr(key_ref); - - /* make the changes with the locks held to prevent races */ - down_write(&key->sem); - - expiry = 0; - if (timeout > 0) { - now = current_kernel_time(); - expiry = now.tv_sec + timeout; - } - - key->expiry = expiry; - key_schedule_gc(key->expiry + key_gc_delay); - - up_write(&key->sem); + key_set_timeout(key, timeout); key_put(key); ret = 0; @@ -1623,6 +1653,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3, (unsigned) arg4, (key_serial_t) arg5); + case KEYCTL_INVALIDATE: + return keyctl_invalidate_key((key_serial_t) arg2); + default: return -EOPNOTSUPP; } diff --git a/security/keys/keyring.c b/security/keys/keyring.c index d605f75292e4..7445875f6818 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -25,6 +25,15 @@ (keyring)->payload.subscriptions, \ rwsem_is_locked((struct rw_semaphore *)&(keyring)->sem))) +#define rcu_deref_link_locked(klist, index, keyring) \ + (rcu_dereference_protected( \ + (klist)->keys[index], \ + rwsem_is_locked((struct rw_semaphore *)&(keyring)->sem))) + +#define MAX_KEYRING_LINKS \ + min_t(size_t, USHRT_MAX - 1, \ + ((PAGE_SIZE - sizeof(struct keyring_list)) / sizeof(struct key *))) + #define KEY_LINK_FIXQUOTA 1UL /* @@ -138,6 +147,11 @@ static int keyring_match(const struct key *keyring, const void *description) /* * Clean up a keyring when it is destroyed. Unpublish its name if it had one * and dispose of its data. + * + * The garbage collector detects the final key_put(), removes the keyring from + * the serial number tree and then does RCU synchronisation before coming here, + * so we shouldn't need to worry about code poking around here with the RCU + * readlock held by this time. */ static void keyring_destroy(struct key *keyring) { @@ -154,11 +168,10 @@ static void keyring_destroy(struct key *keyring) write_unlock(&keyring_name_lock); } - klist = rcu_dereference_check(keyring->payload.subscriptions, - atomic_read(&keyring->usage) == 0); + klist = rcu_access_pointer(keyring->payload.subscriptions); if (klist) { for (loop = klist->nkeys - 1; loop >= 0; loop--) - key_put(klist->keys[loop]); + key_put(rcu_access_pointer(klist->keys[loop])); kfree(klist); } } @@ -214,7 +227,8 @@ static long keyring_read(const struct key *keyring, ret = -EFAULT; for (loop = 0; loop < klist->nkeys; loop++) { - key = klist->keys[loop]; + key = rcu_deref_link_locked(klist, loop, + keyring); tmp = sizeof(key_serial_t); if (tmp > buflen) @@ -309,6 +323,8 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref, bool no_state_check) { struct { + /* Need a separate keylist pointer for RCU purposes */ + struct key *keyring; struct keyring_list *keylist; int kix; } stack[KEYRING_SEARCH_MAX_DEPTH]; @@ -366,13 +382,17 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref, /* otherwise, the top keyring must not be revoked, expired, or * negatively instantiated if we are to search it */ key_ref = ERR_PTR(-EAGAIN); - if (kflags & ((1 << KEY_FLAG_REVOKED) | (1 << KEY_FLAG_NEGATIVE)) || + if (kflags & ((1 << KEY_FLAG_INVALIDATED) | + (1 << KEY_FLAG_REVOKED) | + (1 << KEY_FLAG_NEGATIVE)) || (keyring->expiry && now.tv_sec >= keyring->expiry)) goto error_2; /* start processing a new keyring */ descend: - if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + kflags = keyring->flags; + if (kflags & ((1 << KEY_FLAG_INVALIDATED) | + (1 << KEY_FLAG_REVOKED))) goto not_this_keyring; keylist = rcu_dereference(keyring->payload.subscriptions); @@ -383,16 +403,17 @@ descend: nkeys = keylist->nkeys; smp_rmb(); for (kix = 0; kix < nkeys; kix++) { - key = keylist->keys[kix]; + key = rcu_dereference(keylist->keys[kix]); kflags = key->flags; /* ignore keys not of this type */ if (key->type != type) continue; - /* skip revoked keys and expired keys */ + /* skip invalidated, revoked and expired keys */ if (!no_state_check) { - if (kflags & (1 << KEY_FLAG_REVOKED)) + if (kflags & ((1 << KEY_FLAG_INVALIDATED) | + (1 << KEY_FLAG_REVOKED))) continue; if (key->expiry && now.tv_sec >= key->expiry) @@ -426,7 +447,7 @@ ascend: nkeys = keylist->nkeys; smp_rmb(); for (; kix < nkeys; kix++) { - key = keylist->keys[kix]; + key = rcu_dereference(keylist->keys[kix]); if (key->type != &key_type_keyring) continue; @@ -441,6 +462,7 @@ ascend: continue; /* stack the current position */ + stack[sp].keyring = keyring; stack[sp].keylist = keylist; stack[sp].kix = kix; sp++; @@ -456,6 +478,7 @@ not_this_keyring: if (sp > 0) { /* resume the processing of a keyring higher up in the tree */ sp--; + keyring = stack[sp].keyring; keylist = stack[sp].keylist; kix = stack[sp].kix + 1; goto ascend; @@ -467,6 +490,10 @@ not_this_keyring: /* we found a viable match */ found: atomic_inc(&key->usage); + key->last_used_at = now.tv_sec; + keyring->last_used_at = now.tv_sec; + while (sp > 0) + stack[--sp].keyring->last_used_at = now.tv_sec; key_check(key); key_ref = make_key_ref(key, possessed); error_2: @@ -531,14 +558,14 @@ key_ref_t __keyring_search_one(key_ref_t keyring_ref, nkeys = klist->nkeys; smp_rmb(); for (loop = 0; loop < nkeys ; loop++) { - key = klist->keys[loop]; - + key = rcu_dereference(klist->keys[loop]); if (key->type == ktype && (!key->type->match || key->type->match(key, description)) && key_permission(make_key_ref(key, possessed), perm) == 0 && - !test_bit(KEY_FLAG_REVOKED, &key->flags) + !(key->flags & ((1 << KEY_FLAG_INVALIDATED) | + (1 << KEY_FLAG_REVOKED))) ) goto found; } @@ -549,6 +576,8 @@ key_ref_t __keyring_search_one(key_ref_t keyring_ref, found: atomic_inc(&key->usage); + keyring->last_used_at = key->last_used_at = + current_kernel_time().tv_sec; rcu_read_unlock(); return make_key_ref(key, possessed); } @@ -602,6 +631,7 @@ struct key *find_keyring_by_name(const char *name, bool skip_perm_check) * (ie. it has a zero usage count) */ if (!atomic_inc_not_zero(&keyring->usage)) continue; + keyring->last_used_at = current_kernel_time().tv_sec; goto out; } } @@ -654,7 +684,7 @@ ascend: nkeys = keylist->nkeys; smp_rmb(); for (; kix < nkeys; kix++) { - key = keylist->keys[kix]; + key = rcu_dereference(keylist->keys[kix]); if (key == A) goto cycle_detected; @@ -711,7 +741,7 @@ static void keyring_unlink_rcu_disposal(struct rcu_head *rcu) container_of(rcu, struct keyring_list, rcu); if (klist->delkey != USHRT_MAX) - key_put(klist->keys[klist->delkey]); + key_put(rcu_access_pointer(klist->keys[klist->delkey])); kfree(klist); } @@ -725,8 +755,9 @@ int __key_link_begin(struct key *keyring, const struct key_type *type, struct keyring_list *klist, *nklist; unsigned long prealloc; unsigned max; + time_t lowest_lru; size_t size; - int loop, ret; + int loop, lru, ret; kenter("%d,%s,%s,", key_serial(keyring), type->name, description); @@ -747,31 +778,39 @@ int __key_link_begin(struct key *keyring, const struct key_type *type, klist = rcu_dereference_locked_keyring(keyring); /* see if there's a matching key we can displace */ + lru = -1; if (klist && klist->nkeys > 0) { + lowest_lru = TIME_T_MAX; for (loop = klist->nkeys - 1; loop >= 0; loop--) { - if (klist->keys[loop]->type == type && - strcmp(klist->keys[loop]->description, - description) == 0 - ) { - /* found a match - we'll replace this one with - * the new key */ - size = sizeof(struct key *) * klist->maxkeys; - size += sizeof(*klist); - BUG_ON(size > PAGE_SIZE); - - ret = -ENOMEM; - nklist = kmemdup(klist, size, GFP_KERNEL); - if (!nklist) - goto error_sem; - - /* note replacement slot */ - klist->delkey = nklist->delkey = loop; - prealloc = (unsigned long)nklist; + struct key *key = rcu_deref_link_locked(klist, loop, + keyring); + if (key->type == type && + strcmp(key->description, description) == 0) { + /* Found a match - we'll replace the link with + * one to the new key. We record the slot + * position. + */ + klist->delkey = loop; + prealloc = 0; goto done; } + if (key->last_used_at < lowest_lru) { + lowest_lru = key->last_used_at; + lru = loop; + } } } + /* If the keyring is full then do an LRU discard */ + if (klist && + klist->nkeys == klist->maxkeys && + klist->maxkeys >= MAX_KEYRING_LINKS) { + kdebug("LRU discard %d\n", lru); + klist->delkey = lru; + prealloc = 0; + goto done; + } + /* check that we aren't going to overrun the user's quota */ ret = key_payload_reserve(keyring, keyring->datalen + KEYQUOTA_LINK_BYTES); @@ -780,20 +819,19 @@ int __key_link_begin(struct key *keyring, const struct key_type *type, if (klist && klist->nkeys < klist->maxkeys) { /* there's sufficient slack space to append directly */ - nklist = NULL; + klist->delkey = klist->nkeys; prealloc = KEY_LINK_FIXQUOTA; } else { /* grow the key list */ max = 4; - if (klist) + if (klist) { max += klist->maxkeys; + if (max > MAX_KEYRING_LINKS) + max = MAX_KEYRING_LINKS; + BUG_ON(max <= klist->maxkeys); + } - ret = -ENFILE; - if (max > USHRT_MAX - 1) - goto error_quota; size = sizeof(*klist) + sizeof(struct key *) * max; - if (size > PAGE_SIZE) - goto error_quota; ret = -ENOMEM; nklist = kmalloc(size, GFP_KERNEL); @@ -813,10 +851,10 @@ int __key_link_begin(struct key *keyring, const struct key_type *type, } /* add the key into the new space */ - nklist->keys[nklist->delkey] = NULL; + RCU_INIT_POINTER(nklist->keys[nklist->delkey], NULL); + prealloc = (unsigned long)nklist | KEY_LINK_FIXQUOTA; } - prealloc = (unsigned long)nklist | KEY_LINK_FIXQUOTA; done: *_prealloc = prealloc; kleave(" = 0"); @@ -862,6 +900,7 @@ void __key_link(struct key *keyring, struct key *key, unsigned long *_prealloc) { struct keyring_list *klist, *nklist; + struct key *discard; nklist = (struct keyring_list *)(*_prealloc & ~KEY_LINK_FIXQUOTA); *_prealloc = 0; @@ -871,14 +910,16 @@ void __key_link(struct key *keyring, struct key *key, klist = rcu_dereference_locked_keyring(keyring); atomic_inc(&key->usage); + keyring->last_used_at = key->last_used_at = + current_kernel_time().tv_sec; /* there's a matching key we can displace or an empty slot in a newly * allocated list we can fill */ if (nklist) { - kdebug("replace %hu/%hu/%hu", + kdebug("reissue %hu/%hu/%hu", nklist->delkey, nklist->nkeys, nklist->maxkeys); - nklist->keys[nklist->delkey] = key; + RCU_INIT_POINTER(nklist->keys[nklist->delkey], key); rcu_assign_pointer(keyring->payload.subscriptions, nklist); @@ -889,9 +930,23 @@ void __key_link(struct key *keyring, struct key *key, klist->delkey, klist->nkeys, klist->maxkeys); call_rcu(&klist->rcu, keyring_unlink_rcu_disposal); } + } else if (klist->delkey < klist->nkeys) { + kdebug("replace %hu/%hu/%hu", + klist->delkey, klist->nkeys, klist->maxkeys); + + discard = rcu_dereference_protected( + klist->keys[klist->delkey], + rwsem_is_locked(&keyring->sem)); + rcu_assign_pointer(klist->keys[klist->delkey], key); + /* The garbage collector will take care of RCU + * synchronisation */ + key_put(discard); } else { /* there's sufficient slack space to append directly */ - klist->keys[klist->nkeys] = key; + kdebug("append %hu/%hu/%hu", + klist->delkey, klist->nkeys, klist->maxkeys); + + RCU_INIT_POINTER(klist->keys[klist->delkey], key); smp_wmb(); klist->nkeys++; } @@ -998,7 +1053,7 @@ int key_unlink(struct key *keyring, struct key *key) if (klist) { /* search the keyring for the key */ for (loop = 0; loop < klist->nkeys; loop++) - if (klist->keys[loop] == key) + if (rcu_access_pointer(klist->keys[loop]) == key) goto key_is_present; } @@ -1061,7 +1116,7 @@ static void keyring_clear_rcu_disposal(struct rcu_head *rcu) klist = container_of(rcu, struct keyring_list, rcu); for (loop = klist->nkeys - 1; loop >= 0; loop--) - key_put(klist->keys[loop]); + key_put(rcu_access_pointer(klist->keys[loop])); kfree(klist); } @@ -1128,15 +1183,6 @@ static void keyring_revoke(struct key *keyring) } /* - * Determine whether a key is dead. - */ -static bool key_is_dead(struct key *key, time_t limit) -{ - return test_bit(KEY_FLAG_DEAD, &key->flags) || - (key->expiry > 0 && key->expiry <= limit); -} - -/* * Collect garbage from the contents of a keyring, replacing the old list with * a new one with the pointers all shuffled down. * @@ -1161,7 +1207,8 @@ void keyring_gc(struct key *keyring, time_t limit) /* work out how many subscriptions we're keeping */ keep = 0; for (loop = klist->nkeys - 1; loop >= 0; loop--) - if (!key_is_dead(klist->keys[loop], limit)) + if (!key_is_dead(rcu_deref_link_locked(klist, loop, keyring), + limit)) keep++; if (keep == klist->nkeys) @@ -1182,11 +1229,11 @@ void keyring_gc(struct key *keyring, time_t limit) */ keep = 0; for (loop = klist->nkeys - 1; loop >= 0; loop--) { - key = klist->keys[loop]; + key = rcu_deref_link_locked(klist, loop, keyring); if (!key_is_dead(key, limit)) { if (keep >= max) goto discard_new; - new->keys[keep++] = key_get(key); + RCU_INIT_POINTER(new->keys[keep++], key_get(key)); } } new->nkeys = keep; diff --git a/security/keys/permission.c b/security/keys/permission.c index c35b5229e3cd..57d96363d7f1 100644 --- a/security/keys/permission.c +++ b/security/keys/permission.c @@ -87,32 +87,29 @@ EXPORT_SYMBOL(key_task_permission); * key_validate - Validate a key. * @key: The key to be validated. * - * Check that a key is valid, returning 0 if the key is okay, -EKEYREVOKED if - * the key's type has been removed or if the key has been revoked or - * -EKEYEXPIRED if the key has expired. + * Check that a key is valid, returning 0 if the key is okay, -ENOKEY if the + * key is invalidated, -EKEYREVOKED if the key's type has been removed or if + * the key has been revoked or -EKEYEXPIRED if the key has expired. */ -int key_validate(struct key *key) +int key_validate(const struct key *key) { - struct timespec now; - int ret = 0; - - if (key) { - /* check it's still accessible */ - ret = -EKEYREVOKED; - if (test_bit(KEY_FLAG_REVOKED, &key->flags) || - test_bit(KEY_FLAG_DEAD, &key->flags)) - goto error; - - /* check it hasn't expired */ - ret = 0; - if (key->expiry) { - now = current_kernel_time(); - if (now.tv_sec >= key->expiry) - ret = -EKEYEXPIRED; - } + unsigned long flags = key->flags; + + if (flags & (1 << KEY_FLAG_INVALIDATED)) + return -ENOKEY; + + /* check it's still accessible */ + if (flags & ((1 << KEY_FLAG_REVOKED) | + (1 << KEY_FLAG_DEAD))) + return -EKEYREVOKED; + + /* check it hasn't expired */ + if (key->expiry) { + struct timespec now = current_kernel_time(); + if (now.tv_sec >= key->expiry) + return -EKEYEXPIRED; } -error: - return ret; + return 0; } EXPORT_SYMBOL(key_validate); diff --git a/security/keys/proc.c b/security/keys/proc.c index 49bbc97943ad..30d1ddfd9cef 100644 --- a/security/keys/proc.c +++ b/security/keys/proc.c @@ -242,7 +242,7 @@ static int proc_keys_show(struct seq_file *m, void *v) #define showflag(KEY, LETTER, FLAG) \ (test_bit(FLAG, &(KEY)->flags) ? LETTER : '-') - seq_printf(m, "%08x %c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ", + seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ", key->serial, showflag(key, 'I', KEY_FLAG_INSTANTIATED), showflag(key, 'R', KEY_FLAG_REVOKED), @@ -250,6 +250,7 @@ static int proc_keys_show(struct seq_file *m, void *v) showflag(key, 'Q', KEY_FLAG_IN_QUOTA), showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT), showflag(key, 'N', KEY_FLAG_NEGATIVE), + showflag(key, 'i', KEY_FLAG_INVALIDATED), atomic_read(&key->usage), xbuf, key->perm, diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 1068cb1939b3..e137fcd7042c 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -657,7 +657,8 @@ try_again: goto error; down_read(&cred->request_key_auth->sem); - if (cred->request_key_auth->flags & KEY_FLAG_REVOKED) { + if (test_bit(KEY_FLAG_REVOKED, + &cred->request_key_auth->flags)) { key_ref = ERR_PTR(-EKEYREVOKED); key = NULL; } else { @@ -731,6 +732,8 @@ try_again: if (ret < 0) goto invalid_key; + key->last_used_at = current_kernel_time().tv_sec; + error: put_cred(cred); return key_ref; diff --git a/security/keys/request_key.c b/security/keys/request_key.c index 82465328c39b..cc3790315d2f 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -91,7 +91,7 @@ static void umh_keys_cleanup(struct subprocess_info *info) * Call a usermode helper with a specific session keyring. */ static int call_usermodehelper_keys(char *path, char **argv, char **envp, - struct key *session_keyring, enum umh_wait wait) + struct key *session_keyring, int wait) { gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL; struct subprocess_info *info = diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 293b8c45b1d1..8d8d97dbb389 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -49,8 +49,8 @@ int ipv4_skb_to_auditdata(struct sk_buff *skb, if (ih == NULL) return -EINVAL; - ad->u.net.v4info.saddr = ih->saddr; - ad->u.net.v4info.daddr = ih->daddr; + ad->u.net->v4info.saddr = ih->saddr; + ad->u.net->v4info.daddr = ih->daddr; if (proto) *proto = ih->protocol; @@ -64,8 +64,8 @@ int ipv4_skb_to_auditdata(struct sk_buff *skb, if (th == NULL) break; - ad->u.net.sport = th->source; - ad->u.net.dport = th->dest; + ad->u.net->sport = th->source; + ad->u.net->dport = th->dest; break; } case IPPROTO_UDP: { @@ -73,8 +73,8 @@ int ipv4_skb_to_auditdata(struct sk_buff *skb, if (uh == NULL) break; - ad->u.net.sport = uh->source; - ad->u.net.dport = uh->dest; + ad->u.net->sport = uh->source; + ad->u.net->dport = uh->dest; break; } case IPPROTO_DCCP: { @@ -82,16 +82,16 @@ int ipv4_skb_to_auditdata(struct sk_buff *skb, if (dh == NULL) break; - ad->u.net.sport = dh->dccph_sport; - ad->u.net.dport = dh->dccph_dport; + ad->u.net->sport = dh->dccph_sport; + ad->u.net->dport = dh->dccph_dport; break; } case IPPROTO_SCTP: { struct sctphdr *sh = sctp_hdr(skb); if (sh == NULL) break; - ad->u.net.sport = sh->source; - ad->u.net.dport = sh->dest; + ad->u.net->sport = sh->source; + ad->u.net->dport = sh->dest; break; } default: @@ -119,8 +119,8 @@ int ipv6_skb_to_auditdata(struct sk_buff *skb, ip6 = ipv6_hdr(skb); if (ip6 == NULL) return -EINVAL; - ad->u.net.v6info.saddr = ip6->saddr; - ad->u.net.v6info.daddr = ip6->daddr; + ad->u.net->v6info.saddr = ip6->saddr; + ad->u.net->v6info.daddr = ip6->daddr; ret = 0; /* IPv6 can have several extension header before the Transport header * skip them */ @@ -140,8 +140,8 @@ int ipv6_skb_to_auditdata(struct sk_buff *skb, if (th == NULL) break; - ad->u.net.sport = th->source; - ad->u.net.dport = th->dest; + ad->u.net->sport = th->source; + ad->u.net->dport = th->dest; break; } case IPPROTO_UDP: { @@ -151,8 +151,8 @@ int ipv6_skb_to_auditdata(struct sk_buff *skb, if (uh == NULL) break; - ad->u.net.sport = uh->source; - ad->u.net.dport = uh->dest; + ad->u.net->sport = uh->source; + ad->u.net->dport = uh->dest; break; } case IPPROTO_DCCP: { @@ -162,8 +162,8 @@ int ipv6_skb_to_auditdata(struct sk_buff *skb, if (dh == NULL) break; - ad->u.net.sport = dh->dccph_sport; - ad->u.net.dport = dh->dccph_dport; + ad->u.net->sport = dh->dccph_sport; + ad->u.net->dport = dh->dccph_dport; break; } case IPPROTO_SCTP: { @@ -172,8 +172,8 @@ int ipv6_skb_to_auditdata(struct sk_buff *skb, sh = skb_header_pointer(skb, offset, sizeof(_sctph), &_sctph); if (sh == NULL) break; - ad->u.net.sport = sh->source; - ad->u.net.dport = sh->dest; + ad->u.net->sport = sh->source; + ad->u.net->dport = sh->dest; break; } default: @@ -213,12 +213,15 @@ static void dump_common_audit_data(struct audit_buffer *ab, { struct task_struct *tsk = current; - if (a->tsk) - tsk = a->tsk; - if (tsk && tsk->pid) { - audit_log_format(ab, " pid=%d comm=", tsk->pid); - audit_log_untrustedstring(ab, tsk->comm); - } + /* + * To keep stack sizes in check force programers to notice if they + * start making this union too large! See struct lsm_network_audit + * as an example of how to deal with large data. + */ + BUILD_BUG_ON(sizeof(a->u) > sizeof(void *)*2); + + audit_log_format(ab, " pid=%d comm=", tsk->pid); + audit_log_untrustedstring(ab, tsk->comm); switch (a->type) { case LSM_AUDIT_DATA_NONE: @@ -281,8 +284,8 @@ static void dump_common_audit_data(struct audit_buffer *ab, } break; case LSM_AUDIT_DATA_NET: - if (a->u.net.sk) { - struct sock *sk = a->u.net.sk; + if (a->u.net->sk) { + struct sock *sk = a->u.net->sk; struct unix_sock *u; int len = 0; char *p = NULL; @@ -313,12 +316,8 @@ static void dump_common_audit_data(struct audit_buffer *ab, } case AF_UNIX: u = unix_sk(sk); - if (u->dentry) { - struct path path = { - .dentry = u->dentry, - .mnt = u->mnt - }; - audit_log_d_path(ab, " path=", &path); + if (u->path.dentry) { + audit_log_d_path(ab, " path=", &u->path); break; } if (!u->addr) @@ -334,29 +333,29 @@ static void dump_common_audit_data(struct audit_buffer *ab, } } - switch (a->u.net.family) { + switch (a->u.net->family) { case AF_INET: - print_ipv4_addr(ab, a->u.net.v4info.saddr, - a->u.net.sport, + print_ipv4_addr(ab, a->u.net->v4info.saddr, + a->u.net->sport, "saddr", "src"); - print_ipv4_addr(ab, a->u.net.v4info.daddr, - a->u.net.dport, + print_ipv4_addr(ab, a->u.net->v4info.daddr, + a->u.net->dport, "daddr", "dest"); break; case AF_INET6: - print_ipv6_addr(ab, &a->u.net.v6info.saddr, - a->u.net.sport, + print_ipv6_addr(ab, &a->u.net->v6info.saddr, + a->u.net->sport, "saddr", "src"); - print_ipv6_addr(ab, &a->u.net.v6info.daddr, - a->u.net.dport, + print_ipv6_addr(ab, &a->u.net->v6info.daddr, + a->u.net->dport, "daddr", "dest"); break; } - if (a->u.net.netif > 0) { + if (a->u.net->netif > 0) { struct net_device *dev; /* NOTE: we always use init's namespace */ - dev = dev_get_by_index(&init_net, a->u.net.netif); + dev = dev_get_by_index(&init_net, a->u.net->netif); if (dev) { audit_log_format(ab, " netif=%s", dev->name); dev_put(dev); @@ -382,11 +381,15 @@ static void dump_common_audit_data(struct audit_buffer *ab, /** * common_lsm_audit - generic LSM auditing function * @a: auxiliary audit data + * @pre_audit: lsm-specific pre-audit callback + * @post_audit: lsm-specific post-audit callback * * setup the audit buffer for common security information * uses callback to print LSM specific information */ -void common_lsm_audit(struct common_audit_data *a) +void common_lsm_audit(struct common_audit_data *a, + void (*pre_audit)(struct audit_buffer *, void *), + void (*post_audit)(struct audit_buffer *, void *)) { struct audit_buffer *ab; @@ -398,13 +401,13 @@ void common_lsm_audit(struct common_audit_data *a) if (ab == NULL) return; - if (a->lsm_pre_audit) - a->lsm_pre_audit(ab, a); + if (pre_audit) + pre_audit(ab, a); dump_common_audit_data(ab, a); - if (a->lsm_post_audit) - a->lsm_post_audit(ab, a); + if (post_audit) + post_audit(ab, a); audit_log_end(ab); } diff --git a/security/security.c b/security/security.c index d7542493454d..5497a57fba01 100644 --- a/security/security.c +++ b/security/security.c @@ -19,6 +19,8 @@ #include <linux/integrity.h> #include <linux/ima.h> #include <linux/evm.h> +#include <linux/fsnotify.h> +#include <net/flow.h> #define MAX_LSM_EVM_XATTR 2 @@ -187,25 +189,11 @@ int security_settime(const struct timespec *ts, const struct timezone *tz) return security_ops->settime(ts, tz); } -int security_vm_enough_memory(long pages) -{ - WARN_ON(current->mm == NULL); - return security_ops->vm_enough_memory(current->mm, pages); -} - int security_vm_enough_memory_mm(struct mm_struct *mm, long pages) { - WARN_ON(mm == NULL); return security_ops->vm_enough_memory(mm, pages); } -int security_vm_enough_memory_kern(long pages) -{ - /* If current->mm is a kernel thread then we will pass NULL, - for this specific case that is fine */ - return security_ops->vm_enough_memory(current->mm, pages); -} - int security_bprm_set_creds(struct linux_binprm *bprm) { return security_ops->bprm_set_creds(bprm); @@ -713,11 +701,11 @@ int security_file_receive(struct file *file) return security_ops->file_receive(file); } -int security_dentry_open(struct file *file, const struct cred *cred) +int security_file_open(struct file *file, const struct cred *cred) { int ret; - ret = security_ops->dentry_open(file, cred); + ret = security_ops->file_open(file, cred); if (ret) return ret; @@ -729,6 +717,11 @@ int security_task_create(unsigned long clone_flags) return security_ops->task_create(clone_flags); } +void security_task_free(struct task_struct *task) +{ + security_ops->task_free(task); +} + int security_cred_alloc_blank(struct cred *cred, gfp_t gfp) { return security_ops->cred_alloc_blank(cred, gfp); diff --git a/security/selinux/avc.c b/security/selinux/avc.c index dca1c22d9276..68d82daed257 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -65,14 +65,8 @@ struct avc_cache { }; struct avc_callback_node { - int (*callback) (u32 event, u32 ssid, u32 tsid, - u16 tclass, u32 perms, - u32 *out_retained); + int (*callback) (u32 event); u32 events; - u32 ssid; - u32 tsid; - u16 tclass; - u32 perms; struct avc_callback_node *next; }; @@ -436,9 +430,9 @@ static void avc_audit_pre_callback(struct audit_buffer *ab, void *a) { struct common_audit_data *ad = a; audit_log_format(ab, "avc: %s ", - ad->selinux_audit_data.denied ? "denied" : "granted"); - avc_dump_av(ab, ad->selinux_audit_data.tclass, - ad->selinux_audit_data.audited); + ad->selinux_audit_data->denied ? "denied" : "granted"); + avc_dump_av(ab, ad->selinux_audit_data->tclass, + ad->selinux_audit_data->audited); audit_log_format(ab, " for "); } @@ -452,71 +446,23 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a) { struct common_audit_data *ad = a; audit_log_format(ab, " "); - avc_dump_query(ab, ad->selinux_audit_data.ssid, - ad->selinux_audit_data.tsid, - ad->selinux_audit_data.tclass); + avc_dump_query(ab, ad->selinux_audit_data->ssid, + ad->selinux_audit_data->tsid, + ad->selinux_audit_data->tclass); } -/** - * avc_audit - Audit the granting or denial of permissions. - * @ssid: source security identifier - * @tsid: target security identifier - * @tclass: target security class - * @requested: requested permissions - * @avd: access vector decisions - * @result: result from avc_has_perm_noaudit - * @a: auxiliary audit data - * @flags: VFS walk flags - * - * Audit the granting or denial of permissions in accordance - * with the policy. This function is typically called by - * avc_has_perm() after a permission check, but can also be - * called directly by callers who use avc_has_perm_noaudit() - * in order to separate the permission check from the auditing. - * For example, this separation is useful when the permission check must - * be performed under a lock, to allow the lock to be released - * before calling the auditing code. - */ -int avc_audit(u32 ssid, u32 tsid, - u16 tclass, u32 requested, - struct av_decision *avd, int result, struct common_audit_data *a, - unsigned flags) +/* This is the slow part of avc audit with big stack footprint */ +noinline int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass, + u32 requested, u32 audited, u32 denied, + struct common_audit_data *a, + unsigned flags) { struct common_audit_data stack_data; - u32 denied, audited; - denied = requested & ~avd->allowed; - if (denied) { - audited = denied & avd->auditdeny; - /* - * a->selinux_audit_data.auditdeny is TRICKY! Setting a bit in - * this field means that ANY denials should NOT be audited if - * the policy contains an explicit dontaudit rule for that - * permission. Take notice that this is unrelated to the - * actual permissions that were denied. As an example lets - * assume: - * - * denied == READ - * avd.auditdeny & ACCESS == 0 (not set means explicit rule) - * selinux_audit_data.auditdeny & ACCESS == 1 - * - * We will NOT audit the denial even though the denied - * permission was READ and the auditdeny checks were for - * ACCESS - */ - if (a && - a->selinux_audit_data.auditdeny && - !(a->selinux_audit_data.auditdeny & avd->auditdeny)) - audited = 0; - } else if (result) - audited = denied = requested; - else - audited = requested & avd->auditallow; - if (!audited) - return 0; + struct selinux_audit_data sad; if (!a) { a = &stack_data; - COMMON_AUDIT_DATA_INIT(a, NONE); + a->type = LSM_AUDIT_DATA_NONE; } /* @@ -530,15 +476,16 @@ int avc_audit(u32 ssid, u32 tsid, (flags & MAY_NOT_BLOCK)) return -ECHILD; - a->selinux_audit_data.tclass = tclass; - a->selinux_audit_data.requested = requested; - a->selinux_audit_data.ssid = ssid; - a->selinux_audit_data.tsid = tsid; - a->selinux_audit_data.audited = audited; - a->selinux_audit_data.denied = denied; - a->lsm_pre_audit = avc_audit_pre_callback; - a->lsm_post_audit = avc_audit_post_callback; - common_lsm_audit(a); + sad.tclass = tclass; + sad.requested = requested; + sad.ssid = ssid; + sad.tsid = tsid; + sad.audited = audited; + sad.denied = denied; + + a->selinux_audit_data = &sad; + + common_lsm_audit(a, avc_audit_pre_callback, avc_audit_post_callback); return 0; } @@ -546,27 +493,17 @@ int avc_audit(u32 ssid, u32 tsid, * avc_add_callback - Register a callback for security events. * @callback: callback function * @events: security events - * @ssid: source security identifier or %SECSID_WILD - * @tsid: target security identifier or %SECSID_WILD - * @tclass: target security class - * @perms: permissions * - * Register a callback function for events in the set @events - * related to the SID pair (@ssid, @tsid) - * and the permissions @perms, interpreting - * @perms based on @tclass. Returns %0 on success or - * -%ENOMEM if insufficient memory exists to add the callback. + * Register a callback function for events in the set @events. + * Returns %0 on success or -%ENOMEM if insufficient memory + * exists to add the callback. */ -int avc_add_callback(int (*callback)(u32 event, u32 ssid, u32 tsid, - u16 tclass, u32 perms, - u32 *out_retained), - u32 events, u32 ssid, u32 tsid, - u16 tclass, u32 perms) +int __init avc_add_callback(int (*callback)(u32 event), u32 events) { struct avc_callback_node *c; int rc = 0; - c = kmalloc(sizeof(*c), GFP_ATOMIC); + c = kmalloc(sizeof(*c), GFP_KERNEL); if (!c) { rc = -ENOMEM; goto out; @@ -574,9 +511,6 @@ int avc_add_callback(int (*callback)(u32 event, u32 ssid, u32 tsid, c->callback = callback; c->events = events; - c->ssid = ssid; - c->tsid = tsid; - c->perms = perms; c->next = avc_callbacks; avc_callbacks = c; out: @@ -716,8 +650,7 @@ int avc_ss_reset(u32 seqno) for (c = avc_callbacks; c; c = c->next) { if (c->events & AVC_CALLBACK_RESET) { - tmprc = c->callback(AVC_CALLBACK_RESET, - 0, 0, 0, 0, NULL); + tmprc = c->callback(AVC_CALLBACK_RESET); /* save the first error encountered for the return value and continue processing the callbacks */ if (!rc) @@ -729,6 +662,41 @@ int avc_ss_reset(u32 seqno) return rc; } +/* + * Slow-path helper function for avc_has_perm_noaudit, + * when the avc_node lookup fails. We get called with + * the RCU read lock held, and need to return with it + * still held, but drop if for the security compute. + * + * Don't inline this, since it's the slow-path and just + * results in a bigger stack frame. + */ +static noinline struct avc_node *avc_compute_av(u32 ssid, u32 tsid, + u16 tclass, struct av_decision *avd) +{ + rcu_read_unlock(); + security_compute_av(ssid, tsid, tclass, avd); + rcu_read_lock(); + return avc_insert(ssid, tsid, tclass, avd); +} + +static noinline int avc_denied(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + unsigned flags, + struct av_decision *avd) +{ + if (flags & AVC_STRICT) + return -EACCES; + + if (selinux_enforcing && !(avd->flags & AVD_FLAGS_PERMISSIVE)) + return -EACCES; + + avc_update_node(AVC_CALLBACK_GRANT, requested, ssid, + tsid, tclass, avd->seqno); + return 0; +} + + /** * avc_has_perm_noaudit - Check permissions but perform no auditing. * @ssid: source security identifier @@ -749,7 +717,7 @@ int avc_ss_reset(u32 seqno) * auditing, e.g. in cases where a lock must be held for the check but * should be released for the auditing. */ -int avc_has_perm_noaudit(u32 ssid, u32 tsid, +inline int avc_has_perm_noaudit(u32 ssid, u32 tsid, u16 tclass, u32 requested, unsigned flags, struct av_decision *avd) @@ -764,26 +732,15 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid, node = avc_lookup(ssid, tsid, tclass); if (unlikely(!node)) { - rcu_read_unlock(); - security_compute_av(ssid, tsid, tclass, avd); - rcu_read_lock(); - node = avc_insert(ssid, tsid, tclass, avd); + node = avc_compute_av(ssid, tsid, tclass, avd); } else { memcpy(avd, &node->ae.avd, sizeof(*avd)); avd = &node->ae.avd; } denied = requested & ~(avd->allowed); - - if (denied) { - if (flags & AVC_STRICT) - rc = -EACCES; - else if (!selinux_enforcing || (avd->flags & AVD_FLAGS_PERMISSIVE)) - avc_update_node(AVC_CALLBACK_GRANT, requested, ssid, - tsid, tclass, avd->seqno); - else - rc = -EACCES; - } + if (unlikely(denied)) + rc = avc_denied(ssid, tsid, tclass, requested, flags, avd); rcu_read_unlock(); return rc; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 6a3683e28426..fa2341b68331 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -28,7 +28,6 @@ #include <linux/kernel.h> #include <linux/tracehook.h> #include <linux/errno.h> -#include <linux/ext2_fs.h> #include <linux/sched.h> #include <linux/security.h> #include <linux/xattr.h> @@ -81,6 +80,8 @@ #include <linux/syslog.h> #include <linux/user_namespace.h> #include <linux/export.h> +#include <linux/msg.h> +#include <linux/shm.h> #include "avc.h" #include "objsec.h" @@ -1425,8 +1426,7 @@ static int cred_has_capability(const struct cred *cred, u32 av = CAP_TO_MASK(cap); int rc; - COMMON_AUDIT_DATA_INIT(&ad, CAP); - ad.tsk = current; + ad.type = LSM_AUDIT_DATA_CAP; ad.u.cap = cap; switch (CAP_TO_INDEX(cap)) { @@ -1485,18 +1485,6 @@ static int inode_has_perm(const struct cred *cred, return avc_has_perm_flags(sid, isec->sid, isec->sclass, perms, adp, flags); } -static int inode_has_perm_noadp(const struct cred *cred, - struct inode *inode, - u32 perms, - unsigned flags) -{ - struct common_audit_data ad; - - COMMON_AUDIT_DATA_INIT(&ad, INODE); - ad.u.inode = inode; - return inode_has_perm(cred, inode, perms, &ad, flags); -} - /* Same as inode_has_perm, but pass explicit audit data containing the dentry to help the auditing code to more easily generate the pathname if needed. */ @@ -1507,7 +1495,7 @@ static inline int dentry_has_perm(const struct cred *cred, struct inode *inode = dentry->d_inode; struct common_audit_data ad; - COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; return inode_has_perm(cred, inode, av, &ad, 0); } @@ -1522,7 +1510,7 @@ static inline int path_has_perm(const struct cred *cred, struct inode *inode = path->dentry->d_inode; struct common_audit_data ad; - COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.type = LSM_AUDIT_DATA_PATH; ad.u.path = *path; return inode_has_perm(cred, inode, av, &ad, 0); } @@ -1545,7 +1533,7 @@ static int file_has_perm(const struct cred *cred, u32 sid = cred_sid(cred); int rc; - COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.type = LSM_AUDIT_DATA_PATH; ad.u.path = file->f_path; if (sid != fsec->sid) { @@ -1584,7 +1572,7 @@ static int may_create(struct inode *dir, sid = tsec->sid; newsid = tsec->create_sid; - COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; rc = avc_has_perm(sid, dsec->sid, SECCLASS_DIR, @@ -1637,7 +1625,7 @@ static int may_link(struct inode *dir, dsec = dir->i_security; isec = dentry->d_inode->i_security; - COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; av = DIR__SEARCH; @@ -1683,7 +1671,7 @@ static inline int may_rename(struct inode *old_dir, old_is_dir = S_ISDIR(old_dentry->d_inode->i_mode); new_dsec = new_dir->i_security; - COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = old_dentry; rc = avc_has_perm(sid, old_dsec->sid, SECCLASS_DIR, @@ -1998,6 +1986,13 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) new_tsec->sid = old_tsec->exec_sid; /* Reset exec SID on execve. */ new_tsec->exec_sid = 0; + + /* + * Minimize confusion: if no_new_privs and a transition is + * explicitly requested, then fail the exec. + */ + if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) + return -EPERM; } else { /* Check for a default transition on this program. */ rc = security_transition_sid(old_tsec->sid, isec->sid, @@ -2007,10 +2002,11 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) return rc; } - COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.type = LSM_AUDIT_DATA_PATH; ad.u.path = bprm->file->f_path; - if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) + if ((bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) || + (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS)) new_tsec->sid = old_tsec->sid; if (new_tsec->sid == old_tsec->sid) { @@ -2096,7 +2092,6 @@ static int selinux_bprm_secureexec(struct linux_binprm *bprm) static inline void flush_unauthorized_files(const struct cred *cred, struct files_struct *files) { - struct common_audit_data ad; struct file *file, *devnull = NULL; struct tty_struct *tty; struct fdtable *fdt; @@ -2108,21 +2103,17 @@ static inline void flush_unauthorized_files(const struct cred *cred, spin_lock(&tty_files_lock); if (!list_empty(&tty->tty_files)) { struct tty_file_private *file_priv; - struct inode *inode; /* Revalidate access to controlling tty. - Use inode_has_perm on the tty inode directly rather + Use path_has_perm on the tty path directly rather than using file_has_perm, as this particular open file may belong to another process and we are only interested in the inode-based check here. */ file_priv = list_first_entry(&tty->tty_files, struct tty_file_private, list); file = file_priv->file; - inode = file->f_path.dentry->d_inode; - if (inode_has_perm_noadp(cred, inode, - FILE__READ | FILE__WRITE, 0)) { + if (path_has_perm(cred, &file->f_path, FILE__READ | FILE__WRITE)) drop_tty = 1; - } } spin_unlock(&tty_files_lock); tty_kref_put(tty); @@ -2132,9 +2123,6 @@ static inline void flush_unauthorized_files(const struct cred *cred, no_tty(); /* Revalidate access to inherited open files. */ - - COMMON_AUDIT_DATA_INIT(&ad, INODE); - spin_lock(&files->file_lock); for (;;) { unsigned long set, i; @@ -2145,7 +2133,7 @@ static inline void flush_unauthorized_files(const struct cred *cred, fdt = files_fdtable(files); if (i >= fdt->max_fds) break; - set = fdt->open_fds->fds_bits[j]; + set = fdt->open_fds[j]; if (!set) continue; spin_unlock(&files->file_lock); @@ -2481,7 +2469,7 @@ static int selinux_sb_kern_mount(struct super_block *sb, int flags, void *data) if (flags & MS_KERNMOUNT) return 0; - COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = sb->s_root; return superblock_has_perm(cred, sb, FILESYSTEM__MOUNT, &ad); } @@ -2491,7 +2479,7 @@ static int selinux_sb_statfs(struct dentry *dentry) const struct cred *cred = current_cred(); struct common_audit_data ad; - COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry->d_sb->s_root; return superblock_has_perm(cred, dentry->d_sb, FILESYSTEM__GETATTR, &ad); } @@ -2651,13 +2639,35 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *na return dentry_has_perm(cred, dentry, FILE__READ); } +static noinline int audit_inode_permission(struct inode *inode, + u32 perms, u32 audited, u32 denied, + unsigned flags) +{ + struct common_audit_data ad; + struct inode_security_struct *isec = inode->i_security; + int rc; + + ad.type = LSM_AUDIT_DATA_INODE; + ad.u.inode = inode; + + rc = slow_avc_audit(current_sid(), isec->sid, isec->sclass, perms, + audited, denied, &ad, flags); + if (rc) + return rc; + return 0; +} + static int selinux_inode_permission(struct inode *inode, int mask) { const struct cred *cred = current_cred(); - struct common_audit_data ad; u32 perms; bool from_access; unsigned flags = mask & MAY_NOT_BLOCK; + struct inode_security_struct *isec; + u32 sid; + struct av_decision avd; + int rc, rc2; + u32 audited, denied; from_access = mask & MAY_ACCESS; mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND); @@ -2666,21 +2676,34 @@ static int selinux_inode_permission(struct inode *inode, int mask) if (!mask) return 0; - COMMON_AUDIT_DATA_INIT(&ad, INODE); - ad.u.inode = inode; + validate_creds(cred); - if (from_access) - ad.selinux_audit_data.auditdeny |= FILE__AUDIT_ACCESS; + if (unlikely(IS_PRIVATE(inode))) + return 0; perms = file_mask_to_av(inode->i_mode, mask); - return inode_has_perm(cred, inode, perms, &ad, flags); + sid = cred_sid(cred); + isec = inode->i_security; + + rc = avc_has_perm_noaudit(sid, isec->sid, isec->sclass, perms, 0, &avd); + audited = avc_audit_required(perms, &avd, rc, + from_access ? FILE__AUDIT_ACCESS : 0, + &denied); + if (likely(!audited)) + return rc; + + rc2 = audit_inode_permission(inode, perms, audited, denied, flags); + if (rc2) + return rc2; + return rc; } static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) { const struct cred *cred = current_cred(); unsigned int ia_valid = iattr->ia_valid; + __u32 av = FILE__WRITE; /* ATTR_FORCE is just used for ATTR_KILL_S[UG]ID. */ if (ia_valid & ATTR_FORCE) { @@ -2694,7 +2717,10 @@ static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) ATTR_ATIME_SET | ATTR_MTIME_SET | ATTR_TIMES_SET)) return dentry_has_perm(cred, dentry, FILE__SETATTR); - return dentry_has_perm(cred, dentry, FILE__WRITE); + if (ia_valid & ATTR_SIZE) + av |= FILE__OPEN; + + return dentry_has_perm(cred, dentry, av); } static int selinux_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) @@ -2749,7 +2775,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, if (!inode_owner_or_capable(inode)) return -EPERM; - COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; rc = avc_has_perm(sid, isec->sid, isec->sclass, @@ -2759,8 +2785,25 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, rc = security_context_to_sid(value, size, &newsid); if (rc == -EINVAL) { - if (!capable(CAP_MAC_ADMIN)) + if (!capable(CAP_MAC_ADMIN)) { + struct audit_buffer *ab; + size_t audit_size; + const char *str; + + /* We strip a nul only if it is at the end, otherwise the + * context contains a nul and we should audit that */ + str = value; + if (str[size - 1] == '\0') + audit_size = size - 1; + else + audit_size = size; + ab = audit_log_start(current->audit_context, GFP_ATOMIC, AUDIT_SELINUX_ERR); + audit_log_format(ab, "op=setxattr invalid_context="); + audit_log_n_untrustedstring(ab, value, audit_size); + audit_log_end(ab); + return rc; + } rc = security_context_to_sid_force(value, size, &newsid); } if (rc) @@ -2940,7 +2983,7 @@ static int selinux_file_permission(struct file *file, int mask) if (sid == fsec->sid && fsec->isid == isec->sid && fsec->pseqno == avc_policy_seqno()) - /* No change since dentry_open check. */ + /* No change since file_open check. */ return 0; return selinux_revalidate_file_permission(file, mask); @@ -2969,15 +3012,15 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd, /* fall through */ case FIGETBSZ: /* fall through */ - case EXT2_IOC_GETFLAGS: + case FS_IOC_GETFLAGS: /* fall through */ - case EXT2_IOC_GETVERSION: + case FS_IOC_GETVERSION: error = file_has_perm(cred, file, FILE__GETATTR); break; - case EXT2_IOC_SETFLAGS: + case FS_IOC_SETFLAGS: /* fall through */ - case EXT2_IOC_SETVERSION: + case FS_IOC_SETVERSION: error = file_has_perm(cred, file, FILE__SETATTR); break; @@ -3199,15 +3242,13 @@ static int selinux_file_receive(struct file *file) return file_has_perm(cred, file, file_to_av(file)); } -static int selinux_dentry_open(struct file *file, const struct cred *cred) +static int selinux_file_open(struct file *file, const struct cred *cred) { struct file_security_struct *fsec; - struct inode *inode; struct inode_security_struct *isec; - inode = file->f_path.dentry->d_inode; fsec = file->f_security; - isec = inode->i_security; + isec = file->f_path.dentry->d_inode->i_security; /* * Save inode label and policy sequence number * at open-time so that selinux_file_permission @@ -3225,7 +3266,7 @@ static int selinux_dentry_open(struct file *file, const struct cred *cred) * new inode label or new policy. * This check is not redundant - do not remove. */ - return inode_has_perm_noadp(cred, inode, open_file_to_av(file), 0); + return path_has_perm(cred, &file->f_path, open_file_to_av(file)); } /* task security operations */ @@ -3347,7 +3388,7 @@ static int selinux_kernel_module_request(char *kmod_name) sid = task_sid(current); - COMMON_AUDIT_DATA_INIT(&ad, KMOD); + ad.type = LSM_AUDIT_DATA_KMOD; ad.u.kmod_name = kmod_name; return avc_has_perm(sid, SECINITSID_KERNEL, SECCLASS_SYSTEM, @@ -3486,8 +3527,8 @@ static int selinux_parse_skb_ipv4(struct sk_buff *skb, if (ihlen < sizeof(_iph)) goto out; - ad->u.net.v4info.saddr = ih->saddr; - ad->u.net.v4info.daddr = ih->daddr; + ad->u.net->v4info.saddr = ih->saddr; + ad->u.net->v4info.daddr = ih->daddr; ret = 0; if (proto) @@ -3505,8 +3546,8 @@ static int selinux_parse_skb_ipv4(struct sk_buff *skb, if (th == NULL) break; - ad->u.net.sport = th->source; - ad->u.net.dport = th->dest; + ad->u.net->sport = th->source; + ad->u.net->dport = th->dest; break; } @@ -3521,8 +3562,8 @@ static int selinux_parse_skb_ipv4(struct sk_buff *skb, if (uh == NULL) break; - ad->u.net.sport = uh->source; - ad->u.net.dport = uh->dest; + ad->u.net->sport = uh->source; + ad->u.net->dport = uh->dest; break; } @@ -3537,8 +3578,8 @@ static int selinux_parse_skb_ipv4(struct sk_buff *skb, if (dh == NULL) break; - ad->u.net.sport = dh->dccph_sport; - ad->u.net.dport = dh->dccph_dport; + ad->u.net->sport = dh->dccph_sport; + ad->u.net->dport = dh->dccph_dport; break; } @@ -3565,8 +3606,8 @@ static int selinux_parse_skb_ipv6(struct sk_buff *skb, if (ip6 == NULL) goto out; - ad->u.net.v6info.saddr = ip6->saddr; - ad->u.net.v6info.daddr = ip6->daddr; + ad->u.net->v6info.saddr = ip6->saddr; + ad->u.net->v6info.daddr = ip6->daddr; ret = 0; nexthdr = ip6->nexthdr; @@ -3586,8 +3627,8 @@ static int selinux_parse_skb_ipv6(struct sk_buff *skb, if (th == NULL) break; - ad->u.net.sport = th->source; - ad->u.net.dport = th->dest; + ad->u.net->sport = th->source; + ad->u.net->dport = th->dest; break; } @@ -3598,8 +3639,8 @@ static int selinux_parse_skb_ipv6(struct sk_buff *skb, if (uh == NULL) break; - ad->u.net.sport = uh->source; - ad->u.net.dport = uh->dest; + ad->u.net->sport = uh->source; + ad->u.net->dport = uh->dest; break; } @@ -3610,8 +3651,8 @@ static int selinux_parse_skb_ipv6(struct sk_buff *skb, if (dh == NULL) break; - ad->u.net.sport = dh->dccph_sport; - ad->u.net.dport = dh->dccph_dport; + ad->u.net->sport = dh->dccph_sport; + ad->u.net->dport = dh->dccph_dport; break; } @@ -3631,13 +3672,13 @@ static int selinux_parse_skb(struct sk_buff *skb, struct common_audit_data *ad, char *addrp; int ret; - switch (ad->u.net.family) { + switch (ad->u.net->family) { case PF_INET: ret = selinux_parse_skb_ipv4(skb, ad, proto); if (ret) goto parse_error; - addrp = (char *)(src ? &ad->u.net.v4info.saddr : - &ad->u.net.v4info.daddr); + addrp = (char *)(src ? &ad->u.net->v4info.saddr : + &ad->u.net->v4info.daddr); goto okay; #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) @@ -3645,8 +3686,8 @@ static int selinux_parse_skb(struct sk_buff *skb, struct common_audit_data *ad, ret = selinux_parse_skb_ipv6(skb, ad, proto); if (ret) goto parse_error; - addrp = (char *)(src ? &ad->u.net.v6info.saddr : - &ad->u.net.v6info.daddr); + addrp = (char *)(src ? &ad->u.net->v6info.saddr : + &ad->u.net->v6info.daddr); goto okay; #endif /* IPV6 */ default: @@ -3720,13 +3761,15 @@ static int sock_has_perm(struct task_struct *task, struct sock *sk, u32 perms) { struct sk_security_struct *sksec = sk->sk_security; struct common_audit_data ad; + struct lsm_network_audit net = {0,}; u32 tsid = task_sid(task); if (sksec->sid == SECINITSID_KERNEL) return 0; - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.sk = sk; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sk = sk; return avc_has_perm(tsid, sksec->sid, sksec->sclass, perms, &ad); } @@ -3804,6 +3847,7 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in char *addrp; struct sk_security_struct *sksec = sk->sk_security; struct common_audit_data ad; + struct lsm_network_audit net = {0,}; struct sockaddr_in *addr4 = NULL; struct sockaddr_in6 *addr6 = NULL; unsigned short snum; @@ -3829,9 +3873,10 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in snum, &sid); if (err) goto out; - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.sport = htons(snum); - ad.u.net.family = family; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sport = htons(snum); + ad.u.net->family = family; err = avc_has_perm(sksec->sid, sid, sksec->sclass, SOCKET__NAME_BIND, &ad); @@ -3862,14 +3907,15 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in if (err) goto out; - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.sport = htons(snum); - ad.u.net.family = family; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sport = htons(snum); + ad.u.net->family = family; if (family == PF_INET) - ad.u.net.v4info.saddr = addr4->sin_addr.s_addr; + ad.u.net->v4info.saddr = addr4->sin_addr.s_addr; else - ad.u.net.v6info.saddr = addr6->sin6_addr; + ad.u.net->v6info.saddr = addr6->sin6_addr; err = avc_has_perm(sksec->sid, sid, sksec->sclass, node_perm, &ad); @@ -3896,6 +3942,7 @@ static int selinux_socket_connect(struct socket *sock, struct sockaddr *address, if (sksec->sclass == SECCLASS_TCP_SOCKET || sksec->sclass == SECCLASS_DCCP_SOCKET) { struct common_audit_data ad; + struct lsm_network_audit net = {0,}; struct sockaddr_in *addr4 = NULL; struct sockaddr_in6 *addr6 = NULL; unsigned short snum; @@ -3920,9 +3967,10 @@ static int selinux_socket_connect(struct socket *sock, struct sockaddr *address, perm = (sksec->sclass == SECCLASS_TCP_SOCKET) ? TCP_SOCKET__NAME_CONNECT : DCCP_SOCKET__NAME_CONNECT; - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.dport = htons(snum); - ad.u.net.family = sk->sk_family; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->dport = htons(snum); + ad.u.net->family = sk->sk_family; err = avc_has_perm(sksec->sid, sid, sksec->sclass, perm, &ad); if (err) goto out; @@ -4011,10 +4059,12 @@ static int selinux_socket_unix_stream_connect(struct sock *sock, struct sk_security_struct *sksec_other = other->sk_security; struct sk_security_struct *sksec_new = newsk->sk_security; struct common_audit_data ad; + struct lsm_network_audit net = {0,}; int err; - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.sk = other; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sk = other; err = avc_has_perm(sksec_sock->sid, sksec_other->sid, sksec_other->sclass, @@ -4041,9 +4091,11 @@ static int selinux_socket_unix_may_send(struct socket *sock, struct sk_security_struct *ssec = sock->sk->sk_security; struct sk_security_struct *osec = other->sk->sk_security; struct common_audit_data ad; + struct lsm_network_audit net = {0,}; - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.sk = other->sk; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sk = other->sk; return avc_has_perm(ssec->sid, osec->sid, osec->sclass, SOCKET__SENDTO, &ad); @@ -4079,11 +4131,13 @@ static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, struct sk_security_struct *sksec = sk->sk_security; u32 sk_sid = sksec->sid; struct common_audit_data ad; + struct lsm_network_audit net = {0,}; char *addrp; - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.netif = skb->skb_iif; - ad.u.net.family = family; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->netif = skb->skb_iif; + ad.u.net->family = family; err = selinux_parse_skb(skb, &ad, &addrp, 1, NULL); if (err) return err; @@ -4110,6 +4164,7 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) u16 family = sk->sk_family; u32 sk_sid = sksec->sid; struct common_audit_data ad; + struct lsm_network_audit net = {0,}; char *addrp; u8 secmark_active; u8 peerlbl_active; @@ -4133,9 +4188,10 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) if (!secmark_active && !peerlbl_active) return 0; - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.netif = skb->skb_iif; - ad.u.net.family = family; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->netif = skb->skb_iif; + ad.u.net->family = family; err = selinux_parse_skb(skb, &ad, &addrp, 1, NULL); if (err) return err; @@ -4470,6 +4526,7 @@ static unsigned int selinux_ip_forward(struct sk_buff *skb, int ifindex, char *addrp; u32 peer_sid; struct common_audit_data ad; + struct lsm_network_audit net = {0,}; u8 secmark_active; u8 netlbl_active; u8 peerlbl_active; @@ -4486,9 +4543,10 @@ static unsigned int selinux_ip_forward(struct sk_buff *skb, int ifindex, if (selinux_skb_peerlbl_sid(skb, family, &peer_sid) != 0) return NF_DROP; - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.netif = ifindex; - ad.u.net.family = family; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->netif = ifindex; + ad.u.net->family = family; if (selinux_parse_skb(skb, &ad, &addrp, 1, NULL) != 0) return NF_DROP; @@ -4575,6 +4633,7 @@ static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, struct sock *sk = skb->sk; struct sk_security_struct *sksec; struct common_audit_data ad; + struct lsm_network_audit net = {0,}; char *addrp; u8 proto; @@ -4582,9 +4641,10 @@ static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, return NF_ACCEPT; sksec = sk->sk_security; - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.netif = ifindex; - ad.u.net.family = family; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->netif = ifindex; + ad.u.net->family = family; if (selinux_parse_skb(skb, &ad, &addrp, 0, &proto)) return NF_DROP; @@ -4606,6 +4666,7 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, u32 peer_sid; struct sock *sk; struct common_audit_data ad; + struct lsm_network_audit net = {0,}; char *addrp; u8 secmark_active; u8 peerlbl_active; @@ -4651,9 +4712,10 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, secmark_perm = PACKET__SEND; } - COMMON_AUDIT_DATA_INIT(&ad, NET); - ad.u.net.netif = ifindex; - ad.u.net.family = family; + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->netif = ifindex; + ad.u.net->family = family; if (selinux_parse_skb(skb, &ad, &addrp, 0, NULL)) return NF_DROP; @@ -4772,7 +4834,7 @@ static int ipc_has_perm(struct kern_ipc_perm *ipc_perms, isec = ipc_perms->security; - COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = ipc_perms->key; return avc_has_perm(sid, isec->sid, isec->sclass, perms, &ad); @@ -4802,7 +4864,7 @@ static int selinux_msg_queue_alloc_security(struct msg_queue *msq) isec = msq->q_perm.security; - COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = msq->q_perm.key; rc = avc_has_perm(sid, isec->sid, SECCLASS_MSGQ, @@ -4827,7 +4889,7 @@ static int selinux_msg_queue_associate(struct msg_queue *msq, int msqflg) isec = msq->q_perm.security; - COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = msq->q_perm.key; return avc_has_perm(sid, isec->sid, SECCLASS_MSGQ, @@ -4887,7 +4949,7 @@ static int selinux_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg, return rc; } - COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = msq->q_perm.key; /* Can this process write to the queue? */ @@ -4918,7 +4980,7 @@ static int selinux_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg, isec = msq->q_perm.security; msec = msg->security; - COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = msq->q_perm.key; rc = avc_has_perm(sid, isec->sid, @@ -4943,7 +5005,7 @@ static int selinux_shm_alloc_security(struct shmid_kernel *shp) isec = shp->shm_perm.security; - COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = shp->shm_perm.key; rc = avc_has_perm(sid, isec->sid, SECCLASS_SHM, @@ -4968,7 +5030,7 @@ static int selinux_shm_associate(struct shmid_kernel *shp, int shmflg) isec = shp->shm_perm.security; - COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = shp->shm_perm.key; return avc_has_perm(sid, isec->sid, SECCLASS_SHM, @@ -5035,7 +5097,7 @@ static int selinux_sem_alloc_security(struct sem_array *sma) isec = sma->sem_perm.security; - COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = sma->sem_perm.key; rc = avc_has_perm(sid, isec->sid, SECCLASS_SEM, @@ -5060,7 +5122,7 @@ static int selinux_sem_associate(struct sem_array *sma, int semflg) isec = sma->sem_perm.security; - COMMON_AUDIT_DATA_INIT(&ad, IPC); + ad.type = LSM_AUDIT_DATA_IPC; ad.u.ipc_id = sma->sem_perm.key; return avc_has_perm(sid, isec->sid, SECCLASS_SEM, @@ -5240,8 +5302,23 @@ static int selinux_setprocattr(struct task_struct *p, } error = security_context_to_sid(value, size, &sid); if (error == -EINVAL && !strcmp(name, "fscreate")) { - if (!capable(CAP_MAC_ADMIN)) + if (!capable(CAP_MAC_ADMIN)) { + struct audit_buffer *ab; + size_t audit_size; + + /* We strip a nul only if it is at the end, otherwise the + * context contains a nul and we should audit that */ + if (str[size - 1] == '\0') + audit_size = size - 1; + else + audit_size = size; + ab = audit_log_start(current->audit_context, GFP_ATOMIC, AUDIT_SELINUX_ERR); + audit_log_format(ab, "op=fscreate invalid_context="); + audit_log_n_untrustedstring(ab, value, audit_size); + audit_log_end(ab); + return error; + } error = security_context_to_sid_force(value, size, &sid); } @@ -5501,7 +5578,7 @@ static struct security_operations selinux_ops = { .file_send_sigiotask = selinux_file_send_sigiotask, .file_receive = selinux_file_receive, - .dentry_open = selinux_dentry_open, + .file_open = selinux_file_open, .task_create = selinux_task_create, .cred_alloc_blank = selinux_cred_alloc_blank, diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index 47fda963495d..92d0ab561db8 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h @@ -15,7 +15,6 @@ #include <linux/audit.h> #include <linux/lsm_audit.h> #include <linux/in6.h> -#include <asm/system.h> #include "flask.h" #include "av_permissions.h" #include "security.h" @@ -48,16 +47,99 @@ struct avc_cache_stats { }; /* + * We only need this data after we have decided to send an audit message. + */ +struct selinux_audit_data { + u32 ssid; + u32 tsid; + u16 tclass; + u32 requested; + u32 audited; + u32 denied; + int result; +}; + +/* * AVC operations */ void __init avc_init(void); -int avc_audit(u32 ssid, u32 tsid, - u16 tclass, u32 requested, - struct av_decision *avd, - int result, - struct common_audit_data *a, unsigned flags); +static inline u32 avc_audit_required(u32 requested, + struct av_decision *avd, + int result, + u32 auditdeny, + u32 *deniedp) +{ + u32 denied, audited; + denied = requested & ~avd->allowed; + if (unlikely(denied)) { + audited = denied & avd->auditdeny; + /* + * auditdeny is TRICKY! Setting a bit in + * this field means that ANY denials should NOT be audited if + * the policy contains an explicit dontaudit rule for that + * permission. Take notice that this is unrelated to the + * actual permissions that were denied. As an example lets + * assume: + * + * denied == READ + * avd.auditdeny & ACCESS == 0 (not set means explicit rule) + * auditdeny & ACCESS == 1 + * + * We will NOT audit the denial even though the denied + * permission was READ and the auditdeny checks were for + * ACCESS + */ + if (auditdeny && !(auditdeny & avd->auditdeny)) + audited = 0; + } else if (result) + audited = denied = requested; + else + audited = requested & avd->auditallow; + *deniedp = denied; + return audited; +} + +int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass, + u32 requested, u32 audited, u32 denied, + struct common_audit_data *a, + unsigned flags); + +/** + * avc_audit - Audit the granting or denial of permissions. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions + * @avd: access vector decisions + * @result: result from avc_has_perm_noaudit + * @a: auxiliary audit data + * @flags: VFS walk flags + * + * Audit the granting or denial of permissions in accordance + * with the policy. This function is typically called by + * avc_has_perm() after a permission check, but can also be + * called directly by callers who use avc_has_perm_noaudit() + * in order to separate the permission check from the auditing. + * For example, this separation is useful when the permission check must + * be performed under a lock, to allow the lock to be released + * before calling the auditing code. + */ +static inline int avc_audit(u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct av_decision *avd, + int result, + struct common_audit_data *a, unsigned flags) +{ + u32 audited, denied; + audited = avc_audit_required(requested, avd, result, 0, &denied); + if (likely(!audited)) + return 0; + return slow_avc_audit(ssid, tsid, tclass, + requested, audited, denied, + a, flags); +} #define AVC_STRICT 1 /* Ignore permissive mode. */ int avc_has_perm_noaudit(u32 ssid, u32 tsid, @@ -88,11 +170,7 @@ u32 avc_policy_seqno(void); #define AVC_CALLBACK_AUDITDENY_ENABLE 64 #define AVC_CALLBACK_AUDITDENY_DISABLE 128 -int avc_add_callback(int (*callback)(u32 event, u32 ssid, u32 tsid, - u16 tclass, u32 perms, - u32 *out_retained), - u32 events, u32 ssid, u32 tsid, - u16 tclass, u32 perms); +int avc_add_callback(int (*callback)(u32 event), u32 events); /* Exported to selinuxfs */ int avc_get_hash_stats(char *page); diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index d871e8ad2103..dde2005407aa 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -31,13 +31,15 @@ #define POLICYDB_VERSION_BOUNDARY 24 #define POLICYDB_VERSION_FILENAME_TRANS 25 #define POLICYDB_VERSION_ROLETRANS 26 +#define POLICYDB_VERSION_NEW_OBJECT_DEFAULTS 27 +#define POLICYDB_VERSION_DEFAULT_TYPE 28 /* Range of policy versions we understand*/ #define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE #ifdef CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX #define POLICYDB_VERSION_MAX CONFIG_SECURITY_SELINUX_POLICYDB_VERSION_MAX_VALUE #else -#define POLICYDB_VERSION_MAX POLICYDB_VERSION_ROLETRANS +#define POLICYDB_VERSION_MAX POLICYDB_VERSION_DEFAULT_TYPE #endif /* Mask for just the mount related flags */ diff --git a/security/selinux/include/xfrm.h b/security/selinux/include/xfrm.h index b43813c9e049..c220f314709c 100644 --- a/security/selinux/include/xfrm.h +++ b/security/selinux/include/xfrm.h @@ -7,6 +7,8 @@ #ifndef _SELINUX_XFRM_H_ #define _SELINUX_XFRM_H_ +#include <net/flow.h> + int selinux_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, struct xfrm_user_sec_ctx *sec_ctx); int selinux_xfrm_policy_clone(struct xfrm_sec_ctx *old_ctx, diff --git a/security/selinux/netif.c b/security/selinux/netif.c index 326f22cbe405..47a49d1a6f6a 100644 --- a/security/selinux/netif.c +++ b/security/selinux/netif.c @@ -252,8 +252,7 @@ static void sel_netif_flush(void) spin_unlock_bh(&sel_netif_lock); } -static int sel_netif_avc_callback(u32 event, u32 ssid, u32 tsid, - u16 class, u32 perms, u32 *retained) +static int sel_netif_avc_callback(u32 event) { if (event == AVC_CALLBACK_RESET) { sel_netif_flush(); @@ -292,8 +291,7 @@ static __init int sel_netif_init(void) register_netdevice_notifier(&sel_netif_netdev_notifier); - err = avc_add_callback(sel_netif_avc_callback, AVC_CALLBACK_RESET, - SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); + err = avc_add_callback(sel_netif_avc_callback, AVC_CALLBACK_RESET); if (err) panic("avc_add_callback() failed, error %d\n", err); diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c index 86365857c088..28f911cdd7c7 100644 --- a/security/selinux/netnode.c +++ b/security/selinux/netnode.c @@ -297,8 +297,7 @@ static void sel_netnode_flush(void) spin_unlock_bh(&sel_netnode_lock); } -static int sel_netnode_avc_callback(u32 event, u32 ssid, u32 tsid, - u16 class, u32 perms, u32 *retained) +static int sel_netnode_avc_callback(u32 event) { if (event == AVC_CALLBACK_RESET) { sel_netnode_flush(); @@ -320,8 +319,7 @@ static __init int sel_netnode_init(void) sel_netnode_hash[iter].size = 0; } - ret = avc_add_callback(sel_netnode_avc_callback, AVC_CALLBACK_RESET, - SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); + ret = avc_add_callback(sel_netnode_avc_callback, AVC_CALLBACK_RESET); if (ret != 0) panic("avc_add_callback() failed, error %d\n", ret); diff --git a/security/selinux/netport.c b/security/selinux/netport.c index 7b9eb1faf68b..d35379781c2c 100644 --- a/security/selinux/netport.c +++ b/security/selinux/netport.c @@ -234,8 +234,7 @@ static void sel_netport_flush(void) spin_unlock_bh(&sel_netport_lock); } -static int sel_netport_avc_callback(u32 event, u32 ssid, u32 tsid, - u16 class, u32 perms, u32 *retained) +static int sel_netport_avc_callback(u32 event) { if (event == AVC_CALLBACK_RESET) { sel_netport_flush(); @@ -257,8 +256,7 @@ static __init int sel_netport_init(void) sel_netport_hash[iter].size = 0; } - ret = avc_add_callback(sel_netport_avc_callback, AVC_CALLBACK_RESET, - SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); + ret = avc_add_callback(sel_netport_avc_callback, AVC_CALLBACK_RESET); if (ret != 0) panic("avc_add_callback() failed, error %d\n", ret); diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c index 0920ea3bf599..d309e7f472d8 100644 --- a/security/selinux/nlmsgtab.c +++ b/security/selinux/nlmsgtab.c @@ -14,7 +14,6 @@ #include <linux/netlink.h> #include <linux/rtnetlink.h> #include <linux/if.h> -#include <linux/netfilter_ipv4/ip_queue.h> #include <linux/inet_diag.h> #include <linux/xfrm.h> #include <linux/audit.h> @@ -70,12 +69,6 @@ static struct nlmsg_perm nlmsg_route_perms[] = { RTM_SETDCB, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, }; -static struct nlmsg_perm nlmsg_firewall_perms[] = -{ - { IPQM_MODE, NETLINK_FIREWALL_SOCKET__NLMSG_WRITE }, - { IPQM_VERDICT, NETLINK_FIREWALL_SOCKET__NLMSG_WRITE }, -}; - static struct nlmsg_perm nlmsg_tcpdiag_perms[] = { { TCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, @@ -145,12 +138,6 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm) sizeof(nlmsg_route_perms)); break; - case SECCLASS_NETLINK_FIREWALL_SOCKET: - case SECCLASS_NETLINK_IP6FW_SOCKET: - err = nlmsg_perm(nlmsg_type, perm, nlmsg_firewall_perms, - sizeof(nlmsg_firewall_perms)); - break; - case SECCLASS_NETLINK_TCPDIAG_SOCKET: err = nlmsg_perm(nlmsg_type, perm, nlmsg_tcpdiag_perms, sizeof(nlmsg_tcpdiag_perms)); diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 48a7d0014b4f..4e93f9ef970b 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -344,7 +344,7 @@ static int sel_make_classes(void); static int sel_make_policycap(void); /* declaration for sel_make_class_dirs */ -static int sel_make_dir(struct inode *dir, struct dentry *dentry, +static struct dentry *sel_make_dir(struct dentry *dir, const char *name, unsigned long *ino); static ssize_t sel_read_mls(struct file *filp, char __user *buf, @@ -496,6 +496,7 @@ static const struct file_operations sel_policy_ops = { .read = sel_read_policy, .mmap = sel_mmap_policy, .release = sel_release_policy, + .llseek = generic_file_llseek, }; static ssize_t sel_write_load(struct file *file, const char __user *buf, @@ -1232,6 +1233,7 @@ static int sel_make_bools(void) kfree(bool_pending_names[i]); kfree(bool_pending_names); kfree(bool_pending_values); + bool_num = 0; bool_pending_names = NULL; bool_pending_values = NULL; @@ -1532,11 +1534,6 @@ static int sel_make_initcon_files(struct dentry *dir) return 0; } -static inline unsigned int sel_div(unsigned long a, unsigned long b) -{ - return a / b - (a % b < 0); -} - static inline unsigned long sel_class_to_ino(u16 class) { return (class * (SEL_VEC_MAX + 1)) | SEL_CLASS_INO_OFFSET; @@ -1544,7 +1541,7 @@ static inline unsigned long sel_class_to_ino(u16 class) static inline u16 sel_ino_to_class(unsigned long ino) { - return sel_div(ino & SEL_INO_MASK, SEL_VEC_MAX + 1); + return (ino & SEL_INO_MASK) / (SEL_VEC_MAX + 1); } static inline unsigned long sel_perm_to_ino(u16 class, u32 perm) @@ -1678,13 +1675,9 @@ static int sel_make_class_dir_entries(char *classname, int index, inode->i_ino = sel_class_to_ino(index); d_add(dentry, inode); - dentry = d_alloc_name(dir, "perms"); - if (!dentry) - return -ENOMEM; - - rc = sel_make_dir(dir->d_inode, dentry, &last_class_ino); - if (rc) - return rc; + dentry = sel_make_dir(dir, "perms", &last_class_ino); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); rc = sel_make_perm_files(classname, index, dentry); @@ -1733,15 +1726,12 @@ static int sel_make_classes(void) for (i = 0; i < nclasses; i++) { struct dentry *class_name_dir; - rc = -ENOMEM; - class_name_dir = d_alloc_name(class_dir, classes[i]); - if (!class_name_dir) - goto out; - - rc = sel_make_dir(class_dir->d_inode, class_name_dir, + class_name_dir = sel_make_dir(class_dir, classes[i], &last_class_ino); - if (rc) + if (IS_ERR(class_name_dir)) { + rc = PTR_ERR(class_name_dir); goto out; + } /* i+1 since class values are 1-indexed */ rc = sel_make_class_dir_entries(classes[i], i + 1, @@ -1787,14 +1777,20 @@ static int sel_make_policycap(void) return 0; } -static int sel_make_dir(struct inode *dir, struct dentry *dentry, +static struct dentry *sel_make_dir(struct dentry *dir, const char *name, unsigned long *ino) { + struct dentry *dentry = d_alloc_name(dir, name); struct inode *inode; - inode = sel_make_inode(dir->i_sb, S_IFDIR | S_IRUGO | S_IXUGO); - if (!inode) - return -ENOMEM; + if (!dentry) + return ERR_PTR(-ENOMEM); + + inode = sel_make_inode(dir->d_sb, S_IFDIR | S_IRUGO | S_IXUGO); + if (!inode) { + dput(dentry); + return ERR_PTR(-ENOMEM); + } inode->i_op = &simple_dir_inode_operations; inode->i_fop = &simple_dir_operations; @@ -1803,16 +1799,16 @@ static int sel_make_dir(struct inode *dir, struct dentry *dentry, inc_nlink(inode); d_add(dentry, inode); /* bump link count on parent directory, too */ - inc_nlink(dir); + inc_nlink(dir->d_inode); - return 0; + return dentry; } static int sel_fill_super(struct super_block *sb, void *data, int silent) { int ret; struct dentry *dentry; - struct inode *inode, *root_inode; + struct inode *inode; struct inode_security_struct *isec; static struct tree_descr selinux_files[] = { @@ -1832,25 +1828,19 @@ static int sel_fill_super(struct super_block *sb, void *data, int silent) [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO}, [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO}, [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO}, - [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUSR}, + [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO}, /* last one */ {""} }; ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files); if (ret) goto err; - root_inode = sb->s_root->d_inode; - - ret = -ENOMEM; - dentry = d_alloc_name(sb->s_root, BOOL_DIR_NAME); - if (!dentry) + bool_dir = sel_make_dir(sb->s_root, BOOL_DIR_NAME, &sel_last_ino); + if (IS_ERR(bool_dir)) { + ret = PTR_ERR(bool_dir); + bool_dir = NULL; goto err; - - ret = sel_make_dir(root_inode, dentry, &sel_last_ino); - if (ret) - goto err; - - bool_dir = dentry; + } ret = -ENOMEM; dentry = d_alloc_name(sb->s_root, NULL_FILE_NAME); @@ -1872,54 +1862,39 @@ static int sel_fill_super(struct super_block *sb, void *data, int silent) d_add(dentry, inode); selinux_null = dentry; - ret = -ENOMEM; - dentry = d_alloc_name(sb->s_root, "avc"); - if (!dentry) - goto err; - - ret = sel_make_dir(root_inode, dentry, &sel_last_ino); - if (ret) + dentry = sel_make_dir(sb->s_root, "avc", &sel_last_ino); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); goto err; + } ret = sel_make_avc_files(dentry); if (ret) goto err; - ret = -ENOMEM; - dentry = d_alloc_name(sb->s_root, "initial_contexts"); - if (!dentry) - goto err; - - ret = sel_make_dir(root_inode, dentry, &sel_last_ino); - if (ret) + dentry = sel_make_dir(sb->s_root, "initial_contexts", &sel_last_ino); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); goto err; + } ret = sel_make_initcon_files(dentry); if (ret) goto err; - ret = -ENOMEM; - dentry = d_alloc_name(sb->s_root, "class"); - if (!dentry) - goto err; - - ret = sel_make_dir(root_inode, dentry, &sel_last_ino); - if (ret) - goto err; - - class_dir = dentry; - - ret = -ENOMEM; - dentry = d_alloc_name(sb->s_root, "policy_capabilities"); - if (!dentry) + class_dir = sel_make_dir(sb->s_root, "class", &sel_last_ino); + if (IS_ERR(class_dir)) { + ret = PTR_ERR(class_dir); + class_dir = NULL; goto err; + } - ret = sel_make_dir(root_inode, dentry, &sel_last_ino); - if (ret) + policycap_dir = sel_make_dir(sb->s_root, "policy_capabilities", &sel_last_ino); + if (IS_ERR(policycap_dir)) { + ret = PTR_ERR(policycap_dir); + policycap_dir = NULL; goto err; - - policycap_dir = dentry; - + } return 0; err: printk(KERN_ERR "SELinux: %s: failed while creating inodes\n", diff --git a/security/selinux/ss/context.h b/security/selinux/ss/context.h index 45e8fb0515f8..212e3479a0d9 100644 --- a/security/selinux/ss/context.h +++ b/security/selinux/ss/context.h @@ -74,6 +74,26 @@ out: return rc; } +/* + * Sets both levels in the MLS range of 'dst' to the high level of 'src'. + */ +static inline int mls_context_cpy_high(struct context *dst, struct context *src) +{ + int rc; + + dst->range.level[0].sens = src->range.level[1].sens; + rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[1].cat); + if (rc) + goto out; + + dst->range.level[1].sens = src->range.level[1].sens; + rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[1].cat); + if (rc) + ebitmap_destroy(&dst->range.level[0].cat); +out: + return rc; +} + static inline int mls_context_cmp(struct context *c1, struct context *c2) { return ((c1->range.level[0].sens == c2->range.level[0].sens) && diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index fbf9c5816c71..40de8d3f208e 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -517,6 +517,8 @@ int mls_compute_sid(struct context *scontext, { struct range_trans rtr; struct mls_range *r; + struct class_datum *cladatum; + int default_range = 0; if (!policydb.mls_enabled) return 0; @@ -530,6 +532,28 @@ int mls_compute_sid(struct context *scontext, r = hashtab_search(policydb.range_tr, &rtr); if (r) return mls_range_set(newcontext, r); + + if (tclass && tclass <= policydb.p_classes.nprim) { + cladatum = policydb.class_val_to_struct[tclass - 1]; + if (cladatum) + default_range = cladatum->default_range; + } + + switch (default_range) { + case DEFAULT_SOURCE_LOW: + return mls_context_cpy_low(newcontext, scontext); + case DEFAULT_SOURCE_HIGH: + return mls_context_cpy_high(newcontext, scontext); + case DEFAULT_SOURCE_LOW_HIGH: + return mls_context_cpy(newcontext, scontext); + case DEFAULT_TARGET_LOW: + return mls_context_cpy_low(newcontext, tcontext); + case DEFAULT_TARGET_HIGH: + return mls_context_cpy_high(newcontext, tcontext); + case DEFAULT_TARGET_LOW_HIGH: + return mls_context_cpy(newcontext, tcontext); + } + /* Fallthrough */ case AVTAB_CHANGE: if ((tclass == policydb.process_class) || (sock == true)) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index a7f61d52f05c..9cd9b7c661ec 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -133,6 +133,16 @@ static struct policydb_compat_info policydb_compat[] = { .sym_num = SYM_NUM, .ocon_num = OCON_NUM, }, + { + .version = POLICYDB_VERSION_NEW_OBJECT_DEFAULTS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_DEFAULT_TYPE, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, }; static struct policydb_compat_info *policydb_lookup_compat(int version) @@ -1306,6 +1316,23 @@ static int class_read(struct policydb *p, struct hashtab *h, void *fp) goto bad; } + if (p->policyvers >= POLICYDB_VERSION_NEW_OBJECT_DEFAULTS) { + rc = next_entry(buf, fp, sizeof(u32) * 3); + if (rc) + goto bad; + + cladatum->default_user = le32_to_cpu(buf[0]); + cladatum->default_role = le32_to_cpu(buf[1]); + cladatum->default_range = le32_to_cpu(buf[2]); + } + + if (p->policyvers >= POLICYDB_VERSION_DEFAULT_TYPE) { + rc = next_entry(buf, fp, sizeof(u32) * 1); + if (rc) + goto bad; + cladatum->default_type = le32_to_cpu(buf[0]); + } + rc = hashtab_insert(h, key, cladatum); if (rc) goto bad; @@ -2832,6 +2859,23 @@ static int class_write(void *vkey, void *datum, void *ptr) if (rc) return rc; + if (p->policyvers >= POLICYDB_VERSION_NEW_OBJECT_DEFAULTS) { + buf[0] = cpu_to_le32(cladatum->default_user); + buf[1] = cpu_to_le32(cladatum->default_role); + buf[2] = cpu_to_le32(cladatum->default_range); + + rc = put_entry(buf, sizeof(uint32_t), 3, fp); + if (rc) + return rc; + } + + if (p->policyvers >= POLICYDB_VERSION_DEFAULT_TYPE) { + buf[0] = cpu_to_le32(cladatum->default_type); + rc = put_entry(buf, sizeof(uint32_t), 1, fp); + if (rc) + return rc; + } + return 0; } diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index b846c0387180..da637471d4ce 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -60,6 +60,20 @@ struct class_datum { struct symtab permissions; /* class-specific permission symbol table */ struct constraint_node *constraints; /* constraints on class permissions */ struct constraint_node *validatetrans; /* special transition rules */ +/* Options how a new object user, role, and type should be decided */ +#define DEFAULT_SOURCE 1 +#define DEFAULT_TARGET 2 + char default_user; + char default_role; + char default_type; +/* Options how a new object range should be decided */ +#define DEFAULT_SOURCE_LOW 1 +#define DEFAULT_SOURCE_HIGH 2 +#define DEFAULT_SOURCE_LOW_HIGH 3 +#define DEFAULT_TARGET_LOW 4 +#define DEFAULT_TARGET_HIGH 5 +#define DEFAULT_TARGET_LOW_HIGH 6 + char default_range; }; /* Role attributes */ diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 185f849a26f6..4321b8fc8863 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -1018,9 +1018,11 @@ static int context_struct_to_string(struct context *context, char **scontext, u3 if (context->len) { *scontext_len = context->len; - *scontext = kstrdup(context->str, GFP_ATOMIC); - if (!(*scontext)) - return -ENOMEM; + if (scontext) { + *scontext = kstrdup(context->str, GFP_ATOMIC); + if (!(*scontext)) + return -ENOMEM; + } return 0; } @@ -1389,6 +1391,7 @@ static int security_compute_sid(u32 ssid, u32 *out_sid, bool kern) { + struct class_datum *cladatum = NULL; struct context *scontext = NULL, *tcontext = NULL, newcontext; struct role_trans *roletr = NULL; struct avtab_key avkey; @@ -1437,12 +1440,20 @@ static int security_compute_sid(u32 ssid, goto out_unlock; } + if (tclass && tclass <= policydb.p_classes.nprim) + cladatum = policydb.class_val_to_struct[tclass - 1]; + /* Set the user identity. */ switch (specified) { case AVTAB_TRANSITION: case AVTAB_CHANGE: - /* Use the process user identity. */ - newcontext.user = scontext->user; + if (cladatum && cladatum->default_user == DEFAULT_TARGET) { + newcontext.user = tcontext->user; + } else { + /* notice this gets both DEFAULT_SOURCE and unset */ + /* Use the process user identity. */ + newcontext.user = scontext->user; + } break; case AVTAB_MEMBER: /* Use the related object owner. */ @@ -1450,16 +1461,31 @@ static int security_compute_sid(u32 ssid, break; } - /* Set the role and type to default values. */ - if ((tclass == policydb.process_class) || (sock == true)) { - /* Use the current role and type of process. */ + /* Set the role to default values. */ + if (cladatum && cladatum->default_role == DEFAULT_SOURCE) { newcontext.role = scontext->role; - newcontext.type = scontext->type; + } else if (cladatum && cladatum->default_role == DEFAULT_TARGET) { + newcontext.role = tcontext->role; } else { - /* Use the well-defined object role. */ - newcontext.role = OBJECT_R_VAL; - /* Use the type of the related object. */ + if ((tclass == policydb.process_class) || (sock == true)) + newcontext.role = scontext->role; + else + newcontext.role = OBJECT_R_VAL; + } + + /* Set the type to default values. */ + if (cladatum && cladatum->default_type == DEFAULT_SOURCE) { + newcontext.type = scontext->type; + } else if (cladatum && cladatum->default_type == DEFAULT_TARGET) { newcontext.type = tcontext->type; + } else { + if ((tclass == policydb.process_class) || (sock == true)) { + /* Use the type of process. */ + newcontext.type = scontext->type; + } else { + /* Use the type of the related object. */ + newcontext.type = tcontext->type; + } } /* Look for a type transition/member/change rule. */ @@ -3018,8 +3044,7 @@ out: static int (*aurule_callback)(void) = audit_update_lsm_rules; -static int aurule_avc_callback(u32 event, u32 ssid, u32 tsid, - u16 class, u32 perms, u32 *retained) +static int aurule_avc_callback(u32 event) { int err = 0; @@ -3032,8 +3057,7 @@ static int __init aurule_init(void) { int err; - err = avc_add_callback(aurule_avc_callback, AVC_CALLBACK_RESET, - SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0); + err = avc_add_callback(aurule_avc_callback, AVC_CALLBACK_RESET); if (err) panic("avc_add_callback() failed, error %d\n", err); diff --git a/security/smack/smack.h b/security/smack/smack.h index 2ad00657b801..cc361b8f3d13 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -23,13 +23,19 @@ #include <linux/lsm_audit.h> /* + * Smack labels were limited to 23 characters for a long time. + */ +#define SMK_LABELLEN 24 +#define SMK_LONGLABEL 256 + +/* + * Maximum number of bytes for the levels in a CIPSO IP option. * Why 23? CIPSO is constrained to 30, so a 32 byte buffer is * bigger than can be used, and 24 is the next lower multiple * of 8, and there are too many issues if there isn't space set * aside for the terminating null byte. */ -#define SMK_MAXLEN 23 -#define SMK_LABELLEN (SMK_MAXLEN+1) +#define SMK_CIPSOLEN 24 struct superblock_smack { char *smk_root; @@ -66,6 +72,7 @@ struct task_smack { #define SMK_INODE_INSTANT 0x01 /* inode is instantiated */ #define SMK_INODE_TRANSMUTE 0x02 /* directory is transmuting */ +#define SMK_INODE_CHANGED 0x04 /* smack was transmuted */ /* * A label access rule. @@ -78,15 +85,6 @@ struct smack_rule { }; /* - * An entry in the table mapping smack values to - * CIPSO level/category-set values. - */ -struct smack_cipso { - int smk_level; - char smk_catset[SMK_LABELLEN]; -}; - -/* * An entry in the table identifying hosts. */ struct smk_netlbladdr { @@ -113,22 +111,19 @@ struct smk_netlbladdr { * interfaces don't. The secid should go away when all of * these components have been repaired. * - * If there is a cipso value associated with the label it - * gets stored here, too. This will most likely be rare as - * the cipso direct mapping in used internally. + * The cipso value associated with the label gets stored here, too. * * Keep the access rules for this subject label here so that * the entire set of rules does not need to be examined every * time. */ struct smack_known { - struct list_head list; - char smk_known[SMK_LABELLEN]; - u32 smk_secid; - struct smack_cipso *smk_cipso; - spinlock_t smk_cipsolock; /* for changing cipso map */ - struct list_head smk_rules; /* access rules */ - struct mutex smk_rules_lock; /* lock for the rules */ + struct list_head list; + char *smk_known; + u32 smk_secid; + struct netlbl_lsm_secattr smk_netlabel; /* on wire labels */ + struct list_head smk_rules; /* access rules */ + struct mutex smk_rules_lock; /* lock for rules */ }; /* @@ -165,6 +160,7 @@ struct smack_known { #define SMACK_CIPSO_DOI_DEFAULT 3 /* Historical */ #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 */ @@ -185,6 +181,15 @@ struct smack_known { */ #define SMK_NUM_ACCESS_TYPE 5 +/* SMACK data */ +struct smack_audit_data { + const char *function; + char *subject; + char *object; + char *request; + int result; +}; + /* * Smack audit data; is empty if CONFIG_AUDIT not set * to save some stack @@ -192,6 +197,7 @@ struct smack_known { struct smk_audit_info { #ifdef CONFIG_AUDIT struct common_audit_data a; + struct smack_audit_data sad; #endif }; /* @@ -205,10 +211,9 @@ struct inode_smack *new_inode_smack(char *); int smk_access_entry(char *, char *, struct list_head *); int smk_access(char *, char *, int, struct smk_audit_info *); int smk_curacc(char *, u32, struct smk_audit_info *); -int smack_to_cipso(const char *, struct smack_cipso *); -char *smack_from_cipso(u32, char *); char *smack_from_secid(const u32); -void smk_parse_smack(const char *string, int len, char *smack); +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_find_entry(const char *); @@ -218,6 +223,7 @@ u32 smack_to_secid(const char *); * Shared data. */ extern int smack_cipso_direct; +extern int smack_cipso_mapped; extern char *smack_net_ambient; extern char *smack_onlycap; extern const char *smack_cipso_option; @@ -229,24 +235,13 @@ 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 security_operations smack_ops; /* - * Stricly for CIPSO level manipulation. - * Set the category bit number in a smack label sized buffer. - */ -static inline void smack_catset_bit(int cat, char *catsetp) -{ - if (cat > SMK_LABELLEN * 8) - return; - - catsetp[(cat - 1) / 8] |= 0x80 >> ((cat - 1) % 8); -} - -/* * Is the directory transmuting? */ static inline int smk_inode_transmutable(const struct inode *isp) @@ -309,9 +304,18 @@ void smack_log(char *subject_label, char *object_label, static inline void smk_ad_init(struct smk_audit_info *a, const char *func, char type) { - memset(a, 0, sizeof(*a)); + memset(&a->sad, 0, sizeof(a->sad)); a->a.type = type; - a->a.smack_audit_data.function = func; + a->a.smack_audit_data = &a->sad; + a->a.smack_audit_data->function = func; +} + +static inline void smk_ad_init_net(struct smk_audit_info *a, const char *func, + char type, struct lsm_network_audit *net) +{ + smk_ad_init(a, func, type); + memset(net, 0, sizeof(*net)); + a->a.u.net = net; } static inline void smk_ad_setfield_u_tsk(struct smk_audit_info *a, @@ -337,7 +341,7 @@ static inline void smk_ad_setfield_u_fs_path(struct smk_audit_info *a, static inline void smk_ad_setfield_u_net_sk(struct smk_audit_info *a, struct sock *sk) { - a->a.u.net.sk = sk; + a->a.u.net->sk = sk; } #else /* no AUDIT */ diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index cc7cb6edba08..9f3705e92712 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -19,37 +19,31 @@ struct smack_known smack_known_huh = { .smk_known = "?", .smk_secid = 2, - .smk_cipso = NULL, }; struct smack_known smack_known_hat = { .smk_known = "^", .smk_secid = 3, - .smk_cipso = NULL, }; struct smack_known smack_known_star = { .smk_known = "*", .smk_secid = 4, - .smk_cipso = NULL, }; struct smack_known smack_known_floor = { .smk_known = "_", .smk_secid = 5, - .smk_cipso = NULL, }; struct smack_known smack_known_invalid = { .smk_known = "", .smk_secid = 6, - .smk_cipso = NULL, }; struct smack_known smack_known_web = { .smk_known = "@", .smk_secid = 7, - .smk_cipso = NULL, }; LIST_HEAD(smack_known_list); @@ -275,9 +269,9 @@ static inline void smack_str_from_perm(char *string, int access) static void smack_log_callback(struct audit_buffer *ab, void *a) { struct common_audit_data *ad = a; - struct smack_audit_data *sad = &ad->smack_audit_data; + struct smack_audit_data *sad = ad->smack_audit_data; audit_log_format(ab, "lsm=SMACK fn=%s action=%s", - ad->smack_audit_data.function, + ad->smack_audit_data->function, sad->result ? "denied" : "granted"); audit_log_format(ab, " subject="); audit_log_untrustedstring(ab, sad->subject); @@ -310,19 +304,19 @@ void smack_log(char *subject_label, char *object_label, int request, if (result == 0 && (log_policy & SMACK_AUDIT_ACCEPT) == 0) return; - if (a->smack_audit_data.function == NULL) - a->smack_audit_data.function = "unknown"; + sad = a->smack_audit_data; + + if (sad->function == NULL) + sad->function = "unknown"; /* end preparing the audit data */ - sad = &a->smack_audit_data; smack_str_from_perm(request_buffer, request); sad->subject = subject_label; sad->object = object_label; sad->request = request_buffer; sad->result = result; - a->lsm_pre_audit = smack_log_callback; - common_lsm_audit(a); + common_lsm_audit(a, smack_log_callback, NULL); } #else /* #ifdef CONFIG_AUDIT */ void smack_log(char *subject_label, char *object_label, int request, @@ -331,7 +325,7 @@ void smack_log(char *subject_label, char *object_label, int request, } #endif -static DEFINE_MUTEX(smack_known_lock); +DEFINE_MUTEX(smack_known_lock); /** * smk_find_entry - find a label on the list, return the list entry @@ -345,7 +339,7 @@ struct smack_known *smk_find_entry(const char *string) struct smack_known *skp; list_for_each_entry_rcu(skp, &smack_known_list, list) { - if (strncmp(skp->smk_known, string, SMK_MAXLEN) == 0) + if (strcmp(skp->smk_known, string) == 0) return skp; } @@ -356,27 +350,76 @@ struct smack_known *smk_find_entry(const char *string) * 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. - * @smack: parsed smack label, or NULL if parse error + * + * Returns a pointer to the clean label, or NULL */ -void smk_parse_smack(const char *string, int len, char *smack) +char *smk_parse_smack(const char *string, int len) { - int found; + char *smack; int i; - if (len <= 0 || len > SMK_MAXLEN) - len = SMK_MAXLEN; - - for (i = 0, found = 0; i < SMK_LABELLEN; i++) { - if (found) - smack[i] = '\0'; - else if (i >= len || string[i] > '~' || string[i] <= ' ' || - string[i] == '/' || string[i] == '"' || - string[i] == '\\' || string[i] == '\'') { - smack[i] = '\0'; - found = 1; - } else - smack[i] = string[i]; + if (len <= 0) + len = strlen(string) + 1; + + /* + * Reserve a leading '-' as an indicator that + * this isn't a label, but an option to interfaces + * including /smack/cipso and /smack/cipso2 + */ + if (string[0] == '-') + return NULL; + + for (i = 0; i < len; i++) + if (string[i] > '~' || string[i] <= ' ' || string[i] == '/' || + string[i] == '"' || string[i] == '\\' || string[i] == '\'') + break; + + if (i == 0 || i >= SMK_LONGLABEL) + return NULL; + + smack = kzalloc(i + 1, GFP_KERNEL); + if (smack != NULL) { + strncpy(smack, string, i + 1); + smack[i] = '\0'; } + return smack; +} + +/** + * smk_netlbl_mls - convert a catset to netlabel mls categories + * @catset: the Smack categories + * @sap: where to put the netlabel categories + * + * Allocates and fills attr.mls + * Returns 0 on success, error code on failure. + */ +int smk_netlbl_mls(int level, char *catset, struct netlbl_lsm_secattr *sap, + int len) +{ + unsigned char *cp; + unsigned char m; + int cat; + int rc; + int byte; + + sap->flags |= NETLBL_SECATTR_MLS_CAT; + sap->attr.mls.lvl = level; + sap->attr.mls.cat = netlbl_secattr_catmap_alloc(GFP_ATOMIC); + sap->attr.mls.cat->startbit = 0; + + 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); + if (rc < 0) { + netlbl_secattr_catmap_free(sap->attr.mls.cat); + return rc; + } + } + + return 0; } /** @@ -390,33 +433,59 @@ void smk_parse_smack(const char *string, int len, char *smack) struct smack_known *smk_import_entry(const char *string, int len) { struct smack_known *skp; - char smack[SMK_LABELLEN]; + char *smack; + int slen; + int rc; - smk_parse_smack(string, len, smack); - if (smack[0] == '\0') + smack = smk_parse_smack(string, len); + if (smack == NULL) return NULL; mutex_lock(&smack_known_lock); skp = smk_find_entry(smack); + if (skp != NULL) + goto freeout; - if (skp == NULL) { - skp = kzalloc(sizeof(struct smack_known), GFP_KERNEL); - if (skp != NULL) { - strncpy(skp->smk_known, smack, SMK_MAXLEN); - skp->smk_secid = smack_next_secid++; - skp->smk_cipso = NULL; - INIT_LIST_HEAD(&skp->smk_rules); - spin_lock_init(&skp->smk_cipsolock); - mutex_init(&skp->smk_rules_lock); - /* - * Make sure that the entry is actually - * filled before putting it on the list. - */ - list_add_rcu(&skp->list, &smack_known_list); - } - } + skp = kzalloc(sizeof(*skp), GFP_KERNEL); + if (skp == NULL) + 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)); + + if (rc >= 0) { + INIT_LIST_HEAD(&skp->smk_rules); + mutex_init(&skp->smk_rules_lock); + /* + * Make sure that the entry is actually + * filled before putting it on the list. + */ + list_add_rcu(&skp->list, &smack_known_list); + goto unlockout; + } + /* + * smk_netlbl_mls failed. + */ + kfree(skp); + skp = NULL; +freeout: + kfree(smack); +unlockout: mutex_unlock(&smack_known_lock); return skp; @@ -479,79 +548,9 @@ char *smack_from_secid(const u32 secid) */ u32 smack_to_secid(const char *smack) { - struct smack_known *skp; - - rcu_read_lock(); - list_for_each_entry_rcu(skp, &smack_known_list, list) { - if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) { - rcu_read_unlock(); - return skp->smk_secid; - } - } - rcu_read_unlock(); - return 0; -} - -/** - * smack_from_cipso - find the Smack label associated with a CIPSO option - * @level: Bell & LaPadula level from the network - * @cp: Bell & LaPadula categories from the network - * - * This is a simple lookup in the label table. - * - * Return the matching label from the label list or NULL. - */ -char *smack_from_cipso(u32 level, char *cp) -{ - struct smack_known *kp; - char *final = NULL; - - rcu_read_lock(); - list_for_each_entry(kp, &smack_known_list, list) { - if (kp->smk_cipso == NULL) - continue; - - spin_lock_bh(&kp->smk_cipsolock); + struct smack_known *skp = smk_find_entry(smack); - if (kp->smk_cipso->smk_level == level && - memcmp(kp->smk_cipso->smk_catset, cp, SMK_LABELLEN) == 0) - final = kp->smk_known; - - spin_unlock_bh(&kp->smk_cipsolock); - - if (final != NULL) - break; - } - rcu_read_unlock(); - - return final; -} - -/** - * smack_to_cipso - find the CIPSO option to go with a Smack label - * @smack: a pointer to the smack label in question - * @cp: where to put the result - * - * Returns zero if a value is available, non-zero otherwise. - */ -int smack_to_cipso(const char *smack, struct smack_cipso *cp) -{ - struct smack_known *kp; - int found = 0; - - rcu_read_lock(); - list_for_each_entry_rcu(kp, &smack_known_list, list) { - if (kp->smk_known == smack || - strcmp(kp->smk_known, smack) == 0) { - found = 1; - break; - } - } - rcu_read_unlock(); - - if (found == 0 || kp->smk_cipso == NULL) - return -ENOENT; - - memcpy(cp, kp->smk_cipso, sizeof(struct smack_cipso)); - return 0; + if (skp == NULL) + return 0; + return skp->smk_secid; } diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index e8af5b0ba80f..d583c0545808 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -30,12 +30,14 @@ #include <linux/slab.h> #include <linux/mutex.h> #include <linux/pipe_fs_i.h> -#include <net/netlabel.h> #include <net/cipso_ipv4.h> #include <linux/audit.h> #include <linux/magic.h> #include <linux/dcache.h> #include <linux/personality.h> +#include <linux/msg.h> +#include <linux/shm.h> +#include <linux/binfmts.h> #include "smack.h" #define task_security(task) (task_cred_xxx((task), security)) @@ -54,16 +56,23 @@ static char *smk_fetch(const char *name, struct inode *ip, struct dentry *dp) { int rc; - char in[SMK_LABELLEN]; + char *buffer; + char *result = NULL; if (ip->i_op->getxattr == NULL) return NULL; - rc = ip->i_op->getxattr(dp, name, in, SMK_LABELLEN); - if (rc < 0) + buffer = kzalloc(SMK_LONGLABEL, GFP_KERNEL); + if (buffer == NULL) return NULL; - return smk_import(in, rc); + rc = ip->i_op->getxattr(dp, name, buffer, SMK_LONGLABEL); + if (rc > 0) + result = smk_import(buffer, rc); + + kfree(buffer); + + return result; } /** @@ -76,7 +85,7 @@ struct inode_smack *new_inode_smack(char *smack) { struct inode_smack *isp; - isp = kzalloc(sizeof(struct inode_smack), GFP_KERNEL); + isp = kzalloc(sizeof(struct inode_smack), GFP_NOFS); if (isp == NULL) return NULL; @@ -553,13 +562,14 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, void **value, size_t *len) { struct smack_known *skp; + struct inode_smack *issp = inode->i_security; char *csp = smk_of_current(); char *isp = smk_of_inode(inode); char *dsp = smk_of_inode(dir); int may; if (name) { - *name = kstrdup(XATTR_SMACK_SUFFIX, GFP_KERNEL); + *name = kstrdup(XATTR_SMACK_SUFFIX, GFP_NOFS); if (*name == NULL) return -ENOMEM; } @@ -574,12 +584,15 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, * If the access rule allows transmutation and * the directory requests transmutation then * by all means transmute. + * Mark the inode as changed. */ if (may > 0 && ((may & MAY_TRANSMUTE) != 0) && - smk_inode_transmutable(dir)) + smk_inode_transmutable(dir)) { isp = dsp; + issp->smk_flags |= SMK_INODE_CHANGED; + } - *value = kstrdup(isp, GFP_KERNEL); + *value = kstrdup(isp, GFP_NOFS); if (*value == NULL) return -ENOMEM; } @@ -818,7 +831,7 @@ static int smack_inode_setxattr(struct dentry *dentry, const char *name, * check label validity here so import wont fail on * post_setxattr */ - if (size == 0 || size >= SMK_LABELLEN || + if (size == 0 || size >= SMK_LONGLABEL || smk_import(value, size) == NULL) rc = -EINVAL; } else if (strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0) { @@ -1346,7 +1359,7 @@ static int smack_file_receive(struct file *file) } /** - * smack_dentry_open - Smack dentry open processing + * smack_file_open - Smack dentry open processing * @file: the object * @cred: unused * @@ -1354,7 +1367,7 @@ static int smack_file_receive(struct file *file) * * Returns 0 */ -static int smack_dentry_open(struct file *file, const struct cred *cred) +static int smack_file_open(struct file *file, const struct cred *cred) { struct inode_smack *isp = file->f_path.dentry->d_inode->i_security; @@ -1817,65 +1830,6 @@ static char *smack_host_label(struct sockaddr_in *sip) } /** - * smack_set_catset - convert a capset to netlabel mls categories - * @catset: the Smack categories - * @sap: where to put the netlabel categories - * - * Allocates and fills attr.mls.cat - */ -static void smack_set_catset(char *catset, struct netlbl_lsm_secattr *sap) -{ - unsigned char *cp; - unsigned char m; - int cat; - int rc; - int byte; - - if (!catset) - return; - - sap->flags |= NETLBL_SECATTR_MLS_CAT; - sap->attr.mls.cat = netlbl_secattr_catmap_alloc(GFP_ATOMIC); - sap->attr.mls.cat->startbit = 0; - - for (cat = 1, cp = catset, byte = 0; byte < SMK_LABELLEN; 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); - } -} - -/** - * smack_to_secattr - fill a secattr from a smack value - * @smack: the smack value - * @nlsp: where the result goes - * - * Casey says that CIPSO is good enough for now. - * It can be used to effect. - * It can also be abused to effect when necessary. - * Apologies to the TSIG group in general and GW in particular. - */ -static void smack_to_secattr(char *smack, struct netlbl_lsm_secattr *nlsp) -{ - struct smack_cipso cipso; - int rc; - - nlsp->domain = smack; - nlsp->flags = NETLBL_SECATTR_DOMAIN | NETLBL_SECATTR_MLS_LVL; - - rc = smack_to_cipso(smack, &cipso); - if (rc == 0) { - nlsp->attr.mls.lvl = cipso.smk_level; - smack_set_catset(cipso.smk_catset, nlsp); - } else { - nlsp->attr.mls.lvl = smack_cipso_direct; - smack_set_catset(smack, nlsp); - } -} - -/** * smack_netlabel - Set the secattr on a socket * @sk: the socket * @labeled: socket label scheme @@ -1887,8 +1841,8 @@ static void smack_to_secattr(char *smack, struct netlbl_lsm_secattr *nlsp) */ static int smack_netlabel(struct sock *sk, int labeled) { + struct smack_known *skp; struct socket_smack *ssp = sk->sk_security; - struct netlbl_lsm_secattr secattr; int rc = 0; /* @@ -1906,10 +1860,8 @@ static int smack_netlabel(struct sock *sk, int labeled) labeled == SMACK_UNLABELED_SOCKET) netlbl_sock_delattr(sk); else { - netlbl_secattr_init(&secattr); - smack_to_secattr(ssp->smk_out, &secattr); - rc = netlbl_sock_setattr(sk, sk->sk_family, &secattr); - netlbl_secattr_destroy(&secattr); + skp = smk_find_entry(ssp->smk_out); + rc = netlbl_sock_setattr(sk, sk->sk_family, &skp->smk_netlabel); } bh_unlock_sock(sk); @@ -1940,13 +1892,15 @@ static int smack_netlabel_send(struct sock *sk, struct sockaddr_in *sap) rcu_read_lock(); hostsp = smack_host_label(sap); if (hostsp != NULL) { - sk_lbl = SMACK_UNLABELED_SOCKET; #ifdef CONFIG_AUDIT - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NET); - ad.a.u.net.family = sap->sin_family; - ad.a.u.net.dport = sap->sin_port; - ad.a.u.net.v4info.daddr = sap->sin_addr.s_addr; + struct lsm_network_audit net; + + smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); + ad.a.u.net->family = sap->sin_family; + 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; rc = smk_access(ssp->smk_out, hostsp, MAY_WRITE, &ad); } else { sk_lbl = SMACK_CIPSO_SOCKET; @@ -1980,7 +1934,7 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name, struct socket *sock; int rc = 0; - if (value == NULL || size > SMK_LABELLEN || size == 0) + if (value == NULL || size > SMK_LONGLABEL || size == 0) return -EACCES; sp = smk_import(value, size); @@ -2547,6 +2501,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) char *final; char trattr[TRANS_TRUE_SIZE]; int transflag = 0; + int rc; struct dentry *dp; if (inode == NULL) @@ -2665,17 +2620,38 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) */ dp = dget(opt_dentry); fetched = smk_fetch(XATTR_NAME_SMACK, inode, dp); - if (fetched != NULL) { + if (fetched != NULL) final = fetched; - if (S_ISDIR(inode->i_mode)) { - trattr[0] = '\0'; - inode->i_op->getxattr(dp, + + /* + * Transmuting directory + */ + if (S_ISDIR(inode->i_mode)) { + /* + * If this is a new directory and the label was + * transmuted when the inode was initialized + * set the transmute attribute on the directory + * and mark the 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, - trattr, TRANS_TRUE_SIZE); - if (strncmp(trattr, TRANS_TRUE, - TRANS_TRUE_SIZE) == 0) - transflag = SMK_INODE_TRANSMUTE; + 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; } + 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); @@ -2754,7 +2730,7 @@ static int smack_setprocattr(struct task_struct *p, char *name, if (!capable(CAP_MAC_ADMIN)) return -EPERM; - if (value == NULL || size == 0 || size >= SMK_LABELLEN) + if (value == NULL || size == 0 || size >= SMK_LONGLABEL) return -EINVAL; if (strcmp(name, "current") != 0) @@ -2807,8 +2783,12 @@ static int smack_unix_stream_connect(struct sock *sock, struct smk_audit_info ad; int rc = 0; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NET); +#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 (!capable(CAP_MAC_OVERRIDE)) rc = smk_access(ssp->smk_out, osp->smk_in, MAY_WRITE, &ad); @@ -2839,8 +2819,12 @@ static int smack_unix_may_send(struct socket *sock, struct socket *other) struct smk_audit_info ad; int rc = 0; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NET); +#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->sk); +#endif if (!capable(CAP_MAC_OVERRIDE)) rc = smk_access(ssp->smk_out, osp->smk_in, MAY_WRITE, &ad); @@ -2882,10 +2866,9 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, static char *smack_from_secattr(struct netlbl_lsm_secattr *sap, struct socket_smack *ssp) { - struct smack_known *skp; - char smack[SMK_LABELLEN]; + struct smack_known *kp; char *sp; - int pcat; + int found = 0; if ((sap->flags & NETLBL_SECATTR_MLS_LVL) != 0) { /* @@ -2893,59 +2876,27 @@ static char *smack_from_secattr(struct netlbl_lsm_secattr *sap, * If there are flags but no level netlabel isn't * behaving the way we expect it to. * - * Get the categories, if any + * Look it up in the label table * Without guidance regarding the smack value * for the packet fall back on the network * ambient value. */ - memset(smack, '\0', SMK_LABELLEN); - if ((sap->flags & NETLBL_SECATTR_MLS_CAT) != 0) - for (pcat = -1;;) { - pcat = netlbl_secattr_catmap_walk( - sap->attr.mls.cat, pcat + 1); - if (pcat < 0) - break; - smack_catset_bit(pcat, smack); - } - /* - * If it is CIPSO using smack direct mapping - * we are already done. WeeHee. - */ - if (sap->attr.mls.lvl == smack_cipso_direct) { - /* - * The label sent is usually on the label list. - * - * If it is not we may still want to allow the - * delivery. - * - * If the recipient is accepting all packets - * because it is using the star ("*") label - * for SMACK64IPIN provide the web ("@") label - * so that a directed response will succeed. - * This is not very correct from a MAC point - * of view, but gets around the problem that - * locking prevents adding the newly discovered - * label to the list. - * The case where the recipient is not using - * the star label should obviously fail. - * The easy way to do this is to provide the - * star label as the subject label. - */ - skp = smk_find_entry(smack); - if (skp != NULL) - return skp->smk_known; - if (ssp != NULL && - ssp->smk_in == smack_known_star.smk_known) - return smack_known_web.smk_known; - return smack_known_star.smk_known; + rcu_read_lock(); + list_for_each_entry(kp, &smack_known_list, list) { + if (sap->attr.mls.lvl != kp->smk_netlabel.attr.mls.lvl) + continue; + if (memcmp(sap->attr.mls.cat, + kp->smk_netlabel.attr.mls.cat, + SMK_CIPSOLEN) != 0) + continue; + found = 1; + break; } - /* - * Look it up in the supplied table if it is not - * a direct mapping. - */ - sp = smack_from_cipso(sap->attr.mls.lvl, smack); - if (sp != NULL) - return sp; + rcu_read_unlock(); + + if (found) + return kp->smk_known; + if (ssp != NULL && ssp->smk_in == smack_known_star.smk_known) return smack_known_web.smk_known; return smack_known_star.smk_known; @@ -2987,6 +2938,9 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) char *csp; int rc; struct smk_audit_info ad; +#ifdef CONFIG_AUDIT + struct lsm_network_audit net; +#endif if (sk->sk_family != PF_INET && sk->sk_family != PF_INET6) return 0; @@ -3004,9 +2958,9 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) netlbl_secattr_destroy(&secattr); #ifdef CONFIG_AUDIT - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NET); - ad.a.u.net.family = sk->sk_family; - ad.a.u.net.netif = skb->skb_iif; + smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); + ad.a.u.net->family = sk->sk_family; + ad.a.u.net->netif = skb->skb_iif; ipv4_skb_to_auditdata(skb, &ad.a, NULL); #endif /* @@ -3142,13 +3096,18 @@ static int smack_inet_conn_request(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 sockaddr_in addr; struct iphdr *hdr; char *sp; + char *hsp; int rc; struct smk_audit_info ad; +#ifdef CONFIG_AUDIT + struct lsm_network_audit net; +#endif /* handle mapped IPv4 packets arriving via IPv6 sockets */ if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) @@ -3163,9 +3122,9 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, netlbl_secattr_destroy(&secattr); #ifdef CONFIG_AUDIT - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_NET); - ad.a.u.net.family = family; - ad.a.u.net.netif = skb->skb_iif; + 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; ipv4_skb_to_auditdata(skb, &ad.a, NULL); #endif /* @@ -3190,16 +3149,14 @@ 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(); - if (smack_host_label(&addr) == NULL) { - rcu_read_unlock(); - netlbl_secattr_init(&secattr); - smack_to_secattr(sp, &secattr); - rc = netlbl_req_setattr(req, &secattr); - netlbl_secattr_destroy(&secattr); - } else { - rcu_read_unlock(); + hsp = smack_host_label(&addr); + rcu_read_unlock(); + + if (hsp == NULL) { + skp = smk_find_entry(sp); + rc = netlbl_req_setattr(req, &skp->smk_netlabel); + } else netlbl_req_delattr(req); - } return rc; } @@ -3381,7 +3338,7 @@ static int smack_audit_rule_match(u32 secid, u32 field, u32 op, void *vrule, char *rule = vrule; if (!rule) { - audit_log(actx, GFP_KERNEL, AUDIT_SELINUX_ERR, + audit_log(actx, GFP_ATOMIC, AUDIT_SELINUX_ERR, "Smack: missing rule\n"); return -ENOENT; } @@ -3530,7 +3487,7 @@ struct security_operations smack_ops = { .file_send_sigiotask = smack_file_send_sigiotask, .file_receive = smack_file_receive, - .dentry_open = smack_dentry_open, + .file_open = smack_file_open, .cred_alloc_blank = smack_cred_alloc_blank, .cred_free = smack_cred_free, @@ -3621,8 +3578,29 @@ struct security_operations smack_ops = { }; -static __init void init_smack_know_list(void) +static __init void init_smack_known_list(void) { + /* + * Initialize rule list locks + */ + mutex_init(&smack_known_huh.smk_rules_lock); + 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 + */ + INIT_LIST_HEAD(&smack_known_huh.smk_rules); + 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); @@ -3657,16 +3635,8 @@ static __init int smack_init(void) cred = (struct cred *) current->cred; cred->security = tsp; - /* initialize the smack_know_list */ - init_smack_know_list(); - /* - * Initialize locks - */ - spin_lock_init(&smack_known_huh.smk_cipsolock); - spin_lock_init(&smack_known_hat.smk_cipsolock); - spin_lock_init(&smack_known_star.smk_cipsolock); - spin_lock_init(&smack_known_floor.smk_cipsolock); - spin_lock_init(&smack_known_invalid.smk_cipsolock); + /* initialize the smack_known_list */ + init_smack_known_list(); /* * Register with LSM diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 5c32f36ff706..1810c9a4ed48 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -22,7 +22,6 @@ #include <linux/mutex.h> #include <linux/slab.h> #include <net/net_namespace.h> -#include <net/netlabel.h> #include <net/cipso_ipv4.h> #include <linux/seq_file.h> #include <linux/ctype.h> @@ -45,6 +44,11 @@ enum smk_inos { SMK_LOGGING = 10, /* logging */ SMK_LOAD_SELF = 11, /* task specific rules */ SMK_ACCESSES = 12, /* access policy */ + SMK_MAPPED = 13, /* CIPSO level indicating mapped label */ + SMK_LOAD2 = 14, /* load policy with long labels */ + SMK_LOAD_SELF2 = 15, /* load task specific rules with long labels */ + SMK_ACCESS2 = 16, /* make an access check with long labels */ + SMK_CIPSO2 = 17, /* load long label -> CIPSO mapping */ }; /* @@ -60,7 +64,7 @@ static DEFINE_MUTEX(smk_netlbladdr_lock); * If it isn't somehow marked, use this. * It can be reset via smackfs/ambient */ -char *smack_net_ambient = smack_known_floor.smk_known; +char *smack_net_ambient; /* * This is the level in a CIPSO header that indicates a @@ -70,6 +74,13 @@ char *smack_net_ambient = smack_known_floor.smk_known; int smack_cipso_direct = SMACK_CIPSO_DIRECT_DEFAULT; /* + * This is the level in a CIPSO header that indicates a + * secid is contained directly in the category set. + * It can be reset via smackfs/mapped + */ +int smack_cipso_mapped = SMACK_CIPSO_MAPPED_DEFAULT; + +/* * 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 @@ -89,7 +100,7 @@ LIST_HEAD(smk_netlbladdr_list); /* * Rule lists are maintained for each label. - * This master list is just for reading /smack/load. + * This master list is just for reading /smack/load and /smack/load2. */ struct smack_master_list { struct list_head list; @@ -125,6 +136,18 @@ const char *smack_cipso_option = SMACK_CIPSO_OPTION; #define SMK_OLOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_OACCESSLEN) #define SMK_LOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_ACCESSLEN) +/* + * Stricly 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) +{ + if (cat == 0 || cat > (SMK_CIPSOLEN * 8)) + return; + + catsetp[(cat - 1) / 8] |= 0x80 >> ((cat - 1) % 8); +} + /** * smk_netlabel_audit_set - fill a netlbl_audit struct * @nap: structure to fill @@ -137,12 +160,10 @@ static void smk_netlabel_audit_set(struct netlbl_audit *nap) } /* - * Values for parsing single label host rules + * Value for parsing single label host rules * "1.2.3.4 X" - * "192.168.138.129/32 abcdefghijklmnopqrstuvw" */ #define SMK_NETLBLADDRMIN 9 -#define SMK_NETLBLADDRMAX 42 /** * smk_set_access - add a rule to the rule list @@ -188,33 +209,47 @@ static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list, } /** - * smk_parse_rule - parse Smack rule from load string - * @data: string to be parsed whose size is SMK_LOADLEN + * smk_fill_rule - Fill Smack rule from strings + * @subject: subject label string + * @object: object label string + * @access: access string * @rule: Smack rule * @import: if non-zero, import labels + * + * Returns 0 on success, -1 on failure */ -static int smk_parse_rule(const char *data, struct smack_rule *rule, int import) +static int smk_fill_rule(const char *subject, const char *object, + const char *access, struct smack_rule *rule, + int import) { - char smack[SMK_LABELLEN]; + int rc = -1; + int done; + const char *cp; struct smack_known *skp; if (import) { - rule->smk_subject = smk_import(data, 0); + rule->smk_subject = smk_import(subject, 0); if (rule->smk_subject == NULL) return -1; - rule->smk_object = smk_import(data + SMK_LABELLEN, 0); + rule->smk_object = smk_import(object, 0); if (rule->smk_object == NULL) return -1; } else { - smk_parse_smack(data, 0, smack); - skp = smk_find_entry(smack); + cp = smk_parse_smack(subject, 0); + if (cp == NULL) + return -1; + skp = smk_find_entry(cp); + kfree(cp); if (skp == NULL) return -1; rule->smk_subject = skp->smk_known; - smk_parse_smack(data + SMK_LABELLEN, 0, smack); - skp = smk_find_entry(smack); + cp = smk_parse_smack(object, 0); + if (cp == NULL) + return -1; + skp = smk_find_entry(cp); + kfree(cp); if (skp == NULL) return -1; rule->smk_object = skp->smk_known; @@ -222,90 +257,127 @@ static int smk_parse_rule(const char *data, struct smack_rule *rule, int import) rule->smk_access = 0; - switch (data[SMK_LABELLEN + SMK_LABELLEN]) { - case '-': - break; - case 'r': - case 'R': - rule->smk_access |= MAY_READ; - break; - default: - return -1; + for (cp = access, done = 0; *cp && !done; cp++) { + switch (*cp) { + case '-': + break; + case 'r': + case 'R': + rule->smk_access |= MAY_READ; + break; + case 'w': + case 'W': + rule->smk_access |= MAY_WRITE; + break; + case 'x': + case 'X': + rule->smk_access |= MAY_EXEC; + break; + case 'a': + case 'A': + rule->smk_access |= MAY_APPEND; + break; + case 't': + case 'T': + rule->smk_access |= MAY_TRANSMUTE; + break; + default: + done = 1; + break; + } } + rc = 0; - switch (data[SMK_LABELLEN + SMK_LABELLEN + 1]) { - case '-': - break; - case 'w': - case 'W': - rule->smk_access |= MAY_WRITE; - break; - default: - return -1; - } + return rc; +} - switch (data[SMK_LABELLEN + SMK_LABELLEN + 2]) { - case '-': - break; - case 'x': - case 'X': - rule->smk_access |= MAY_EXEC; - break; - default: - return -1; - } +/** + * smk_parse_rule - parse Smack rule from load string + * @data: string to be parsed whose size is SMK_LOADLEN + * @rule: Smack rule + * @import: if non-zero, import labels + * + * Returns 0 on success, -1 on errors. + */ +static int smk_parse_rule(const char *data, struct smack_rule *rule, int import) +{ + int rc; - switch (data[SMK_LABELLEN + SMK_LABELLEN + 3]) { - case '-': - break; - case 'a': - case 'A': - rule->smk_access |= MAY_APPEND; - break; - default: - return -1; - } + rc = smk_fill_rule(data, data + SMK_LABELLEN, + data + SMK_LABELLEN + SMK_LABELLEN, rule, import); + return rc; +} - switch (data[SMK_LABELLEN + SMK_LABELLEN + 4]) { - case '-': - break; - case 't': - case 'T': - rule->smk_access |= MAY_TRANSMUTE; - break; - default: - return -1; - } +/** + * smk_parse_long_rule - parse Smack rule from rule string + * @data: string to be parsed, null terminated + * @rule: Smack rule + * @import: if non-zero, import labels + * + * Returns 0 on success, -1 on failure + */ +static int smk_parse_long_rule(const char *data, struct smack_rule *rule, + int import) +{ + char *subject; + char *object; + char *access; + int datalen; + int rc = -1; - return 0; + /* + * This is probably inefficient, but safe. + */ + datalen = strlen(data); + subject = kzalloc(datalen, GFP_KERNEL); + if (subject == NULL) + return -1; + object = kzalloc(datalen, GFP_KERNEL); + if (object == NULL) + goto free_out_s; + access = kzalloc(datalen, GFP_KERNEL); + if (access == NULL) + goto free_out_o; + + if (sscanf(data, "%s %s %s", subject, object, access) == 3) + rc = smk_fill_rule(subject, object, access, rule, import); + + kfree(access); +free_out_o: + kfree(object); +free_out_s: + kfree(subject); + return rc; } +#define SMK_FIXED24_FMT 0 /* Fixed 24byte label format */ +#define SMK_LONG_FMT 1 /* Variable long label format */ /** - * smk_write_load_list - write() for any /smack/load + * smk_write_rules_list - write() for any /smack rule file * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent * @ppos: where to start - must be 0 * @rule_list: the list of rules to write to * @rule_lock: lock for the rule list + * @format: /smack/load or /smack/load2 format. * * Get one smack access rule from above. - * The format is exactly: - * char subject[SMK_LABELLEN] - * char object[SMK_LABELLEN] - * char access[SMK_ACCESSLEN] - * - * writes must be SMK_LABELLEN+SMK_LABELLEN+SMK_ACCESSLEN bytes. + * The format for SMK_LONG_FMT is: + * "subject<whitespace>object<whitespace>access[<whitespace>...]" + * The format for SMK_FIXED24_FMT is exactly: + * "subject object rwxat" */ -static ssize_t smk_write_load_list(struct file *file, const char __user *buf, - size_t count, loff_t *ppos, - struct list_head *rule_list, - struct mutex *rule_lock) +static ssize_t smk_write_rules_list(struct file *file, const char __user *buf, + size_t count, loff_t *ppos, + struct list_head *rule_list, + struct mutex *rule_lock, int format) { struct smack_master_list *smlp; struct smack_known *skp; struct smack_rule *rule; char *data; + int datalen; int rc = -EINVAL; int load = 0; @@ -315,13 +387,18 @@ static ssize_t smk_write_load_list(struct file *file, const char __user *buf, */ if (*ppos != 0) return -EINVAL; - /* - * Minor hack for backward compatibility - */ - if (count < (SMK_OLOADLEN) || count > SMK_LOADLEN) - return -EINVAL; - data = kzalloc(SMK_LOADLEN, GFP_KERNEL); + if (format == SMK_FIXED24_FMT) { + /* + * Minor hack for backward compatibility + */ + 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; @@ -330,20 +407,29 @@ static ssize_t smk_write_load_list(struct file *file, const char __user *buf, goto out; } - /* - * More on the minor hack for backward compatibility - */ - if (count == (SMK_OLOADLEN)) - data[SMK_OLOADLEN] = '-'; - rule = kzalloc(sizeof(*rule), GFP_KERNEL); if (rule == NULL) { rc = -ENOMEM; goto out; } - if (smk_parse_rule(data, rule, 1)) - goto out_free_rule; + if (format == SMK_LONG_FMT) { + /* + * Be sure the data string is terminated. + */ + data[count] = '\0'; + if (smk_parse_long_rule(data, rule, 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; + } + if (rule_list == NULL) { load = 1; @@ -354,18 +440,20 @@ static ssize_t smk_write_load_list(struct file *file, const char __user *buf, rc = count; /* - * If this is "load" as opposed to "load-self" and a new rule + * If this is a global as opposed to self and a new rule * it needs to get added for reporting. * smk_set_access returns true if there was already a rule * for the subject/object pair, and false if it was new. */ - if (load && !smk_set_access(rule, rule_list, rule_lock)) { - smlp = kzalloc(sizeof(*smlp), GFP_KERNEL); - if (smlp != NULL) { - smlp->smk_rule = rule; - list_add_rcu(&smlp->list, &smack_rule_list); - } else - rc = -ENOMEM; + if (!smk_set_access(rule, rule_list, rule_lock)) { + if (load) { + smlp = kzalloc(sizeof(*smlp), GFP_KERNEL); + if (smlp != NULL) { + smlp->smk_rule = rule; + list_add_rcu(&smlp->list, &smack_rule_list); + } else + rc = -ENOMEM; + } goto out; } @@ -421,29 +509,18 @@ static void smk_seq_stop(struct seq_file *s, void *v) /* No-op */ } -/* - * Seq_file read operations for /smack/load - */ - -static void *load_seq_start(struct seq_file *s, loff_t *pos) -{ - return smk_seq_start(s, pos, &smack_rule_list); -} - -static void *load_seq_next(struct seq_file *s, void *v, loff_t *pos) +static void smk_rule_show(struct seq_file *s, struct smack_rule *srp, int max) { - return smk_seq_next(s, v, pos, &smack_rule_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 = smlp->smk_rule; + /* + * Don't show any rules with label names too long for + * interface file (/smack/load or /smack/load2) + * because you should expect to be able to write + * anything you read back. + */ + if (strlen(srp->smk_subject) >= max || strlen(srp->smk_object) >= max) + return; - seq_printf(s, "%s %s", (char *)srp->smk_subject, - (char *)srp->smk_object); + seq_printf(s, "%s %s", srp->smk_subject, srp->smk_object); seq_putc(s, ' '); @@ -461,13 +538,36 @@ static int load_seq_show(struct seq_file *s, void *v) seq_putc(s, '-'); seq_putc(s, '\n'); +} + +/* + * Seq_file read operations for /smack/load + */ + +static void *load2_seq_start(struct seq_file *s, loff_t *pos) +{ + return smk_seq_start(s, pos, &smack_rule_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); +} + +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); + + smk_rule_show(s, smlp->smk_rule, SMK_LABELLEN); return 0; } static const struct seq_operations load_seq_ops = { - .start = load_seq_start, - .next = load_seq_next, + .start = load2_seq_start, + .next = load2_seq_next, .show = load_seq_show, .stop = smk_seq_stop, }; @@ -504,7 +604,8 @@ static ssize_t smk_write_load(struct file *file, const char __user *buf, if (!capable(CAP_MAC_ADMIN)) return -EPERM; - return smk_write_load_list(file, buf, count, ppos, NULL, NULL); + return smk_write_rules_list(file, buf, count, ppos, NULL, NULL, + SMK_FIXED24_FMT); } static const struct file_operations smk_load_ops = { @@ -574,6 +675,8 @@ static void smk_unlbl_ambient(char *oldambient) printk(KERN_WARNING "%s:%d remove rc = %d\n", __func__, __LINE__, rc); } + if (smack_net_ambient == NULL) + smack_net_ambient = smack_known_floor.smk_known; rc = netlbl_cfg_unlbl_map_add(smack_net_ambient, PF_INET, NULL, NULL, &nai); @@ -605,27 +708,28 @@ 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 smack_cipso *scp = skp->smk_cipso; - char *cbp; + struct netlbl_lsm_secattr_catmap *cmp = skp->smk_netlabel.attr.mls.cat; char sep = '/'; - int cat = 1; int i; - unsigned char m; - if (scp == NULL) + /* + * Don't show a label that could not have been set using + * /smack/cipso. This is in support of the notion that + * anything read from /smack/cipso ought to be writeable + * to /smack/cipso. + * + * /smack/cipso2 should be used instead. + */ + if (strlen(skp->smk_known) >= SMK_LABELLEN) return 0; - seq_printf(s, "%s %3d", (char *)&skp->smk_known, scp->smk_level); + seq_printf(s, "%s %3d", skp->smk_known, skp->smk_netlabel.attr.mls.lvl); - cbp = scp->smk_catset; - for (i = 0; i < SMK_LABELLEN; i++) - for (m = 0x80; m != 0; m >>= 1) { - if (m & cbp[i]) { - seq_printf(s, "%c%d", sep, cat); - sep = ','; - } - cat++; - } + for (i = netlbl_secattr_catmap_walk(cmp, 0); i >= 0; + i = netlbl_secattr_catmap_walk(cmp, i + 1)) { + seq_printf(s, "%c%d", sep, i); + sep = ','; + } seq_putc(s, '\n'); @@ -653,23 +757,24 @@ static int smk_open_cipso(struct inode *inode, struct file *file) } /** - * smk_write_cipso - write() for /smack/cipso + * smk_set_cipso - do the work for write() for cipso and cipso2 * @file: file pointer, not actually used * @buf: where to get the data from * @count: bytes sent * @ppos: where to start + * @format: /smack/cipso or /smack/cipso2 * * Accepts only one cipso rule per write call. * Returns number of bytes written or error code, as appropriate */ -static ssize_t smk_write_cipso(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) +static ssize_t smk_set_cipso(struct file *file, const char __user *buf, + size_t count, loff_t *ppos, int format) { struct smack_known *skp; - struct smack_cipso *scp = NULL; - char mapcatset[SMK_LABELLEN]; + struct netlbl_lsm_secattr ncats; + char mapcatset[SMK_CIPSOLEN]; int maplevel; - int cat; + unsigned int cat; int catlen; ssize_t rc = -EINVAL; char *data = NULL; @@ -686,7 +791,8 @@ static ssize_t smk_write_cipso(struct file *file, const char __user *buf, return -EPERM; if (*ppos != 0) return -EINVAL; - if (count < SMK_CIPSOMIN || count > SMK_CIPSOMAX) + if (format == SMK_FIXED24_FMT && + (count < SMK_CIPSOMIN || count > SMK_CIPSOMAX)) return -EINVAL; data = kzalloc(count + 1, GFP_KERNEL); @@ -698,11 +804,6 @@ static ssize_t smk_write_cipso(struct file *file, const char __user *buf, goto unlockedout; } - /* labels cannot begin with a '-' */ - if (data[0] == '-') { - rc = -EINVAL; - goto unlockedout; - } data[count] = '\0'; rule = data; /* @@ -715,7 +816,11 @@ static ssize_t smk_write_cipso(struct file *file, const char __user *buf, if (skp == NULL) goto out; - rule += SMK_LABELLEN; + if (format == SMK_FIXED24_FMT) + rule += SMK_LABELLEN; + else + rule += strlen(skp->smk_known); + ret = sscanf(rule, "%d", &maplevel); if (ret != 1 || maplevel > SMACK_CIPSO_MAXLEVEL) goto out; @@ -725,41 +830,29 @@ static ssize_t smk_write_cipso(struct file *file, const char __user *buf, if (ret != 1 || catlen > SMACK_CIPSO_MAXCATNUM) goto out; - if (count != (SMK_CIPSOMIN + catlen * SMK_DIGITLEN)) + if (format == SMK_FIXED24_FMT && + count != (SMK_CIPSOMIN + catlen * SMK_DIGITLEN)) goto out; memset(mapcatset, 0, sizeof(mapcatset)); for (i = 0; i < catlen; i++) { rule += SMK_DIGITLEN; - ret = sscanf(rule, "%d", &cat); + ret = sscanf(rule, "%u", &cat); if (ret != 1 || cat > SMACK_CIPSO_MAXCATVAL) goto out; smack_catset_bit(cat, mapcatset); } - if (skp->smk_cipso == NULL) { - scp = kzalloc(sizeof(struct smack_cipso), GFP_KERNEL); - if (scp == NULL) { - rc = -ENOMEM; - goto out; - } + 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; + skp->smk_netlabel.attr.mls.lvl = ncats.attr.mls.lvl; + rc = count; } - spin_lock_bh(&skp->smk_cipsolock); - - if (scp == NULL) - scp = skp->smk_cipso; - else - skp->smk_cipso = scp; - - scp->smk_level = maplevel; - memcpy(scp->smk_catset, mapcatset, sizeof(mapcatset)); - - spin_unlock_bh(&skp->smk_cipsolock); - - rc = count; out: mutex_unlock(&smack_cipso_lock); unlockedout: @@ -767,6 +860,22 @@ unlockedout: return rc; } +/** + * smk_write_cipso - write() for /smack/cipso + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Accepts only one cipso rule per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_cipso(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return smk_set_cipso(file, buf, count, ppos, SMK_FIXED24_FMT); +} + static const struct file_operations smk_cipso_ops = { .open = smk_open_cipso, .read = seq_read, @@ -776,6 +885,80 @@ static const struct file_operations smk_cipso_ops = { }; /* + * Seq_file read operations for /smack/cipso2 + */ + +/* + * Print cipso labels in format: + * label level[/cat[,cat]] + */ +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; + 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)) { + seq_printf(s, "%c%d", sep, i); + sep = ','; + } + + seq_putc(s, '\n'); + + return 0; +} + +static const struct seq_operations cipso2_seq_ops = { + .start = cipso_seq_start, + .next = cipso_seq_next, + .show = cipso2_seq_show, + .stop = smk_seq_stop, +}; + +/** + * smk_open_cipso2 - open() for /smack/cipso2 + * @inode: inode structure representing file + * @file: "cipso2" file pointer + * + * Connect our cipso_seq_* operations with /smack/cipso2 + * file_operations + */ +static int smk_open_cipso2(struct inode *inode, struct file *file) +{ + return seq_open(file, &cipso2_seq_ops); +} + +/** + * smk_write_cipso2 - write() for /smack/cipso2 + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Accepts only one cipso rule per write call. + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t smk_write_cipso2(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return smk_set_cipso(file, buf, count, ppos, SMK_LONG_FMT); +} + +static const struct file_operations smk_cipso2_ops = { + .open = smk_open_cipso2, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_cipso2, + .release = seq_release, +}; + +/* * Seq_file read operations for /smack/netlabel */ @@ -887,9 +1070,9 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, { struct smk_netlbladdr *skp; struct sockaddr_in newname; - char smack[SMK_LABELLEN]; + char *smack; char *sp; - char data[SMK_NETLBLADDRMAX + 1]; + char *data; char *host = (char *)&newname.sin_addr.s_addr; int rc; struct netlbl_audit audit_info; @@ -911,10 +1094,23 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, return -EPERM; if (*ppos != 0) return -EINVAL; - if (count < SMK_NETLBLADDRMIN || count > SMK_NETLBLADDRMAX) + if (count < SMK_NETLBLADDRMIN) return -EINVAL; - if (copy_from_user(data, buf, count) != 0) - return -EFAULT; + + 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; + } + + smack = kzalloc(count + 1, GFP_KERNEL); + if (smack == NULL) { + rc = -ENOMEM; + goto free_data_out; + } data[count] = '\0'; @@ -923,24 +1119,34 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, if (rc != 6) { rc = sscanf(data, "%hhd.%hhd.%hhd.%hhd %s", &host[0], &host[1], &host[2], &host[3], smack); - if (rc != 5) - return -EINVAL; + if (rc != 5) { + rc = -EINVAL; + goto free_out; + } m = BEBITS; } - if (m > BEBITS) - return -EINVAL; + if (m > BEBITS) { + rc = -EINVAL; + goto free_out; + } - /* if smack begins with '-', its an option, don't import it */ + /* + * If smack begins with '-', it is an option, don't import it + */ if (smack[0] != '-') { sp = smk_import(smack, 0); - if (sp == NULL) - return -EINVAL; + if (sp == NULL) { + rc = -EINVAL; + goto free_out; + } } else { /* check known options */ if (strcmp(smack, smack_cipso_option) == 0) sp = (char *)smack_cipso_option; - else - return -EINVAL; + else { + rc = -EINVAL; + goto free_out; + } } for (temp_mask = 0; m > 0; m--) { @@ -1006,6 +1212,11 @@ static ssize_t smk_write_netlbladdr(struct file *file, const char __user *buf, mutex_unlock(&smk_netlbladdr_lock); +free_out: + kfree(smack); +free_data_out: + kfree(data); + return rc; } @@ -1119,6 +1330,7 @@ static ssize_t smk_read_direct(struct file *filp, char __user *buf, static ssize_t smk_write_direct(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { + struct smack_known *skp; char temp[80]; int i; @@ -1136,7 +1348,20 @@ static ssize_t smk_write_direct(struct file *file, const char __user *buf, if (sscanf(temp, "%d", &i) != 1) return -EINVAL; - smack_cipso_direct = i; + /* + * Don't do anything if the value hasn't actually changed. + * If it is changing reset the level on entries that were + * set up to be direct when they were created. + */ + if (smack_cipso_direct != i) { + mutex_lock(&smack_known_lock); + list_for_each_entry_rcu(skp, &smack_known_list, list) + if (skp->smk_netlabel.attr.mls.lvl == + smack_cipso_direct) + skp->smk_netlabel.attr.mls.lvl = i; + smack_cipso_direct = i; + mutex_unlock(&smack_known_lock); + } return count; } @@ -1148,6 +1373,84 @@ static const struct file_operations smk_direct_ops = { }; /** + * smk_read_mapped - read() for /smack/mapped + * @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_mapped(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char temp[80]; + ssize_t rc; + + if (*ppos != 0) + return 0; + + sprintf(temp, "%d", smack_cipso_mapped); + rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); + + return rc; +} + +/** + * smk_write_mapped - write() for /smack/mapped + * @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_mapped(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct smack_known *skp; + char temp[80]; + int i; + + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + if (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; + + /* + * Don't do anything if the value hasn't actually changed. + * If it is changing reset the level on entries that were + * set up to be mapped when they were created. + */ + if (smack_cipso_mapped != i) { + mutex_lock(&smack_known_lock); + list_for_each_entry_rcu(skp, &smack_known_list, list) + if (skp->smk_netlabel.attr.mls.lvl == + smack_cipso_mapped) + skp->smk_netlabel.attr.mls.lvl = i; + smack_cipso_mapped = i; + mutex_unlock(&smack_known_lock); + } + + return count; +} + +static const struct file_operations smk_mapped_ops = { + .read = smk_read_mapped, + .write = smk_write_mapped, + .llseek = default_llseek, +}; + +/** * smk_read_ambient - read() for /smack/ambient * @filp: file pointer, not actually used * @buf: where to put the result @@ -1195,22 +1498,28 @@ static ssize_t smk_read_ambient(struct file *filp, char __user *buf, static ssize_t smk_write_ambient(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - char in[SMK_LABELLEN]; char *oldambient; - char *smack; + char *smack = NULL; + char *data; + int rc = count; if (!capable(CAP_MAC_ADMIN)) return -EPERM; - if (count >= SMK_LABELLEN) - return -EINVAL; + data = kzalloc(count + 1, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; - if (copy_from_user(in, buf, count) != 0) - return -EFAULT; + if (copy_from_user(data, buf, count) != 0) { + rc = -EFAULT; + goto out; + } - smack = smk_import(in, count); - if (smack == NULL) - return -EINVAL; + smack = smk_import(data, count); + if (smack == NULL) { + rc = -EINVAL; + goto out; + } mutex_lock(&smack_ambient_lock); @@ -1220,7 +1529,9 @@ static ssize_t smk_write_ambient(struct file *file, const char __user *buf, mutex_unlock(&smack_ambient_lock); - return count; +out: + kfree(data); + return rc; } static const struct file_operations smk_ambient_ops = { @@ -1271,8 +1582,9 @@ static ssize_t smk_read_onlycap(struct file *filp, char __user *buf, static ssize_t smk_write_onlycap(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - char in[SMK_LABELLEN]; + char *data; char *sp = smk_of_task(current->cred->security); + int rc = count; if (!capable(CAP_MAC_ADMIN)) return -EPERM; @@ -1285,11 +1597,9 @@ static ssize_t smk_write_onlycap(struct file *file, const char __user *buf, if (smack_onlycap != NULL && smack_onlycap != sp) return -EPERM; - if (count >= SMK_LABELLEN) - return -EINVAL; - - if (copy_from_user(in, buf, count) != 0) - return -EFAULT; + data = kzalloc(count, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; /* * Should the null string be passed in unset the onlycap value. @@ -1297,10 +1607,17 @@ static ssize_t smk_write_onlycap(struct file *file, const char __user *buf, * 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. + * + * smk_import will also reject a label beginning with '-', + * so "-usecapabilities" will also work. */ - smack_onlycap = smk_import(in, count); + if (copy_from_user(data, buf, count) != 0) + rc = -EFAULT; + else + smack_onlycap = smk_import(data, count); - return count; + kfree(data); + return rc; } static const struct file_operations smk_onlycap_ops = { @@ -1398,25 +1715,7 @@ static int load_self_seq_show(struct seq_file *s, void *v) struct smack_rule *srp = list_entry(list, struct smack_rule, list); - seq_printf(s, "%s %s", (char *)srp->smk_subject, - (char *)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'); - if (srp->smk_access == 0) - seq_putc(s, '-'); - - seq_putc(s, '\n'); + smk_rule_show(s, srp, SMK_LABELLEN); return 0; } @@ -1430,7 +1729,7 @@ static const struct seq_operations load_self_seq_ops = { /** - * smk_open_load_self - open() for /smack/load-self + * smk_open_load_self - open() for /smack/load-self2 * @inode: inode structure representing file * @file: "load" file pointer * @@ -1454,8 +1753,8 @@ static ssize_t smk_write_load_self(struct file *file, const char __user *buf, { struct task_smack *tsp = current_security(); - return smk_write_load_list(file, buf, count, ppos, &tsp->smk_rules, - &tsp->smk_rules_lock); + return smk_write_rules_list(file, buf, count, ppos, &tsp->smk_rules, + &tsp->smk_rules_lock, SMK_FIXED24_FMT); } static const struct file_operations smk_load_self_ops = { @@ -1467,24 +1766,42 @@ static const struct file_operations smk_load_self_ops = { }; /** - * smk_write_access - handle access check transaction + * smk_user_access - handle access check transaction * @file: file pointer * @buf: data from user space * @count: bytes sent * @ppos: where to start - must be 0 */ -static ssize_t smk_write_access(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) +static ssize_t smk_user_access(struct file *file, const char __user *buf, + size_t count, loff_t *ppos, int format) { struct smack_rule rule; char *data; + char *cod; int res; data = simple_transaction_get(file, buf, count); if (IS_ERR(data)) return PTR_ERR(data); - if (count < SMK_LOADLEN || smk_parse_rule(data, &rule, 0)) + if (format == SMK_FIXED24_FMT) { + if (count < SMK_LOADLEN) + return -EINVAL; + res = smk_parse_rule(data, &rule, 0); + } else { + /* + * Copy the data to make sure the string is terminated. + */ + 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); + kfree(cod); + } + + if (res) return -EINVAL; res = smk_access(rule.smk_subject, rule.smk_object, rule.smk_access, @@ -1493,7 +1810,23 @@ static ssize_t smk_write_access(struct file *file, const char __user *buf, data[1] = '\0'; simple_transaction_set(file, 2); - return SMK_LOADLEN; + + if (format == SMK_FIXED24_FMT) + return SMK_LOADLEN; + return count; +} + +/** + * smk_write_access - handle access check transaction + * @file: file pointer + * @buf: data from user space + * @count: bytes sent + * @ppos: where to start - must be 0 + */ +static ssize_t smk_write_access(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return smk_user_access(file, buf, count, ppos, SMK_FIXED24_FMT); } static const struct file_operations smk_access_ops = { @@ -1503,6 +1836,163 @@ static const struct file_operations smk_access_ops = { .llseek = generic_file_llseek, }; + +/* + * Seq_file read operations for /smack/load2 + */ + +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); + + smk_rule_show(s, smlp->smk_rule, SMK_LONGLABEL); + + return 0; +} + +static const struct seq_operations load2_seq_ops = { + .start = load2_seq_start, + .next = load2_seq_next, + .show = load2_seq_show, + .stop = smk_seq_stop, +}; + +/** + * smk_open_load2 - open() for /smack/load2 + * @inode: inode structure representing file + * @file: "load2" file pointer + * + * For reading, use load2_seq_* seq_file reading operations. + */ +static int smk_open_load2(struct inode *inode, struct file *file) +{ + return seq_open(file, &load2_seq_ops); +} + +/** + * smk_write_load2 - write() for /smack/load2 + * @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_load2(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* + * Must have privilege. + */ + if (!capable(CAP_MAC_ADMIN)) + return -EPERM; + + return smk_write_rules_list(file, buf, count, ppos, NULL, NULL, + SMK_LONG_FMT); +} + +static const struct file_operations smk_load2_ops = { + .open = smk_open_load2, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_load2, + .release = seq_release, +}; + +/* + * Seq_file read operations for /smack/load-self2 + */ + +static void *load_self2_seq_start(struct seq_file *s, loff_t *pos) +{ + struct task_smack *tsp = current_security(); + + 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(); + + return smk_seq_next(s, v, pos, &tsp->smk_rules); +} + +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); + + smk_rule_show(s, srp, SMK_LONGLABEL); + + return 0; +} + +static const struct seq_operations load_self2_seq_ops = { + .start = load_self2_seq_start, + .next = load_self2_seq_next, + .show = load_self2_seq_show, + .stop = smk_seq_stop, +}; + +/** + * smk_open_load_self2 - open() for /smack/load-self2 + * @inode: inode structure representing file + * @file: "load" file pointer + * + * For reading, use load_seq_* seq_file reading operations. + */ +static int smk_open_load_self2(struct inode *inode, struct file *file) +{ + return seq_open(file, &load_self2_seq_ops); +} + +/** + * smk_write_load_self2 - write() for /smack/load-self2 + * @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_load_self2(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct task_smack *tsp = current_security(); + + return smk_write_rules_list(file, buf, count, ppos, &tsp->smk_rules, + &tsp->smk_rules_lock, SMK_LONG_FMT); +} + +static const struct file_operations smk_load_self2_ops = { + .open = smk_open_load_self2, + .read = seq_read, + .llseek = seq_lseek, + .write = smk_write_load_self2, + .release = seq_release, +}; + +/** + * smk_write_access2 - handle access check transaction + * @file: file pointer + * @buf: data from user space + * @count: bytes sent + * @ppos: where to start - must be 0 + */ +static ssize_t smk_write_access2(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return smk_user_access(file, buf, count, ppos, SMK_LONG_FMT); +} + +static const struct file_operations smk_access2_ops = { + .write = smk_write_access2, + .read = simple_transaction_read, + .release = simple_transaction_release, + .llseek = generic_file_llseek, +}; + /** * smk_fill_super - fill the /smackfs superblock * @sb: the empty superblock @@ -1539,6 +2029,16 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent) "load-self", &smk_load_self_ops, S_IRUGO|S_IWUGO}, [SMK_ACCESSES] = { "access", &smk_access_ops, S_IRUGO|S_IWUGO}, + [SMK_MAPPED] = { + "mapped", &smk_mapped_ops, S_IRUGO|S_IWUSR}, + [SMK_LOAD2] = { + "load2", &smk_load2_ops, S_IRUGO|S_IWUSR}, + [SMK_LOAD_SELF2] = { + "load-self2", &smk_load_self2_ops, S_IRUGO|S_IWUGO}, + [SMK_ACCESS2] = { + "access2", &smk_access2_ops, S_IRUGO|S_IWUGO}, + [SMK_CIPSO2] = { + "cipso2", &smk_cipso2_ops, S_IRUGO|S_IWUSR}, /* last one */ {""} }; @@ -1581,6 +2081,15 @@ static struct file_system_type smk_fs_type = { 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 * @@ -1597,6 +2106,7 @@ static struct vfsmount *smackfs_mount; static int __init init_smk_fs(void) { int err; + int rc; if (!security_module_enable(&smack_ops)) return 0; @@ -1614,19 +2124,24 @@ static int __init init_smk_fs(void) smk_cipso_doi(); smk_unlbl_ambient(NULL); - mutex_init(&smack_known_floor.smk_rules_lock); - mutex_init(&smack_known_hat.smk_rules_lock); - mutex_init(&smack_known_huh.smk_rules_lock); - mutex_init(&smack_known_invalid.smk_rules_lock); - mutex_init(&smack_known_star.smk_rules_lock); - mutex_init(&smack_known_web.smk_rules_lock); - - INIT_LIST_HEAD(&smack_known_floor.smk_rules); - INIT_LIST_HEAD(&smack_known_hat.smk_rules); - INIT_LIST_HEAD(&smack_known_huh.smk_rules); - INIT_LIST_HEAD(&smack_known_invalid.smk_rules); - INIT_LIST_HEAD(&smack_known_star.smk_rules); - INIT_LIST_HEAD(&smack_known_web.smk_rules); + rc = smk_preset_netlabel(&smack_known_floor); + if (err == 0 && rc < 0) + err = rc; + rc = smk_preset_netlabel(&smack_known_hat); + if (err == 0 && rc < 0) + err = rc; + rc = smk_preset_netlabel(&smack_known_huh); + if (err == 0 && rc < 0) + err = rc; + rc = smk_preset_netlabel(&smack_known_invalid); + if (err == 0 && rc < 0) + err = rc; + rc = smk_preset_netlabel(&smack_known_star); + if (err == 0 && rc < 0) + err = rc; + rc = smk_preset_netlabel(&smack_known_web); + if (err == 0 && rc < 0) + err = rc; return err; } diff --git a/security/tomoyo/audit.c b/security/tomoyo/audit.c index 5ca47ea3049f..7ef9fa3e37e0 100644 --- a/security/tomoyo/audit.c +++ b/security/tomoyo/audit.c @@ -446,11 +446,11 @@ void tomoyo_read_log(struct tomoyo_io_buffer *head) * tomoyo_poll_log - Wait for an audit log. * * @file: Pointer to "struct file". - * @wait: Pointer to "poll_table". + * @wait: Pointer to "poll_table". Maybe NULL. * * Returns POLLIN | POLLRDNORM when ready to read an audit log. */ -int tomoyo_poll_log(struct file *file, poll_table *wait) +unsigned int tomoyo_poll_log(struct file *file, poll_table *wait) { if (tomoyo_log_count) return POLLIN | POLLRDNORM; diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index c47d3ce6c733..2e0f12c62938 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -850,14 +850,9 @@ static int tomoyo_update_manager_entry(const char *manager, policy_list[TOMOYO_ID_MANAGER], }; int error = is_delete ? -ENOENT : -ENOMEM; - if (tomoyo_domain_def(manager)) { - if (!tomoyo_correct_domain(manager)) - return -EINVAL; - e.is_domain = true; - } else { - if (!tomoyo_correct_path(manager)) - return -EINVAL; - } + if (!tomoyo_correct_domain(manager) && + !tomoyo_correct_word(manager)) + return -EINVAL; e.manager = tomoyo_get_name(manager); if (e.manager) { error = tomoyo_update_policy(&e.head, sizeof(e), ¶m, @@ -932,23 +927,14 @@ static bool tomoyo_manager(void) return true; if (!tomoyo_manage_by_non_root && (task->cred->uid || task->cred->euid)) return false; - list_for_each_entry_rcu(ptr, &tomoyo_kernel_namespace. - policy_list[TOMOYO_ID_MANAGER], head.list) { - if (!ptr->head.is_deleted && ptr->is_domain - && !tomoyo_pathcmp(domainname, ptr->manager)) { - found = true; - break; - } - } - if (found) - return true; exe = tomoyo_get_exe(); if (!exe) return false; list_for_each_entry_rcu(ptr, &tomoyo_kernel_namespace. policy_list[TOMOYO_ID_MANAGER], head.list) { - if (!ptr->head.is_deleted && !ptr->is_domain - && !strcmp(exe, ptr->manager->name)) { + if (!ptr->head.is_deleted && + (!tomoyo_pathcmp(domainname, ptr->manager) || + !strcmp(exe, ptr->manager->name))) { found = true; break; } @@ -1069,7 +1055,7 @@ static int tomoyo_write_task(struct tomoyo_acl_param *param) * * @domainname: The name of domain. * - * Returns 0. + * Returns 0 on success, negative value otherwise. * * Caller holds tomoyo_read_lock(). */ @@ -1081,7 +1067,7 @@ static int tomoyo_delete_domain(char *domainname) name.name = domainname; tomoyo_fill_path_info(&name); if (mutex_lock_interruptible(&tomoyo_policy_lock)) - return 0; + return -EINTR; /* Is there an active domain? */ list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { /* Never delete tomoyo_kernel_domain */ @@ -1164,15 +1150,16 @@ static int tomoyo_write_domain(struct tomoyo_io_buffer *head) bool is_select = !is_delete && tomoyo_str_starts(&data, "select "); unsigned int profile; if (*data == '<') { + int ret = 0; domain = NULL; if (is_delete) - tomoyo_delete_domain(data); + ret = tomoyo_delete_domain(data); else if (is_select) domain = tomoyo_find_domain(data); else domain = tomoyo_assign_domain(data, false); head->w.domain = domain; - return 0; + return ret; } if (!domain) return -EINVAL; @@ -2111,7 +2098,7 @@ static struct tomoyo_domain_info *tomoyo_find_domain_by_qid struct tomoyo_domain_info *domain = NULL; spin_lock(&tomoyo_query_list_lock); list_for_each_entry(ptr, &tomoyo_query_list, list) { - if (ptr->serial != serial || ptr->answer) + if (ptr->serial != serial) continue; domain = ptr->domain; break; @@ -2130,28 +2117,13 @@ static struct tomoyo_domain_info *tomoyo_find_domain_by_qid * * Waits for access requests which violated policy in enforcing mode. */ -static int tomoyo_poll_query(struct file *file, poll_table *wait) +static unsigned int tomoyo_poll_query(struct file *file, poll_table *wait) { - struct list_head *tmp; - bool found = false; - u8 i; - for (i = 0; i < 2; i++) { - spin_lock(&tomoyo_query_list_lock); - list_for_each(tmp, &tomoyo_query_list) { - struct tomoyo_query *ptr = - list_entry(tmp, typeof(*ptr), list); - if (ptr->answer) - continue; - found = true; - break; - } - spin_unlock(&tomoyo_query_list_lock); - if (found) - return POLLIN | POLLRDNORM; - if (i) - break; - poll_wait(file, &tomoyo_query_wait, wait); - } + if (!list_empty(&tomoyo_query_list)) + return POLLIN | POLLRDNORM; + poll_wait(file, &tomoyo_query_wait, wait); + if (!list_empty(&tomoyo_query_list)) + return POLLIN | POLLRDNORM; return 0; } @@ -2175,8 +2147,6 @@ static void tomoyo_read_query(struct tomoyo_io_buffer *head) spin_lock(&tomoyo_query_list_lock); list_for_each(tmp, &tomoyo_query_list) { struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list); - if (ptr->answer) - continue; if (pos++ != head->r.query_index) continue; len = ptr->query_len; @@ -2194,8 +2164,6 @@ static void tomoyo_read_query(struct tomoyo_io_buffer *head) spin_lock(&tomoyo_query_list_lock); list_for_each(tmp, &tomoyo_query_list) { struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list); - if (ptr->answer) - continue; if (pos++ != head->r.query_index) continue; /* @@ -2243,8 +2211,10 @@ static int tomoyo_write_answer(struct tomoyo_io_buffer *head) struct tomoyo_query *ptr = list_entry(tmp, typeof(*ptr), list); if (ptr->serial != serial) continue; - if (!ptr->answer) - ptr->answer = answer; + ptr->answer = answer; + /* Remove from tomoyo_query_list. */ + if (ptr->answer) + list_del_init(&ptr->list); break; } spin_unlock(&tomoyo_query_list_lock); @@ -2477,18 +2447,17 @@ int tomoyo_open_control(const u8 type, struct file *file) * tomoyo_poll_control - poll() for /sys/kernel/security/tomoyo/ interface. * * @file: Pointer to "struct file". - * @wait: Pointer to "poll_table". + * @wait: Pointer to "poll_table". Maybe NULL. * - * Waits for read readiness. - * /sys/kernel/security/tomoyo/query is handled by /usr/sbin/tomoyo-queryd and - * /sys/kernel/security/tomoyo/audit is handled by /usr/sbin/tomoyo-auditd. + * Returns POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM if ready to read/write, + * POLLOUT | POLLWRNORM otherwise. */ -int tomoyo_poll_control(struct file *file, poll_table *wait) +unsigned int tomoyo_poll_control(struct file *file, poll_table *wait) { struct tomoyo_io_buffer *head = file->private_data; - if (!head->poll) - return -ENOSYS; - return head->poll(file, wait); + if (head->poll) + return head->poll(file, wait) | POLLOUT | POLLWRNORM; + return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM; } /** diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index 9512222d5581..75e4dc1c02a0 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -788,7 +788,7 @@ struct tomoyo_acl_param { struct tomoyo_io_buffer { void (*read) (struct tomoyo_io_buffer *); int (*write) (struct tomoyo_io_buffer *); - int (*poll) (struct file *file, poll_table *wait); + unsigned int (*poll) (struct file *file, poll_table *wait); /* Exclusive lock for this structure. */ struct mutex io_sem; char __user *read_user_buf; @@ -860,7 +860,6 @@ struct tomoyo_aggregator { /* Structure for policy manager. */ struct tomoyo_manager { struct tomoyo_acl_head head; - bool is_domain; /* True if manager is a domainname. */ /* A path to program or a domainname. */ const struct tomoyo_path_info *manager; }; @@ -981,8 +980,8 @@ int tomoyo_path_number_perm(const u8 operation, struct path *path, unsigned long number); int tomoyo_path_perm(const u8 operation, struct path *path, const char *target); -int tomoyo_poll_control(struct file *file, poll_table *wait); -int tomoyo_poll_log(struct file *file, poll_table *wait); +unsigned int tomoyo_poll_control(struct file *file, poll_table *wait); +unsigned int tomoyo_poll_log(struct file *file, poll_table *wait); int tomoyo_socket_bind_permission(struct socket *sock, struct sockaddr *addr, int addr_len); int tomoyo_socket_connect_permission(struct socket *sock, diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index 9027ac1534af..38651454ed08 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -886,12 +886,12 @@ bool tomoyo_dump_page(struct linux_binprm *bprm, unsigned long pos, * But remove_arg_zero() uses kmap_atomic()/kunmap_atomic(). * So do I. */ - char *kaddr = kmap_atomic(page, KM_USER0); + char *kaddr = kmap_atomic(page); dump->page = page; memcpy(dump->data + offset, kaddr + offset, PAGE_SIZE - offset); - kunmap_atomic(kaddr, KM_USER0); + kunmap_atomic(kaddr); } /* Same with put_arg_page(page) in fs/exec.c */ #ifdef CONFIG_MMU diff --git a/security/tomoyo/load_policy.c b/security/tomoyo/load_policy.c index 67975405140f..078fac0bb4c5 100644 --- a/security/tomoyo/load_policy.c +++ b/security/tomoyo/load_policy.c @@ -102,7 +102,7 @@ void tomoyo_load_policy(const char *filename) envp[0] = "HOME=/"; envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; envp[2] = NULL; - call_usermodehelper(argv[0], argv, envp, 1); + call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC); tomoyo_check_profile(); } diff --git a/security/tomoyo/mount.c b/security/tomoyo/mount.c index bee09d062057..fe00cdfd0267 100644 --- a/security/tomoyo/mount.c +++ b/security/tomoyo/mount.c @@ -199,30 +199,32 @@ int tomoyo_mount_permission(char *dev_name, struct path *path, if (flags & MS_REMOUNT) { type = tomoyo_mounts[TOMOYO_MOUNT_REMOUNT]; flags &= ~MS_REMOUNT; - } - if (flags & MS_MOVE) { - type = tomoyo_mounts[TOMOYO_MOUNT_MOVE]; - flags &= ~MS_MOVE; - } - if (flags & MS_BIND) { + } else if (flags & MS_BIND) { type = tomoyo_mounts[TOMOYO_MOUNT_BIND]; flags &= ~MS_BIND; - } - if (flags & MS_UNBINDABLE) { - type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_UNBINDABLE]; - flags &= ~MS_UNBINDABLE; - } - if (flags & MS_PRIVATE) { + } else if (flags & MS_SHARED) { + if (flags & (MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE)) + return -EINVAL; + type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_SHARED]; + flags &= ~MS_SHARED; + } else if (flags & MS_PRIVATE) { + if (flags & (MS_SHARED | MS_SLAVE | MS_UNBINDABLE)) + return -EINVAL; type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_PRIVATE]; flags &= ~MS_PRIVATE; - } - if (flags & MS_SLAVE) { + } else if (flags & MS_SLAVE) { + if (flags & (MS_SHARED | MS_PRIVATE | MS_UNBINDABLE)) + return -EINVAL; type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_SLAVE]; flags &= ~MS_SLAVE; - } - if (flags & MS_SHARED) { - type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_SHARED]; - flags &= ~MS_SHARED; + } else if (flags & MS_UNBINDABLE) { + if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE)) + return -EINVAL; + type = tomoyo_mounts[TOMOYO_MOUNT_MAKE_UNBINDABLE]; + flags &= ~MS_UNBINDABLE; + } else if (flags & MS_MOVE) { + type = tomoyo_mounts[TOMOYO_MOUNT_MOVE]; + flags &= ~MS_MOVE; } if (!type) type = "<NULL>"; diff --git a/security/tomoyo/securityfs_if.c b/security/tomoyo/securityfs_if.c index 482b2a5f48f0..8592f2fc6ebb 100644 --- a/security/tomoyo/securityfs_if.c +++ b/security/tomoyo/securityfs_if.c @@ -157,9 +157,10 @@ static int tomoyo_release(struct inode *inode, struct file *file) * tomoyo_poll - poll() for /sys/kernel/security/tomoyo/ interface. * * @file: Pointer to "struct file". - * @wait: Pointer to "poll_table". + * @wait: Pointer to "poll_table". Maybe NULL. * - * Returns 0 on success, negative value otherwise. + * Returns POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM if ready to read/write, + * POLLOUT | POLLWRNORM otherwise. */ static unsigned int tomoyo_poll(struct file *file, poll_table *wait) { diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index 620d37c159a3..c2d04a50f76a 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -319,14 +319,14 @@ static int tomoyo_file_fcntl(struct file *file, unsigned int cmd, } /** - * tomoyo_dentry_open - Target for security_dentry_open(). + * tomoyo_file_open - Target for security_file_open(). * * @f: Pointer to "struct file". * @cred: Pointer to "struct cred". * * Returns 0 on success, negative value otherwise. */ -static int tomoyo_dentry_open(struct file *f, const struct cred *cred) +static int tomoyo_file_open(struct file *f, const struct cred *cred) { int flags = f->f_flags; /* Don't check read permission here if called from do_execve(). */ @@ -510,7 +510,7 @@ static struct security_operations tomoyo_security_ops = { .bprm_set_creds = tomoyo_bprm_set_creds, .bprm_check_security = tomoyo_bprm_check_security, .file_fcntl = tomoyo_file_fcntl, - .dentry_open = tomoyo_dentry_open, + .file_open = tomoyo_file_open, .path_truncate = tomoyo_path_truncate, .path_unlink = tomoyo_path_unlink, .path_mkdir = tomoyo_path_mkdir, diff --git a/security/yama/Kconfig b/security/yama/Kconfig new file mode 100644 index 000000000000..51d6709d8bbd --- /dev/null +++ b/security/yama/Kconfig @@ -0,0 +1,13 @@ +config SECURITY_YAMA + bool "Yama support" + depends on SECURITY + select SECURITYFS + select SECURITY_PATH + default n + help + This selects Yama, which extends DAC support with additional + system-wide security settings beyond regular Linux discretionary + access controls. Currently available is ptrace scope restriction. + Further information can be found in Documentation/security/Yama.txt. + + If you are unsure how to answer this question, answer N. diff --git a/security/yama/Makefile b/security/yama/Makefile new file mode 100644 index 000000000000..8b5e06588456 --- /dev/null +++ b/security/yama/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SECURITY_YAMA) := yama.o + +yama-y := yama_lsm.o diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c new file mode 100644 index 000000000000..83554ee8a587 --- /dev/null +++ b/security/yama/yama_lsm.c @@ -0,0 +1,362 @@ +/* + * Yama Linux Security Module + * + * Author: Kees Cook <keescook@chromium.org> + * + * Copyright (C) 2010 Canonical, Ltd. + * Copyright (C) 2011 The Chromium OS Authors. + * + * 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/security.h> +#include <linux/sysctl.h> +#include <linux/ptrace.h> +#include <linux/prctl.h> +#include <linux/ratelimit.h> + +#define YAMA_SCOPE_DISABLED 0 +#define YAMA_SCOPE_RELATIONAL 1 +#define YAMA_SCOPE_CAPABILITY 2 +#define YAMA_SCOPE_NO_ATTACH 3 + +static int ptrace_scope = YAMA_SCOPE_RELATIONAL; + +/* describe a ptrace relationship for potential exception */ +struct ptrace_relation { + struct task_struct *tracer; + struct task_struct *tracee; + struct list_head node; +}; + +static LIST_HEAD(ptracer_relations); +static DEFINE_SPINLOCK(ptracer_relations_lock); + +/** + * yama_ptracer_add - add/replace an exception for this tracer/tracee pair + * @tracer: the task_struct of the process doing the ptrace + * @tracee: the task_struct of the process to be ptraced + * + * Each tracee can have, at most, one tracer registered. Each time this + * is called, the prior registered tracer will be replaced for the tracee. + * + * Returns 0 if relationship was added, -ve on error. + */ +static int yama_ptracer_add(struct task_struct *tracer, + struct task_struct *tracee) +{ + int rc = 0; + struct ptrace_relation *added; + struct ptrace_relation *entry, *relation = NULL; + + added = kmalloc(sizeof(*added), GFP_KERNEL); + if (!added) + return -ENOMEM; + + spin_lock_bh(&ptracer_relations_lock); + list_for_each_entry(entry, &ptracer_relations, node) + if (entry->tracee == tracee) { + relation = entry; + break; + } + if (!relation) { + relation = added; + relation->tracee = tracee; + list_add(&relation->node, &ptracer_relations); + } + relation->tracer = tracer; + + spin_unlock_bh(&ptracer_relations_lock); + if (added != relation) + kfree(added); + + return rc; +} + +/** + * yama_ptracer_del - remove exceptions related to the given tasks + * @tracer: remove any relation where tracer task matches + * @tracee: remove any relation where tracee task matches + */ +static void yama_ptracer_del(struct task_struct *tracer, + struct task_struct *tracee) +{ + struct ptrace_relation *relation, *safe; + + spin_lock_bh(&ptracer_relations_lock); + list_for_each_entry_safe(relation, safe, &ptracer_relations, node) + if (relation->tracee == tracee || + (tracer && relation->tracer == tracer)) { + list_del(&relation->node); + kfree(relation); + } + spin_unlock_bh(&ptracer_relations_lock); +} + +/** + * yama_task_free - check for task_pid to remove from exception list + * @task: task being removed + */ +static void yama_task_free(struct task_struct *task) +{ + yama_ptracer_del(task, task); +} + +/** + * yama_task_prctl - check for Yama-specific prctl operations + * @option: operation + * @arg2: argument + * @arg3: argument + * @arg4: argument + * @arg5: argument + * + * Return 0 on success, -ve on error. -ENOSYS is returned when Yama + * does not handle the given option. + */ +static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, + unsigned long arg4, unsigned long arg5) +{ + int rc; + struct task_struct *myself = current; + + rc = cap_task_prctl(option, arg2, arg3, arg4, arg5); + if (rc != -ENOSYS) + return rc; + + switch (option) { + case PR_SET_PTRACER: + /* Since a thread can call prctl(), find the group leader + * before calling _add() or _del() on it, since we want + * process-level granularity of control. The tracer group + * leader checking is handled later when walking the ancestry + * at the time of PTRACE_ATTACH check. + */ + rcu_read_lock(); + if (!thread_group_leader(myself)) + myself = rcu_dereference(myself->group_leader); + get_task_struct(myself); + rcu_read_unlock(); + + if (arg2 == 0) { + yama_ptracer_del(NULL, myself); + rc = 0; + } else if (arg2 == PR_SET_PTRACER_ANY) { + rc = yama_ptracer_add(NULL, myself); + } else { + struct task_struct *tracer; + + rcu_read_lock(); + tracer = find_task_by_vpid(arg2); + if (tracer) + get_task_struct(tracer); + else + rc = -EINVAL; + rcu_read_unlock(); + + if (tracer) { + rc = yama_ptracer_add(tracer, myself); + put_task_struct(tracer); + } + } + + put_task_struct(myself); + break; + } + + return rc; +} + +/** + * task_is_descendant - walk up a process family tree looking for a match + * @parent: the process to compare against while walking up from child + * @child: the process to start from while looking upwards for parent + * + * Returns 1 if child is a descendant of parent, 0 if not. + */ +static int task_is_descendant(struct task_struct *parent, + struct task_struct *child) +{ + int rc = 0; + struct task_struct *walker = child; + + if (!parent || !child) + return 0; + + rcu_read_lock(); + if (!thread_group_leader(parent)) + parent = rcu_dereference(parent->group_leader); + while (walker->pid > 0) { + if (!thread_group_leader(walker)) + walker = rcu_dereference(walker->group_leader); + if (walker == parent) { + rc = 1; + break; + } + walker = rcu_dereference(walker->real_parent); + } + rcu_read_unlock(); + + return rc; +} + +/** + * ptracer_exception_found - tracer registered as exception for this tracee + * @tracer: the task_struct of the process attempting ptrace + * @tracee: the task_struct of the process to be ptraced + * + * Returns 1 if tracer has is ptracer exception ancestor for tracee. + */ +static int ptracer_exception_found(struct task_struct *tracer, + struct task_struct *tracee) +{ + int rc = 0; + struct ptrace_relation *relation; + struct task_struct *parent = NULL; + bool found = false; + + spin_lock_bh(&ptracer_relations_lock); + rcu_read_lock(); + if (!thread_group_leader(tracee)) + tracee = rcu_dereference(tracee->group_leader); + list_for_each_entry(relation, &ptracer_relations, node) + if (relation->tracee == tracee) { + parent = relation->tracer; + found = true; + break; + } + + if (found && (parent == NULL || task_is_descendant(parent, tracer))) + rc = 1; + rcu_read_unlock(); + spin_unlock_bh(&ptracer_relations_lock); + + return rc; +} + +/** + * yama_ptrace_access_check - validate PTRACE_ATTACH calls + * @child: task that current task is attempting to ptrace + * @mode: ptrace attach mode + * + * Returns 0 if following the ptrace is allowed, -ve on error. + */ +static int yama_ptrace_access_check(struct task_struct *child, + unsigned int mode) +{ + int rc; + + /* If standard caps disallows it, so does Yama. We should + * only tighten restrictions further. + */ + rc = cap_ptrace_access_check(child, mode); + if (rc) + return rc; + + /* require ptrace target be a child of ptracer on attach */ + if (mode == PTRACE_MODE_ATTACH) { + switch (ptrace_scope) { + case YAMA_SCOPE_DISABLED: + /* No additional restrictions. */ + break; + case YAMA_SCOPE_RELATIONAL: + if (!task_is_descendant(current, child) && + !ptracer_exception_found(current, child) && + !ns_capable(task_user_ns(child), CAP_SYS_PTRACE)) + rc = -EPERM; + break; + case YAMA_SCOPE_CAPABILITY: + if (!ns_capable(task_user_ns(child), CAP_SYS_PTRACE)) + rc = -EPERM; + break; + case YAMA_SCOPE_NO_ATTACH: + default: + rc = -EPERM; + break; + } + } + + if (rc) { + char name[sizeof(current->comm)]; + printk_ratelimited(KERN_NOTICE + "ptrace of pid %d was attempted by: %s (pid %d)\n", + child->pid, + get_task_comm(name, current), + current->pid); + } + + return rc; +} + +static struct security_operations yama_ops = { + .name = "yama", + + .ptrace_access_check = yama_ptrace_access_check, + .task_prctl = yama_task_prctl, + .task_free = yama_task_free, +}; + +#ifdef CONFIG_SYSCTL +static int yama_dointvec_minmax(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int rc; + + if (write && !capable(CAP_SYS_PTRACE)) + return -EPERM; + + rc = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + if (rc) + return rc; + + /* Lock the max value if it ever gets set. */ + if (write && *(int *)table->data == *(int *)table->extra2) + table->extra1 = table->extra2; + + return rc; +} + +static int zero; +static int max_scope = YAMA_SCOPE_NO_ATTACH; + +struct ctl_path yama_sysctl_path[] = { + { .procname = "kernel", }, + { .procname = "yama", }, + { } +}; + +static struct ctl_table yama_sysctl_table[] = { + { + .procname = "ptrace_scope", + .data = &ptrace_scope, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = yama_dointvec_minmax, + .extra1 = &zero, + .extra2 = &max_scope, + }, + { } +}; +#endif /* CONFIG_SYSCTL */ + +static __init int yama_init(void) +{ + if (!security_module_enable(&yama_ops)) + return 0; + + printk(KERN_INFO "Yama: becoming mindful.\n"); + + if (register_security(&yama_ops)) + panic("Yama: kernel registration failed.\n"); + +#ifdef CONFIG_SYSCTL + if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table)) + panic("Yama: sysctl registration failed.\n"); +#endif + + return 0; +} + +security_initcall(yama_init); |