mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 16:35:02 +01:00
8199 lines
226 KiB
Diff
8199 lines
226 KiB
Diff
Index: linux-2.6.19/security/apparmor/Kconfig
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/Kconfig
|
|
@@ -0,0 +1,9 @@
|
|
+config SECURITY_APPARMOR
|
|
+ tristate "AppArmor support"
|
|
+ depends on SECURITY!=n
|
|
+ help
|
|
+ This enables the AppArmor security module.
|
|
+ Required userspace tools (if they are not included in your
|
|
+ distribution) and further information may be found at
|
|
+ <http://forge.novell.com/modules/xfmod/project/?apparmor>
|
|
+ If you are unsure how to answer this question, answer N.
|
|
Index: linux-2.6.19/security/apparmor/Makefile
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/Makefile
|
|
@@ -0,0 +1,6 @@
|
|
+# Makefile for AppArmor Linux Security Module
|
|
+#
|
|
+obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o match/
|
|
+
|
|
+apparmor-y := main.o list.o procattr.o lsm.o apparmorfs.o capabilities.o \
|
|
+ module_interface.o
|
|
Index: linux-2.6.19/security/apparmor/apparmor.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/apparmor.h
|
|
@@ -0,0 +1,338 @@
|
|
+/*
|
|
+ * Copyright (C) 1998-2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * AppArmor internal prototypes
|
|
+ */
|
|
+
|
|
+#ifndef __APPARMOR_H
|
|
+#define __APPARMOR_H
|
|
+
|
|
+#include <linux/fs.h> /* Include for defn of iattr */
|
|
+#include <linux/binfmts.h> /* defn of linux_binprm */
|
|
+#include <linux/rcupdate.h>
|
|
+
|
|
+#include "shared.h"
|
|
+
|
|
+/* Control parameters (0 or 1), settable thru module/boot flags or
|
|
+ * via /sys/kernel/security/apparmor/control */
|
|
+extern int apparmor_complain;
|
|
+extern int apparmor_debug;
|
|
+extern int apparmor_audit;
|
|
+extern int apparmor_logsyscall;
|
|
+
|
|
+/* PIPEFS_MAGIC */
|
|
+#include <linux/pipe_fs_i.h>
|
|
+/* from net/socket.c */
|
|
+#define SOCKFS_MAGIC 0x534F434B
|
|
+/* from inotify.c */
|
|
+#define INOTIFYFS_MAGIC 0xBAD1DEA
|
|
+
|
|
+#define VALID_FSTYPE(inode) ((inode)->i_sb->s_magic != PIPEFS_MAGIC && \
|
|
+ (inode)->i_sb->s_magic != SOCKFS_MAGIC && \
|
|
+ (inode)->i_sb->s_magic != INOTIFYFS_MAGIC)
|
|
+
|
|
+#define PROFILE_COMPLAIN(_profile) \
|
|
+ (apparmor_complain == 1 || ((_profile) && (_profile)->flags.complain))
|
|
+
|
|
+#define SUBDOMAIN_COMPLAIN(_sd) \
|
|
+ (apparmor_complain == 1 || \
|
|
+ ((_sd) && (_sd)->active && (_sd)->active->flags.complain))
|
|
+
|
|
+#define PROFILE_AUDIT(_profile) \
|
|
+ (apparmor_audit == 1 || ((_profile) && (_profile)->flags.audit))
|
|
+
|
|
+#define SUBDOMAIN_AUDIT(_sd) \
|
|
+ (apparmor_audit == 1 || \
|
|
+ ((_sd) && (_sd)->active && (_sd)->active->flags.audit))
|
|
+
|
|
+/*
|
|
+ * DEBUG remains global (no per profile flag) since it is mostly used in sysctl
|
|
+ * which is not related to profile accesses.
|
|
+ */
|
|
+
|
|
+#define AA_DEBUG(fmt, args...) \
|
|
+ do { \
|
|
+ if (apparmor_debug) \
|
|
+ printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
|
|
+ } while (0)
|
|
+#define AA_INFO(fmt, args...) printk(KERN_INFO "AppArmor: " fmt, ##args)
|
|
+#define AA_WARN(fmt, args...) printk(KERN_WARNING "AppArmor: " fmt, ##args)
|
|
+#define AA_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args)
|
|
+
|
|
+/* basic AppArmor data structures */
|
|
+
|
|
+struct flagval {
|
|
+ int debug;
|
|
+ int complain;
|
|
+ int audit;
|
|
+};
|
|
+
|
|
+enum entry_match_type {
|
|
+ aa_entry_literal,
|
|
+ aa_entry_tailglob,
|
|
+ aa_entry_pattern,
|
|
+ aa_entry_invalid
|
|
+};
|
|
+
|
|
+/* struct aa_entry - file ACL *
|
|
+ * @filename: filename controlled by this ACL
|
|
+ * @mode: permissions granted by ACL
|
|
+ * @type: type of match to perform against @filename
|
|
+ * @extradata: any extra data needed by an extended matching type
|
|
+ * @list: list the ACL is on
|
|
+ * @listp: permission partitioned lists this ACL is on.
|
|
+ *
|
|
+ * Each entry describes a file and an allowed access mode.
|
|
+ */
|
|
+struct aa_entry {
|
|
+ char *filename;
|
|
+ int mode; /* mode is 'or' of READ, WRITE, EXECUTE,
|
|
+ * INHERIT, UNCONSTRAINED, and LIBRARY
|
|
+ * (meaning don't prefetch). */
|
|
+
|
|
+ enum entry_match_type type;
|
|
+ void *extradata;
|
|
+
|
|
+ struct list_head list;
|
|
+ struct list_head listp[POS_AA_FILE_MAX + 1];
|
|
+};
|
|
+
|
|
+#define AA_SECURE_EXEC_NEEDED 0x00000001
|
|
+
|
|
+#define AA_EXEC_MODIFIER_MASK(mask) ((mask) & AA_EXEC_MODIFIERS)
|
|
+#define AA_EXEC_MASK(mask) ((mask) & (AA_MAY_EXEC | AA_EXEC_MODIFIERS))
|
|
+#define AA_EXEC_UNSAFE_MASK(mask) ((mask) & (AA_MAY_EXEC | AA_EXEC_MODIFIERS |\
|
|
+ AA_EXEC_UNSAFE))
|
|
+
|
|
+/* struct aaprofile - basic confinement data
|
|
+ * @parent: non refcounted pointer to parent profile
|
|
+ * @name: the profiles name
|
|
+ * @file_entry: file ACL
|
|
+ * @file_entryp: vector of file ACL by permission granted
|
|
+ * @list: list this profile is on
|
|
+ * @sub: profiles list of subprofiles (HATS)
|
|
+ * @flags: flags controlling profile behavior
|
|
+ * @null_profile: if needed per profile learning and null confinement profile
|
|
+ * @isstale: flag to indicate the profile is stale
|
|
+ * @num_file_entries: number of file entries the profile contains
|
|
+ * @num_file_pentries: number of file entries for each partitioned list
|
|
+ * @capabilities: capabilities granted by the process
|
|
+ * @rcu: rcu head used when freeing the profile
|
|
+ * @count: reference count of the profile
|
|
+ *
|
|
+ * The AppArmor profile contains the basic confinement data. Each profile
|
|
+ * has a name and potentially a list of profile entries. The profiles are
|
|
+ * connected in a list
|
|
+ */
|
|
+struct aaprofile {
|
|
+ struct aaprofile *parent;
|
|
+ char *name;
|
|
+
|
|
+ struct list_head file_entry;
|
|
+ struct list_head file_entryp[POS_AA_FILE_MAX + 1];
|
|
+ struct list_head list;
|
|
+ struct list_head sub;
|
|
+ struct flagval flags;
|
|
+ struct aaprofile *null_profile;
|
|
+ int isstale;
|
|
+
|
|
+ int num_file_entries;
|
|
+ int num_file_pentries[POS_AA_FILE_MAX + 1];
|
|
+
|
|
+ kernel_cap_t capabilities;
|
|
+
|
|
+ struct rcu_head rcu;
|
|
+
|
|
+ struct kref count;
|
|
+};
|
|
+
|
|
+enum aafile_type {
|
|
+ aa_file_default,
|
|
+ aa_file_shmem
|
|
+};
|
|
+
|
|
+/**
|
|
+ * aafile - file pointer confinement data
|
|
+ *
|
|
+ * Data structure assigned to each open file (by apparmor_file_alloc_security)
|
|
+ */
|
|
+struct aafile {
|
|
+ enum aafile_type type;
|
|
+ struct aaprofile *profile;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct subdomain - primary label for confined tasks
|
|
+ * @active: the current active profile
|
|
+ * @hat_magic: the magic token controling the ability to leave a hat
|
|
+ * @list: list this subdomain is on
|
|
+ * @task: task that the subdomain confines
|
|
+ *
|
|
+ * Contains the tasks current active profile (which could change due to
|
|
+ * change_hat). Plus the hat_magic needed during change_hat.
|
|
+ *
|
|
+ * N.B AppArmor's previous product name SubDomain was derived from the name
|
|
+ * of this structure/concept (changehat reducing a task into a sub-domain).
|
|
+ */
|
|
+struct subdomain {
|
|
+ struct aaprofile *active; /* The current active profile */
|
|
+ u32 hat_magic; /* used with change_hat */
|
|
+ struct list_head list; /* list of subdomains */
|
|
+ struct task_struct *task;
|
|
+};
|
|
+
|
|
+typedef int (*aa_iter) (struct subdomain *, void *);
|
|
+
|
|
+/* aa_path_data
|
|
+ * temp (cookie) data used by aa_path_* functions, see inline.h
|
|
+ */
|
|
+struct aa_path_data {
|
|
+ struct dentry *root, *dentry;
|
|
+ struct namespace *namespace;
|
|
+ struct list_head *head, *pos;
|
|
+ int errno;
|
|
+};
|
|
+
|
|
+#define AA_SUBDOMAIN(sec) ((struct subdomain*)(sec))
|
|
+#define AA_PROFILE(sec) ((struct aaprofile*)(sec))
|
|
+
|
|
+/* Lock protecting access to 'struct subdomain' accesses */
|
|
+extern spinlock_t sd_lock;
|
|
+
|
|
+extern struct aaprofile *null_complain_profile;
|
|
+
|
|
+/* aa_audit - AppArmor auditing structure
|
|
+ * Structure is populated by access control code and passed to aa_audit which
|
|
+ * provides for a single point of logging.
|
|
+ */
|
|
+
|
|
+struct aa_audit {
|
|
+ unsigned short type, flags;
|
|
+ unsigned int result;
|
|
+ gfp_t gfp_mask;
|
|
+ int error_code;
|
|
+
|
|
+ const char *name;
|
|
+ unsigned int ival;
|
|
+ union {
|
|
+ const void *pval;
|
|
+ va_list vaval;
|
|
+ };
|
|
+};
|
|
+
|
|
+/* audit types */
|
|
+#define AA_AUDITTYPE_FILE 1
|
|
+#define AA_AUDITTYPE_DIR 2
|
|
+#define AA_AUDITTYPE_ATTR 3
|
|
+#define AA_AUDITTYPE_XATTR 4
|
|
+#define AA_AUDITTYPE_LINK 5
|
|
+#define AA_AUDITTYPE_CAP 6
|
|
+#define AA_AUDITTYPE_MSG 7
|
|
+#define AA_AUDITTYPE_SYSCALL 8
|
|
+#define AA_AUDITTYPE__END 9
|
|
+
|
|
+/* audit flags */
|
|
+#define AA_AUDITFLAG_AUDITSS_SYSCALL 1 /* log syscall context */
|
|
+#define AA_AUDITFLAG_LOGERR 2 /* log operations that failed due to
|
|
+ non permission errors */
|
|
+
|
|
+#define HINT_UNKNOWN_HAT "unknown_hat"
|
|
+#define HINT_FORK "fork"
|
|
+#define HINT_MANDPROF "missing_mandatory_profile"
|
|
+#define HINT_CHGPROF "changing_profile"
|
|
+
|
|
+#define LOG_HINT(p, gfp, hint, fmt, args...) \
|
|
+ do {\
|
|
+ aa_audit_message(p, gfp, 0, \
|
|
+ "LOGPROF-HINT " hint " " fmt, ##args);\
|
|
+ } while(0)
|
|
+
|
|
+/* directory op type, for aa_perm_dir */
|
|
+enum aa_diroptype {
|
|
+ aa_dir_mkdir,
|
|
+ aa_dir_rmdir
|
|
+};
|
|
+
|
|
+/* xattr op type, for aa_xattr */
|
|
+enum aa_xattroptype {
|
|
+ aa_xattr_get,
|
|
+ aa_xattr_set,
|
|
+ aa_xattr_list,
|
|
+ aa_xattr_remove
|
|
+};
|
|
+
|
|
+#define BASE_PROFILE(p) ((p)->parent ? (p)->parent : (p))
|
|
+#define IN_SUBPROFILE(p) ((p)->parent)
|
|
+
|
|
+/* main.c */
|
|
+extern int alloc_null_complain_profile(void);
|
|
+extern void free_null_complain_profile(void);
|
|
+extern int attach_nullprofile(struct aaprofile *profile);
|
|
+extern int aa_audit_message(struct aaprofile *active, gfp_t gfp, int,
|
|
+ const char *, ...);
|
|
+extern int aa_audit_syscallreject(struct aaprofile *active, gfp_t gfp,
|
|
+ const char *);
|
|
+extern int aa_audit(struct aaprofile *active, const struct aa_audit *);
|
|
+extern char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt);
|
|
+
|
|
+extern int aa_attr(struct aaprofile *active, struct dentry *dentry,
|
|
+ struct iattr *iattr);
|
|
+extern int aa_xattr(struct aaprofile *active, struct dentry *dentry,
|
|
+ const char *xattr, enum aa_xattroptype xattroptype);
|
|
+extern int aa_capability(struct aaprofile *active, int cap);
|
|
+extern int aa_perm(struct aaprofile *active, struct dentry *dentry,
|
|
+ struct vfsmount *mnt, int mask);
|
|
+extern int aa_perm_nameidata(struct aaprofile *active, struct nameidata *nd,
|
|
+ int mask);
|
|
+extern int aa_perm_dentry(struct aaprofile *active, struct dentry *dentry,
|
|
+ int mask);
|
|
+extern int aa_perm_dir(struct aaprofile *active, struct dentry *dentry,
|
|
+ enum aa_diroptype diroptype);
|
|
+extern int aa_link(struct aaprofile *active,
|
|
+ struct dentry *link, struct dentry *target);
|
|
+extern int aa_fork(struct task_struct *p);
|
|
+extern int aa_register(struct linux_binprm *bprm);
|
|
+extern void aa_release(struct task_struct *p);
|
|
+extern int aa_change_hat(const char *id, u32 hat_magic);
|
|
+extern int aa_associate_filp(struct file *filp);
|
|
+
|
|
+/* list.c */
|
|
+extern struct aaprofile *aa_profilelist_find(const char *name);
|
|
+extern int aa_profilelist_add(struct aaprofile *profile);
|
|
+extern struct aaprofile *aa_profilelist_remove(const char *name);
|
|
+extern void aa_profilelist_release(void);
|
|
+extern struct aaprofile *aa_profilelist_replace(struct aaprofile *profile);
|
|
+extern void aa_profile_dump(struct aaprofile *);
|
|
+extern void aa_profilelist_dump(void);
|
|
+extern void aa_subdomainlist_add(struct subdomain *);
|
|
+extern void aa_subdomainlist_remove(struct subdomain *);
|
|
+extern void aa_subdomainlist_iterate(aa_iter, void *);
|
|
+extern void aa_subdomainlist_iterateremove(aa_iter, void *);
|
|
+extern void aa_subdomainlist_release(void);
|
|
+
|
|
+/* module_interface.c */
|
|
+extern ssize_t aa_file_prof_add(void *, size_t);
|
|
+extern ssize_t aa_file_prof_repl(void *, size_t);
|
|
+extern ssize_t aa_file_prof_remove(const char *, size_t);
|
|
+extern void free_aaprofile(struct aaprofile *profile);
|
|
+extern void free_aaprofile_kref(struct kref *kref);
|
|
+
|
|
+/* procattr.c */
|
|
+extern size_t aa_getprocattr(struct aaprofile *active, char *str, size_t size);
|
|
+extern int aa_setprocattr_changehat(char *hatinfo, size_t infosize);
|
|
+extern int aa_setprocattr_setprofile(struct task_struct *p, char *profilename,
|
|
+ size_t profilesize);
|
|
+
|
|
+/* apparmorfs.c */
|
|
+extern int create_apparmorfs(void);
|
|
+extern void destroy_apparmorfs(void);
|
|
+
|
|
+/* capabilities.c */
|
|
+extern const char *capability_to_name(unsigned int cap);
|
|
+
|
|
+#endif /* __APPARMOR_H */
|
|
Index: linux-2.6.19/security/apparmor/apparmorfs.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/apparmorfs.c
|
|
@@ -0,0 +1,432 @@
|
|
+/*
|
|
+ * Copyright (C) 2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * AppArmor filesystem (part of securityfs)
|
|
+ */
|
|
+
|
|
+#include <linux/security.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/seq_file.h>
|
|
+#include <asm/uaccess.h>
|
|
+
|
|
+#include "apparmor.h"
|
|
+#include "inline.h"
|
|
+#include "match/match.h"
|
|
+
|
|
+#define SECFS_AA "apparmor"
|
|
+static struct dentry *aafs_dentry = NULL;
|
|
+
|
|
+/* profile */
|
|
+extern struct seq_operations apparmorfs_profiles_op;
|
|
+static int aa_prof_open(struct inode *inode, struct file *file);
|
|
+static int aa_prof_release(struct inode *inode, struct file *file);
|
|
+
|
|
+static struct file_operations apparmorfs_profiles_fops = {
|
|
+ .open = aa_prof_open,
|
|
+ .read = seq_read,
|
|
+ .llseek = seq_lseek,
|
|
+ .release = aa_prof_release,
|
|
+};
|
|
+
|
|
+/* matching */
|
|
+static ssize_t aa_matching_read(struct file *file, char __user *buf,
|
|
+ size_t size, loff_t *ppos);
|
|
+
|
|
+static struct file_operations apparmorfs_matching_fops = {
|
|
+ .read = aa_matching_read,
|
|
+};
|
|
+
|
|
+
|
|
+/* interface */
|
|
+static ssize_t aa_profile_load(struct file *f, const char __user *buf,
|
|
+ size_t size, loff_t *pos);
|
|
+static ssize_t aa_profile_replace(struct file *f, const char __user *buf,
|
|
+ size_t size, loff_t *pos);
|
|
+static ssize_t aa_profile_remove(struct file *f, const char __user *buf,
|
|
+ size_t size, loff_t *pos);
|
|
+
|
|
+static struct file_operations apparmorfs_profile_load = {
|
|
+ .write = aa_profile_load
|
|
+};
|
|
+
|
|
+static struct file_operations apparmorfs_profile_replace = {
|
|
+ .write = aa_profile_replace
|
|
+};
|
|
+
|
|
+static struct file_operations apparmorfs_profile_remove = {
|
|
+ .write = aa_profile_remove
|
|
+};
|
|
+
|
|
+
|
|
+/* control */
|
|
+static u64 aa_control_get(void *data);
|
|
+static void aa_control_set(void *data, u64 val);
|
|
+
|
|
+DEFINE_SIMPLE_ATTRIBUTE(apparmorfs_control_fops, aa_control_get,
|
|
+ aa_control_set, "%lld\n");
|
|
+
|
|
+
|
|
+
|
|
+/* table of static entries */
|
|
+
|
|
+static struct root_entry {
|
|
+ const char *name;
|
|
+ int mode;
|
|
+ int access;
|
|
+ struct file_operations *fops;
|
|
+ void *data;
|
|
+
|
|
+ /* internal fields */
|
|
+ struct dentry *dentry;
|
|
+ int parent_index;
|
|
+} root_entries[] = {
|
|
+ /* our root, normally /sys/kernel/security/apparmor */
|
|
+ {SECFS_AA, S_IFDIR, 0550}, /* DO NOT EDIT/MOVE */
|
|
+
|
|
+ /* interface for obtaining list of profiles currently loaded */
|
|
+ {"profiles", S_IFREG, 0440, &apparmorfs_profiles_fops,
|
|
+ NULL},
|
|
+
|
|
+ /* interface for obtaining matching features supported */
|
|
+ {"matching", S_IFREG, 0440, &apparmorfs_matching_fops,
|
|
+ NULL},
|
|
+
|
|
+ /* interface for loading/removing/replacing profiles */
|
|
+ {".load", S_IFREG, 0640, &apparmorfs_profile_load,
|
|
+ NULL},
|
|
+ {".replace", S_IFREG, 0640, &apparmorfs_profile_replace,
|
|
+ NULL},
|
|
+ {".remove", S_IFREG, 0640, &apparmorfs_profile_remove,
|
|
+ NULL},
|
|
+
|
|
+ /* interface for setting binary config values */
|
|
+ {"control", S_IFDIR, 0550},
|
|
+ {"complain", S_IFREG, 0640, &apparmorfs_control_fops,
|
|
+ &apparmor_complain},
|
|
+ {"audit", S_IFREG, 0640, &apparmorfs_control_fops,
|
|
+ &apparmor_audit},
|
|
+ {"debug", S_IFREG, 0640, &apparmorfs_control_fops,
|
|
+ &apparmor_debug},
|
|
+ {"logsyscall", S_IFREG, 0640, &apparmorfs_control_fops,
|
|
+ &apparmor_logsyscall},
|
|
+ {NULL, S_IFDIR, 0},
|
|
+
|
|
+ /* root end */
|
|
+ {NULL, S_IFDIR, 0}
|
|
+};
|
|
+
|
|
+#define AAFS_DENTRY root_entries[0].dentry
|
|
+
|
|
+static const unsigned int num_entries =
|
|
+ sizeof(root_entries) / sizeof(struct root_entry);
|
|
+
|
|
+
|
|
+
|
|
+static int aa_prof_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ return seq_open(file, &apparmorfs_profiles_op);
|
|
+}
|
|
+
|
|
+
|
|
+static int aa_prof_release(struct inode *inode, struct file *file)
|
|
+{
|
|
+ return seq_release(inode, file);
|
|
+}
|
|
+
|
|
+static ssize_t aa_matching_read(struct file *file, char __user *buf,
|
|
+ size_t size, loff_t *ppos)
|
|
+{
|
|
+ const char *matching = aamatch_features();
|
|
+
|
|
+ return simple_read_from_buffer(buf, size, ppos, matching,
|
|
+ strlen(matching));
|
|
+}
|
|
+
|
|
+static char *aa_simple_write_to_buffer(const char __user *userbuf,
|
|
+ size_t alloc_size, size_t copy_size,
|
|
+ loff_t *pos, const char *msg)
|
|
+{
|
|
+ struct aaprofile *active;
|
|
+ char *data;
|
|
+
|
|
+ if (*pos != 0) {
|
|
+ /* only writes from pos 0, that is complete writes */
|
|
+ data = ERR_PTR(-ESPIPE);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* Don't allow confined processes to load/replace/remove profiles.
|
|
+ * No sane person would add rules allowing this to a profile
|
|
+ * but we enforce the restriction anyways.
|
|
+ */
|
|
+ rcu_read_lock();
|
|
+ active = get_activeptr_rcu();
|
|
+ if (active) {
|
|
+ AA_WARN("REJECTING access to profile %s (%s(%d) "
|
|
+ "profile %s active %s)\n",
|
|
+ msg, current->comm, current->pid,
|
|
+ BASE_PROFILE(active)->name, active->name);
|
|
+
|
|
+ data = ERR_PTR(-EPERM);
|
|
+ goto out;
|
|
+ }
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ data = vmalloc(alloc_size);
|
|
+ if (data == NULL) {
|
|
+ data = ERR_PTR(-ENOMEM);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (copy_from_user(data, userbuf, copy_size)) {
|
|
+ vfree(data);
|
|
+ data = ERR_PTR(-EFAULT);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return data;
|
|
+}
|
|
+
|
|
+static ssize_t aa_profile_load(struct file *f, const char __user *buf,
|
|
+ size_t size, loff_t *pos)
|
|
+{
|
|
+ char *data;
|
|
+ ssize_t error;
|
|
+
|
|
+ data = aa_simple_write_to_buffer(buf, size, size, pos, "load");
|
|
+
|
|
+ if (!IS_ERR(data)) {
|
|
+ error = aa_file_prof_add(data, size);
|
|
+ vfree(data);
|
|
+ } else {
|
|
+ error = PTR_ERR(data);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static ssize_t aa_profile_replace(struct file *f, const char __user *buf,
|
|
+ size_t size, loff_t *pos)
|
|
+{
|
|
+ char *data;
|
|
+ ssize_t error;
|
|
+
|
|
+ data = aa_simple_write_to_buffer(buf, size, size, pos, "replacement");
|
|
+
|
|
+ if (!IS_ERR(data)) {
|
|
+ error = aa_file_prof_repl(data, size);
|
|
+ vfree(data);
|
|
+ } else {
|
|
+ error = PTR_ERR(data);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static ssize_t aa_profile_remove(struct file *f, const char __user *buf,
|
|
+ size_t size, loff_t *pos)
|
|
+{
|
|
+ char *data;
|
|
+ ssize_t error;
|
|
+
|
|
+ /* aa_file_prof_remove needs a null terminated string so 1 extra
|
|
+ * byte is allocated and null the copied data is then null terminated
|
|
+ */
|
|
+ data = aa_simple_write_to_buffer(buf, size+1, size, pos, "removal");
|
|
+
|
|
+ if (!IS_ERR(data)) {
|
|
+ data[size] = 0;
|
|
+ error = aa_file_prof_remove(data, size);
|
|
+ vfree(data);
|
|
+ } else {
|
|
+ error = PTR_ERR(data);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static u64 aa_control_get(void *data)
|
|
+{
|
|
+ return *(int *)data;
|
|
+}
|
|
+
|
|
+static void aa_control_set(void *data, u64 val)
|
|
+{
|
|
+ if (val > 1)
|
|
+ val = 1;
|
|
+
|
|
+ *(int*)data = (int)val;
|
|
+}
|
|
+
|
|
+static void clear_apparmorfs(void)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i=0; i < num_entries;i++) {
|
|
+ unsigned int index;
|
|
+
|
|
+ if (root_entries[i].mode == S_IFDIR) {
|
|
+ if (root_entries[i].name)
|
|
+ /* defer dir free till all sub-entries freed */
|
|
+ continue;
|
|
+ else
|
|
+ /* cleanup parent */
|
|
+ index = root_entries[i].parent_index;
|
|
+ } else {
|
|
+ index = i;
|
|
+ }
|
|
+
|
|
+ if (root_entries[index].dentry) {
|
|
+ securityfs_remove(root_entries[index].dentry);
|
|
+
|
|
+ AA_DEBUG("%s: deleted apparmorfs entry name=%s "
|
|
+ "dentry=%p\n",
|
|
+ __FUNCTION__,
|
|
+ root_entries[index].name,
|
|
+ root_entries[index].dentry);
|
|
+
|
|
+ root_entries[index].dentry = NULL;
|
|
+ root_entries[index].parent_index = 0;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static int populate_apparmorfs(struct dentry *root)
|
|
+{
|
|
+ unsigned int i, parent_index, depth;
|
|
+
|
|
+ for (i = 0; i < num_entries; i++) {
|
|
+ root_entries[i].dentry = NULL;
|
|
+ root_entries[i].parent_index = 0;
|
|
+ }
|
|
+
|
|
+ /* 1. Verify entry 0 is valid [sanity check] */
|
|
+ if (num_entries == 0 ||
|
|
+ !root_entries[0].name ||
|
|
+ strcmp(root_entries[0].name, SECFS_AA) != 0 ||
|
|
+ root_entries[0].mode != S_IFDIR) {
|
|
+ AA_ERROR("%s: root entry 0 is not SECFS_AA/dir\n",
|
|
+ __FUNCTION__);
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ /* 2. Build back pointers */
|
|
+ parent_index = 0;
|
|
+ depth = 1;
|
|
+
|
|
+ for (i = 1; i < num_entries; i++) {
|
|
+ root_entries[i].parent_index = parent_index;
|
|
+
|
|
+ if (root_entries[i].name &&
|
|
+ root_entries[i].mode == S_IFDIR) {
|
|
+ depth++;
|
|
+ parent_index = i;
|
|
+ } else if (!root_entries[i].name) {
|
|
+ if (root_entries[i].mode != S_IFDIR || depth == 0) {
|
|
+ AA_ERROR("%s: root_entry %d invalid (%u %d)",
|
|
+ __FUNCTION__, i,
|
|
+ root_entries[i].mode,
|
|
+ root_entries[i].parent_index);
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ depth--;
|
|
+ parent_index = root_entries[parent_index].parent_index;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (depth != 0) {
|
|
+ AA_ERROR("%s: root_entry table not correctly terminated\n",
|
|
+ __FUNCTION__);
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ /* 3. Create root (parent=NULL) */
|
|
+ root_entries[0].dentry = securityfs_create_file(
|
|
+ root_entries[0].name,
|
|
+ root_entries[0].mode |
|
|
+ root_entries[0].access,
|
|
+ NULL, NULL, NULL);
|
|
+
|
|
+ if (IS_ERR(root_entries[0].dentry))
|
|
+ goto error;
|
|
+ else
|
|
+ AA_DEBUG("%s: created securityfs/apparmor [dentry=%p]\n",
|
|
+ __FUNCTION__, root_entries[0].dentry);
|
|
+
|
|
+
|
|
+ /* 4. create remaining nodes */
|
|
+ for (i = 1; i < num_entries; i++) {
|
|
+ struct dentry *parent;
|
|
+ void *data = NULL;
|
|
+ struct file_operations *fops = NULL;
|
|
+
|
|
+ /* end of directory ? */
|
|
+ if (!root_entries[i].name)
|
|
+ continue;
|
|
+
|
|
+ parent = root_entries[root_entries[i].parent_index].dentry;
|
|
+
|
|
+ if (root_entries[i].mode != S_IFDIR) {
|
|
+ data = root_entries[i].data;
|
|
+ fops = root_entries[i].fops;
|
|
+ }
|
|
+
|
|
+ root_entries[i].dentry = securityfs_create_file(
|
|
+ root_entries[i].name,
|
|
+ root_entries[i].mode |
|
|
+ root_entries[i].access,
|
|
+ parent,
|
|
+ data,
|
|
+ fops);
|
|
+
|
|
+ if (IS_ERR(root_entries[i].dentry))
|
|
+ goto cleanup_error;
|
|
+
|
|
+ AA_DEBUG("%s: added apparmorfs entry "
|
|
+ "name=%s mode=%x dentry=%p [parent %p]\n",
|
|
+ __FUNCTION__, root_entries[i].name,
|
|
+ root_entries[i].mode|root_entries[i].access,
|
|
+ root_entries[i].dentry, parent);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+cleanup_error:
|
|
+ clear_apparmorfs();
|
|
+
|
|
+error:
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+int create_apparmorfs(void)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ if (AAFS_DENTRY) {
|
|
+ error = -EEXIST;
|
|
+ AA_ERROR("%s: AppArmor securityfs already exists\n",
|
|
+ __FUNCTION__);
|
|
+ } else {
|
|
+ error = populate_apparmorfs(aafs_dentry);
|
|
+ if (error != 0) {
|
|
+ AA_ERROR("%s: Error populating AppArmor securityfs\n",
|
|
+ __FUNCTION__);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+void destroy_apparmorfs(void)
|
|
+{
|
|
+ if (AAFS_DENTRY)
|
|
+ clear_apparmorfs();
|
|
+}
|
|
Index: linux-2.6.19/security/apparmor/capabilities.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/capabilities.c
|
|
@@ -0,0 +1,54 @@
|
|
+/*
|
|
+ * Copyright (C) 2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * AppArmor capability definitions
|
|
+ */
|
|
+
|
|
+#include "apparmor.h"
|
|
+
|
|
+static const char *cap_names[] = {
|
|
+ "chown",
|
|
+ "dac_override",
|
|
+ "dac_read_search",
|
|
+ "fowner",
|
|
+ "fsetid",
|
|
+ "kill",
|
|
+ "setgid",
|
|
+ "setuid",
|
|
+ "setpcap",
|
|
+ "linux_immutable",
|
|
+ "net_bind_service",
|
|
+ "net_broadcast",
|
|
+ "net_admin",
|
|
+ "net_raw",
|
|
+ "ipc_lock",
|
|
+ "ipc_owner",
|
|
+ "sys_module",
|
|
+ "sys_rawio",
|
|
+ "sys_chroot",
|
|
+ "sys_ptrace",
|
|
+ "sys_pacct",
|
|
+ "sys_admin",
|
|
+ "sys_boot",
|
|
+ "sys_nice",
|
|
+ "sys_resource",
|
|
+ "sys_time",
|
|
+ "sys_tty_config",
|
|
+ "mknod",
|
|
+ "lease"
|
|
+};
|
|
+
|
|
+const char *capability_to_name(unsigned int cap)
|
|
+{
|
|
+ const char *name;
|
|
+
|
|
+ name = (cap < (sizeof(cap_names) / sizeof(char *))
|
|
+ ? cap_names[cap] : "invalid-capability");
|
|
+
|
|
+ return name;
|
|
+}
|
|
Index: linux-2.6.19/security/apparmor/inline.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/inline.h
|
|
@@ -0,0 +1,335 @@
|
|
+/*
|
|
+ * Copyright (C) 2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ */
|
|
+
|
|
+#ifndef __INLINE_H
|
|
+#define __INLINE_H
|
|
+
|
|
+#include <linux/namespace.h>
|
|
+
|
|
+static inline int __aa_is_confined(struct subdomain *sd)
|
|
+{
|
|
+ return (sd && sd->active);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_is_confined
|
|
+ * Determine whether current task contains a valid profile (confined).
|
|
+ * Return %1 if confined, %0 otherwise.
|
|
+ */
|
|
+static inline int aa_is_confined(void)
|
|
+{
|
|
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
|
|
+ return __aa_is_confined(sd);
|
|
+}
|
|
+
|
|
+static inline int __aa_sub_defined(struct subdomain *sd)
|
|
+{
|
|
+ return __aa_is_confined(sd) && !list_empty(&BASE_PROFILE(sd->active)->sub);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_sub_defined - check to see if current task has any subprofiles
|
|
+ * Return 1 if true, 0 otherwise
|
|
+ */
|
|
+static inline int aa_sub_defined(void)
|
|
+{
|
|
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
|
|
+ return __aa_sub_defined(sd);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * get_aaprofile - increment refcount on profile @p
|
|
+ * @p: profile
|
|
+ */
|
|
+static inline struct aaprofile *get_aaprofile(struct aaprofile *p)
|
|
+{
|
|
+ if (p)
|
|
+ kref_get(&(BASE_PROFILE(p)->count));
|
|
+
|
|
+ return p;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * put_aaprofile - decrement refcount on profile @p
|
|
+ * @p: profile
|
|
+ */
|
|
+static inline void put_aaprofile(struct aaprofile *p)
|
|
+{
|
|
+ if (p)
|
|
+ kref_put(&BASE_PROFILE(p)->count, free_aaprofile_kref);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * get_task_activeptr_rcu - get pointer to @tsk's active profile.
|
|
+ * @tsk: task to get active profile from
|
|
+ *
|
|
+ * Requires rcu_read_lock is held
|
|
+ */
|
|
+static inline struct aaprofile *get_task_activeptr_rcu(struct task_struct *tsk)
|
|
+{
|
|
+ struct subdomain *sd = AA_SUBDOMAIN(tsk->security);
|
|
+ struct aaprofile *active = NULL;
|
|
+
|
|
+ if (sd)
|
|
+ active = (struct aaprofile *) rcu_dereference(sd->active);
|
|
+
|
|
+ return active;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * get_activeptr_rcu - get pointer to current task's active profile
|
|
+ * Requires rcu_read_lock is held
|
|
+ */
|
|
+static inline struct aaprofile *get_activeptr_rcu(void)
|
|
+{
|
|
+ return get_task_activeptr_rcu(current);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * get_task_active_aaprofile - get a reference to tsk's active profile.
|
|
+ * @tsk: the task to get the active profile reference for
|
|
+ */
|
|
+static inline struct aaprofile *get_task_active_aaprofile(struct task_struct *tsk)
|
|
+{
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ rcu_read_lock();
|
|
+ active = get_aaprofile(get_task_activeptr_rcu(tsk));
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ return active;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * get_active_aaprofile - get a reference to the current tasks active profile
|
|
+ */
|
|
+static inline struct aaprofile *get_active_aaprofile(void)
|
|
+{
|
|
+ return get_task_active_aaprofile(current);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_switch - change subdomain to use a new profile
|
|
+ * @sd: subdomain to switch the active profile on
|
|
+ * @newactive: new active profile
|
|
+ *
|
|
+ * aa_switch handles the changing of a subdomain's active profile. The
|
|
+ * sd_lock must be held to ensure consistency against other writers.
|
|
+ * Some write paths (ex. aa_register) require sd->active not to change
|
|
+ * over several operations, so the calling function is responsible
|
|
+ * for grabing the sd_lock to meet its consistency constraints before
|
|
+ * calling aa_switch
|
|
+ */
|
|
+static inline void aa_switch(struct subdomain *sd, struct aaprofile *newactive)
|
|
+{
|
|
+ struct aaprofile *oldactive = sd->active;
|
|
+
|
|
+ /* noop if NULL */
|
|
+ rcu_assign_pointer(sd->active, get_aaprofile(newactive));
|
|
+ put_aaprofile(oldactive);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_switch_unconfined - change subdomain to be unconfined (no profile)
|
|
+ * @sd: subdomain to switch
|
|
+ *
|
|
+ * aa_switch_unconfined handles the removal of a subdomain's active profile.
|
|
+ * The sd_lock must be held to ensure consistency against other writers.
|
|
+ * Like aa_switch the sd_lock is used to maintain consistency.
|
|
+ */
|
|
+static inline void aa_switch_unconfined(struct subdomain *sd)
|
|
+{
|
|
+ aa_switch(sd, NULL);
|
|
+
|
|
+ /* reset magic in case we were in a subhat before */
|
|
+ sd->hat_magic = 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * alloc_subdomain - allocate a new subdomain
|
|
+ * @tsk: task struct
|
|
+ *
|
|
+ * Allocate a new subdomain including a backpointer to it's referring task.
|
|
+ */
|
|
+static inline struct subdomain *alloc_subdomain(struct task_struct *tsk)
|
|
+{
|
|
+ struct subdomain *sd;
|
|
+
|
|
+ sd = kzalloc(sizeof(struct subdomain), GFP_KERNEL);
|
|
+ if (!sd)
|
|
+ goto out;
|
|
+
|
|
+ /* back pointer to task */
|
|
+ sd->task = tsk;
|
|
+
|
|
+ /* any readers of the list must make sure that they can handle
|
|
+ * case where sd->active is not yet set (null)
|
|
+ */
|
|
+ aa_subdomainlist_add(sd);
|
|
+
|
|
+out:
|
|
+ return sd;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * free_subdomain - Free a subdomain previously allocated by alloc_subdomain
|
|
+ * @sd: subdomain
|
|
+ */
|
|
+static inline void free_subdomain(struct subdomain *sd)
|
|
+{
|
|
+ aa_subdomainlist_remove(sd);
|
|
+ kfree(sd);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * alloc_aaprofile - Allocate, initialize and return a new zeroed profile.
|
|
+ * Returns NULL on failure.
|
|
+ */
|
|
+static inline struct aaprofile *alloc_aaprofile(void)
|
|
+{
|
|
+ struct aaprofile *profile;
|
|
+
|
|
+ profile = (struct aaprofile *)kzalloc(sizeof(struct aaprofile),
|
|
+ GFP_KERNEL);
|
|
+ AA_DEBUG("%s(%p)\n", __FUNCTION__, profile);
|
|
+ if (profile) {
|
|
+ int i;
|
|
+
|
|
+ INIT_LIST_HEAD(&profile->list);
|
|
+ INIT_LIST_HEAD(&profile->sub);
|
|
+ INIT_LIST_HEAD(&profile->file_entry);
|
|
+ for (i = 0; i <= POS_AA_FILE_MAX; i++) {
|
|
+ INIT_LIST_HEAD(&profile->file_entryp[i]);
|
|
+ }
|
|
+ INIT_RCU_HEAD(&profile->rcu);
|
|
+ kref_init(&profile->count);
|
|
+ }
|
|
+ return profile;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_put_name
|
|
+ * @name: name to release.
|
|
+ *
|
|
+ * Release space (free_page) allocated to hold pathname
|
|
+ * name may be NULL (checked for by free_page)
|
|
+ */
|
|
+static inline void aa_put_name(const char *name)
|
|
+{
|
|
+ free_page((unsigned long)name);
|
|
+}
|
|
+
|
|
+/** __aa_find_profile
|
|
+ * @name: name of profile to find
|
|
+ * @head: list to search
|
|
+ *
|
|
+ * Return reference counted copy of profile. NULL if not found
|
|
+ * Caller must hold any necessary locks
|
|
+ */
|
|
+static inline struct aaprofile *__aa_find_profile(const char *name,
|
|
+ struct list_head *head)
|
|
+{
|
|
+ struct aaprofile *p;
|
|
+
|
|
+ if (!name || !head)
|
|
+ return NULL;
|
|
+
|
|
+ AA_DEBUG("%s: finding profile %s\n", __FUNCTION__, name);
|
|
+ list_for_each_entry(p, head, list) {
|
|
+ if (!strcmp(p->name, name)) {
|
|
+ /* return refcounted object */
|
|
+ p = get_aaprofile(p);
|
|
+ return p;
|
|
+ } else {
|
|
+ AA_DEBUG("%s: skipping %s\n", __FUNCTION__, p->name);
|
|
+ }
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/** __aa_path_begin
|
|
+ * @rdentry: filesystem root dentry (searching for vfsmnts matching this)
|
|
+ * @dentry: dentry object to obtain pathname from (relative to matched vfsmnt)
|
|
+ *
|
|
+ * Setup data for iterating over vfsmounts (in current tasks namespace).
|
|
+ */
|
|
+static inline void __aa_path_begin(struct dentry *rdentry,
|
|
+ struct dentry *dentry,
|
|
+ struct aa_path_data *data)
|
|
+{
|
|
+ data->dentry = dentry;
|
|
+ data->root = dget(rdentry->d_sb->s_root);
|
|
+ data->namespace = current->namespace;
|
|
+ data->head = &data->namespace->list;
|
|
+ data->pos = data->head->next;
|
|
+ prefetch(data->pos->next);
|
|
+ data->errno = 0;
|
|
+
|
|
+ down_read(&namespace_sem);
|
|
+}
|
|
+
|
|
+/** aa_path_begin
|
|
+ * @dentry: filesystem root dentry and object to obtain pathname from
|
|
+ *
|
|
+ * Utility function for calling _aa_path_begin for when the dentry we are
|
|
+ * looking for and the root are the same (this is the usual case).
|
|
+ */
|
|
+static inline void aa_path_begin(struct dentry *dentry,
|
|
+ struct aa_path_data *data)
|
|
+{
|
|
+ __aa_path_begin(dentry, dentry, data);
|
|
+}
|
|
+
|
|
+/** aa_path_end
|
|
+ * @data: data object previously initialized by aa_path_begin
|
|
+ *
|
|
+ * End iterating over vfsmounts.
|
|
+ * If an error occured in begin or get, it is returned. Otherwise 0.
|
|
+ */
|
|
+static inline int aa_path_end(struct aa_path_data *data)
|
|
+{
|
|
+ up_read(&namespace_sem);
|
|
+ dput(data->root);
|
|
+
|
|
+ return data->errno;
|
|
+}
|
|
+
|
|
+/** aa_path_getname
|
|
+ * @data: data object previously initialized by aa_path_begin
|
|
+ *
|
|
+ * Return the next mountpoint which has the same root dentry as data->root.
|
|
+ * If no more mount points exist (or in case of error) NULL is returned
|
|
+ * (caller should call aa_path_end() and inspect return code to differentiate)
|
|
+ */
|
|
+static inline char *aa_path_getname(struct aa_path_data *data)
|
|
+{
|
|
+ char *name = NULL;
|
|
+ struct vfsmount *mnt;
|
|
+
|
|
+ while (data->pos != data->head) {
|
|
+ mnt = list_entry(data->pos, struct vfsmount, mnt_list);
|
|
+
|
|
+ /* advance to next -- so that it is done before we break */
|
|
+ data->pos = data->pos->next;
|
|
+ prefetch(data->pos->next);
|
|
+
|
|
+ if (mnt->mnt_root == data->root) {
|
|
+ name = aa_get_name(data->dentry, mnt);
|
|
+ if (IS_ERR(name)) {
|
|
+ data->errno = PTR_ERR(name);
|
|
+ name = NULL;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return name;
|
|
+}
|
|
+
|
|
+#endif /* __INLINE_H__ */
|
|
Index: linux-2.6.19/security/apparmor/list.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/list.c
|
|
@@ -0,0 +1,268 @@
|
|
+/*
|
|
+ * Copyright (C) 1998-2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * AppArmor Profile List Management
|
|
+ */
|
|
+
|
|
+#include <linux/seq_file.h>
|
|
+#include "apparmor.h"
|
|
+#include "inline.h"
|
|
+
|
|
+/* list of all profiles and lock */
|
|
+static LIST_HEAD(profile_list);
|
|
+static rwlock_t profile_lock = RW_LOCK_UNLOCKED;
|
|
+
|
|
+/* list of all subdomains and lock */
|
|
+static LIST_HEAD(subdomain_list);
|
|
+static rwlock_t subdomain_lock = RW_LOCK_UNLOCKED;
|
|
+
|
|
+/**
|
|
+ * aa_profilelist_find
|
|
+ * @name: profile name (program name)
|
|
+ *
|
|
+ * Search the profile list for profile @name. Return refcounted profile on
|
|
+ * success, NULL on failure.
|
|
+ */
|
|
+struct aaprofile *aa_profilelist_find(const char *name)
|
|
+{
|
|
+ struct aaprofile *p = NULL;
|
|
+ if (name) {
|
|
+ read_lock(&profile_lock);
|
|
+ p = __aa_find_profile(name, &profile_list);
|
|
+ read_unlock(&profile_lock);
|
|
+ }
|
|
+ return p;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_profilelist_add - add new profile to list
|
|
+ * @profile: new profile to add to list
|
|
+ *
|
|
+ * NOTE: Caller must allocate necessary reference count that will be used
|
|
+ * by the profile_list. This is because profile allocation alloc_aaprofile()
|
|
+ * returns an unreferenced object with a initial count of %1.
|
|
+ *
|
|
+ * Return %1 on success, %0 on failure (already exists)
|
|
+ */
|
|
+int aa_profilelist_add(struct aaprofile *profile)
|
|
+{
|
|
+ struct aaprofile *old_profile;
|
|
+ int ret = 0;
|
|
+
|
|
+ if (!profile)
|
|
+ goto out;
|
|
+
|
|
+ write_lock(&profile_lock);
|
|
+ old_profile = __aa_find_profile(profile->name, &profile_list);
|
|
+ if (old_profile) {
|
|
+ put_aaprofile(old_profile);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ list_add(&profile->list, &profile_list);
|
|
+ ret = 1;
|
|
+ out:
|
|
+ write_unlock(&profile_lock);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_profilelist_remove - remove a profile from the list by name
|
|
+ * @name: name of profile to be removed
|
|
+ *
|
|
+ * If the profile exists remove profile from list and return its reference.
|
|
+ * The reference count on profile is not decremented and should be decremented
|
|
+ * when the profile is no longer needed
|
|
+ */
|
|
+struct aaprofile *aa_profilelist_remove(const char *name)
|
|
+{
|
|
+ struct aaprofile *profile = NULL;
|
|
+ struct aaprofile *p, *tmp;
|
|
+
|
|
+ if (!name)
|
|
+ goto out;
|
|
+
|
|
+ write_lock(&profile_lock);
|
|
+ list_for_each_entry_safe(p, tmp, &profile_list, list) {
|
|
+ if (!strcmp(p->name, name)) {
|
|
+ list_del_init(&p->list);
|
|
+ /* mark old profile as stale */
|
|
+ p->isstale = 1;
|
|
+ profile = p;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ write_unlock(&profile_lock);
|
|
+
|
|
+out:
|
|
+ return profile;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_profilelist_replace - replace a profile on the list
|
|
+ * @profile: new profile
|
|
+ *
|
|
+ * Replace a profile on the profile list. Find the old profile by name in
|
|
+ * the list, and replace it with the new profile. NOTE: Caller must allocate
|
|
+ * necessary initial reference count for new profile as aa_profilelist_add().
|
|
+ *
|
|
+ * This is an atomic list operation. Returns the old profile (which is still
|
|
+ * refcounted) if there was one, or NULL.
|
|
+ */
|
|
+struct aaprofile *aa_profilelist_replace(struct aaprofile *profile)
|
|
+{
|
|
+ struct aaprofile *oldprofile;
|
|
+
|
|
+ write_lock(&profile_lock);
|
|
+ oldprofile = __aa_find_profile(profile->name, &profile_list);
|
|
+ if (oldprofile) {
|
|
+ list_del_init(&oldprofile->list);
|
|
+ /* mark old profile as stale */
|
|
+ oldprofile->isstale = 1;
|
|
+
|
|
+ /* __aa_find_profile incremented count, so adjust down */
|
|
+ put_aaprofile(oldprofile);
|
|
+ }
|
|
+
|
|
+ list_add(&profile->list, &profile_list);
|
|
+ write_unlock(&profile_lock);
|
|
+
|
|
+ return oldprofile;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_profilelist_release - Remove all profiles from profile_list
|
|
+ */
|
|
+void aa_profilelist_release(void)
|
|
+{
|
|
+ struct aaprofile *p, *tmp;
|
|
+
|
|
+ write_lock(&profile_lock);
|
|
+ list_for_each_entry_safe(p, tmp, &profile_list, list) {
|
|
+ list_del_init(&p->list);
|
|
+ put_aaprofile(p);
|
|
+ }
|
|
+ write_unlock(&profile_lock);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_subdomainlist_add - Add subdomain to subdomain_list
|
|
+ * @sd: new subdomain
|
|
+ */
|
|
+void aa_subdomainlist_add(struct subdomain *sd)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ if (!sd) {
|
|
+ AA_INFO("%s: bad subdomain\n", __FUNCTION__);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ write_lock_irqsave(&subdomain_lock, flags);
|
|
+ /* new subdomains must be added to the end of the list due to a
|
|
+ * subtle interaction between fork and profile replacement.
|
|
+ */
|
|
+ list_add_tail(&sd->list, &subdomain_list);
|
|
+ write_unlock_irqrestore(&subdomain_lock, flags);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_subdomainlist_remove - Remove subdomain from subdomain_list
|
|
+ * @sd: subdomain to be removed
|
|
+ */
|
|
+void aa_subdomainlist_remove(struct subdomain *sd)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ if (sd) {
|
|
+ write_lock_irqsave(&subdomain_lock, flags);
|
|
+ list_del_init(&sd->list);
|
|
+ write_unlock_irqrestore(&subdomain_lock, flags);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_subdomainlist_iterate - iterate over the subdomain list applying @func
|
|
+ * @func: method to be called for each element
|
|
+ * @cookie: user passed data
|
|
+ *
|
|
+ * Iterate over subdomain list applying @func, stop when @func returns
|
|
+ * non zero
|
|
+ */
|
|
+void aa_subdomainlist_iterate(aa_iter func, void *cookie)
|
|
+{
|
|
+ struct subdomain *node;
|
|
+ int ret = 0;
|
|
+ unsigned long flags;
|
|
+
|
|
+ read_lock_irqsave(&subdomain_lock, flags);
|
|
+ list_for_each_entry(node, &subdomain_list, list) {
|
|
+ ret = (*func) (node, cookie);
|
|
+ if (ret != 0)
|
|
+ break;
|
|
+ }
|
|
+ read_unlock_irqrestore(&subdomain_lock, flags);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_subdomainlist_release - Remove all subdomains from subdomain_list
|
|
+ */
|
|
+void aa_subdomainlist_release(void)
|
|
+{
|
|
+ struct subdomain *node, *tmp;
|
|
+ unsigned long flags;
|
|
+
|
|
+ write_lock_irqsave(&subdomain_lock, flags);
|
|
+ list_for_each_entry_safe(node, tmp, &subdomain_list, list) {
|
|
+ list_del_init(&node->list);
|
|
+ }
|
|
+ write_unlock_irqrestore(&subdomain_lock, flags);
|
|
+}
|
|
+
|
|
+/* seq_file helper routines
|
|
+ * Used by apparmorfs.c to iterate over profile_list
|
|
+ */
|
|
+static void *p_start(struct seq_file *f, loff_t *pos)
|
|
+{
|
|
+ struct aaprofile *node;
|
|
+ loff_t l = *pos;
|
|
+
|
|
+ read_lock(&profile_lock);
|
|
+ list_for_each_entry(node, &profile_list, list)
|
|
+ if (!l--)
|
|
+ return node;
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void *p_next(struct seq_file *f, void *p, loff_t *pos)
|
|
+{
|
|
+ struct list_head *lh = ((struct aaprofile *)p)->list.next;
|
|
+ (*pos)++;
|
|
+ return lh == &profile_list ?
|
|
+ NULL : list_entry(lh, struct aaprofile, list);
|
|
+}
|
|
+
|
|
+static void p_stop(struct seq_file *f, void *v)
|
|
+{
|
|
+ read_unlock(&profile_lock);
|
|
+}
|
|
+
|
|
+static int seq_show_profile(struct seq_file *f, void *v)
|
|
+{
|
|
+ struct aaprofile *profile = (struct aaprofile *)v;
|
|
+ seq_printf(f, "%s (%s)\n", profile->name,
|
|
+ PROFILE_COMPLAIN(profile) ? "complain" : "enforce");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+struct seq_operations apparmorfs_profiles_op = {
|
|
+ .start = p_start,
|
|
+ .next = p_next,
|
|
+ .stop = p_stop,
|
|
+ .show = seq_show_profile,
|
|
+};
|
|
Index: linux-2.6.19/security/apparmor/lsm.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/lsm.c
|
|
@@ -0,0 +1,898 @@
|
|
+/*
|
|
+ * Copyright (C) 2002-2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * http://forge.novell.com/modules/xfmod/project/?apparmor
|
|
+ *
|
|
+ * Immunix AppArmor LSM interface
|
|
+ */
|
|
+
|
|
+#include <linux/security.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/mman.h>
|
|
+
|
|
+#include "apparmor.h"
|
|
+#include "inline.h"
|
|
+
|
|
+/* struct subdomain write update lock (read side is RCU). */
|
|
+spinlock_t sd_lock = SPIN_LOCK_UNLOCKED;
|
|
+
|
|
+/* Flag values, also controllable via apparmorfs/control.
|
|
+ * We explicitly do not allow these to be modifiable when exported via
|
|
+ * /sys/modules/parameters, as we want to do additional mediation and
|
|
+ * don't want to add special path code. */
|
|
+
|
|
+/* Complain mode -- in complain mode access failures result in auditing only
|
|
+ * and task is allowed access. audit events are processed by userspace to
|
|
+ * generate policy. Default is 'enforce' (0).
|
|
+ * Value is also togglable per profile and referenced when global value is
|
|
+ * enforce.
|
|
+ */
|
|
+int apparmor_complain = 0;
|
|
+module_param_named(complain, apparmor_complain, int, S_IRUSR);
|
|
+MODULE_PARM_DESC(apparmor_complain, "Toggle AppArmor complain mode");
|
|
+
|
|
+/* Debug mode */
|
|
+int apparmor_debug = 0;
|
|
+module_param_named(debug, apparmor_debug, int, S_IRUSR);
|
|
+MODULE_PARM_DESC(apparmor_debug, "Toggle AppArmor debug mode");
|
|
+
|
|
+/* Audit mode */
|
|
+int apparmor_audit = 0;
|
|
+module_param_named(audit, apparmor_audit, int, S_IRUSR);
|
|
+MODULE_PARM_DESC(apparmor_audit, "Toggle AppArmor audit mode");
|
|
+
|
|
+/* Syscall logging mode */
|
|
+int apparmor_logsyscall = 0;
|
|
+module_param_named(logsyscall, apparmor_logsyscall, int, S_IRUSR);
|
|
+MODULE_PARM_DESC(apparmor_logsyscall, "Toggle AppArmor logsyscall mode");
|
|
+
|
|
+#ifndef MODULE
|
|
+static int __init aa_getopt_complain(char *str)
|
|
+{
|
|
+ get_option(&str, &apparmor_complain);
|
|
+ return 1;
|
|
+}
|
|
+__setup("apparmor_complain=", aa_getopt_complain);
|
|
+
|
|
+static int __init aa_getopt_debug(char *str)
|
|
+{
|
|
+ get_option(&str, &apparmor_debug);
|
|
+ return 1;
|
|
+}
|
|
+__setup("apparmor_debug=", aa_getopt_debug);
|
|
+
|
|
+static int __init aa_getopt_audit(char *str)
|
|
+{
|
|
+ get_option(&str, &apparmor_audit);
|
|
+ return 1;
|
|
+}
|
|
+__setup("apparmor_audit=", aa_getopt_audit);
|
|
+
|
|
+static int __init aa_getopt_logsyscall(char *str)
|
|
+{
|
|
+ get_option(&str, &apparmor_logsyscall);
|
|
+ return 1;
|
|
+}
|
|
+__setup("apparmor_logsyscall=", aa_getopt_logsyscall);
|
|
+#endif
|
|
+
|
|
+static int apparmor_ptrace(struct task_struct *parent,
|
|
+ struct task_struct *child)
|
|
+{
|
|
+ int error;
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ error = cap_ptrace(parent, child);
|
|
+
|
|
+ active = get_task_active_aaprofile(parent);
|
|
+
|
|
+ if (!error && active) {
|
|
+ error = aa_audit_syscallreject(active, GFP_KERNEL, "ptrace");
|
|
+ WARN_ON(error != -EPERM);
|
|
+ }
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_capget(struct task_struct *target,
|
|
+ kernel_cap_t *effective,
|
|
+ kernel_cap_t *inheritable,
|
|
+ kernel_cap_t *permitted)
|
|
+{
|
|
+ return cap_capget(target, effective, inheritable, permitted);
|
|
+}
|
|
+
|
|
+static int apparmor_capset_check(struct task_struct *target,
|
|
+ kernel_cap_t *effective,
|
|
+ kernel_cap_t *inheritable,
|
|
+ kernel_cap_t *permitted)
|
|
+{
|
|
+ return cap_capset_check(target, effective, inheritable, permitted);
|
|
+}
|
|
+
|
|
+static void apparmor_capset_set(struct task_struct *target,
|
|
+ kernel_cap_t *effective,
|
|
+ kernel_cap_t *inheritable,
|
|
+ kernel_cap_t *permitted)
|
|
+{
|
|
+ cap_capset_set(target, effective, inheritable, permitted);
|
|
+ return;
|
|
+}
|
|
+
|
|
+static int apparmor_capable(struct task_struct *tsk, int cap)
|
|
+{
|
|
+ int error;
|
|
+
|
|
+ /* cap_capable returns 0 on success, else -EPERM */
|
|
+ error = cap_capable(tsk, cap);
|
|
+
|
|
+ if (error == 0) {
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_task_active_aaprofile(tsk);
|
|
+
|
|
+ if (active)
|
|
+ error = aa_capability(active, cap);
|
|
+
|
|
+ put_aaprofile(active);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_sysctl(struct ctl_table *table, int op)
|
|
+{
|
|
+ int error = 0;
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ if ((op & 002) && active && !capable(CAP_SYS_ADMIN)) {
|
|
+ error = aa_audit_syscallreject(active, GFP_KERNEL,
|
|
+ "sysctl (write)");
|
|
+ WARN_ON(error != -EPERM);
|
|
+ }
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_syslog(int type)
|
|
+{
|
|
+ return cap_syslog(type);
|
|
+}
|
|
+
|
|
+static int apparmor_netlink_send(struct sock *sk, struct sk_buff *skb)
|
|
+{
|
|
+ return cap_netlink_send(sk, skb);
|
|
+}
|
|
+
|
|
+static int apparmor_netlink_recv(struct sk_buff *skb, int cap)
|
|
+{
|
|
+ return cap_netlink_recv(skb, cap);
|
|
+}
|
|
+
|
|
+static void apparmor_bprm_apply_creds(struct linux_binprm *bprm, int unsafe)
|
|
+{
|
|
+ cap_bprm_apply_creds(bprm, unsafe);
|
|
+ return;
|
|
+}
|
|
+
|
|
+static int apparmor_bprm_set_security(struct linux_binprm *bprm)
|
|
+{
|
|
+ /* handle capability bits with setuid, etc */
|
|
+ cap_bprm_set_security(bprm);
|
|
+ /* already set based on script name */
|
|
+ if (bprm->sh_bang)
|
|
+ return 0;
|
|
+ return aa_register(bprm);
|
|
+}
|
|
+
|
|
+static int apparmor_bprm_secureexec(struct linux_binprm *bprm)
|
|
+{
|
|
+ int ret = cap_bprm_secureexec(bprm);
|
|
+
|
|
+ if (ret == 0 &&
|
|
+ (unsigned long)bprm->security & AA_SECURE_EXEC_NEEDED) {
|
|
+ AA_DEBUG("%s: secureexec required for %s\n",
|
|
+ __FUNCTION__, bprm->filename);
|
|
+ ret = 1;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int apparmor_sb_mount(char *dev_name, struct nameidata *nd, char *type,
|
|
+ unsigned long flags, void *data)
|
|
+{
|
|
+ int error = 0;
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ if (active) {
|
|
+ error = aa_audit_syscallreject(active, GFP_KERNEL, "mount");
|
|
+ WARN_ON(error != -EPERM);
|
|
+ }
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_umount(struct vfsmount *mnt, int flags)
|
|
+{
|
|
+ int error = 0;
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ if (active) {
|
|
+ error = aa_audit_syscallreject(active, GFP_ATOMIC, "umount");
|
|
+ WARN_ON(error != -EPERM);
|
|
+ }
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_mkdir(struct inode *inode, struct dentry *dentry,
|
|
+ int mask)
|
|
+{
|
|
+ struct aaprofile *active;
|
|
+ int error = 0;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ if (active)
|
|
+ error = aa_perm_dir(active, dentry, aa_dir_mkdir);
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_rmdir(struct inode *inode, struct dentry *dentry)
|
|
+{
|
|
+ struct aaprofile *active;
|
|
+ int error = 0;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ if (active)
|
|
+ error = aa_perm_dir(active, dentry, aa_dir_rmdir);
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_create(struct inode *inode, struct dentry *dentry,
|
|
+ int mask)
|
|
+{
|
|
+ struct aaprofile *active;
|
|
+ int error = 0;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ /* At a minimum, need write perm to create */
|
|
+ if (active)
|
|
+ error = aa_perm_dentry(active, dentry, MAY_WRITE);
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_link(struct dentry *old_dentry, struct inode *inode,
|
|
+ struct dentry *new_dentry)
|
|
+{
|
|
+ int error = 0;
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ if (active)
|
|
+ error = aa_link(active, new_dentry, old_dentry);
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_unlink(struct inode *inode, struct dentry *dentry)
|
|
+{
|
|
+ struct aaprofile *active;
|
|
+ int error = 0;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ if (active)
|
|
+ error = aa_perm_dentry(active, dentry, MAY_WRITE);
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_mknod(struct inode *inode, struct dentry *dentry,
|
|
+ int mode, dev_t dev)
|
|
+{
|
|
+ struct aaprofile *active;
|
|
+ int error = 0;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ if (active)
|
|
+ error = aa_perm_dentry(active, dentry, MAY_WRITE);
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_rename(struct inode *old_inode,
|
|
+ struct dentry *old_dentry,
|
|
+ struct inode *new_inode,
|
|
+ struct dentry *new_dentry)
|
|
+{
|
|
+ struct aaprofile *active;
|
|
+ int error = 0;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ if (active) {
|
|
+ error = aa_perm_dentry(active, old_dentry, MAY_READ |
|
|
+ MAY_WRITE);
|
|
+
|
|
+ if (!error)
|
|
+ error = aa_perm_dentry(active, new_dentry,
|
|
+ MAY_WRITE);
|
|
+ }
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_permission(struct inode *inode, int mask,
|
|
+ struct nameidata *nd)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ /* Do not perform check on pipes or sockets
|
|
+ * Same as apparmor_file_permission
|
|
+ */
|
|
+ if (VALID_FSTYPE(inode)) {
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+ if (active)
|
|
+ error = aa_perm_nameidata(active, nd, mask);
|
|
+ put_aaprofile(active);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_setattr(struct dentry *dentry, struct iattr *iattr)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ if (VALID_FSTYPE(dentry->d_inode)) {
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+ /*
|
|
+ * Mediate any attempt to change attributes of a file
|
|
+ * (chmod, chown, chgrp, etc)
|
|
+ */
|
|
+ if (active)
|
|
+ error = aa_attr(active, dentry, iattr);
|
|
+
|
|
+ put_aaprofile(active);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_setxattr(struct dentry *dentry, char *name,
|
|
+ void *value, size_t size, int flags)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ if (VALID_FSTYPE(dentry->d_inode)) {
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+ if (active)
|
|
+ error = aa_xattr(active, dentry, name, aa_xattr_set);
|
|
+ put_aaprofile(active);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_getxattr(struct dentry *dentry, char *name)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ if (VALID_FSTYPE(dentry->d_inode)) {
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+ if (active)
|
|
+ error = aa_xattr(active, dentry, name, aa_xattr_get);
|
|
+ put_aaprofile(active);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+static int apparmor_inode_listxattr(struct dentry *dentry)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ if (VALID_FSTYPE(dentry->d_inode)) {
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+ if (active)
|
|
+ error = aa_xattr(active, dentry, NULL, aa_xattr_list);
|
|
+ put_aaprofile(active);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_inode_removexattr(struct dentry *dentry, char *name)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ if (VALID_FSTYPE(dentry->d_inode)) {
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+ if (active)
|
|
+ error = aa_xattr(active, dentry, name,
|
|
+ aa_xattr_remove);
|
|
+ put_aaprofile(active);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_file_permission(struct file *file, int mask)
|
|
+{
|
|
+ struct aaprofile *active;
|
|
+ struct aafile *aaf;
|
|
+ int error = 0;
|
|
+
|
|
+ aaf = (struct aafile *)file->f_security;
|
|
+ /* bail out early if this isn't a mediated file */
|
|
+ if (!aaf || !VALID_FSTYPE(file->f_dentry->d_inode))
|
|
+ goto out;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+ if (active && aaf->profile != active)
|
|
+ error = aa_perm(active, file->f_dentry, file->f_vfsmnt,
|
|
+ mask & (MAY_EXEC | MAY_WRITE | MAY_READ));
|
|
+ put_aaprofile(active);
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_file_alloc_security(struct file *file)
|
|
+{
|
|
+ struct aaprofile *active;
|
|
+ int error = 0;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+ if (active) {
|
|
+ struct aafile *aaf;
|
|
+ aaf = kmalloc(sizeof(struct aafile), GFP_KERNEL);
|
|
+
|
|
+ if (aaf) {
|
|
+ aaf->type = aa_file_default;
|
|
+ aaf->profile = get_aaprofile(active);
|
|
+ } else {
|
|
+ error = -ENOMEM;
|
|
+ }
|
|
+ file->f_security = aaf;
|
|
+ }
|
|
+ put_aaprofile(active);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static void apparmor_file_free_security(struct file *file)
|
|
+{
|
|
+ struct aafile *aaf = (struct aafile *)file->f_security;
|
|
+
|
|
+ if (aaf) {
|
|
+ put_aaprofile(aaf->profile);
|
|
+ kfree(aaf);
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline int aa_mmap(struct file *file, unsigned long prot,
|
|
+ unsigned long flags)
|
|
+{
|
|
+ int error = 0, mask = 0;
|
|
+ struct aaprofile *active;
|
|
+ struct aafile *aaf;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+ if (!active || !file ||
|
|
+ !(aaf = (struct aafile *)file->f_security) ||
|
|
+ aaf->type == aa_file_shmem)
|
|
+ goto out;
|
|
+
|
|
+ if (prot & PROT_READ)
|
|
+ mask |= MAY_READ;
|
|
+
|
|
+ /* Private mappings don't require write perms since they don't
|
|
+ * write back to the files */
|
|
+ if (prot & PROT_WRITE && !(flags & MAP_PRIVATE))
|
|
+ mask |= MAY_WRITE;
|
|
+ if (prot & PROT_EXEC)
|
|
+ mask |= AA_EXEC_MMAP;
|
|
+
|
|
+ AA_DEBUG("%s: 0x%x\n", __FUNCTION__, mask);
|
|
+
|
|
+ if (mask)
|
|
+ error = aa_perm(active, file->f_dentry, file->f_vfsmnt, mask);
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_file_mmap(struct file *file, unsigned long reqprot,
|
|
+ unsigned long prot, unsigned long flags)
|
|
+{
|
|
+ return aa_mmap(file, prot, flags);
|
|
+}
|
|
+
|
|
+static int apparmor_file_mprotect(struct vm_area_struct *vma,
|
|
+ unsigned long reqprot, unsigned long prot)
|
|
+{
|
|
+ return aa_mmap(vma->vm_file, prot,
|
|
+ !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
|
|
+}
|
|
+
|
|
+static int apparmor_task_alloc_security(struct task_struct *p)
|
|
+{
|
|
+ return aa_fork(p);
|
|
+}
|
|
+
|
|
+static void apparmor_task_free_security(struct task_struct *p)
|
|
+{
|
|
+ aa_release(p);
|
|
+}
|
|
+
|
|
+static int apparmor_task_post_setuid(uid_t id0, uid_t id1, uid_t id2,
|
|
+ int flags)
|
|
+{
|
|
+ return cap_task_post_setuid(id0, id1, id2, flags);
|
|
+}
|
|
+
|
|
+static void apparmor_task_reparent_to_init(struct task_struct *p)
|
|
+{
|
|
+ cap_task_reparent_to_init(p);
|
|
+ return;
|
|
+}
|
|
+
|
|
+static int apparmor_shm_shmat(struct shmid_kernel *shp, char __user *shmaddr,
|
|
+ int shmflg)
|
|
+{
|
|
+ struct aafile *aaf = (struct aafile *)shp->shm_file->f_security;
|
|
+
|
|
+ if (aaf)
|
|
+ aaf->type = aa_file_shmem;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int apparmor_getprocattr(struct task_struct *p, char *name, void *value,
|
|
+ size_t size)
|
|
+{
|
|
+ int error;
|
|
+ struct aaprofile *active;
|
|
+ char *str = value;
|
|
+
|
|
+ /* AppArmor only supports the "current" process attribute */
|
|
+ if (strcmp(name, "current") != 0) {
|
|
+ error = -EINVAL;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* must be task querying itself or admin */
|
|
+ if (current != p && !capable(CAP_SYS_ADMIN)) {
|
|
+ error = -EPERM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ active = get_task_active_aaprofile(p);
|
|
+ error = aa_getprocattr(active, str, size);
|
|
+ put_aaprofile(active);
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int apparmor_setprocattr(struct task_struct *p, char *name, void *value,
|
|
+ size_t size)
|
|
+{
|
|
+ const char *cmd_changehat = "changehat ",
|
|
+ *cmd_setprofile = "setprofile ";
|
|
+
|
|
+ int error = -EACCES; /* default to a perm denied */
|
|
+ char *cmd = (char *)value;
|
|
+
|
|
+ /* only support messages to current */
|
|
+ if (strcmp(name, "current") != 0) {
|
|
+ error = -EINVAL;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (!size) {
|
|
+ error = -ERANGE;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* CHANGE HAT -- switch task into a subhat (subprofile) if defined */
|
|
+ if (size > strlen(cmd_changehat) &&
|
|
+ strncmp(cmd, cmd_changehat, strlen(cmd_changehat)) == 0) {
|
|
+ char *hatinfo = cmd + strlen(cmd_changehat);
|
|
+ size_t infosize = size - strlen(cmd_changehat);
|
|
+
|
|
+ /* Only the current process may change it's hat */
|
|
+ if (current != p) {
|
|
+ AA_WARN("%s: Attempt by foreign task %s(%d) "
|
|
+ "[user %d] to changehat of task %s(%d)\n",
|
|
+ __FUNCTION__,
|
|
+ current->comm,
|
|
+ current->pid,
|
|
+ current->uid,
|
|
+ p->comm,
|
|
+ p->pid);
|
|
+
|
|
+ error = -EACCES;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ error = aa_setprocattr_changehat(hatinfo, infosize);
|
|
+ if (error == 0)
|
|
+ /* success, set return to #bytes in orig request */
|
|
+ error = size;
|
|
+
|
|
+ /* SET NEW PROFILE */
|
|
+ } else if (size > strlen(cmd_setprofile) &&
|
|
+ strncmp(cmd, cmd_setprofile, strlen(cmd_setprofile)) == 0) {
|
|
+ struct aaprofile *active;
|
|
+
|
|
+ /* only an unconfined process with admin capabilities
|
|
+ * may change the profile of another task
|
|
+ */
|
|
+
|
|
+ if (!capable(CAP_SYS_ADMIN)) {
|
|
+ AA_WARN("%s: Unprivileged attempt by task %s(%d) "
|
|
+ "[user %d] to assign profile to task %s(%d)\n",
|
|
+ __FUNCTION__,
|
|
+ current->comm,
|
|
+ current->pid,
|
|
+ current->uid,
|
|
+ p->comm,
|
|
+ p->pid);
|
|
+ error = -EACCES;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+ if (!active) {
|
|
+ char *profile = cmd + strlen(cmd_setprofile);
|
|
+ size_t profilesize = size - strlen(cmd_setprofile);
|
|
+
|
|
+ error = aa_setprocattr_setprofile(p, profile, profilesize);
|
|
+ if (error == 0)
|
|
+ /* success,
|
|
+ * set return to #bytes in orig request
|
|
+ */
|
|
+ error = size;
|
|
+ } else {
|
|
+ AA_WARN("%s: Attempt by confined task %s(%d) "
|
|
+ "[user %d] to assign profile to task %s(%d)\n",
|
|
+ __FUNCTION__,
|
|
+ current->comm,
|
|
+ current->pid,
|
|
+ current->uid,
|
|
+ p->comm,
|
|
+ p->pid);
|
|
+
|
|
+ error = -EACCES;
|
|
+ }
|
|
+ put_aaprofile(active);
|
|
+ } else {
|
|
+ /* unknown operation */
|
|
+ AA_WARN("%s: Unknown setprocattr command '%.*s' by task %s(%d) "
|
|
+ "[user %d] for task %s(%d)\n",
|
|
+ __FUNCTION__,
|
|
+ size < 16 ? (int)size : 16,
|
|
+ cmd,
|
|
+ current->comm,
|
|
+ current->pid,
|
|
+ current->uid,
|
|
+ p->comm,
|
|
+ p->pid);
|
|
+
|
|
+ error = -EINVAL;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+struct security_operations apparmor_ops = {
|
|
+ .ptrace = apparmor_ptrace,
|
|
+ .capget = apparmor_capget,
|
|
+ .capset_check = apparmor_capset_check,
|
|
+ .capset_set = apparmor_capset_set,
|
|
+ .sysctl = apparmor_sysctl,
|
|
+ .capable = apparmor_capable,
|
|
+ .syslog = apparmor_syslog,
|
|
+
|
|
+ .netlink_send = apparmor_netlink_send,
|
|
+ .netlink_recv = apparmor_netlink_recv,
|
|
+
|
|
+ .bprm_apply_creds = apparmor_bprm_apply_creds,
|
|
+ .bprm_set_security = apparmor_bprm_set_security,
|
|
+ .bprm_secureexec = apparmor_bprm_secureexec,
|
|
+
|
|
+ .sb_mount = apparmor_sb_mount,
|
|
+ .sb_umount = apparmor_umount,
|
|
+
|
|
+ .inode_mkdir = apparmor_inode_mkdir,
|
|
+ .inode_rmdir = apparmor_inode_rmdir,
|
|
+ .inode_create = apparmor_inode_create,
|
|
+ .inode_link = apparmor_inode_link,
|
|
+ .inode_unlink = apparmor_inode_unlink,
|
|
+ .inode_mknod = apparmor_inode_mknod,
|
|
+ .inode_rename = apparmor_inode_rename,
|
|
+ .inode_permission = apparmor_inode_permission,
|
|
+ .inode_setattr = apparmor_inode_setattr,
|
|
+ .inode_setxattr = apparmor_inode_setxattr,
|
|
+ .inode_getxattr = apparmor_inode_getxattr,
|
|
+ .inode_listxattr = apparmor_inode_listxattr,
|
|
+ .inode_removexattr = apparmor_inode_removexattr,
|
|
+ .file_permission = apparmor_file_permission,
|
|
+ .file_alloc_security = apparmor_file_alloc_security,
|
|
+ .file_free_security = apparmor_file_free_security,
|
|
+ .file_mmap = apparmor_file_mmap,
|
|
+ .file_mprotect = apparmor_file_mprotect,
|
|
+
|
|
+ .task_alloc_security = apparmor_task_alloc_security,
|
|
+ .task_free_security = apparmor_task_free_security,
|
|
+ .task_post_setuid = apparmor_task_post_setuid,
|
|
+ .task_reparent_to_init = apparmor_task_reparent_to_init,
|
|
+
|
|
+ .shm_shmat = apparmor_shm_shmat,
|
|
+
|
|
+ .getprocattr = apparmor_getprocattr,
|
|
+ .setprocattr = apparmor_setprocattr,
|
|
+};
|
|
+
|
|
+static int __init apparmor_init(void)
|
|
+{
|
|
+ int error;
|
|
+ const char *complainmsg = ": complainmode enabled";
|
|
+
|
|
+ if ((error = create_apparmorfs())) {
|
|
+ AA_ERROR("Unable to activate AppArmor filesystem\n");
|
|
+ goto createfs_out;
|
|
+ }
|
|
+
|
|
+ if ((error = alloc_null_complain_profile())){
|
|
+ AA_ERROR("Unable to allocate null complain profile\n");
|
|
+ goto alloc_out;
|
|
+ }
|
|
+
|
|
+ if ((error = register_security(&apparmor_ops))) {
|
|
+ AA_ERROR("Unable to load AppArmor\n");
|
|
+ goto register_security_out;
|
|
+ }
|
|
+
|
|
+ AA_INFO("AppArmor initialized%s\n",
|
|
+ apparmor_complain ? complainmsg : "");
|
|
+ aa_audit_message(NULL, GFP_KERNEL, 0,
|
|
+ "AppArmor initialized%s\n",
|
|
+ apparmor_complain ? complainmsg : "");
|
|
+
|
|
+ return error;
|
|
+
|
|
+register_security_out:
|
|
+ free_null_complain_profile();
|
|
+
|
|
+alloc_out:
|
|
+ (void)destroy_apparmorfs();
|
|
+
|
|
+createfs_out:
|
|
+ return error;
|
|
+
|
|
+}
|
|
+
|
|
+static int apparmor_exit_removeall_iter(struct subdomain *sd, void *cookie)
|
|
+{
|
|
+ /* spin_lock(&sd_lock) held here */
|
|
+
|
|
+ if (__aa_is_confined(sd)) {
|
|
+ AA_DEBUG("%s: Dropping profiles %s(%d) "
|
|
+ "profile %s(%p) active %s(%p)\n",
|
|
+ __FUNCTION__,
|
|
+ sd->task->comm, sd->task->pid,
|
|
+ BASE_PROFILE(sd->active)->name,
|
|
+ BASE_PROFILE(sd->active),
|
|
+ sd->active->name, sd->active);
|
|
+ aa_switch_unconfined(sd);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit apparmor_exit(void)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ /* Remove profiles from the global profile list.
|
|
+ * This is just for tidyness as there is no way to reference this
|
|
+ * list once the AppArmor lsm hooks are detached (below)
|
|
+ */
|
|
+ aa_profilelist_release();
|
|
+
|
|
+ /* Remove profiles from active tasks
|
|
+ * If this is not done, if module is reloaded after being removed,
|
|
+ * old profiles (still refcounted in memory) will become 'magically'
|
|
+ * reattached
|
|
+ */
|
|
+
|
|
+ spin_lock_irqsave(&sd_lock, flags);
|
|
+ aa_subdomainlist_iterate(apparmor_exit_removeall_iter, NULL);
|
|
+ spin_unlock_irqrestore(&sd_lock, flags);
|
|
+
|
|
+ /* Free up list of active subdomain */
|
|
+ aa_subdomainlist_release();
|
|
+
|
|
+ free_null_complain_profile();
|
|
+
|
|
+ destroy_apparmorfs();
|
|
+
|
|
+ if (unregister_security(&apparmor_ops))
|
|
+ AA_WARN("Unable to properly unregister AppArmor\n");
|
|
+
|
|
+ /* delay for an rcu cycle to make ensure that profiles pending
|
|
+ * destruction in the rcu callback are freed.
|
|
+ */
|
|
+ synchronize_rcu();
|
|
+
|
|
+ AA_INFO("AppArmor protection removed\n");
|
|
+ aa_audit_message(NULL, GFP_KERNEL, 0,
|
|
+ "AppArmor protection removed\n");
|
|
+}
|
|
+
|
|
+module_init(apparmor_init);
|
|
+module_exit(apparmor_exit);
|
|
+
|
|
+MODULE_DESCRIPTION("AppArmor process confinement");
|
|
+MODULE_AUTHOR("Tony Jones <tonyj@suse.de>");
|
|
+MODULE_LICENSE("GPL");
|
|
Index: linux-2.6.19/security/apparmor/main.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/main.c
|
|
@@ -0,0 +1,1687 @@
|
|
+/*
|
|
+ * Copyright (C) 2002-2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * AppArmor Core
|
|
+ */
|
|
+
|
|
+#include <linux/security.h>
|
|
+#include <linux/namei.h>
|
|
+#include <linux/audit.h>
|
|
+
|
|
+#include "apparmor.h"
|
|
+#include "match/match.h"
|
|
+
|
|
+#include "inline.h"
|
|
+
|
|
+/* NULL complain profile
|
|
+ *
|
|
+ * Used when in complain mode, to emit Permitting messages for non-existant
|
|
+ * profiles and hats. This is necessary because of selective mode, in which
|
|
+ * case we need a complain null_profile and enforce null_profile
|
|
+ *
|
|
+ * The null_complain_profile cannot be statically allocated, because it
|
|
+ * can be associated to files which keep their reference even if apparmor is
|
|
+ * unloaded
|
|
+ */
|
|
+struct aaprofile *null_complain_profile;
|
|
+
|
|
+/***************************
|
|
+ * Private utility functions
|
|
+ **************************/
|
|
+
|
|
+/**
|
|
+ * dentry_xlate_error
|
|
+ * @dentry: pointer to dentry
|
|
+ * @error: error number
|
|
+ * @dtype: type of dentry
|
|
+ *
|
|
+ * Display error message when a dentry translation error occured
|
|
+ */
|
|
+static void dentry_xlate_error(struct dentry *dentry, int error, char *dtype)
|
|
+{
|
|
+ const unsigned int len = 16;
|
|
+ char buf[len];
|
|
+
|
|
+ if (dentry->d_inode) {
|
|
+ snprintf(buf, len, "%lu", dentry->d_inode->i_ino);
|
|
+ } else {
|
|
+ strncpy(buf, "<negative>", len);
|
|
+ buf[len-1]=0;
|
|
+ }
|
|
+
|
|
+ AA_ERROR("An error occured while translating %s %p "
|
|
+ "inode# %s to a pathname. Error %d\n",
|
|
+ dtype,
|
|
+ dentry,
|
|
+ buf,
|
|
+ error);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_taskattr_access
|
|
+ * @procrelname: name of file to check permission
|
|
+ *
|
|
+ * Determine if request is for write access to /proc/self/attr/current
|
|
+ * This file is the usermode iterface for changing it's hat.
|
|
+ */
|
|
+static inline int aa_taskattr_access(const char *procrelname)
|
|
+{
|
|
+ char buf[sizeof("/attr/current") + 10];
|
|
+ const int maxbuflen = sizeof(buf);
|
|
+ /* assumption, 32bit pid (10 decimal digits incl \0) */
|
|
+
|
|
+ snprintf(buf, maxbuflen, "%d/attr/current", current->pid);
|
|
+ buf[maxbuflen - 1] = 0;
|
|
+
|
|
+ return strcmp(buf, procrelname) == 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_file_mode - get full mode for file entry from profile
|
|
+ * @profile: profile
|
|
+ * @name: filename
|
|
+ */
|
|
+static inline int aa_file_mode(struct aaprofile *profile, const char *name)
|
|
+{
|
|
+ struct aa_entry *entry;
|
|
+ int mode = 0;
|
|
+
|
|
+ AA_DEBUG("%s: %s\n", __FUNCTION__, name);
|
|
+ if (!name) {
|
|
+ AA_DEBUG("%s: no name\n", __FUNCTION__);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (!profile) {
|
|
+ AA_DEBUG("%s: no profile\n", __FUNCTION__);
|
|
+ goto out;
|
|
+ }
|
|
+ list_for_each_entry(entry, &profile->file_entry, list) {
|
|
+ if (aamatch_match(name, entry->filename,
|
|
+ entry->type, entry->extradata))
|
|
+ mode |= entry->mode;
|
|
+ }
|
|
+out:
|
|
+ return mode;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_get_execmode - calculate what qualifier to apply to an exec
|
|
+ * @active: profile to search
|
|
+ * @name: name of file to exec
|
|
+ * @xmod: pointer to a execution mode bit for the rule that was matched
|
|
+ * if the rule has no execuition qualifier {pui} then
|
|
+ * %AA_MAY_EXEC is returned indicating a naked x
|
|
+ * if the has an exec qualifier then only the qualifier bit {pui}
|
|
+ * is returned (%AA_MAY_EXEC) is not set.
|
|
+ * @unsafe: true if secure_exec should be overriden
|
|
+ * Returns %0 (false):
|
|
+ * if unable to find profile or there are conflicting pattern matches.
|
|
+ * *xmod - is not modified
|
|
+ * *unsafe - is not modified
|
|
+ *
|
|
+ * Returns %1 (true):
|
|
+ * if exec rule matched
|
|
+ * if the rule has an execution mode qualifier {pui} then
|
|
+ * *xmod = the execution qualifier of the rule {pui}
|
|
+ * else
|
|
+ * *xmod = %AA_MAY_EXEC
|
|
+ * unsafe = presence of unsage flag
|
|
+ */
|
|
+static inline int aa_get_execmode(struct aaprofile *active, const char *name,
|
|
+ int *xmod, int *unsafe)
|
|
+{
|
|
+ struct aa_entry *entry;
|
|
+ struct aa_entry *match = NULL;
|
|
+
|
|
+ int pattern_match_invalid = 0, rc = 0;
|
|
+
|
|
+ /* search list of profiles with 'x' permission
|
|
+ * this will also include entries with 'p', 'u' and 'i'
|
|
+ * qualifiers.
|
|
+ *
|
|
+ * If we find a pattern match we will keep looking for an exact match
|
|
+ * If we find conflicting pattern matches we will flag (while still
|
|
+ * looking for an exact match). If all we have is a conflict, FALSE
|
|
+ * is returned.
|
|
+ */
|
|
+
|
|
+ list_for_each_entry(entry, &active->file_entryp[POS_AA_MAY_EXEC],
|
|
+ listp[POS_AA_MAY_EXEC]) {
|
|
+ if (!pattern_match_invalid &&
|
|
+ entry->type == aa_entry_pattern &&
|
|
+ aamatch_match(name, entry->filename,
|
|
+ entry->type, entry->extradata)) {
|
|
+ if (match &&
|
|
+ AA_EXEC_UNSAFE_MASK(entry->mode) !=
|
|
+ AA_EXEC_UNSAFE_MASK(match->mode))
|
|
+ pattern_match_invalid = 1;
|
|
+ else
|
|
+ /* keep searching for an exact match */
|
|
+ match = entry;
|
|
+ } else if ((entry->type == aa_entry_literal ||
|
|
+ (!pattern_match_invalid &&
|
|
+ entry->type == aa_entry_tailglob)) &&
|
|
+ aamatch_match(name, entry->filename,
|
|
+ entry->type,
|
|
+ entry->extradata)) {
|
|
+ if (entry->type == aa_entry_literal) {
|
|
+ /* got an exact match -- there can be only
|
|
+ * one, asserted at profile load time
|
|
+ */
|
|
+ match = entry;
|
|
+ pattern_match_invalid = 0;
|
|
+ break;
|
|
+ } else {
|
|
+ if (match &&
|
|
+ AA_EXEC_UNSAFE_MASK(entry->mode) !=
|
|
+ AA_EXEC_UNSAFE_MASK(match->mode))
|
|
+ pattern_match_invalid = 1;
|
|
+ else
|
|
+ /* got a tailglob match, keep searching
|
|
+ * for an exact match
|
|
+ */
|
|
+ match = entry;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+ rc = match && !pattern_match_invalid;
|
|
+
|
|
+ if (rc) {
|
|
+ int mode = AA_EXEC_MASK(match->mode);
|
|
+
|
|
+ /* check for qualifiers, if present
|
|
+ * we just return the qualifier
|
|
+ */
|
|
+ if (mode & ~AA_MAY_EXEC)
|
|
+ mode = mode & ~AA_MAY_EXEC;
|
|
+
|
|
+ *xmod = mode;
|
|
+ *unsafe = (match->mode & AA_EXEC_UNSAFE);
|
|
+ } else if (!match) {
|
|
+ AA_DEBUG("%s: Unable to find execute entry in profile "
|
|
+ "for image '%s'\n",
|
|
+ __FUNCTION__,
|
|
+ name);
|
|
+ } else if (pattern_match_invalid) {
|
|
+ AA_WARN("%s: Inconsistency in profile %s. "
|
|
+ "Two (or more) patterns specify conflicting exec "
|
|
+ "qualifiers ('u', 'i' or 'p') for image %s\n",
|
|
+ __FUNCTION__,
|
|
+ active->name,
|
|
+ name);
|
|
+ }
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_filter_mask
|
|
+ * @mask: requested mask
|
|
+ * @inode: potential directory inode
|
|
+ *
|
|
+ * This fn performs pre-verification of the requested mask
|
|
+ * We ignore append. Previously we required 'w' on a dir to add a file.
|
|
+ * No longer. Now we require 'w' on just the file itself. Traversal 'x' is
|
|
+ * also ignored for directories.
|
|
+ *
|
|
+ * Returned value of %0 indicates no need to perform a perm check.
|
|
+ */
|
|
+static inline int aa_filter_mask(int mask, struct inode *inode)
|
|
+{
|
|
+ if (mask) {
|
|
+ int elim = MAY_APPEND;
|
|
+
|
|
+ if (inode && S_ISDIR(inode->i_mode))
|
|
+ elim |= (MAY_EXEC | MAY_WRITE);
|
|
+
|
|
+ mask &= ~elim;
|
|
+ }
|
|
+
|
|
+ return mask;
|
|
+}
|
|
+
|
|
+static inline void aa_permerror2result(int perm_result, struct aa_audit *sa)
|
|
+{
|
|
+ if (perm_result == 0) { /* success */
|
|
+ sa->result = 1;
|
|
+ sa->error_code = 0;
|
|
+ } else { /* -ve internal error code or +ve mask of denied perms */
|
|
+ sa->result = 0;
|
|
+ sa->error_code = perm_result;
|
|
+ }
|
|
+}
|
|
+
|
|
+/*************************
|
|
+ * Main internal functions
|
|
+ ************************/
|
|
+
|
|
+/**
|
|
+ * aa_file_perm - calculate access mode for file
|
|
+ * @active: profile to check against
|
|
+ * @name: name of file to calculate mode for
|
|
+ * @mask: permission mask requested for file
|
|
+ *
|
|
+ * Search the aa_entry list in @active.
|
|
+ * Search looking to verify all permissions passed in mask.
|
|
+ * Perform the search by looking at the partitioned list of entries, one
|
|
+ * partition per permission bit.
|
|
+ *
|
|
+ * Return %0 on success, else mask of non-allowed permissions
|
|
+ */
|
|
+static unsigned int aa_file_perm(struct aaprofile *active, const char *name,
|
|
+ int mask)
|
|
+{
|
|
+ int i, error = 0, mode;
|
|
+
|
|
+#define PROCPFX "/proc/"
|
|
+#define PROCLEN sizeof(PROCPFX) - 1
|
|
+
|
|
+ AA_DEBUG("%s: %s 0x%x\n", __FUNCTION__, name, mask);
|
|
+
|
|
+ /* should not enter with other than R/W/M/X/L */
|
|
+ WARN_ON(mask &
|
|
+ ~(AA_MAY_READ | AA_MAY_WRITE | AA_MAY_EXEC | AA_EXEC_MMAP |
|
|
+ AA_MAY_LINK));
|
|
+
|
|
+ /* Special case access to /proc/self/attr/current
|
|
+ * Currently we only allow access if opened O_WRONLY
|
|
+ */
|
|
+ if (mask == MAY_WRITE && strncmp(PROCPFX, name, PROCLEN) == 0 &&
|
|
+ aa_taskattr_access(name + PROCLEN))
|
|
+ goto done;
|
|
+
|
|
+ mode = 0;
|
|
+
|
|
+ /* iterate over partition, one permission bit at a time */
|
|
+ for (i = 0; i <= POS_AA_FILE_MAX; i++) {
|
|
+ struct aa_entry *entry;
|
|
+
|
|
+ /* do we have to accumulate this bit?
|
|
+ * or have we already accumulated it (shortcut below)? */
|
|
+ if (!(mask & (1 << i)) || mode & (1 << i))
|
|
+ continue;
|
|
+
|
|
+ list_for_each_entry(entry, &active->file_entryp[i],
|
|
+ listp[i]) {
|
|
+ if (aamatch_match(name, entry->filename,
|
|
+ entry->type, entry->extradata)) {
|
|
+ /* Shortcut, accumulate all bits present */
|
|
+ mode |= entry->mode;
|
|
+
|
|
+ /* Mask bits are overloaded
|
|
+ * MAY_{EXEC,WRITE,READ,APPEND} are used by
|
|
+ * kernel, other values are used locally only.
|
|
+ */
|
|
+ if ((mode & mask) == mask) {
|
|
+ AA_DEBUG("MATCH! %s=0x%x [total mode=0x%x]\n",
|
|
+ name, mask, mode);
|
|
+
|
|
+ goto done;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* return permissions not satisfied */
|
|
+ error = mask & ~mode;
|
|
+
|
|
+done:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_link_perm - test permission to link to a file
|
|
+ * @active: profile to check against
|
|
+ * @link: name of link being created
|
|
+ * @target: name of target to be linked to
|
|
+ *
|
|
+ * Look up permission mode on both @link and @target. @link must have same
|
|
+ * permission mode as @target. At least @link must have the link bit enabled.
|
|
+ * Return %0 on success, error otherwise.
|
|
+ */
|
|
+static int aa_link_perm(struct aaprofile *active,
|
|
+ const char *link, const char *target)
|
|
+{
|
|
+ int l_mode, t_mode, ret;
|
|
+
|
|
+ l_mode = aa_file_mode(active, link);
|
|
+ if (l_mode & AA_MAY_LINK) {
|
|
+ /* mask off link bit */
|
|
+ l_mode &= ~AA_MAY_LINK;
|
|
+
|
|
+ t_mode = aa_file_mode(active, target);
|
|
+ t_mode &= ~AA_MAY_LINK;
|
|
+
|
|
+ ret = (l_mode == t_mode);
|
|
+ } else {
|
|
+ ret = 0;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * _aa_perm_dentry
|
|
+ * @active: profile to check against
|
|
+ * @dentry: requested dentry
|
|
+ * @mask: mask of requested operations
|
|
+ * @pname: pointer to hold matched pathname (if any)
|
|
+ *
|
|
+ * Helper function. Obtain pathname for specified dentry. Verify if profile
|
|
+ * authorizes mask operations on pathname (due to lack of vfsmnt it is sadly
|
|
+ * necessary to search mountpoints in namespace -- when nameidata is passed
|
|
+ * more fully, this code can go away). If more than one mountpoint matches
|
|
+ * but none satisfy the profile, only the first pathname (mountpoint) is
|
|
+ * returned for subsequent logging.
|
|
+ *
|
|
+ * Return %0 (success), +ve (mask of permissions not satisfied) or -ve (system
|
|
+ * error, most likely -%ENOMEM).
|
|
+ */
|
|
+static int _aa_perm_dentry(struct aaprofile *active, struct dentry *dentry,
|
|
+ int mask, const char **pname)
|
|
+{
|
|
+ char *name = NULL, *failed_name = NULL;
|
|
+ struct aa_path_data data;
|
|
+ int error = 0, failed_error = 0, path_error,
|
|
+ complain = PROFILE_COMPLAIN(active);
|
|
+
|
|
+ /* search all paths to dentry */
|
|
+
|
|
+ aa_path_begin(dentry, &data);
|
|
+ do {
|
|
+ name = aa_path_getname(&data);
|
|
+ if (name) {
|
|
+ /* error here is 0 (success) or +ve (mask of perms) */
|
|
+ error = aa_file_perm(active, name, mask);
|
|
+
|
|
+ /* access via any path is enough */
|
|
+ if (complain || error == 0)
|
|
+ break; /* Caller must free name */
|
|
+
|
|
+ /* Already have an path that failed? */
|
|
+ if (failed_name) {
|
|
+ aa_put_name(name);
|
|
+ } else {
|
|
+ failed_name = name;
|
|
+ failed_error = error;
|
|
+ }
|
|
+ }
|
|
+ } while (name);
|
|
+
|
|
+ if ((path_error = aa_path_end(&data)) != 0) {
|
|
+ dentry_xlate_error(dentry, path_error, "dentry");
|
|
+ WARN_ON(name); /* name should not be set if error */
|
|
+ error = path_error;
|
|
+ name = NULL;
|
|
+ } else if (name) {
|
|
+ if (failed_name)
|
|
+ aa_put_name(failed_name);
|
|
+ } else {
|
|
+ name = failed_name;
|
|
+ error = failed_error;
|
|
+ }
|
|
+
|
|
+ *pname = name;
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**************************
|
|
+ * Global utility functions
|
|
+ *************************/
|
|
+
|
|
+/**
|
|
+ * attach_nullprofile - allocate and attach a null_profile hat to profile
|
|
+ * @profile: profile to attach a null_profile hat to.
|
|
+ *
|
|
+ * Return %0 (success) or error (-%ENOMEM)
|
|
+ */
|
|
+int attach_nullprofile(struct aaprofile *profile)
|
|
+{
|
|
+ struct aaprofile *hat = NULL;
|
|
+ char *hatname = NULL;
|
|
+
|
|
+ hat = alloc_aaprofile();
|
|
+ if (!hat)
|
|
+ goto fail;
|
|
+ if (profile->flags.complain)
|
|
+ hatname = kstrdup("null-complain-profile", GFP_KERNEL);
|
|
+ else
|
|
+ hatname = kstrdup("null-profile", GFP_KERNEL);
|
|
+ if (!hatname)
|
|
+ goto fail;
|
|
+
|
|
+ hat->flags.complain = profile->flags.complain;
|
|
+ hat->name = hatname;
|
|
+ hat->parent = profile;
|
|
+
|
|
+ profile->null_profile = hat;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fail:
|
|
+ kfree(hatname);
|
|
+ free_aaprofile(hat);
|
|
+
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+
|
|
+/**
|
|
+ * alloc_null_complain_profile - Allocate the global null_complain_profile.
|
|
+ *
|
|
+ * Return %0 (success) or error (-%ENOMEM)
|
|
+ */
|
|
+int alloc_null_complain_profile(void)
|
|
+{
|
|
+ null_complain_profile = alloc_aaprofile();
|
|
+ if (!null_complain_profile)
|
|
+ goto fail;
|
|
+
|
|
+ null_complain_profile->name =
|
|
+ kstrdup("null-complain-profile", GFP_KERNEL);
|
|
+
|
|
+ if (!null_complain_profile->name)
|
|
+ goto fail;
|
|
+
|
|
+ null_complain_profile->flags.complain = 1;
|
|
+ if (attach_nullprofile(null_complain_profile))
|
|
+ goto fail;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fail:
|
|
+ /* free_aaprofile is safe for freeing partially constructed objects */
|
|
+ free_aaprofile(null_complain_profile);
|
|
+ null_complain_profile = NULL;
|
|
+
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * free_null_complain_profile - Free null profiles
|
|
+ */
|
|
+void free_null_complain_profile(void)
|
|
+{
|
|
+ put_aaprofile(null_complain_profile);
|
|
+ null_complain_profile = NULL;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_audit_message - Log a message to the audit subsystem
|
|
+ * @active: profile to check against
|
|
+ * @gfp: allocation flags
|
|
+ * @flags: audit flags
|
|
+ * @fmt: varargs fmt
|
|
+ */
|
|
+int aa_audit_message(struct aaprofile *active, gfp_t gfp, int flags,
|
|
+ const char *fmt, ...)
|
|
+{
|
|
+ int ret;
|
|
+ struct aa_audit sa;
|
|
+
|
|
+ sa.type = AA_AUDITTYPE_MSG;
|
|
+ sa.name = fmt;
|
|
+ va_start(sa.vaval, fmt);
|
|
+ sa.flags = flags;
|
|
+ sa.gfp_mask = gfp;
|
|
+ sa.error_code = 0;
|
|
+ sa.result = 0; /* fake failure: force message to be logged */
|
|
+
|
|
+ ret = aa_audit(active, &sa);
|
|
+
|
|
+ va_end(sa.vaval);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_audit_syscallreject - Log a syscall rejection to the audit subsystem
|
|
+ * @active: profile to check against
|
|
+ * @msg: string describing syscall being rejected
|
|
+ * @gfp: memory allocation flags
|
|
+ */
|
|
+int aa_audit_syscallreject(struct aaprofile *active, gfp_t gfp,
|
|
+ const char *msg)
|
|
+{
|
|
+ struct aa_audit sa;
|
|
+
|
|
+ sa.type = AA_AUDITTYPE_SYSCALL;
|
|
+ sa.name = msg;
|
|
+ sa.flags = 0;
|
|
+ sa.gfp_mask = gfp;
|
|
+ sa.error_code = 0;
|
|
+ sa.result = 0; /* failure */
|
|
+
|
|
+ return aa_audit(active, &sa);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_audit - Log an audit event to the audit subsystem
|
|
+ * @active: profile to check against
|
|
+ * @sa: audit event
|
|
+ */
|
|
+int aa_audit(struct aaprofile *active, const struct aa_audit *sa)
|
|
+{
|
|
+ struct audit_buffer *ab = NULL;
|
|
+ struct audit_context *ctx;
|
|
+
|
|
+ const char *logcls;
|
|
+ unsigned int flags;
|
|
+ int audit = 0,
|
|
+ complain = 0,
|
|
+ error = -EINVAL,
|
|
+ opspec_error = -EACCES;
|
|
+
|
|
+ const gfp_t gfp_mask = sa->gfp_mask;
|
|
+
|
|
+ WARN_ON(sa->type >= AA_AUDITTYPE__END);
|
|
+
|
|
+ /*
|
|
+ * sa->result: 1 success, 0 failure
|
|
+ * sa->error_code: success: 0
|
|
+ * failure: +ve mask of failed permissions or -ve
|
|
+ * system error
|
|
+ */
|
|
+
|
|
+ if (likely(sa->result)) {
|
|
+ if (likely(!PROFILE_AUDIT(active))) {
|
|
+ /* nothing to log */
|
|
+ error = 0;
|
|
+ goto out;
|
|
+ } else {
|
|
+ audit = 1;
|
|
+ logcls = "AUDITING";
|
|
+ }
|
|
+ } else if (sa->error_code < 0) {
|
|
+ audit_log(current->audit_context, gfp_mask, AUDIT_SD,
|
|
+ "Internal error auditing event type %d (error %d)",
|
|
+ sa->type, sa->error_code);
|
|
+ AA_ERROR("Internal error auditing event type %d (error %d)\n",
|
|
+ sa->type, sa->error_code);
|
|
+ error = sa->error_code;
|
|
+ goto out;
|
|
+ } else if (sa->type == AA_AUDITTYPE_SYSCALL) {
|
|
+ /* Currently AA_AUDITTYPE_SYSCALL is for rejects only.
|
|
+ * Values set by aa_audit_syscallreject will get us here.
|
|
+ */
|
|
+ logcls = "REJECTING";
|
|
+ } else {
|
|
+ complain = PROFILE_COMPLAIN(active);
|
|
+ logcls = complain ? "PERMITTING" : "REJECTING";
|
|
+ }
|
|
+
|
|
+ /* In future extend w/ per-profile flags
|
|
+ * (flags |= sa->active->flags)
|
|
+ */
|
|
+ flags = sa->flags;
|
|
+ if (apparmor_logsyscall)
|
|
+ flags |= AA_AUDITFLAG_AUDITSS_SYSCALL;
|
|
+
|
|
+
|
|
+ /* Force full audit syscall logging regardless of global setting if
|
|
+ * we are rejecting a syscall
|
|
+ */
|
|
+ if (sa->type == AA_AUDITTYPE_SYSCALL) {
|
|
+ ctx = current->audit_context;
|
|
+ } else {
|
|
+ ctx = (flags & AA_AUDITFLAG_AUDITSS_SYSCALL) ?
|
|
+ current->audit_context : NULL;
|
|
+ }
|
|
+
|
|
+ ab = audit_log_start(ctx, gfp_mask, AUDIT_SD);
|
|
+
|
|
+ if (!ab) {
|
|
+ AA_ERROR("Unable to log event (%d) to audit subsys\n",
|
|
+ sa->type);
|
|
+ if (complain)
|
|
+ error = 0;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* messages get special handling */
|
|
+ if (sa->type == AA_AUDITTYPE_MSG) {
|
|
+ audit_log_vformat(ab, sa->name, sa->vaval);
|
|
+ audit_log_end(ab);
|
|
+ error = 0;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* log operation */
|
|
+
|
|
+ audit_log_format(ab, "%s ", logcls); /* REJECTING/ALLOWING/etc */
|
|
+
|
|
+ if (sa->type == AA_AUDITTYPE_FILE) {
|
|
+ int perm = audit ? sa->ival : sa->error_code;
|
|
+
|
|
+ audit_log_format(ab, "%s%s%s%s%s access to %s ",
|
|
+ perm & AA_EXEC_MMAP ? "m" : "",
|
|
+ perm & AA_MAY_READ ? "r" : "",
|
|
+ perm & AA_MAY_WRITE ? "w" : "",
|
|
+ perm & AA_MAY_EXEC ? "x" : "",
|
|
+ perm & AA_MAY_LINK ? "l" : "",
|
|
+ sa->name);
|
|
+
|
|
+ opspec_error = -EPERM;
|
|
+
|
|
+ } else if (sa->type == AA_AUDITTYPE_DIR) {
|
|
+ audit_log_format(ab, "%s on %s ",
|
|
+ sa->ival == aa_dir_mkdir ? "mkdir" : "rmdir",
|
|
+ sa->name);
|
|
+
|
|
+ } else if (sa->type == AA_AUDITTYPE_ATTR) {
|
|
+ struct iattr *iattr = (struct iattr*)sa->pval;
|
|
+
|
|
+ audit_log_format(ab,
|
|
+ "attribute (%s%s%s%s%s%s%s) change to %s ",
|
|
+ iattr->ia_valid & ATTR_MODE ? "mode," : "",
|
|
+ iattr->ia_valid & ATTR_UID ? "uid," : "",
|
|
+ iattr->ia_valid & ATTR_GID ? "gid," : "",
|
|
+ iattr->ia_valid & ATTR_SIZE ? "size," : "",
|
|
+ ((iattr->ia_valid & ATTR_ATIME_SET) ||
|
|
+ (iattr->ia_valid & ATTR_ATIME)) ? "atime," : "",
|
|
+ ((iattr->ia_valid & ATTR_MTIME_SET) ||
|
|
+ (iattr->ia_valid & ATTR_MTIME)) ? "mtime," : "",
|
|
+ iattr->ia_valid & ATTR_CTIME ? "ctime," : "",
|
|
+ sa->name);
|
|
+
|
|
+ } else if (sa->type == AA_AUDITTYPE_XATTR) {
|
|
+ const char *fmt;
|
|
+ switch (sa->ival) {
|
|
+ case aa_xattr_get:
|
|
+ fmt = "xattr get";
|
|
+ break;
|
|
+ case aa_xattr_set:
|
|
+ fmt = "xattr set";
|
|
+ break;
|
|
+ case aa_xattr_list:
|
|
+ fmt = "xattr list";
|
|
+ break;
|
|
+ case aa_xattr_remove:
|
|
+ fmt = "xattr remove";
|
|
+ break;
|
|
+ default:
|
|
+ fmt = "xattr <unknown>";
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ audit_log_format(ab, "%s on %s ", fmt, sa->name);
|
|
+
|
|
+ } else if (sa->type == AA_AUDITTYPE_LINK) {
|
|
+ audit_log_format(ab,
|
|
+ "link access from %s to %s ",
|
|
+ sa->name,
|
|
+ (char*)sa->pval);
|
|
+
|
|
+ } else if (sa->type == AA_AUDITTYPE_CAP) {
|
|
+ audit_log_format(ab,
|
|
+ "access to capability '%s' ",
|
|
+ capability_to_name(sa->ival));
|
|
+
|
|
+ opspec_error = -EPERM;
|
|
+ } else if (sa->type == AA_AUDITTYPE_SYSCALL) {
|
|
+ audit_log_format(ab, "access to syscall '%s' ", sa->name);
|
|
+
|
|
+ opspec_error = -EPERM;
|
|
+ } else {
|
|
+ /* -EINVAL -- will WARN_ON above */
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ audit_log_format(ab, "(%s(%d) profile %s active %s)",
|
|
+ current->comm, current->pid,
|
|
+ BASE_PROFILE(active)->name, active->name);
|
|
+
|
|
+ audit_log_end(ab);
|
|
+
|
|
+ if (complain)
|
|
+ error = 0;
|
|
+ else
|
|
+ error = sa->result ? 0 : opspec_error;
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_get_name - retrieve fully qualified path name
|
|
+ * @dentry: relative path element
|
|
+ * @mnt: where in tree
|
|
+ *
|
|
+ * Returns fully qualified path name on sucess, NULL on failure.
|
|
+ * aa_put_name must be used to free allocated buffer.
|
|
+ */
|
|
+char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt)
|
|
+{
|
|
+ char *page, *name;
|
|
+
|
|
+ page = (char *)__get_free_page(GFP_KERNEL);
|
|
+ if (!page) {
|
|
+ name = ERR_PTR(-ENOMEM);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ name = d_path(dentry, mnt, page, PAGE_SIZE);
|
|
+ /* check for (deleted) that d_path appends to pathnames if the dentry
|
|
+ * has been removed from the cache.
|
|
+ * The size > deleted_size and strcmp checks are redundant safe guards.
|
|
+ */
|
|
+ if (IS_ERR(name)) {
|
|
+ free_page((unsigned long)page);
|
|
+ } else {
|
|
+ const char deleted_str[] = " (deleted)";
|
|
+ const size_t deleted_size = sizeof(deleted_str) - 1;
|
|
+ size_t size;
|
|
+ size = strlen(name);
|
|
+ if (!IS_ROOT(dentry) && d_unhashed(dentry) &&
|
|
+ size > deleted_size &&
|
|
+ strcmp(name + size - deleted_size, deleted_str) == 0)
|
|
+ name[size - deleted_size] = '\0';
|
|
+ AA_DEBUG("%s: full_path=%s\n", __FUNCTION__, name);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return name;
|
|
+}
|
|
+
|
|
+/***********************************
|
|
+ * Global permission check functions
|
|
+ ***********************************/
|
|
+
|
|
+/**
|
|
+ * aa_attr - check whether attribute change allowed
|
|
+ * @active: profile to check against
|
|
+ * @dentry: file to check
|
|
+ * @iattr: attribute changes requested
|
|
+ */
|
|
+int aa_attr(struct aaprofile *active, struct dentry *dentry,
|
|
+ struct iattr *iattr)
|
|
+{
|
|
+ int error = 0, permerror;
|
|
+ struct aa_audit sa;
|
|
+
|
|
+ sa.type = AA_AUDITTYPE_ATTR;
|
|
+ sa.pval = iattr;
|
|
+ sa.flags = 0;
|
|
+ sa.gfp_mask = GFP_KERNEL;
|
|
+
|
|
+ permerror = _aa_perm_dentry(active, dentry, MAY_WRITE, &sa.name);
|
|
+ aa_permerror2result(permerror, &sa);
|
|
+
|
|
+ error = aa_audit(active, &sa);
|
|
+
|
|
+ aa_put_name(sa.name);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_xattr - check whether xattr attribute change allowed
|
|
+ * @active: profile to check against
|
|
+ * @dentry: file to check
|
|
+ * @xattr: xattr to check
|
|
+ * @xattroptype: type of xattr operation
|
|
+ */
|
|
+int aa_xattr(struct aaprofile *active, struct dentry *dentry,
|
|
+ const char *xattr, enum aa_xattroptype xattroptype)
|
|
+{
|
|
+ int error = 0, permerror, mask = 0;
|
|
+ struct aa_audit sa;
|
|
+
|
|
+ /* if not confined or empty mask permission granted */
|
|
+ if (!active)
|
|
+ goto out;
|
|
+
|
|
+ if (xattroptype == aa_xattr_get || xattroptype == aa_xattr_list)
|
|
+ mask = MAY_READ;
|
|
+ else if (xattroptype == aa_xattr_set || xattroptype == aa_xattr_remove)
|
|
+ mask = MAY_WRITE;
|
|
+
|
|
+ sa.type = AA_AUDITTYPE_XATTR;
|
|
+ sa.ival = xattroptype;
|
|
+ sa.pval = xattr;
|
|
+ sa.flags = 0;
|
|
+ sa.gfp_mask = GFP_KERNEL;
|
|
+
|
|
+ permerror = _aa_perm_dentry(active, dentry, mask, &sa.name);
|
|
+ aa_permerror2result(permerror, &sa);
|
|
+
|
|
+ error = aa_audit(active, &sa);
|
|
+
|
|
+ aa_put_name(sa.name);
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_perm - basic apparmor permissions check
|
|
+ * @active: profile to check against
|
|
+ * @dentry: dentry
|
|
+ * @mnt: mountpoint
|
|
+ * @mask: access mode requested
|
|
+ *
|
|
+ * Determine if access (mask) for dentry is authorized by active
|
|
+ * profile. Result, %0 (success), -ve (error)
|
|
+ */
|
|
+int aa_perm(struct aaprofile *active, struct dentry *dentry,
|
|
+ struct vfsmount *mnt, int mask)
|
|
+{
|
|
+ int error = 0, permerror;
|
|
+ struct aa_audit sa;
|
|
+
|
|
+ if (!active)
|
|
+ goto out;
|
|
+
|
|
+ if ((mask = aa_filter_mask(mask, dentry->d_inode)) == 0)
|
|
+ goto out;
|
|
+
|
|
+ sa.type = AA_AUDITTYPE_FILE;
|
|
+ sa.name = aa_get_name(dentry, mnt);
|
|
+ sa.ival = mask;
|
|
+ sa.flags = 0;
|
|
+ sa.gfp_mask = GFP_KERNEL;
|
|
+
|
|
+ if (IS_ERR(sa.name)) {
|
|
+ permerror = PTR_ERR(sa.name);
|
|
+ sa.name = NULL;
|
|
+ } else {
|
|
+ permerror = aa_file_perm(active, sa.name, mask);
|
|
+ }
|
|
+
|
|
+ aa_permerror2result(permerror, &sa);
|
|
+
|
|
+ error = aa_audit(active, &sa);
|
|
+
|
|
+ aa_put_name(sa.name);
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_perm_nameidata: interface to sd_perm accepting nameidata
|
|
+ * @active: profile to check against
|
|
+ * @nd: namespace data (for vfsmnt and dentry)
|
|
+ * @mask: access mode requested
|
|
+ */
|
|
+int aa_perm_nameidata(struct aaprofile *active, struct nameidata *nd, int mask)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ if (nd)
|
|
+ error = aa_perm(active, nd->dentry, nd->mnt, mask);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_perm_dentry - file permissions interface when no vfsmnt available
|
|
+ * @active: profile to check against
|
|
+ * @dentry: requested dentry
|
|
+ * @mask: access mode requested
|
|
+ *
|
|
+ * Determine if access (mask) for dentry is authorized by active profile.
|
|
+ * Result, %0 (success), -ve (error)
|
|
+ */
|
|
+int aa_perm_dentry(struct aaprofile *active, struct dentry *dentry, int mask)
|
|
+{
|
|
+ int error = 0, permerror;
|
|
+ struct aa_audit sa;
|
|
+
|
|
+ if (!active)
|
|
+ goto out;
|
|
+
|
|
+ if ((mask = aa_filter_mask(mask, dentry->d_inode)) == 0)
|
|
+ goto out;
|
|
+
|
|
+ sa.type = AA_AUDITTYPE_FILE;
|
|
+ sa.ival = mask;
|
|
+ sa.flags = 0;
|
|
+ sa.gfp_mask = GFP_KERNEL;
|
|
+
|
|
+ permerror = _aa_perm_dentry(active, dentry, mask, &sa.name);
|
|
+ aa_permerror2result(permerror, &sa);
|
|
+
|
|
+ error = aa_audit(active, &sa);
|
|
+
|
|
+ aa_put_name(sa.name);
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_perm_dir
|
|
+ * @active: profile to check against
|
|
+ * @dentry: requested dentry
|
|
+ * @diroptype: aa_dir_mkdir or aa_dir_rmdir
|
|
+ *
|
|
+ * Determine if directory operation (make/remove) for dentry is authorized
|
|
+ * by @active profile.
|
|
+ * Result, %0 (success), -ve (error)
|
|
+ */
|
|
+int aa_perm_dir(struct aaprofile *active, struct dentry *dentry,
|
|
+ enum aa_diroptype diroptype)
|
|
+{
|
|
+ int error = 0, permerror, mask;
|
|
+ struct aa_audit sa;
|
|
+
|
|
+ WARN_ON(diroptype != aa_dir_mkdir && diroptype != aa_dir_rmdir);
|
|
+
|
|
+ if (!active)
|
|
+ goto out;
|
|
+
|
|
+ mask = MAY_WRITE;
|
|
+
|
|
+ sa.type = AA_AUDITTYPE_DIR;
|
|
+ sa.ival = diroptype;
|
|
+ sa.flags = 0;
|
|
+ sa.gfp_mask = GFP_KERNEL;
|
|
+
|
|
+ permerror = _aa_perm_dentry(active, dentry, mask, &sa.name);
|
|
+ aa_permerror2result(permerror, &sa);
|
|
+
|
|
+ error = aa_audit(active, &sa);
|
|
+
|
|
+ aa_put_name(sa.name);
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_capability - test permission to use capability
|
|
+ * @active: profile to check against
|
|
+ * @cap: capability to be tested
|
|
+ *
|
|
+ * Look up capability in active profile capability set.
|
|
+ * Return %0 (success), -%EPERM (error)
|
|
+ */
|
|
+int aa_capability(struct aaprofile *active, int cap)
|
|
+{
|
|
+ int error = 0;
|
|
+
|
|
+ struct aa_audit sa;
|
|
+
|
|
+ sa.type = AA_AUDITTYPE_CAP;
|
|
+ sa.name = NULL;
|
|
+ sa.ival = cap;
|
|
+ sa.flags = 0;
|
|
+ sa.error_code = 0;
|
|
+ sa.result = cap_raised(active->capabilities, cap);
|
|
+ sa.gfp_mask = GFP_ATOMIC;
|
|
+
|
|
+ error = aa_audit(active, &sa);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_link - hard link check
|
|
+ * @active: profile to check against
|
|
+ * @link: dentry for link being created
|
|
+ * @target: dentry for link target
|
|
+ *
|
|
+ * Checks link permissions for all possible name combinations. This is
|
|
+ * particularly ugly. Returns %0 on sucess, error otherwise.
|
|
+ */
|
|
+int aa_link(struct aaprofile *active, struct dentry *link,
|
|
+ struct dentry *target)
|
|
+{
|
|
+ char *iname = NULL, *oname = NULL,
|
|
+ *failed_iname = NULL, *failed_oname = NULL;
|
|
+ unsigned int result = 0;
|
|
+ int error, path_error, error_code = 0, match = 0,
|
|
+ complain = PROFILE_COMPLAIN(active);
|
|
+ struct aa_path_data idata, odata;
|
|
+ struct aa_audit sa;
|
|
+
|
|
+ if (!active)
|
|
+ return 0;
|
|
+
|
|
+ /* Perform nested lookup for names.
|
|
+ * This is necessary in the case where /dev/block is mounted
|
|
+ * multiple times, i.e /dev/block->/a and /dev/block->/b
|
|
+ * This allows us to detect links where src/dest are on different
|
|
+ * mounts. N.B no support yet for links across bind mounts of
|
|
+ * the form mount -bind /mnt/subpath /mnt2
|
|
+ *
|
|
+ * Getting direct access to vfsmounts (via nameidata) for link and
|
|
+ * target would allow all this uglyness to go away.
|
|
+ *
|
|
+ * If more than one mountpoint matches but none satisfy the profile,
|
|
+ * only the first pathname (mountpoint) is logged.
|
|
+ */
|
|
+
|
|
+ __aa_path_begin(target, link, &odata);
|
|
+ do {
|
|
+ oname = aa_path_getname(&odata);
|
|
+ if (oname) {
|
|
+ aa_path_begin(target, &idata);
|
|
+ do {
|
|
+ iname = aa_path_getname(&idata);
|
|
+ if (iname) {
|
|
+ result = aa_link_perm(active, oname,
|
|
+ iname);
|
|
+
|
|
+ /* access via any path is enough */
|
|
+ if (result || complain) {
|
|
+ match = 1;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* Already have an path that failed? */
|
|
+ if (failed_iname) {
|
|
+ aa_put_name(iname);
|
|
+ } else {
|
|
+ failed_iname = iname;
|
|
+ failed_oname = oname;
|
|
+ }
|
|
+ }
|
|
+ } while (iname && !match);
|
|
+
|
|
+ /* should not be possible if we matched */
|
|
+ if ((path_error = aa_path_end(&idata)) != 0) {
|
|
+ dentry_xlate_error(target, path_error,
|
|
+ "inner dentry [link]");
|
|
+
|
|
+ /* name should not be set if error */
|
|
+ WARN_ON(iname);
|
|
+
|
|
+ error_code = path_error;
|
|
+ }
|
|
+
|
|
+ /* don't release if we're saving it */
|
|
+ if (!match && failed_oname != oname)
|
|
+ aa_put_name(oname);
|
|
+ }
|
|
+ } while (oname && !match);
|
|
+
|
|
+ if (error_code != 0) {
|
|
+ /* inner error */
|
|
+ (void)aa_path_end(&odata);
|
|
+ } else if ((path_error = aa_path_end(&odata)) != 0) {
|
|
+ dentry_xlate_error(link, path_error, "outer dentry [link]");
|
|
+ error_code = path_error;
|
|
+ }
|
|
+
|
|
+ if (error_code != 0) {
|
|
+ /* inner or outer error */
|
|
+ result = 0;
|
|
+ } else if (match) {
|
|
+ result = 1;
|
|
+ } else {
|
|
+ /* failed to match */
|
|
+ WARN_ON(iname);
|
|
+ WARN_ON(oname);
|
|
+
|
|
+ result = 0;
|
|
+ iname = failed_iname;
|
|
+ oname = failed_oname;
|
|
+ }
|
|
+
|
|
+ sa.type = AA_AUDITTYPE_LINK;
|
|
+ sa.name = oname; /* link */
|
|
+ sa.pval = iname; /* target */
|
|
+ sa.flags = 0;
|
|
+ sa.error_code = error_code;
|
|
+ sa.result = result;
|
|
+ sa.gfp_mask = GFP_KERNEL;
|
|
+
|
|
+ error = aa_audit(active, &sa);
|
|
+
|
|
+ if (failed_oname != oname)
|
|
+ aa_put_name(failed_oname);
|
|
+ if (failed_iname != iname)
|
|
+ aa_put_name(failed_iname);
|
|
+
|
|
+ aa_put_name(oname);
|
|
+ aa_put_name(iname);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/*******************************
|
|
+ * Global task related functions
|
|
+ *******************************/
|
|
+
|
|
+/**
|
|
+ * aa_fork - create a new subdomain
|
|
+ * @p: new process
|
|
+ *
|
|
+ * Create a new subdomain for newly created process @p if it's parent
|
|
+ * is already confined. Otherwise a subdomain will be lazily allocated
|
|
+ * will get one with NULL values. Return 0 on sucess.
|
|
+ * for the child if it subsequently execs (in aa_register).
|
|
+ * Return 0 on sucess.
|
|
+ *
|
|
+ * The sd_lock is used to maintain consistency against profile
|
|
+ * replacement/removal.
|
|
+ */
|
|
+
|
|
+int aa_fork(struct task_struct *p)
|
|
+{
|
|
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
|
|
+ struct subdomain *newsd = NULL;
|
|
+
|
|
+ AA_DEBUG("%s\n", __FUNCTION__);
|
|
+
|
|
+ if (__aa_is_confined(sd)) {
|
|
+ unsigned long flags;
|
|
+
|
|
+ newsd = alloc_subdomain(p);
|
|
+
|
|
+ if (!newsd)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /* Use locking here instead of getting the reference
|
|
+ * because we need both the old reference and the
|
|
+ * new reference to be consistent.
|
|
+ */
|
|
+ spin_lock_irqsave(&sd_lock, flags);
|
|
+ aa_switch(newsd, sd->active);
|
|
+ newsd->hat_magic = sd->hat_magic;
|
|
+ spin_unlock_irqrestore(&sd_lock, flags);
|
|
+
|
|
+ if (SUBDOMAIN_COMPLAIN(sd) &&
|
|
+ sd->active == null_complain_profile)
|
|
+ LOG_HINT(sd->active, GFP_KERNEL, HINT_FORK,
|
|
+ "pid=%d child=%d\n",
|
|
+ current->pid, p->pid);
|
|
+ }
|
|
+ p->security = newsd;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_register - register a new program
|
|
+ * @bprm: binprm of program being registered
|
|
+ *
|
|
+ * Try to register a new program during execve(). This should give the
|
|
+ * new program a valid subdomain.
|
|
+ */
|
|
+int aa_register(struct linux_binprm *bprm)
|
|
+{
|
|
+ char *filename;
|
|
+ struct file *filp = bprm->file;
|
|
+ struct aaprofile *active;
|
|
+ struct aaprofile *newprofile = NULL, unconstrained_flag;
|
|
+ int error = -ENOMEM,
|
|
+ exec_mode = 0,
|
|
+ find_profile = 0,
|
|
+ find_profile_mandatory = 0,
|
|
+ unsafe_exec = 0,
|
|
+ complain = 0;
|
|
+
|
|
+ AA_DEBUG("%s\n", __FUNCTION__);
|
|
+
|
|
+ filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt);
|
|
+ if (IS_ERR(filename)) {
|
|
+ AA_WARN("%s: Failed to get filename\n", __FUNCTION__);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ error = 0;
|
|
+
|
|
+ active = get_active_aaprofile();
|
|
+
|
|
+ if (!active) {
|
|
+ /* Unconfined task, load profile if it exists */
|
|
+ find_profile = 1;
|
|
+ goto find_profile;
|
|
+ }
|
|
+
|
|
+ complain = PROFILE_COMPLAIN(active);
|
|
+
|
|
+ /* Confined task, determine what mode inherit, unconstrained or
|
|
+ * mandatory to load new profile
|
|
+ */
|
|
+ if (aa_get_execmode(active, filename, &exec_mode, &unsafe_exec)) {
|
|
+ switch (exec_mode) {
|
|
+ case AA_EXEC_INHERIT:
|
|
+ /* do nothing - setting of profile
|
|
+ * already handed in aa_fork
|
|
+ */
|
|
+ AA_DEBUG("%s: INHERIT %s\n",
|
|
+ __FUNCTION__,
|
|
+ filename);
|
|
+ break;
|
|
+
|
|
+ case AA_EXEC_UNCONSTRAINED:
|
|
+ AA_DEBUG("%s: UNCONSTRAINED %s\n",
|
|
+ __FUNCTION__,
|
|
+ filename);
|
|
+
|
|
+ /* unload profile */
|
|
+ newprofile = &unconstrained_flag;
|
|
+ break;
|
|
+
|
|
+ case AA_EXEC_PROFILE:
|
|
+ AA_DEBUG("%s: PROFILE %s\n",
|
|
+ __FUNCTION__,
|
|
+ filename);
|
|
+
|
|
+ find_profile = 1;
|
|
+ find_profile_mandatory = 1;
|
|
+ break;
|
|
+
|
|
+ case AA_MAY_EXEC:
|
|
+ /* this should not happen, entries
|
|
+ * with just EXEC only should be
|
|
+ * rejected at profile load time
|
|
+ */
|
|
+ AA_ERROR("%s: Rejecting exec(2) of image '%s'. "
|
|
+ "AA_MAY_EXEC without exec qualifier invalid "
|
|
+ "(%s(%d) profile %s active %s\n",
|
|
+ __FUNCTION__,
|
|
+ filename,
|
|
+ current->comm, current->pid,
|
|
+ BASE_PROFILE(active)->name, active->name);
|
|
+ error = -EPERM;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ AA_ERROR("%s: Rejecting exec(2) of image '%s'. "
|
|
+ "Unknown exec qualifier %x "
|
|
+ "(%s (pid %d) profile %s active %s)\n",
|
|
+ __FUNCTION__,
|
|
+ filename,
|
|
+ exec_mode,
|
|
+ current->comm, current->pid,
|
|
+ BASE_PROFILE(active)->name, active->name);
|
|
+ error = -EPERM;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ } else if (complain) {
|
|
+ /* There was no entry in calling profile
|
|
+ * describing mode to execute image in.
|
|
+ * Drop into null-profile (disabling secure exec).
|
|
+ */
|
|
+ newprofile = get_aaprofile(null_complain_profile);
|
|
+ unsafe_exec = 1;
|
|
+ } else {
|
|
+ AA_WARN("%s: Rejecting exec(2) of image '%s'. "
|
|
+ "Unable to determine exec qualifier "
|
|
+ "(%s (pid %d) profile %s active %s)\n",
|
|
+ __FUNCTION__,
|
|
+ filename,
|
|
+ current->comm, current->pid,
|
|
+ BASE_PROFILE(active)->name, active->name);
|
|
+ error = -EPERM;
|
|
+ }
|
|
+
|
|
+
|
|
+find_profile:
|
|
+ if (!find_profile)
|
|
+ goto apply_profile;
|
|
+
|
|
+ /* Locate new profile */
|
|
+ newprofile = aa_profilelist_find(filename);
|
|
+ if (newprofile) {
|
|
+ AA_DEBUG("%s: setting profile %s\n",
|
|
+ __FUNCTION__, newprofile->name);
|
|
+ } else if (find_profile_mandatory) {
|
|
+ /* Profile (mandatory) could not be found */
|
|
+
|
|
+ if (complain) {
|
|
+ LOG_HINT(active, GFP_KERNEL, HINT_MANDPROF,
|
|
+ "image=%s pid=%d profile=%s active=%s\n",
|
|
+ filename,
|
|
+ current->pid,
|
|
+ BASE_PROFILE(active)->name, active->name);
|
|
+
|
|
+ newprofile = get_aaprofile(null_complain_profile);
|
|
+ } else {
|
|
+ AA_WARN("REJECTING exec(2) of image '%s'. "
|
|
+ "Profile mandatory and not found "
|
|
+ "(%s(%d) profile %s active %s)\n",
|
|
+ filename,
|
|
+ current->comm, current->pid,
|
|
+ BASE_PROFILE(active)->name, active->name);
|
|
+ error = -EPERM;
|
|
+ }
|
|
+ } else {
|
|
+ /* Profile (non-mandatory) could not be found */
|
|
+
|
|
+ /* Only way we can get into this code is if task
|
|
+ * is unconstrained.
|
|
+ */
|
|
+
|
|
+ WARN_ON(active);
|
|
+
|
|
+ AA_DEBUG("%s: No profile found for exec image %s\n",
|
|
+ __FUNCTION__,
|
|
+ filename);
|
|
+ } /* newprofile */
|
|
+
|
|
+
|
|
+apply_profile:
|
|
+ /* Apply profile if necessary */
|
|
+ if (newprofile) {
|
|
+ struct subdomain *sd, *lazy_sd = NULL;
|
|
+ unsigned long flags;
|
|
+
|
|
+ if (newprofile == &unconstrained_flag)
|
|
+ newprofile = NULL;
|
|
+
|
|
+ /* grab a lock - this is to guarentee consistency against
|
|
+ * other writers of subdomain (replacement/removal)
|
|
+ *
|
|
+ * Several things may have changed since the code above
|
|
+ *
|
|
+ * - Task may be presently unconfined (have no sd). In which
|
|
+ * case we have to lazily allocate one. Note we may be raced
|
|
+ * to this allocation by a setprofile.
|
|
+ *
|
|
+ * - If we are a confined process, active is a refcounted copy
|
|
+ * of the profile that was on the subdomain at entry.
|
|
+ * This allows us to not have to hold a lock around
|
|
+ * all this code. If profile replacement has taken place
|
|
+ * our active may not equal sd->active any more.
|
|
+ * This is okay since the operation is treated as if
|
|
+ * the transition occured before replacement.
|
|
+ *
|
|
+ * - If newprofile points to an actual profile (result of
|
|
+ * aa_profilelist_find above), this profile may have been
|
|
+ * replaced. We need to fix it up. Doing this to avoid
|
|
+ * having to hold a lock around all this code.
|
|
+ */
|
|
+
|
|
+ if (!active && !(sd = AA_SUBDOMAIN(current->security))) {
|
|
+ lazy_sd = alloc_subdomain(current);
|
|
+ if (!lazy_sd) {
|
|
+ AA_ERROR("%s: Failed to allocate subdomain\n",
|
|
+ __FUNCTION__);
|
|
+ error = -ENOMEM;
|
|
+ goto cleanup;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&sd_lock, flags);
|
|
+
|
|
+ sd = AA_SUBDOMAIN(current->security);
|
|
+ if (lazy_sd) {
|
|
+ if (sd) {
|
|
+ /* raced by setprofile - created sd */
|
|
+ free_subdomain(lazy_sd);
|
|
+ lazy_sd = NULL;
|
|
+ } else {
|
|
+ /* Not rcu used to get the write barrier
|
|
+ * correct */
|
|
+ rcu_assign_pointer(current->security, lazy_sd);
|
|
+ sd = lazy_sd;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Determine if profile we found earlier is stale.
|
|
+ * If so, reobtain it. N.B stale flag should never be
|
|
+ * set on null_complain profile.
|
|
+ */
|
|
+ if (newprofile && unlikely(newprofile->isstale)) {
|
|
+ WARN_ON(newprofile == null_complain_profile);
|
|
+
|
|
+ /* drop refcnt obtained from earlier get_aaprofile */
|
|
+ put_aaprofile(newprofile);
|
|
+
|
|
+ newprofile = aa_profilelist_find(filename);
|
|
+
|
|
+ if (!newprofile) {
|
|
+ /* Race, profile was removed, not replaced.
|
|
+ * Redo with error checking
|
|
+ */
|
|
+ spin_unlock_irqrestore(&sd_lock, flags);
|
|
+ goto find_profile;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Handle confined exec.
|
|
+ * Can be at this point for the following reasons:
|
|
+ * 1. unconfined switching to confined
|
|
+ * 2. confined switching to different confinement
|
|
+ * 3. confined switching to unconfined
|
|
+ *
|
|
+ * Cases 2 and 3 are marked as requiring secure exec
|
|
+ * (unless policy specified "unsafe exec")
|
|
+ */
|
|
+ if (__aa_is_confined(sd) && !unsafe_exec) {
|
|
+ unsigned long bprm_flags;
|
|
+
|
|
+ bprm_flags = AA_SECURE_EXEC_NEEDED;
|
|
+ bprm->security = (void*)
|
|
+ ((unsigned long)bprm->security | bprm_flags);
|
|
+ }
|
|
+
|
|
+ aa_switch(sd, newprofile);
|
|
+ put_aaprofile(newprofile);
|
|
+
|
|
+ if (complain && newprofile == null_complain_profile)
|
|
+ LOG_HINT(newprofile, GFP_ATOMIC, HINT_CHGPROF,
|
|
+ "pid=%d\n",
|
|
+ current->pid);
|
|
+
|
|
+ spin_unlock_irqrestore(&sd_lock, flags);
|
|
+ }
|
|
+
|
|
+cleanup:
|
|
+ aa_put_name(filename);
|
|
+
|
|
+ put_aaprofile(active);
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_release - release the task's subdomain
|
|
+ * @p: task being released
|
|
+ *
|
|
+ * This is called after a task has exited and the parent has reaped it.
|
|
+ * @p->security blob is freed.
|
|
+ *
|
|
+ * This is the one case where we don't need to hold the sd_lock before
|
|
+ * removing a profile from a subdomain. Once the subdomain has been
|
|
+ * removed from the subdomain_list, we are no longer racing other writers.
|
|
+ * There may still be other readers so we must still use aa_switch
|
|
+ * to put the subdomain's reference safely.
|
|
+ */
|
|
+void aa_release(struct task_struct *p)
|
|
+{
|
|
+ struct subdomain *sd = AA_SUBDOMAIN(p->security);
|
|
+ if (sd) {
|
|
+ p->security = NULL;
|
|
+
|
|
+ aa_subdomainlist_remove(sd);
|
|
+ aa_switch_unconfined(sd);
|
|
+
|
|
+ kfree(sd);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*****************************
|
|
+ * global subprofile functions
|
|
+ ****************************/
|
|
+
|
|
+/**
|
|
+ * do_change_hat - actually switch hats
|
|
+ * @hat_name: name of hat to swtich to
|
|
+ * @sd: current subdomain
|
|
+ *
|
|
+ * Switch to a new hat. Return %0 on success, error otherwise.
|
|
+ */
|
|
+static inline int do_change_hat(const char *hat_name, struct subdomain *sd)
|
|
+{
|
|
+ struct aaprofile *sub;
|
|
+ int error = 0;
|
|
+
|
|
+ sub = __aa_find_profile(hat_name, &BASE_PROFILE(sd->active)->sub);
|
|
+
|
|
+ if (sub) {
|
|
+ /* change hat */
|
|
+ aa_switch(sd, sub);
|
|
+ put_aaprofile(sub);
|
|
+ } else {
|
|
+ /* There is no such subprofile change to a NULL profile.
|
|
+ * The NULL profile grants no file access.
|
|
+ *
|
|
+ * This feature is used by changehat_apache.
|
|
+ *
|
|
+ * N.B from the null-profile the task can still changehat back
|
|
+ * out to the parent profile (assuming magic != NULL)
|
|
+ */
|
|
+ if (SUBDOMAIN_COMPLAIN(sd)) {
|
|
+ LOG_HINT(sd->active, GFP_ATOMIC, HINT_UNKNOWN_HAT,
|
|
+ "%s pid=%d "
|
|
+ "profile=%s active=%s\n",
|
|
+ hat_name,
|
|
+ current->pid,
|
|
+ BASE_PROFILE(sd->active)->name,
|
|
+ sd->active->name);
|
|
+ } else {
|
|
+ AA_DEBUG("%s: Unknown hatname '%s'. "
|
|
+ "Changing to NULL profile "
|
|
+ "(%s(%d) profile %s active %s)\n",
|
|
+ __FUNCTION__,
|
|
+ hat_name,
|
|
+ current->comm, current->pid,
|
|
+ BASE_PROFILE(sd->active)->name,
|
|
+ sd->active->name);
|
|
+ error = -EACCES;
|
|
+ }
|
|
+ aa_switch(sd, sd->active->null_profile);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_change_hat - change hat to/from subprofile
|
|
+ * @hat_name: specifies hat to change to
|
|
+ * @hat_magic: token to validate hat change
|
|
+ *
|
|
+ * Change to new @hat_name when current hat is top level profile, and store
|
|
+ * the @hat_magic in the current subdomain. If the new @hat_name is
|
|
+ * %NULL, and the @hat_magic matches that stored in the current subdomain
|
|
+ * return to original top level profile. Returns %0 on success, error
|
|
+ * otherwise.
|
|
+ */
|
|
+int aa_change_hat(const char *hat_name, u32 hat_magic)
|
|
+{
|
|
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
|
|
+ int error = 0;
|
|
+
|
|
+ AA_DEBUG("%s: %p, 0x%x (pid %d)\n",
|
|
+ __FUNCTION__,
|
|
+ hat_name, hat_magic,
|
|
+ current->pid);
|
|
+
|
|
+ /* Dump out above debugging in WARN mode if we are in AUDIT mode */
|
|
+ if (SUBDOMAIN_AUDIT(sd)) {
|
|
+ AA_WARN("%s: %s, 0x%x (pid %d)\n",
|
|
+ __FUNCTION__, hat_name ? hat_name : "NULL",
|
|
+ hat_magic, current->pid);
|
|
+ }
|
|
+
|
|
+ /* check to see if an unconfined process is doing a changehat. */
|
|
+ if (!__aa_is_confined(sd)) {
|
|
+ error = -EPERM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* check to see if the confined process has any hats. */
|
|
+ if (list_empty(&BASE_PROFILE(sd->active)->sub) &&
|
|
+ !PROFILE_COMPLAIN(sd->active)) {
|
|
+ error = -ECHILD;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* Check whether current domain is parent
|
|
+ * or one of the sibling children
|
|
+ */
|
|
+ if (!IN_SUBPROFILE(sd->active)) {
|
|
+ /*
|
|
+ * parent
|
|
+ */
|
|
+ if (hat_name) {
|
|
+ AA_DEBUG("%s: switching to %s, 0x%x\n",
|
|
+ __FUNCTION__,
|
|
+ hat_name,
|
|
+ hat_magic);
|
|
+
|
|
+ /*
|
|
+ * N.B hat_magic == 0 has a special meaning
|
|
+ * this indicates that the task may never changehat
|
|
+ * back to it's parent, it will stay in this subhat
|
|
+ * (or null-profile, if the hat doesn't exist) until
|
|
+ * the task terminates
|
|
+ */
|
|
+ sd->hat_magic = hat_magic;
|
|
+ error = do_change_hat(hat_name, sd);
|
|
+ } else {
|
|
+ /* Got here via changehat(NULL, magic)
|
|
+ *
|
|
+ * We used to simply update the magic cookie.
|
|
+ * That's an odd behaviour, so just do nothing.
|
|
+ */
|
|
+ }
|
|
+ } else {
|
|
+ /*
|
|
+ * child -- check to make sure magic is same as what was
|
|
+ * passed when we switched into this profile,
|
|
+ * Handle special casing of NULL magic which confines task
|
|
+ * to subprofile and prohibits further changehats
|
|
+ */
|
|
+ if (hat_magic == sd->hat_magic && sd->hat_magic) {
|
|
+ if (!hat_name) {
|
|
+ /*
|
|
+ * Got here via changehat(NULL, magic)
|
|
+ * Return from subprofile, back to parent
|
|
+ */
|
|
+ aa_switch(sd, sd->active->parent);
|
|
+
|
|
+ /* Reset hat_magic to zero.
|
|
+ * New value will be passed on next changehat
|
|
+ */
|
|
+ sd->hat_magic = 0;
|
|
+ } else {
|
|
+ /* change to another (sibling) profile */
|
|
+ error = do_change_hat(hat_name, sd);
|
|
+ }
|
|
+ } else if (sd->hat_magic) {
|
|
+ AA_ERROR("KILLING process %s(%d) "
|
|
+ "Invalid change_hat() magic# 0x%x "
|
|
+ "(hatname %s profile %s active %s)\n",
|
|
+ current->comm, current->pid,
|
|
+ hat_magic,
|
|
+ hat_name ? hat_name : "NULL",
|
|
+ BASE_PROFILE(sd->active)->name,
|
|
+ sd->active->name);
|
|
+
|
|
+ /* terminate current process */
|
|
+ (void)send_sig_info(SIGKILL, NULL, current);
|
|
+ } else { /* sd->hat_magic == NULL */
|
|
+ AA_ERROR("KILLING process %s(%d) "
|
|
+ "Task was confined to current subprofile "
|
|
+ "(profile %s active %s)\n",
|
|
+ current->comm, current->pid,
|
|
+ BASE_PROFILE(sd->active)->name,
|
|
+ sd->active->name);
|
|
+
|
|
+ /* terminate current process */
|
|
+ (void)send_sig_info(SIGKILL, NULL, current);
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
Index: linux-2.6.19/security/apparmor/module_interface.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/module_interface.c
|
|
@@ -0,0 +1,846 @@
|
|
+/*
|
|
+ * Copyright (C) 1998-2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * AppArmor userspace policy interface
|
|
+ */
|
|
+
|
|
+#include <asm/unaligned.h>
|
|
+
|
|
+#include "apparmor.h"
|
|
+#include "inline.h"
|
|
+#include "module_interface.h"
|
|
+#include "match/match.h"
|
|
+
|
|
+/* aa_code defined in module_interface.h */
|
|
+
|
|
+const int aacode_datasize[] = { 1, 2, 4, 8, 2, 2, 4, 0, 0, 0, 0, 0, 0 };
|
|
+
|
|
+struct aa_taskreplace_data {
|
|
+ struct aaprofile *old_profile;
|
|
+ struct aaprofile *new_profile;
|
|
+};
|
|
+
|
|
+/* inlines must be forward of there use in newer version of gcc,
|
|
+ just forward declaring with a prototype won't work anymore */
|
|
+
|
|
+static inline void free_aa_entry(struct aa_entry *entry)
|
|
+{
|
|
+ if (entry) {
|
|
+ kfree(entry->filename);
|
|
+ aamatch_free(entry->extradata);
|
|
+ kfree(entry);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * alloc_aa_entry - create new empty aa_entry
|
|
+ * This routine allocates, initializes, and returns a new aa_entry
|
|
+ * file entry structure. Structure is zeroed. Returns new structure on
|
|
+ * success, %NULL on failure.
|
|
+ */
|
|
+static inline struct aa_entry *alloc_aa_entry(void)
|
|
+{
|
|
+ struct aa_entry *entry;
|
|
+
|
|
+ AA_DEBUG("%s\n", __FUNCTION__);
|
|
+ entry = kzalloc(sizeof(struct aa_entry), GFP_KERNEL);
|
|
+ if (entry) {
|
|
+ int i;
|
|
+ INIT_LIST_HEAD(&entry->list);
|
|
+ for (i = 0; i <= POS_AA_FILE_MAX; i++) {
|
|
+ INIT_LIST_HEAD(&entry->listp[i]);
|
|
+ }
|
|
+ }
|
|
+ return entry;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * free_aaprofile_rcu - rcu callback for free profiles
|
|
+ * @head: rcu_head struct of the profile whose reference is being put.
|
|
+ *
|
|
+ * the rcu callback routine, which delays the freeing of a profile when
|
|
+ * its last reference is put.
|
|
+ */
|
|
+static void free_aaprofile_rcu(struct rcu_head *head)
|
|
+{
|
|
+ struct aaprofile *p = container_of(head, struct aaprofile, rcu);
|
|
+ free_aaprofile(p);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * task_remove - remove profile from a task's subdomain
|
|
+ * @sd: task's subdomain
|
|
+ *
|
|
+ * remove the active profile from a task's subdomain, switching the task
|
|
+ * to an unconfined state.
|
|
+ */
|
|
+static inline void task_remove(struct subdomain *sd)
|
|
+{
|
|
+ /* spin_lock(&sd_lock) held here */
|
|
+ AA_DEBUG("%s: removing profile from task %s(%d) profile %s active %s\n",
|
|
+ __FUNCTION__,
|
|
+ sd->task->comm,
|
|
+ sd->task->pid,
|
|
+ BASE_PROFILE(sd->active)->name,
|
|
+ sd->active->name);
|
|
+
|
|
+ aa_switch_unconfined(sd);
|
|
+}
|
|
+
|
|
+/** taskremove_iter - Iterator to unconfine subdomains which match cookie
|
|
+ * @sd: subdomain to consider for profile removal
|
|
+ * @cookie: pointer to the oldprofile which is being removed
|
|
+ *
|
|
+ * If the subdomain's active profile matches old_profile, then call
|
|
+ * task_remove() to remove the profile leaving the task (subdomain) unconfined.
|
|
+ */
|
|
+static int taskremove_iter(struct subdomain *sd, void *cookie)
|
|
+{
|
|
+ struct aaprofile *old_profile = (struct aaprofile *)cookie;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&sd_lock, flags);
|
|
+
|
|
+ if (__aa_is_confined(sd) && BASE_PROFILE(sd->active) == old_profile) {
|
|
+ task_remove(sd);
|
|
+ }
|
|
+
|
|
+ spin_unlock_irqrestore(&sd_lock, flags);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/** task_replace - replace subdomain's current profile with a new profile
|
|
+ * @sd: subdomain to replace the profile on
|
|
+ * @new: new profile
|
|
+ *
|
|
+ * Replace a task's (subdomain's) active profile with a new profile. If
|
|
+ * task was in a hat then the new profile will also be in the equivalent
|
|
+ * hat in the new profile if it exists. If it doesn't exist the
|
|
+ * task will be placed in the special null_profile state.
|
|
+ */
|
|
+static inline void task_replace(struct subdomain *sd, struct aaprofile *new)
|
|
+{
|
|
+ AA_DEBUG("%s: replacing profile for task %s(%d) "
|
|
+ "profile=%s (%p) active=%s (%p)\n",
|
|
+ __FUNCTION__,
|
|
+ sd->task->comm, sd->task->pid,
|
|
+ BASE_PROFILE(sd->active)->name, BASE_PROFILE(sd->active),
|
|
+ sd->active->name, sd->active);
|
|
+
|
|
+ if (!sd->active)
|
|
+ goto out;
|
|
+
|
|
+ if (IN_SUBPROFILE(sd->active)) {
|
|
+ struct aaprofile *nactive;
|
|
+
|
|
+ /* The old profile was in a hat, check to see if the new
|
|
+ * profile has an equivalent hat */
|
|
+ nactive = __aa_find_profile(sd->active->name, &new->sub);
|
|
+
|
|
+ if (!nactive)
|
|
+ nactive = get_aaprofile(new->null_profile);
|
|
+
|
|
+ aa_switch(sd, nactive);
|
|
+ put_aaprofile(nactive);
|
|
+ } else {
|
|
+ aa_switch(sd, new);
|
|
+ }
|
|
+
|
|
+ out:
|
|
+ return;
|
|
+}
|
|
+
|
|
+/** taskreplace_iter - Iterator to replace a subdomain's profile
|
|
+ * @sd: subdomain to consider for profile replacement
|
|
+ * @cookie: pointer to the old profile which is being replaced.
|
|
+ *
|
|
+ * If the subdomain's active profile matches old_profile call
|
|
+ * task_replace() to replace with the subdomain's active profile with
|
|
+ * the new profile.
|
|
+ */
|
|
+static int taskreplace_iter(struct subdomain *sd, void *cookie)
|
|
+{
|
|
+ struct aa_taskreplace_data *data = (struct aa_taskreplace_data *)cookie;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&sd_lock, flags);
|
|
+
|
|
+ if (__aa_is_confined(sd) &&
|
|
+ BASE_PROFILE(sd->active) == data->old_profile)
|
|
+ task_replace(sd, data->new_profile);
|
|
+
|
|
+ spin_unlock_irqrestore(&sd_lock, flags);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline int aa_inbounds(struct aa_ext *e, size_t size)
|
|
+{
|
|
+ return (e->pos + size <= e->end);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aaconvert - convert trailing values of serialized type codes
|
|
+ * @code: type code
|
|
+ * @dest: pointer to object to receive the converted value
|
|
+ * @src: pointer to value to convert
|
|
+ *
|
|
+ * for serialized type codes which have a trailing value, convert it
|
|
+ * and place it in @dest. If a code does not have a trailing value nop.
|
|
+ */
|
|
+static void aaconvert(enum aa_code code, void *dest, void *src)
|
|
+{
|
|
+ switch (code) {
|
|
+ case AA_U8:
|
|
+ *(u8 *)dest = *(u8 *) src;
|
|
+ break;
|
|
+ case AA_U16:
|
|
+ case AA_NAME:
|
|
+ case AA_DYN_STRING:
|
|
+ *(u16 *)dest = le16_to_cpu(get_unaligned((u16 *)src));
|
|
+ break;
|
|
+ case AA_U32:
|
|
+ case AA_STATIC_BLOB:
|
|
+ *(u32 *)dest = le32_to_cpu(get_unaligned((u32 *)src));
|
|
+ break;
|
|
+ case AA_U64:
|
|
+ *(u64 *)dest = le64_to_cpu(get_unaligned((u64 *)src));
|
|
+ break;
|
|
+ default:
|
|
+ /* nop - all other type codes do not have a trailing value */
|
|
+ ;
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_is_X - check if the next element is of type X
|
|
+ * @e: serialized data extent information
|
|
+ * @code: type code
|
|
+ * @data: object located at @e->pos (of type @code) is written into @data
|
|
+ * if @data is non-null. if data is null it means skip this
|
|
+ * entry
|
|
+ * check to see if the next element in the serialized data stream is of type
|
|
+ * X and check that it is with in bounds, if so put the associated value in
|
|
+ * @data.
|
|
+ * return the size of bytes associated with the returned data
|
|
+ * for complex object like blob and string a pointer to the allocated
|
|
+ * data is returned in data, but the size of the blob or string is
|
|
+ * returned.
|
|
+ */
|
|
+static u32 aa_is_X(struct aa_ext *e, enum aa_code code, void *data)
|
|
+{
|
|
+ void *pos = e->pos;
|
|
+ int ret = 0;
|
|
+ if (!aa_inbounds(e, AA_CODE_BYTE + aacode_datasize[code]))
|
|
+ goto fail;
|
|
+ if (code != *(u8 *)e->pos)
|
|
+ goto out;
|
|
+ e->pos += AA_CODE_BYTE;
|
|
+ if (code == AA_NAME) {
|
|
+ u16 size;
|
|
+ /* name codes are followed by X bytes */
|
|
+ size = le16_to_cpu(get_unaligned((u16 *)e->pos));
|
|
+ if (!aa_inbounds(e, (size_t) size))
|
|
+ goto fail;
|
|
+ if (data)
|
|
+ *(u16 *)data = size;
|
|
+ e->pos += aacode_datasize[code];
|
|
+ ret = 1 + aacode_datasize[code];
|
|
+ } else if (code == AA_DYN_STRING) {
|
|
+ u16 size;
|
|
+ char *str;
|
|
+ /* strings codes are followed by X bytes */
|
|
+ size = le16_to_cpu(get_unaligned((u16 *)e->pos));
|
|
+ e->pos += aacode_datasize[code];
|
|
+ if (!aa_inbounds(e, (size_t) size))
|
|
+ goto fail;
|
|
+ if (data) {
|
|
+ * (char **)data = NULL;
|
|
+ str = kmalloc(size, GFP_KERNEL);
|
|
+ if (!str)
|
|
+ goto fail;
|
|
+ memcpy(str, e->pos, (size_t) size);
|
|
+ str[size-1] = '\0';
|
|
+ * (char **)data = str;
|
|
+ }
|
|
+ e->pos += size;
|
|
+ ret = size;
|
|
+ } else if (code == AA_STATIC_BLOB) {
|
|
+ u32 size;
|
|
+ /* blobs are followed by X bytes, that can be 2^32 */
|
|
+ size = le32_to_cpu(get_unaligned((u32 *)e->pos));
|
|
+ e->pos += aacode_datasize[code];
|
|
+ if (!aa_inbounds(e, (size_t) size))
|
|
+ goto fail;
|
|
+ if (data)
|
|
+ memcpy(data, e->pos, (size_t) size);
|
|
+ e->pos += size;
|
|
+ ret = size;
|
|
+ } else {
|
|
+ if (data)
|
|
+ aaconvert(code, data, e->pos);
|
|
+ e->pos += aacode_datasize[code];
|
|
+ ret = 1 + aacode_datasize[code];
|
|
+ }
|
|
+out:
|
|
+ return ret;
|
|
+fail:
|
|
+ e->pos = pos;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_is_nameX - check is the next element is of type X with a name of @name
|
|
+ * @e: serialized data extent information
|
|
+ * @code: type code
|
|
+ * @data: location to store deserialized data if match isX criteria
|
|
+ * @name: name to match to the serialized element.
|
|
+ *
|
|
+ * check that the next serialized data element is of type X and has a tag
|
|
+ * name @name. If the code matches and name (if specified) matches then
|
|
+ * the packed data is unpacked into *data. (Note for strings this is the
|
|
+ * size, and the next data in the stream is the string data)
|
|
+ * returns %0 if either match failes
|
|
+ */
|
|
+static int aa_is_nameX(struct aa_ext *e, enum aa_code code, void *data,
|
|
+ const char *name)
|
|
+{
|
|
+ void *pos = e->pos;
|
|
+ u16 size;
|
|
+ u32 ret;
|
|
+ /* check for presence of a tagname, and if present name size
|
|
+ * AA_NAME tag value is a u16 */
|
|
+ if (aa_is_X(e, AA_NAME, &size)) {
|
|
+ /* if a name is specified it must match. otherwise skip tag */
|
|
+ if (name && ((strlen(name) != size-1) ||
|
|
+ strncmp(name, (char *)e->pos, (size_t)size-1)))
|
|
+ goto fail;
|
|
+ e->pos += size;
|
|
+ } else if (name) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ /* now check if data actually matches */
|
|
+ ret = aa_is_X(e, code, data);
|
|
+ if (!ret)
|
|
+ goto fail;
|
|
+ return ret;
|
|
+
|
|
+fail:
|
|
+ e->pos = pos;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* macro to wrap error case to make a block of reads look nicer */
|
|
+#define AA_READ_X(E, C, D, N) \
|
|
+ do { \
|
|
+ u32 __ret; \
|
|
+ __ret = aa_is_nameX((E), (C), (D), (N)); \
|
|
+ if (!__ret) \
|
|
+ goto fail; \
|
|
+ } while (0)
|
|
+
|
|
+/**
|
|
+ * aa_activate_net_entry - unpacked serialized net entries
|
|
+ * @e: serialized data extent information
|
|
+ *
|
|
+ * Ignore/skips net entries if they are present in the serialized data
|
|
+ * stream. Network confinement rules are currently unsupported but some
|
|
+ * user side tools can generate them so they are currently ignored.
|
|
+ */
|
|
+static inline int aa_activate_net_entry(struct aa_ext *e)
|
|
+{
|
|
+ AA_READ_X(e, AA_STRUCT, NULL, "ne");
|
|
+ AA_READ_X(e, AA_U32, NULL, NULL);
|
|
+ AA_READ_X(e, AA_U32, NULL, NULL);
|
|
+ AA_READ_X(e, AA_U32, NULL, NULL);
|
|
+ AA_READ_X(e, AA_U16, NULL, NULL);
|
|
+ AA_READ_X(e, AA_U16, NULL, NULL);
|
|
+ AA_READ_X(e, AA_U32, NULL, NULL);
|
|
+ AA_READ_X(e, AA_U32, NULL, NULL);
|
|
+ AA_READ_X(e, AA_U16, NULL, NULL);
|
|
+ AA_READ_X(e, AA_U16, NULL, NULL);
|
|
+ /* interface name is optional so just ignore return code */
|
|
+ aa_is_nameX(e, AA_DYN_STRING, NULL, NULL);
|
|
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
|
|
+
|
|
+ return 1;
|
|
+fail:
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_activate_file_entry - unpack serialized file entry
|
|
+ * @e: serialized data extent information
|
|
+ *
|
|
+ * unpack the information used for a file ACL entry.
|
|
+ */
|
|
+static inline struct aa_entry *aa_activate_file_entry(struct aa_ext *e)
|
|
+{
|
|
+ struct aa_entry *entry = NULL;
|
|
+
|
|
+ if (!(entry = alloc_aa_entry()))
|
|
+ goto fail;
|
|
+
|
|
+ AA_READ_X(e, AA_STRUCT, NULL, "fe");
|
|
+ AA_READ_X(e, AA_DYN_STRING, &entry->filename, NULL);
|
|
+ AA_READ_X(e, AA_U32, &entry->mode, NULL);
|
|
+ AA_READ_X(e, AA_U32, &entry->type, NULL);
|
|
+
|
|
+ entry->extradata = aamatch_alloc(entry->type);
|
|
+ if (IS_ERR(entry->extradata)) {
|
|
+ entry->extradata = NULL;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ if (entry->extradata &&
|
|
+ aamatch_serialize(entry->extradata, e, aa_is_nameX) != 0) {
|
|
+ goto fail;
|
|
+ }
|
|
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
|
|
+
|
|
+ switch (entry->type) {
|
|
+ case aa_entry_literal:
|
|
+ AA_DEBUG("%s: %s [no pattern] mode=0x%x\n",
|
|
+ __FUNCTION__,
|
|
+ entry->filename,
|
|
+ entry->mode);
|
|
+ break;
|
|
+ case aa_entry_tailglob:
|
|
+ AA_DEBUG("%s: %s [tailglob] mode=0x%x\n",
|
|
+ __FUNCTION__,
|
|
+ entry->filename,
|
|
+ entry->mode);
|
|
+ break;
|
|
+ case aa_entry_pattern:
|
|
+ AA_DEBUG("%s: %s mode=0x%x\n",
|
|
+ __FUNCTION__,
|
|
+ entry->filename,
|
|
+ entry->mode);
|
|
+ break;
|
|
+ default:
|
|
+ AA_WARN("%s: INVALID entry_match_type %d\n",
|
|
+ __FUNCTION__,
|
|
+ (int)entry->type);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ return entry;
|
|
+
|
|
+fail:
|
|
+ aamatch_free(entry->extradata);
|
|
+ free_aa_entry(entry);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * check_rule_and_add - check a file rule is valid and add to a profile
|
|
+ * @file_entry: file rule to add
|
|
+ * @profile: profile to add the rule to
|
|
+ * @message: error message returned if the addition failes.
|
|
+ *
|
|
+ * perform consistency check to ensure that a file rule entry is valid.
|
|
+ * If the rule is valid it is added to the profile.
|
|
+ */
|
|
+static inline int check_rule_and_add(struct aa_entry *file_entry,
|
|
+ struct aaprofile *profile,
|
|
+ const char **message)
|
|
+{
|
|
+ /* verify consistency of x, px, ix, ux for entry against
|
|
+ possible duplicates for this entry */
|
|
+ int mode = AA_EXEC_MODIFIER_MASK(file_entry->mode);
|
|
+ int i;
|
|
+
|
|
+ if (mode && !(AA_MAY_EXEC & file_entry->mode)) {
|
|
+ *message = "inconsistent rule, x modifiers without x";
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* check that only 1 of the modifiers is set */
|
|
+ if (mode && (mode & (mode - 1))) {
|
|
+ *message = "inconsistent rule, multiple x modifiers";
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* ix -> m (required so that target exec binary may map itself) */
|
|
+ if (mode & AA_EXEC_INHERIT)
|
|
+ file_entry->mode |= AA_EXEC_MMAP;
|
|
+
|
|
+ list_add(&file_entry->list, &profile->file_entry);
|
|
+ profile->num_file_entries++;
|
|
+
|
|
+ mode = file_entry->mode;
|
|
+
|
|
+ /* Handle partitioned lists
|
|
+ * Chain entries onto sublists based on individual
|
|
+ * permission bits. This allows more rapid searching.
|
|
+ */
|
|
+ for (i = 0; i <= POS_AA_FILE_MAX; i++) {
|
|
+ if (mode & (1 << i))
|
|
+ /* profile->file_entryp[i] initially set to
|
|
+ * NULL in alloc_aaprofile() */
|
|
+ list_add(&file_entry->listp[i],
|
|
+ &profile->file_entryp[i]);
|
|
+ }
|
|
+
|
|
+ return 1;
|
|
+
|
|
+out:
|
|
+ free_aa_entry(file_entry);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#define AA_ENTRY_LIST(NAME) \
|
|
+ do { \
|
|
+ if (aa_is_nameX(e, AA_LIST, NULL, (NAME))) { \
|
|
+ rulename = ""; \
|
|
+ error_string = "Invalid file entry"; \
|
|
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) { \
|
|
+ struct aa_entry *file_entry; \
|
|
+ file_entry = aa_activate_file_entry(e); \
|
|
+ if (!file_entry) \
|
|
+ goto fail; \
|
|
+ if (!check_rule_and_add(file_entry, profile, \
|
|
+ &error_string)) { \
|
|
+ rulename = file_entry->filename; \
|
|
+ goto fail; \
|
|
+ } \
|
|
+ } \
|
|
+ } \
|
|
+ } while (0)
|
|
+
|
|
+/**
|
|
+ * aa_activate_profile - unpack a serialized profile
|
|
+ * @e: serialized data extent information
|
|
+ * @error: error code returned if unpacking fails
|
|
+ */
|
|
+static struct aaprofile *aa_activate_profile(struct aa_ext *e, ssize_t *error)
|
|
+{
|
|
+ struct aaprofile *profile = NULL;
|
|
+ const char *rulename = "";
|
|
+ const char *error_string = "Invalid Profile";
|
|
+
|
|
+ *error = -EPROTO;
|
|
+
|
|
+ profile = alloc_aaprofile();
|
|
+ if (!profile) {
|
|
+ error_string = "Could not allocate profile";
|
|
+ *error = -ENOMEM;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ /* check that we have the right struct being passed */
|
|
+ AA_READ_X(e, AA_STRUCT, NULL, "profile");
|
|
+ AA_READ_X(e, AA_DYN_STRING, &profile->name, NULL);
|
|
+
|
|
+ error_string = "Invalid flags";
|
|
+ /* per profile debug flags (debug, complain, audit) */
|
|
+ AA_READ_X(e, AA_STRUCT, NULL, "flags");
|
|
+ AA_READ_X(e, AA_U32, &(profile->flags.debug), NULL);
|
|
+ AA_READ_X(e, AA_U32, &(profile->flags.complain), NULL);
|
|
+ AA_READ_X(e, AA_U32, &(profile->flags.audit), NULL);
|
|
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
|
|
+
|
|
+ error_string = "Invalid capabilities";
|
|
+ AA_READ_X(e, AA_U32, &(profile->capabilities), NULL);
|
|
+
|
|
+ /* get the file entries. */
|
|
+ AA_ENTRY_LIST("pgent"); /* pcre rules */
|
|
+ AA_ENTRY_LIST("sgent"); /* simple globs */
|
|
+ AA_ENTRY_LIST("fent"); /* regular file entries */
|
|
+
|
|
+ /* get the net entries */
|
|
+ if (aa_is_nameX(e, AA_LIST, NULL, "net")) {
|
|
+ error_string = "Invalid net entry";
|
|
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
|
|
+ if (!aa_activate_net_entry(e))
|
|
+ goto fail;
|
|
+ }
|
|
+ }
|
|
+ rulename = "";
|
|
+
|
|
+ /* get subprofiles */
|
|
+ if (aa_is_nameX(e, AA_LIST, NULL, "hats")) {
|
|
+ error_string = "Invalid profile hat";
|
|
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
|
|
+ struct aaprofile *subprofile;
|
|
+ subprofile = aa_activate_profile(e, error);
|
|
+ if (!subprofile)
|
|
+ goto fail;
|
|
+ subprofile->parent = profile;
|
|
+ list_add(&subprofile->list, &profile->sub);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ error_string = "Invalid end of profile";
|
|
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
|
|
+
|
|
+ return profile;
|
|
+
|
|
+fail:
|
|
+ AA_WARN("%s: %s %s in profile %s\n", INTERFACE_ID, rulename,
|
|
+ error_string, profile && profile->name ? profile->name
|
|
+ : "unknown");
|
|
+
|
|
+ if (profile) {
|
|
+ free_aaprofile(profile);
|
|
+ profile = NULL;
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_activate_top_profile - unpack a serialized base profile
|
|
+ * @e: serialized data extent information
|
|
+ * @error: error code returned if unpacking fails
|
|
+ *
|
|
+ * check interface version unpack a profile and all its hats and patch
|
|
+ * in any extra information that the profile needs.
|
|
+ */
|
|
+static void *aa_activate_top_profile(struct aa_ext *e, ssize_t *error)
|
|
+{
|
|
+ struct aaprofile *profile = NULL;
|
|
+
|
|
+ /* get the interface version */
|
|
+ if (!aa_is_nameX(e, AA_U32, &e->version, "version")) {
|
|
+ AA_WARN("%s: version missing\n", INTERFACE_ID);
|
|
+ *error = -EPROTONOSUPPORT;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ /* check that the interface version is currently supported */
|
|
+ if (e->version != 2) {
|
|
+ AA_WARN("%s: unsupported interface version (%d)\n",
|
|
+ INTERFACE_ID, e->version);
|
|
+ *error = -EPROTONOSUPPORT;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ profile = aa_activate_profile(e, error);
|
|
+ if (!profile)
|
|
+ goto fail;
|
|
+
|
|
+ if (!list_empty(&profile->sub) || profile->flags.complain) {
|
|
+ if (attach_nullprofile(profile))
|
|
+ goto fail;
|
|
+ }
|
|
+ return profile;
|
|
+
|
|
+fail:
|
|
+ free_aaprofile(profile);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_file_prof_add - add a new profile to the profile list
|
|
+ * @data: serialized data stream
|
|
+ * @size: size of the serialized data stream
|
|
+ *
|
|
+ * unpack and add a profile to the profile list. Return %0 or error
|
|
+ */
|
|
+ssize_t aa_file_prof_add(void *data, size_t size)
|
|
+{
|
|
+ struct aaprofile *profile = NULL;
|
|
+
|
|
+ struct aa_ext e = {
|
|
+ .start = data,
|
|
+ .end = data + size,
|
|
+ .pos = data
|
|
+ };
|
|
+ ssize_t error;
|
|
+
|
|
+ profile = aa_activate_top_profile(&e, &error);
|
|
+ if (!profile) {
|
|
+ AA_DEBUG("couldn't activate profile\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* aa_activate_top_profile allocates profile with initial 1 count
|
|
+ * aa_profilelist_add transfers that ref to profile list without
|
|
+ * further incrementing
|
|
+ */
|
|
+ if (aa_profilelist_add(profile)) {
|
|
+ error = size;
|
|
+ } else {
|
|
+ AA_WARN("trying to add profile (%s) that already exists.\n",
|
|
+ profile->name);
|
|
+ put_aaprofile(profile);
|
|
+ error = -EEXIST;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_file_prof_repl - replace a profile on the profile list
|
|
+ * @udata: serialized data stream
|
|
+ * @size: size of the serialized data stream
|
|
+ *
|
|
+ * unpack and replace a profile on the profile list and uses of that profile
|
|
+ * by any subdomain. If the profile does not exist on the profile list
|
|
+ * it is added. Return %0 or error.
|
|
+ */
|
|
+ssize_t aa_file_prof_repl(void *udata, size_t size)
|
|
+{
|
|
+ struct aa_taskreplace_data data;
|
|
+ struct aa_ext e = {
|
|
+ .start = udata,
|
|
+ .end = udata + size,
|
|
+ .pos = udata
|
|
+ };
|
|
+
|
|
+ ssize_t error;
|
|
+
|
|
+ data.new_profile = aa_activate_top_profile(&e, &error);
|
|
+ if (!data.new_profile) {
|
|
+ AA_DEBUG("couldn't activate profile\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* Refcount on data.new_profile is 1 (aa_activate_top_profile).
|
|
+ *
|
|
+ * This reference will be inherited by aa_profilelist_replace for it's
|
|
+ * profile list reference but this isn't sufficient.
|
|
+ *
|
|
+ * Another replace (*for-same-profile*) may race us here.
|
|
+ * Task A calls aa_profilelist_replace(new_profile) and is interrupted.
|
|
+ * Task B old_profile = aa_profilelist_replace() will return task A's
|
|
+ * new_profile with the count of 1. If task B proceeeds to put this
|
|
+ * profile it will dissapear from under task A.
|
|
+ *
|
|
+ * Grab extra reference on new_profile to prevent this
|
|
+ */
|
|
+
|
|
+ get_aaprofile(data.new_profile);
|
|
+
|
|
+ data.old_profile = aa_profilelist_replace(data.new_profile);
|
|
+
|
|
+ /* If there was an old profile, find all currently executing tasks
|
|
+ * using this profile and replace the old profile with the new.
|
|
+ */
|
|
+ if (data.old_profile) {
|
|
+ AA_DEBUG("%s: try to replace profile (%p)%s\n",
|
|
+ __FUNCTION__,
|
|
+ data.old_profile,
|
|
+ data.old_profile->name);
|
|
+
|
|
+ aa_subdomainlist_iterate(taskreplace_iter, (void *)&data);
|
|
+
|
|
+ /* it's off global list, and we are done replacing */
|
|
+ put_aaprofile(data.old_profile);
|
|
+ }
|
|
+
|
|
+ /* release extra reference obtained above (race) */
|
|
+ put_aaprofile(data.new_profile);
|
|
+
|
|
+ error = size;
|
|
+
|
|
+out:
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_file_prof_remove - remove a profile from the system
|
|
+ * @name: name of the profile to remove
|
|
+ * @size: size of the name
|
|
+ *
|
|
+ * remove a profile from the profile list and all subdomain references
|
|
+ * to said profile. Return %0 on success, else error.
|
|
+ */
|
|
+ssize_t aa_file_prof_remove(const char *name, size_t size)
|
|
+{
|
|
+ struct aaprofile *old_profile;
|
|
+
|
|
+ /* if the old profile exists it will be removed from the list and
|
|
+ * a reference is returned.
|
|
+ */
|
|
+ old_profile = aa_profilelist_remove(name);
|
|
+
|
|
+ if (old_profile) {
|
|
+ /* remove profile from any tasks using it */
|
|
+ aa_subdomainlist_iterate(taskremove_iter, (void *)old_profile);
|
|
+
|
|
+ /* drop reference obtained by aa_profilelist_remove */
|
|
+ put_aaprofile(old_profile);
|
|
+ } else {
|
|
+ AA_WARN("%s: trying to remove profile (%s) that "
|
|
+ "doesn't exist - skipping.\n", __FUNCTION__, name);
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ return size;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * free_aaprofile_kref - free aaprofile by kref (called by put_aaprofile)
|
|
+ * @kr: kref callback for freeing of a profile
|
|
+ */
|
|
+void free_aaprofile_kref(struct kref *kr)
|
|
+{
|
|
+ struct aaprofile *p=container_of(kr, struct aaprofile, count);
|
|
+
|
|
+ call_rcu(&p->rcu, free_aaprofile_rcu);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * free_aaprofile - free aaprofile structure
|
|
+ * @profile: the profile to free
|
|
+ *
|
|
+ * free a profile, its file entries hats and null_profile. All references
|
|
+ * to the profile, its hats and null_profile must have been put.
|
|
+ * If the profile was referenced by a subdomain free_aaprofile should be
|
|
+ * called from an rcu callback routine.
|
|
+ */
|
|
+void free_aaprofile(struct aaprofile *profile)
|
|
+{
|
|
+ struct aa_entry *ent, *tmp;
|
|
+ struct aaprofile *p, *ptmp;
|
|
+
|
|
+ AA_DEBUG("%s(%p)\n", __FUNCTION__, profile);
|
|
+
|
|
+ if (!profile)
|
|
+ return;
|
|
+
|
|
+ /* profile is still on global profile list -- invalid */
|
|
+ if (!list_empty(&profile->list)) {
|
|
+ AA_ERROR("%s: internal error, "
|
|
+ "profile '%s' still on global list\n",
|
|
+ __FUNCTION__,
|
|
+ profile->name);
|
|
+ BUG();
|
|
+ }
|
|
+
|
|
+ list_for_each_entry_safe(ent, tmp, &profile->file_entry, list) {
|
|
+ if (ent->filename)
|
|
+ AA_DEBUG("freeing aa_entry: %p %s\n",
|
|
+ ent->filename, ent->filename);
|
|
+ list_del_init(&ent->list);
|
|
+ free_aa_entry(ent);
|
|
+ }
|
|
+
|
|
+ /* use free_aaprofile instead of put_aaprofile to destroy the
|
|
+ * null_profile, because the null_profile use the same reference
|
|
+ * counting as hats, ie. the count goes to the base profile.
|
|
+ */
|
|
+ free_aaprofile(profile->null_profile);
|
|
+ list_for_each_entry_safe(p, ptmp, &profile->sub, list) {
|
|
+ list_del_init(&p->list);
|
|
+ p->parent = NULL;
|
|
+ put_aaprofile(p);
|
|
+ }
|
|
+
|
|
+ if (profile->name) {
|
|
+ AA_DEBUG("%s: %s\n", __FUNCTION__, profile->name);
|
|
+ kfree(profile->name);
|
|
+ }
|
|
+
|
|
+ kfree(profile);
|
|
+}
|
|
Index: linux-2.6.19/security/apparmor/module_interface.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/module_interface.h
|
|
@@ -0,0 +1,37 @@
|
|
+#ifndef __MODULEINTERFACE_H
|
|
+#define __MODULEINTERFACE_H
|
|
+
|
|
+/* Codes of the types of basic structures that are understood */
|
|
+#define AA_CODE_BYTE (sizeof(u8))
|
|
+#define INTERFACE_ID "INTERFACE"
|
|
+
|
|
+#define APPARMOR_INTERFACE_VERSION 2
|
|
+
|
|
+enum aa_code {
|
|
+ AA_U8,
|
|
+ AA_U16,
|
|
+ AA_U32,
|
|
+ AA_U64,
|
|
+ AA_NAME, /* same as string except it is items name */
|
|
+ AA_DYN_STRING,
|
|
+ AA_STATIC_BLOB,
|
|
+ AA_STRUCT,
|
|
+ AA_STRUCTEND,
|
|
+ AA_LIST,
|
|
+ AA_LISTEND,
|
|
+ AA_OFFSET,
|
|
+ AA_BAD
|
|
+};
|
|
+
|
|
+/* aa_ext tracks the kernel buffer and read position in it. The interface
|
|
+ * data is copied into a kernel buffer in apparmorfs and then handed off to
|
|
+ * the activate routines.
|
|
+ */
|
|
+struct aa_ext {
|
|
+ void *start;
|
|
+ void *end;
|
|
+ void *pos; /* pointer to current position in the buffer */
|
|
+ u32 version;
|
|
+};
|
|
+
|
|
+#endif /* __MODULEINTERFACE_H */
|
|
Index: linux-2.6.19/security/apparmor/procattr.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/procattr.c
|
|
@@ -0,0 +1,332 @@
|
|
+/*
|
|
+ * Copyright (C) 2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * AppArmor /proc/pid/attr handling
|
|
+ */
|
|
+
|
|
+/* for isspace */
|
|
+#include <linux/ctype.h>
|
|
+
|
|
+#include "apparmor.h"
|
|
+#include "inline.h"
|
|
+
|
|
+size_t aa_getprocattr(struct aaprofile *active, char *str, size_t size)
|
|
+{
|
|
+ int error = -EACCES; /* default to a perm denied */
|
|
+ size_t len;
|
|
+
|
|
+ if (active) {
|
|
+ size_t lena, lenm, lenp = 0;
|
|
+ const char *enforce_str = " (enforce)";
|
|
+ const char *complain_str = " (complain)";
|
|
+ const char *mode_str =
|
|
+ PROFILE_COMPLAIN(active) ? complain_str : enforce_str;
|
|
+
|
|
+ lenm = strlen(mode_str);
|
|
+
|
|
+ lena = strlen(active->name);
|
|
+
|
|
+ len = lena;
|
|
+ if (IN_SUBPROFILE(active)) {
|
|
+ lenp = strlen(BASE_PROFILE(active)->name);
|
|
+ len += (lenp + 1); /* +1 for ^ */
|
|
+ }
|
|
+ /* DONT null terminate strings we output via proc */
|
|
+ len += (lenm + 1); /* for \n */
|
|
+
|
|
+ if (len <= size) {
|
|
+ if (lenp) {
|
|
+ memcpy(str, BASE_PROFILE(active)->name,
|
|
+ lenp);
|
|
+ str += lenp;
|
|
+ *str++ = '^';
|
|
+ }
|
|
+
|
|
+ memcpy(str, active->name, lena);
|
|
+ str += lena;
|
|
+ memcpy(str, mode_str, lenm);
|
|
+ str += lenm;
|
|
+ *str++ = '\n';
|
|
+ error = len;
|
|
+ } else if (size == 0) {
|
|
+ error = len;
|
|
+ } else {
|
|
+ error = -ERANGE;
|
|
+ }
|
|
+ } else {
|
|
+ const char *unconstrained_str = "unconstrained\n";
|
|
+ len = strlen(unconstrained_str);
|
|
+
|
|
+ /* DONT null terminate strings we output via proc */
|
|
+ if (len <= size) {
|
|
+ memcpy(str, unconstrained_str, len);
|
|
+ error = len;
|
|
+ } else if (size == 0) {
|
|
+ error = len;
|
|
+ } else {
|
|
+ error = -ERANGE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+
|
|
+}
|
|
+
|
|
+int aa_setprocattr_changehat(char *hatinfo, size_t infosize)
|
|
+{
|
|
+ int error = -EINVAL;
|
|
+ char *token = NULL, *hat, *smagic, *tmp;
|
|
+ u32 magic;
|
|
+ int rc, len, consumed;
|
|
+ unsigned long flags;
|
|
+
|
|
+ AA_DEBUG("%s: %p %zd\n", __FUNCTION__, hatinfo, infosize);
|
|
+
|
|
+ /* strip leading white space */
|
|
+ while (infosize && isspace(*hatinfo)) {
|
|
+ hatinfo++;
|
|
+ infosize--;
|
|
+ }
|
|
+
|
|
+ if (infosize == 0)
|
|
+ goto out;
|
|
+
|
|
+ /*
|
|
+ * Copy string to a new buffer so we can play with it
|
|
+ * It may be zero terminated but we add a trailing 0
|
|
+ * for 100% safety
|
|
+ */
|
|
+ token = kmalloc(infosize + 1, GFP_KERNEL);
|
|
+
|
|
+ if (!token) {
|
|
+ error = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ memcpy(token, hatinfo, infosize);
|
|
+ token[infosize] = 0;
|
|
+
|
|
+ /* error is INVAL until we have at least parsed something */
|
|
+ error = -EINVAL;
|
|
+
|
|
+ tmp = token;
|
|
+ while (*tmp && *tmp != '^') {
|
|
+ tmp++;
|
|
+ }
|
|
+
|
|
+ if (!*tmp || tmp == token) {
|
|
+ AA_WARN("%s: Invalid input '%s'\n", __FUNCTION__, token);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* split magic and hat into two strings */
|
|
+ *tmp = 0;
|
|
+ smagic = token;
|
|
+
|
|
+ /*
|
|
+ * Initially set consumed=strlen(magic), as if sscanf
|
|
+ * consumes all input via the %x it will not process the %n
|
|
+ * directive. Otherwise, if sscanf does not consume all the
|
|
+ * input it will process the %n and update consumed.
|
|
+ */
|
|
+ consumed = len = strlen(smagic);
|
|
+
|
|
+ rc = sscanf(smagic, "%x%n", &magic, &consumed);
|
|
+
|
|
+ if (rc != 1 || consumed != len) {
|
|
+ AA_WARN("%s: Invalid hex magic %s\n",
|
|
+ __FUNCTION__,
|
|
+ smagic);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ hat = tmp + 1;
|
|
+
|
|
+ if (!*hat)
|
|
+ hat = NULL;
|
|
+
|
|
+ if (!hat && !magic) {
|
|
+ AA_WARN("%s: Invalid input, NULL hat and NULL magic\n",
|
|
+ __FUNCTION__);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ AA_DEBUG("%s: Magic 0x%x Hat '%s'\n",
|
|
+ __FUNCTION__, magic, hat ? hat : NULL);
|
|
+
|
|
+ spin_lock_irqsave(&sd_lock, flags);
|
|
+ error = aa_change_hat(hat, magic);
|
|
+ spin_unlock_irqrestore(&sd_lock, flags);
|
|
+
|
|
+out:
|
|
+ if (token) {
|
|
+ memset(token, 0, infosize);
|
|
+ kfree(token);
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+int aa_setprocattr_setprofile(struct task_struct *p, char *profilename,
|
|
+ size_t profilesize)
|
|
+{
|
|
+ int error = -EINVAL;
|
|
+ struct aaprofile *profile = NULL;
|
|
+ struct subdomain *sd;
|
|
+ char *name = NULL;
|
|
+ unsigned long flags;
|
|
+
|
|
+ AA_DEBUG("%s: current %s(%d)\n",
|
|
+ __FUNCTION__, current->comm, current->pid);
|
|
+
|
|
+ /* strip leading white space */
|
|
+ while (profilesize && isspace(*profilename)) {
|
|
+ profilename++;
|
|
+ profilesize--;
|
|
+ }
|
|
+
|
|
+ if (profilesize == 0)
|
|
+ goto out;
|
|
+
|
|
+ /*
|
|
+ * Copy string to a new buffer so we guarantee it is zero
|
|
+ * terminated
|
|
+ */
|
|
+ name = kmalloc(profilesize + 1, GFP_KERNEL);
|
|
+
|
|
+ if (!name) {
|
|
+ error = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ strncpy(name, profilename, profilesize);
|
|
+ name[profilesize] = 0;
|
|
+
|
|
+ repeat:
|
|
+ if (strcmp(name, "unconstrained") != 0) {
|
|
+ profile = aa_profilelist_find(name);
|
|
+ if (!profile) {
|
|
+ AA_WARN("%s: Unable to switch task %s(%d) to profile"
|
|
+ "'%s'. No such profile.\n",
|
|
+ __FUNCTION__,
|
|
+ p->comm, p->pid,
|
|
+ name);
|
|
+
|
|
+ error = -EINVAL;
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&sd_lock, flags);
|
|
+
|
|
+ sd = AA_SUBDOMAIN(p->security);
|
|
+
|
|
+ /* switch to unconstrained */
|
|
+ if (!profile) {
|
|
+ if (__aa_is_confined(sd)) {
|
|
+ AA_WARN("%s: Unconstraining task %s(%d) "
|
|
+ "profile %s active %s\n",
|
|
+ __FUNCTION__,
|
|
+ p->comm, p->pid,
|
|
+ BASE_PROFILE(sd->active)->name,
|
|
+ sd->active->name);
|
|
+
|
|
+ aa_switch_unconfined(sd);
|
|
+ } else {
|
|
+ AA_WARN("%s: task %s(%d) "
|
|
+ "is already unconstrained\n",
|
|
+ __FUNCTION__, p->comm, p->pid);
|
|
+ }
|
|
+ } else {
|
|
+ if (!sd) {
|
|
+ /* this task was created before module was
|
|
+ * loaded, allocate a subdomain
|
|
+ */
|
|
+ AA_WARN("%s: task %s(%d) has no subdomain\n",
|
|
+ __FUNCTION__, p->comm, p->pid);
|
|
+
|
|
+ /* unlock so we can safely GFP_KERNEL */
|
|
+ spin_unlock_irqrestore(&sd_lock, flags);
|
|
+
|
|
+ sd = alloc_subdomain(p);
|
|
+ if (!sd) {
|
|
+ AA_WARN("%s: Unable to allocate subdomain for "
|
|
+ "task %s(%d). Cannot confine task to "
|
|
+ "profile %s\n",
|
|
+ __FUNCTION__,
|
|
+ p->comm, p->pid,
|
|
+ name);
|
|
+
|
|
+ error = -ENOMEM;
|
|
+ put_aaprofile(profile);
|
|
+
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&sd_lock, flags);
|
|
+ if (!AA_SUBDOMAIN(p->security)) {
|
|
+ p->security = sd;
|
|
+ } else { /* race */
|
|
+ free_subdomain(sd);
|
|
+ sd = AA_SUBDOMAIN(p->security);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* ensure the profile hasn't been replaced */
|
|
+
|
|
+ if (unlikely(profile->isstale)) {
|
|
+ WARN_ON(profile == null_complain_profile);
|
|
+
|
|
+ /* drop refcnt obtained from earlier get_aaprofile */
|
|
+ put_aaprofile(profile);
|
|
+ profile = aa_profilelist_find(name);
|
|
+
|
|
+ if (!profile) {
|
|
+ /* Race, profile was removed. */
|
|
+ spin_unlock_irqrestore(&sd_lock, flags);
|
|
+ goto repeat;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* we do not do a normal task replace since we are not
|
|
+ * replacing with the same profile.
|
|
+ * If existing process is in a hat, it will be moved
|
|
+ * into the new parent profile, even if this new
|
|
+ * profile has a identical named hat.
|
|
+ */
|
|
+
|
|
+ AA_WARN("%s: Switching task %s(%d) "
|
|
+ "profile %s active %s to new profile %s\n",
|
|
+ __FUNCTION__,
|
|
+ p->comm, p->pid,
|
|
+ sd->active ? BASE_PROFILE(sd->active)->name :
|
|
+ "unconstrained",
|
|
+ sd->active ? sd->active->name : "unconstrained",
|
|
+ name);
|
|
+
|
|
+ aa_switch(sd, profile);
|
|
+
|
|
+ put_aaprofile(profile); /* drop ref we obtained above
|
|
+ * from aa_profilelist_find
|
|
+ */
|
|
+
|
|
+ /* Reset magic in case we were in a subhat before
|
|
+ * This is the only case where we zero the magic after
|
|
+ * calling aa_switch
|
|
+ */
|
|
+ sd->hat_magic = 0;
|
|
+ }
|
|
+
|
|
+ spin_unlock_irqrestore(&sd_lock, flags);
|
|
+
|
|
+ error = 0;
|
|
+out:
|
|
+ kfree(name);
|
|
+
|
|
+ return error;
|
|
+}
|
|
Index: linux-2.6.19/security/apparmor/shared.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/shared.h
|
|
@@ -0,0 +1,46 @@
|
|
+/*
|
|
+ * Copyright (C) 2000, 2001, 2004, 2005 Novell/SUSE
|
|
+ *
|
|
+ * Immunix AppArmor LSM
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ */
|
|
+
|
|
+#ifndef _SHARED_H
|
|
+#define _SHARED_H
|
|
+
|
|
+/* start of system offsets */
|
|
+#define POS_AA_FILE_MIN 0
|
|
+#define POS_AA_MAY_EXEC POS_AA_FILE_MIN
|
|
+#define POS_AA_MAY_WRITE (POS_AA_MAY_EXEC + 1)
|
|
+#define POS_AA_MAY_READ (POS_AA_MAY_WRITE + 1)
|
|
+#define POS_AA_MAY_APPEND (POS_AA_MAY_READ + 1)
|
|
+/* end of system offsets */
|
|
+
|
|
+#define POS_AA_MAY_LINK (POS_AA_MAY_APPEND + 1)
|
|
+#define POS_AA_EXEC_INHERIT (POS_AA_MAY_LINK + 1)
|
|
+#define POS_AA_EXEC_UNCONSTRAINED (POS_AA_EXEC_INHERIT + 1)
|
|
+#define POS_AA_EXEC_PROFILE (POS_AA_EXEC_UNCONSTRAINED + 1)
|
|
+#define POS_AA_EXEC_MMAP (POS_AA_EXEC_PROFILE + 1)
|
|
+#define POS_AA_EXEC_UNSAFE (POS_AA_EXEC_MMAP + 1)
|
|
+#define POS_AA_FILE_MAX POS_AA_EXEC_UNSAFE
|
|
+
|
|
+/* Modeled after MAY_READ, MAY_WRITE, MAY_EXEC def'ns */
|
|
+#define AA_MAY_EXEC (0x01 << POS_AA_MAY_EXEC)
|
|
+#define AA_MAY_WRITE (0x01 << POS_AA_MAY_WRITE)
|
|
+#define AA_MAY_READ (0x01 << POS_AA_MAY_READ)
|
|
+#define AA_MAY_LINK (0x01 << POS_AA_MAY_LINK)
|
|
+#define AA_EXEC_INHERIT (0x01 << POS_AA_EXEC_INHERIT)
|
|
+#define AA_EXEC_UNCONSTRAINED (0x01 << POS_AA_EXEC_UNCONSTRAINED)
|
|
+#define AA_EXEC_PROFILE (0x01 << POS_AA_EXEC_PROFILE)
|
|
+#define AA_EXEC_MMAP (0x01 << POS_AA_EXEC_MMAP)
|
|
+#define AA_EXEC_UNSAFE (0x01 << POS_AA_EXEC_UNSAFE)
|
|
+
|
|
+#define AA_EXEC_MODIFIERS (AA_EXEC_INHERIT | \
|
|
+ AA_EXEC_UNCONSTRAINED | \
|
|
+ AA_EXEC_PROFILE)
|
|
+
|
|
+#endif /* _SHARED_H */
|
|
Index: linux-2.6.19/security/apparmor/match/Kbuild
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/match/Kbuild
|
|
@@ -0,0 +1,6 @@
|
|
+# Makefile for AppArmor aamatch submodule
|
|
+#
|
|
+
|
|
+obj-$(CONFIG_SECURITY_APPARMOR) += aamatch_pcre.o
|
|
+
|
|
+aamatch_pcre-y := match_pcre.o pcre_exec.o
|
|
Index: linux-2.6.19/security/apparmor/match/Makefile
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/match/Makefile
|
|
@@ -0,0 +1,5 @@
|
|
+# Makefile for AppArmor aamatch submodule
|
|
+#
|
|
+obj-$(CONFIG_SECURITY_APPARMOR) += aamatch_pcre.o
|
|
+
|
|
+aamatch_pcre-y := match_pcre.o pcre_exec.o
|
|
Index: linux-2.6.19/security/apparmor/match/match.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/match/match.h
|
|
@@ -0,0 +1,132 @@
|
|
+/*
|
|
+ * Copyright (C) 2002-2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * AppArmor submodule (match) prototypes
|
|
+ */
|
|
+
|
|
+#ifndef __MATCH_H
|
|
+#define __MATCH_H
|
|
+
|
|
+#include "../module_interface.h"
|
|
+#include "../apparmor.h"
|
|
+
|
|
+/* The following functions implement an interface used by the primary
|
|
+ * AppArmor module to perform name matching (n.b. "AppArmor" was previously
|
|
+ * called "SubDomain").
|
|
+
|
|
+ * aamatch_alloc
|
|
+ * aamatch_free
|
|
+ * aamatch_features
|
|
+ * aamatch_serialize
|
|
+ * aamatch_match
|
|
+ *
|
|
+ * The intent is for the primary module to export (via virtual fs entries)
|
|
+ * the features provided by the submodule (aamatch_features) so that the
|
|
+ * parser may only load policy that can be supported.
|
|
+ *
|
|
+ * The primary module will call aamatch_serialize to allow the submodule
|
|
+ * to consume submodule specific data from parser data stream and will call
|
|
+ * aamatch_match to determine if a pathname matches an aa_entry.
|
|
+ */
|
|
+
|
|
+typedef int (*aamatch_serializecb)
|
|
+ (struct aa_ext *, enum aa_code, void *, const char *);
|
|
+
|
|
+/**
|
|
+ * aamatch_alloc: allocate extradata (if necessary)
|
|
+ * @type: type of entry being allocated
|
|
+ * Return value: NULL indicates no data was allocated (ERR_PTR(x) on error)
|
|
+ */
|
|
+extern void* aamatch_alloc(enum entry_match_type type);
|
|
+
|
|
+/**
|
|
+ * aamatch_free: release data allocated by aamatch_alloc
|
|
+ * @entry_extradata: data previously allocated by aamatch_alloc
|
|
+ */
|
|
+extern void aamatch_free(void *entry_extradata);
|
|
+
|
|
+/**
|
|
+ * aamatch_features: return match types supported
|
|
+ * Return value: space seperated string (of types supported - use type=value
|
|
+ * to indicate variants of a type)
|
|
+ */
|
|
+extern const char* aamatch_features(void);
|
|
+
|
|
+/**
|
|
+ * aamatch_serialize: serialize extradata
|
|
+ * @entry_extradata: data previously allocated by aamatch_alloc
|
|
+ * @e: input stream
|
|
+ * @cb: callback fn (consume incoming data stream)
|
|
+ * Return value: 0 success, -ve error
|
|
+ */
|
|
+extern int aamatch_serialize(void *entry_extradata, struct aa_ext *e,
|
|
+ aamatch_serializecb cb);
|
|
+
|
|
+/**
|
|
+ * aamatch_match: determine if pathname matches entry
|
|
+ * @pathname: pathname to verify
|
|
+ * @entry_name: entry name
|
|
+ * @type: type of entry
|
|
+ * @entry_extradata: data previously allocated by aamatch_alloc
|
|
+ * Return value: 1 match, 0 othersise
|
|
+ */
|
|
+extern unsigned int aamatch_match(const char *pathname, const char *entry_name,
|
|
+ enum entry_match_type type,
|
|
+ void *entry_extradata);
|
|
+
|
|
+
|
|
+/**
|
|
+ * sd_getmatch_type - return string representation of entry_match_type
|
|
+ * @type: entry match type
|
|
+ */
|
|
+static inline const char *sd_getmatch_type(enum entry_match_type type)
|
|
+{
|
|
+ const char *names[] = {
|
|
+ "aa_entry_literal",
|
|
+ "aa_entry_tailglob",
|
|
+ "aa_entry_pattern",
|
|
+ "aa_entry_invalid"
|
|
+ };
|
|
+
|
|
+ if (type >= aa_entry_invalid) {
|
|
+ type = aa_entry_invalid;
|
|
+ }
|
|
+
|
|
+ return names[type];
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aamatch_match_common - helper function to check if a pathname matches
|
|
+ * a literal/tailglob
|
|
+ * @path: path requested to search for
|
|
+ * @entry_name: name from aa_entry
|
|
+ * @type: type of entry
|
|
+ */
|
|
+static inline int aamatch_match_common(const char *path,
|
|
+ const char *entry_name,
|
|
+ enum entry_match_type type)
|
|
+{
|
|
+ int retval;
|
|
+
|
|
+ /* literal, no pattern matching characters */
|
|
+ if (type == aa_entry_literal) {
|
|
+ retval = (strcmp(entry_name, path) == 0);
|
|
+ /* trailing ** glob pattern */
|
|
+ } else if (type == aa_entry_tailglob) {
|
|
+ retval = (strncmp(entry_name, path,
|
|
+ strlen(entry_name) - 2) == 0);
|
|
+ } else {
|
|
+ AA_WARN("%s: Invalid entry_match_type %d\n",
|
|
+ __FUNCTION__, type);
|
|
+ retval = 0;
|
|
+ }
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+#endif /* __MATCH_H */
|
|
Index: linux-2.6.19/security/apparmor/match/match_default.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/match/match_default.c
|
|
@@ -0,0 +1,57 @@
|
|
+/*
|
|
+ * Copyright (C) 2002-2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * http://forge.novell.com/modules/xfmod/project/?apparmor
|
|
+ *
|
|
+ * AppArmor default match submodule (literal and tailglob)
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include "match.h"
|
|
+
|
|
+static const char *features="literal tailglob";
|
|
+
|
|
+void* aamatch_alloc(enum entry_match_type type)
|
|
+{
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+void aamatch_free(void *ptr)
|
|
+{
|
|
+}
|
|
+
|
|
+const char *aamatch_features(void)
|
|
+{
|
|
+ return features;
|
|
+}
|
|
+
|
|
+int aamatch_serialize(void *entry_extradata, struct aa_ext *e,
|
|
+ aamatch_serializecb cb)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+unsigned int aamatch_match(const char *pathname, const char *entry_name,
|
|
+ enum entry_match_type type, void *entry_extradata)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = aamatch_match_common(pathname, entry_name, type);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+EXPORT_SYMBOL_GPL(aamatch_alloc);
|
|
+EXPORT_SYMBOL_GPL(aamatch_free);
|
|
+EXPORT_SYMBOL_GPL(aamatch_features);
|
|
+EXPORT_SYMBOL_GPL(aamatch_serialize);
|
|
+EXPORT_SYMBOL_GPL(aamatch_match);
|
|
+
|
|
+MODULE_DESCRIPTION("AppArmor match module (aamatch) [default]");
|
|
+MODULE_AUTHOR("Tony Jones <tonyj@suse.de>");
|
|
+MODULE_LICENSE("GPL");
|
|
Index: linux-2.6.19/security/apparmor/match/match_pcre.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/match/match_pcre.c
|
|
@@ -0,0 +1,169 @@
|
|
+/*
|
|
+ * Copyright (C) 2002-2005 Novell/SUSE
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation, version 2 of the
|
|
+ * License.
|
|
+ *
|
|
+ * http://forge.novell.com/modules/xfmod/project/?apparmor
|
|
+ *
|
|
+ * AppArmor aamatch submodule (w/ pattern expansion).
|
|
+ *
|
|
+ * This module makes use of a slightly modified version of the PCRE
|
|
+ * library developed by Philip Hazel <ph10@cam.ac.uk>. See the files
|
|
+ * pcre_* in this directory.
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include "match.h"
|
|
+#include "pcre_exec.h"
|
|
+#include "pcre_tables.h"
|
|
+
|
|
+static const char *features="literal tailglob pattern=pcre";
|
|
+
|
|
+struct aamatch_entry
|
|
+{
|
|
+ char *pattern;
|
|
+ pcre *compiled;
|
|
+};
|
|
+
|
|
+void* aamatch_alloc(enum entry_match_type entry_type)
|
|
+{
|
|
+void *ptr=NULL;
|
|
+
|
|
+ if (entry_type == aa_entry_pattern) {
|
|
+ ptr = kmalloc(sizeof(struct aamatch_entry), GFP_KERNEL);
|
|
+ if (ptr)
|
|
+ memset(ptr, 0, sizeof(struct aamatch_entry));
|
|
+ else
|
|
+ ptr=ERR_PTR(-ENOMEM);
|
|
+ } else if (entry_type != aa_entry_literal &&
|
|
+ entry_type != aa_entry_tailglob) {
|
|
+ ptr = ERR_PTR(-EINVAL);
|
|
+ }
|
|
+
|
|
+ return ptr;
|
|
+}
|
|
+
|
|
+void aamatch_free(void *ptr)
|
|
+{
|
|
+ if (ptr) {
|
|
+ struct aamatch_entry *ed = (struct aamatch_entry *) ptr;
|
|
+ kfree(ed->pattern);
|
|
+ kfree(ed->compiled); /* allocated by AA_READ_X */
|
|
+ }
|
|
+ kfree(ptr);
|
|
+}
|
|
+
|
|
+const char *aamatch_features(void)
|
|
+{
|
|
+ return features;
|
|
+}
|
|
+
|
|
+int aamatch_serialize(void *entry_extradata, struct aa_ext *e,
|
|
+ aamatch_serializecb cb)
|
|
+{
|
|
+#define AA_READ_X(E, C, D, N) \
|
|
+ do { \
|
|
+ if (!cb((E), (C), (D), (N))) { \
|
|
+ error = -EINVAL; \
|
|
+ goto done; \
|
|
+ }\
|
|
+ } while (0)
|
|
+
|
|
+ int error = 0;
|
|
+ u32 size, magic, opts;
|
|
+ u8 t_char;
|
|
+ struct aamatch_entry *ed = (struct aamatch_entry *) entry_extradata;
|
|
+
|
|
+ if (ed == NULL)
|
|
+ goto done;
|
|
+
|
|
+ AA_READ_X(e, AA_DYN_STRING, &ed->pattern, NULL);
|
|
+
|
|
+ /* size determines the real size of the pcre struct,
|
|
+ it is size_t - sizeof(pcre) on user side.
|
|
+ uschar must be the same in user and kernel space */
|
|
+ /* check that we are processing the correct structure */
|
|
+ AA_READ_X(e, AA_STRUCT, NULL, "pcre");
|
|
+ AA_READ_X(e, AA_U32, &size, NULL);
|
|
+ AA_READ_X(e, AA_U32, &magic, NULL);
|
|
+
|
|
+ /* the allocation of pcre is delayed because it depends on the size
|
|
+ * of the pattern */
|
|
+ ed->compiled = (pcre *) kmalloc(size + sizeof(pcre), GFP_KERNEL);
|
|
+ if (!ed->compiled) {
|
|
+ error = -ENOMEM;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ memset(ed->compiled, 0, size + sizeof(pcre));
|
|
+ ed->compiled->magic_number = magic;
|
|
+ ed->compiled->size = size + sizeof(pcre);
|
|
+
|
|
+ AA_READ_X(e, AA_U32, &opts, NULL);
|
|
+ ed->compiled->options = opts;
|
|
+ AA_READ_X(e, AA_U16, &ed->compiled->top_bracket, NULL);
|
|
+ AA_READ_X(e, AA_U16, &ed->compiled->top_backref, NULL);
|
|
+ AA_READ_X(e, AA_U8, &t_char, NULL);
|
|
+ ed->compiled->first_char = t_char;
|
|
+ AA_READ_X(e, AA_U8, &t_char, NULL);
|
|
+ ed->compiled->req_char = t_char;
|
|
+ AA_READ_X(e, AA_U8, &t_char, NULL);
|
|
+ ed->compiled->code[0] = t_char;
|
|
+
|
|
+ AA_READ_X(e, AA_STATIC_BLOB, &ed->compiled->code[1], NULL);
|
|
+
|
|
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
|
|
+
|
|
+ /* stitch in pcre patterns, it was NULLed out by parser
|
|
+ * pcre_default_tables defined in pcre_tables.h */
|
|
+ ed->compiled->tables = pcre_default_tables;
|
|
+
|
|
+done:
|
|
+ if (error != 0 && ed) {
|
|
+ kfree(ed->pattern); /* allocated by AA_READ_X */
|
|
+ kfree(ed->compiled);
|
|
+ ed->pattern = NULL;
|
|
+ ed->compiled = NULL;
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+unsigned int aamatch_match(const char *pathname, const char *entry_name,
|
|
+ enum entry_match_type entry_type, void *entry_extradata)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ if (entry_type == aa_entry_pattern) {
|
|
+ int pcreret;
|
|
+ struct aamatch_entry *ed =
|
|
+ (struct aamatch_entry *) entry_extradata;
|
|
+
|
|
+ pcreret = pcre_exec(ed->compiled, NULL,
|
|
+ pathname, strlen(pathname),
|
|
+ 0, 0, NULL, 0);
|
|
+
|
|
+ ret = (pcreret >= 0);
|
|
+
|
|
+ // XXX - this needs access to subdomain_debug, hmmm
|
|
+ //AA_DEBUG("%s(%d): %s %s %d\n", __FUNCTION__,
|
|
+ // ret, pathname, ed->pattern, pcreret);
|
|
+ } else {
|
|
+ ret = aamatch_match_common(pathname, entry_name, entry_type);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+EXPORT_SYMBOL_GPL(aamatch_alloc);
|
|
+EXPORT_SYMBOL_GPL(aamatch_free);
|
|
+EXPORT_SYMBOL_GPL(aamatch_features);
|
|
+EXPORT_SYMBOL_GPL(aamatch_serialize);
|
|
+EXPORT_SYMBOL_GPL(aamatch_match);
|
|
+
|
|
+MODULE_DESCRIPTION("AppArmor aa_match module [pcre]");
|
|
+MODULE_AUTHOR("Tony Jones <tonyj@suse.de>");
|
|
+MODULE_LICENSE("GPL");
|
|
Index: linux-2.6.19/security/apparmor/match/pcre_exec.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/match/pcre_exec.c
|
|
@@ -0,0 +1,1945 @@
|
|
+/*
|
|
+ * This is a modified version of pcre.c containing only the code/data
|
|
+ * required to support pcre_exec()
|
|
+ */
|
|
+
|
|
+
|
|
+/*************************************************
|
|
+* Perl-Compatible Regular Expressions *
|
|
+*************************************************/
|
|
+
|
|
+/*
|
|
+This is a library of functions to support regular expressions whose syntax
|
|
+and semantics are as close as possible to those of the Perl 5 language. See
|
|
+the file Tech.Notes for some information on the internals.
|
|
+
|
|
+Written by: Philip Hazel <ph10@cam.ac.uk>
|
|
+
|
|
+ Copyright (c) 1997-2001 University of Cambridge
|
|
+
|
|
+-----------------------------------------------------------------------------
|
|
+Permission is granted to anyone to use this software for any purpose on any
|
|
+computer system, and to redistribute it freely, subject to the following
|
|
+restrictions:
|
|
+
|
|
+1. This software is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
+
|
|
+2. The origin of this software must not be misrepresented, either by
|
|
+ explicit claim or by omission.
|
|
+
|
|
+3. Altered versions must be plainly marked as such, and must not be
|
|
+ misrepresented as being the original software.
|
|
+
|
|
+4. If PCRE is embedded in any software that is released under the GNU
|
|
+ General Purpose Licence (GPL), then the terms of that licence shall
|
|
+ supersede any condition above with which it is incompatible.
|
|
+-----------------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+
|
|
+/* Define DEBUG to get debugging output on stdout. */
|
|
+
|
|
+/* #define DEBUG */
|
|
+
|
|
+/* Use a macro for debugging printing, 'cause that eliminates the use of #ifdef
|
|
+inline, and there are *still* stupid compilers about that don't like indented
|
|
+pre-processor statements. I suppose it's only been 10 years... */
|
|
+
|
|
+#ifdef DEBUG
|
|
+#define DPRINTF(p) PCRE_PRINTF p
|
|
+#else
|
|
+#define DPRINTF(p) /*nothing*/
|
|
+#endif
|
|
+
|
|
+/* Include the internals header, which itself includes Standard C headers plus
|
|
+the external pcre header. */
|
|
+
|
|
+#include "pcre_exec.h"
|
|
+
|
|
+
|
|
+/* ---- CODE DELETED ---- */
|
|
+
|
|
+
|
|
+/* Min and max values for the common repeats; for the maxima, 0 => infinity */
|
|
+
|
|
+static const char rep_min[] = { 0, 0, 1, 1, 0, 0 };
|
|
+static const char rep_max[] = { 0, 0, 0, 0, 1, 1 };
|
|
+
|
|
+
|
|
+/* ---- CODE DELETED ---- */
|
|
+
|
|
+
|
|
+/* Structure for building a chain of data that actually lives on the
|
|
+ * stack, for holding the values of the subject pointer at the start of each
|
|
+ * subpattern, so as to detect when an empty string has been matched by a
|
|
+ * subpattern - to break infinite loops. */
|
|
+
|
|
+typedef struct eptrblock {
|
|
+ struct eptrblock *prev;
|
|
+ const uschar *saved_eptr;
|
|
+} eptrblock;
|
|
+
|
|
+/* Flag bits for the match() function */
|
|
+
|
|
+#define match_condassert 0x01 /* Called to check a condition assertion */
|
|
+#define match_isgroup 0x02 /* Set if start of bracketed group */
|
|
+
|
|
+
|
|
+/* ---- CODE DELETED ---- */
|
|
+
|
|
+
|
|
+/*************************************************
|
|
+ * * Global variables *
|
|
+ * *************************************************/
|
|
+
|
|
+/* PCRE is thread-clean and doesn't use any global variables in the normal
|
|
+ * sense. However, it calls memory allocation and free functions via the two
|
|
+ * indirections below, which are can be changed by the caller, but are shared
|
|
+ * between all threads. */
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+static void *kern_malloc(size_t sz)
|
|
+{
|
|
+ return kmalloc(sz, GFP_KERNEL);
|
|
+}
|
|
+void *(*pcre_malloc)(size_t) = kern_malloc;
|
|
+void (*pcre_free)(const void *) = kfree;
|
|
+#else
|
|
+void *(*pcre_malloc)(size_t) = malloc;
|
|
+void (*pcre_free)(const void *) = free;
|
|
+#endif
|
|
+
|
|
+
|
|
+/*************************************************
|
|
+ * * Macros and tables for character handling *
|
|
+ * *************************************************/
|
|
+
|
|
+/* When UTF-8 encoding is being used, a character is no longer just a single
|
|
+ * byte. The macros for character handling generate simple sequences when used in
|
|
+ * byte-mode, and more complicated ones for UTF-8 characters. */
|
|
+
|
|
+#ifndef SUPPORT_UTF8
|
|
+#define GETCHARINC(c, eptr) c = *eptr++;
|
|
+#define GETCHARLEN(c, eptr, len) c = *eptr;
|
|
+#define BACKCHAR(eptr)
|
|
+#endif
|
|
+
|
|
+/* ---- CODE DELETED ---- */
|
|
+
|
|
+#ifdef DEBUG
|
|
+/*************************************************
|
|
+* Debugging function to print chars *
|
|
+*************************************************/
|
|
+
|
|
+/* Print a sequence of chars in printable format, stopping at the end of the
|
|
+subject if the requested.
|
|
+
|
|
+Arguments:
|
|
+ p points to characters
|
|
+ length number to print
|
|
+ is_subject TRUE if printing from within md->start_subject
|
|
+ md pointer to matching data block, if is_subject is TRUE
|
|
+
|
|
+Returns: nothing
|
|
+*/
|
|
+
|
|
+static void
|
|
+pchars(const uschar *p, int length, BOOL is_subject, match_data *md)
|
|
+{
|
|
+int c;
|
|
+if (is_subject && length > md->end_subject - p) length = md->end_subject - p;
|
|
+while (length-- > 0)
|
|
+ if (isprint(c = *(p++))) PCRE_PRINTF("%c", c); else PCRE_PRINTF("\\x%02x", c);
|
|
+}
|
|
+#endif /* DEBUG */
|
|
+
|
|
+/* ---- CODE DELETED ---- */
|
|
+
|
|
+
|
|
+/*************************************************
|
|
+* Match a back-reference *
|
|
+*************************************************/
|
|
+
|
|
+/* If a back reference hasn't been set, the length that is passed is greater
|
|
+than the number of characters left in the string, so the match fails.
|
|
+
|
|
+Arguments:
|
|
+ offset index into the offset vector
|
|
+ eptr points into the subject
|
|
+ length length to be matched
|
|
+ md points to match data block
|
|
+ ims the ims flags
|
|
+
|
|
+Returns: TRUE if matched
|
|
+*/
|
|
+
|
|
+static BOOL
|
|
+match_ref(int offset, register const uschar *eptr, int length, match_data *md,
|
|
+ unsigned long int ims)
|
|
+{
|
|
+const uschar *p = md->start_subject + md->offset_vector[offset];
|
|
+
|
|
+#ifdef DEBUG
|
|
+if (eptr >= md->end_subject)
|
|
+ PCRE_PRINTF("matching subject <null>");
|
|
+else
|
|
+ {
|
|
+ PCRE_PRINTF("matching subject ");
|
|
+ pchars(eptr, length, TRUE, md);
|
|
+ }
|
|
+PCRE_PRINTF(" against backref ");
|
|
+pchars(p, length, FALSE, md);
|
|
+PCRE_PRINTF("\n");
|
|
+#endif
|
|
+
|
|
+/* Always fail if not enough characters left */
|
|
+
|
|
+if (length > md->end_subject - eptr) return FALSE;
|
|
+
|
|
+/* Separate the caselesss case for speed */
|
|
+
|
|
+if ((ims & PCRE_CASELESS) != 0)
|
|
+ {
|
|
+ while (length-- > 0)
|
|
+ if (md->lcc[*p++] != md->lcc[*eptr++]) return FALSE;
|
|
+ }
|
|
+else
|
|
+ { while (length-- > 0) if (*p++ != *eptr++) return FALSE; }
|
|
+
|
|
+return TRUE;
|
|
+}
|
|
+
|
|
+
|
|
+/*************************************************
|
|
+* Match from current position *
|
|
+*************************************************/
|
|
+
|
|
+/* On entry ecode points to the first opcode, and eptr to the first character
|
|
+in the subject string, while eptrb holds the value of eptr at the start of the
|
|
+last bracketed group - used for breaking infinite loops matching zero-length
|
|
+strings.
|
|
+
|
|
+Arguments:
|
|
+ eptr pointer in subject
|
|
+ ecode position in code
|
|
+ offset_top current top pointer
|
|
+ md pointer to "static" info for the match
|
|
+ ims current /i, /m, and /s options
|
|
+ eptrb pointer to chain of blocks containing eptr at start of
|
|
+ brackets - for testing for empty matches
|
|
+ flags can contain
|
|
+ match_condassert - this is an assertion condition
|
|
+ match_isgroup - this is the start of a bracketed group
|
|
+
|
|
+Returns: TRUE if matched
|
|
+*/
|
|
+
|
|
+static BOOL
|
|
+match(register const uschar *eptr, register const uschar *ecode,
|
|
+ int offset_top, match_data *md, unsigned long int ims, eptrblock *eptrb,
|
|
+ int flags)
|
|
+{
|
|
+unsigned long int original_ims = ims; /* Save for resetting on ')' */
|
|
+eptrblock newptrb;
|
|
+
|
|
+/* At the start of a bracketed group, add the current subject pointer to the
|
|
+stack of such pointers, to be re-instated at the end of the group when we hit
|
|
+the closing ket. When match() is called in other circumstances, we don't add to
|
|
+the stack. */
|
|
+
|
|
+if ((flags & match_isgroup) != 0)
|
|
+ {
|
|
+ newptrb.prev = eptrb;
|
|
+ newptrb.saved_eptr = eptr;
|
|
+ eptrb = &newptrb;
|
|
+ }
|
|
+
|
|
+/* Now start processing the operations. */
|
|
+
|
|
+for (;;)
|
|
+ {
|
|
+ int op = (int)*ecode;
|
|
+ int min, max, ctype;
|
|
+ register int i;
|
|
+ register int c;
|
|
+ BOOL minimize = FALSE;
|
|
+
|
|
+ /* Opening capturing bracket. If there is space in the offset vector, save
|
|
+ the current subject position in the working slot at the top of the vector. We
|
|
+ mustn't change the current values of the data slot, because they may be set
|
|
+ from a previous iteration of this group, and be referred to by a reference
|
|
+ inside the group.
|
|
+
|
|
+ If the bracket fails to match, we need to restore this value and also the
|
|
+ values of the final offsets, in case they were set by a previous iteration of
|
|
+ the same bracket.
|
|
+
|
|
+ If there isn't enough space in the offset vector, treat this as if it were a
|
|
+ non-capturing bracket. Don't worry about setting the flag for the error case
|
|
+ here; that is handled in the code for KET. */
|
|
+
|
|
+ if (op > OP_BRA)
|
|
+ {
|
|
+ int offset;
|
|
+ int number = op - OP_BRA;
|
|
+
|
|
+ /* For extended extraction brackets (large number), we have to fish out the
|
|
+ number from a dummy opcode at the start. */
|
|
+
|
|
+ if (number > EXTRACT_BASIC_MAX) number = (ecode[4] << 8) | ecode[5];
|
|
+ offset = number << 1;
|
|
+
|
|
+#ifdef DEBUG
|
|
+ PCRE_PRINTF("start bracket %d subject=", number);
|
|
+ pchars(eptr, 16, TRUE, md);
|
|
+ PCRE_PRINTF("\n");
|
|
+#endif
|
|
+
|
|
+ if (offset < md->offset_max)
|
|
+ {
|
|
+ int save_offset1 = md->offset_vector[offset];
|
|
+ int save_offset2 = md->offset_vector[offset+1];
|
|
+ int save_offset3 = md->offset_vector[md->offset_end - number];
|
|
+
|
|
+ DPRINTF(("saving %d %d %d\n", save_offset1, save_offset2, save_offset3));
|
|
+ md->offset_vector[md->offset_end - number] = eptr - md->start_subject;
|
|
+
|
|
+ do
|
|
+ {
|
|
+ if (match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup))
|
|
+ return TRUE;
|
|
+ ecode += (ecode[1] << 8) + ecode[2];
|
|
+ }
|
|
+ while (*ecode == OP_ALT);
|
|
+
|
|
+ DPRINTF(("bracket %d failed\n", number));
|
|
+
|
|
+ md->offset_vector[offset] = save_offset1;
|
|
+ md->offset_vector[offset+1] = save_offset2;
|
|
+ md->offset_vector[md->offset_end - number] = save_offset3;
|
|
+
|
|
+ return FALSE;
|
|
+ }
|
|
+
|
|
+ /* Insufficient room for saving captured contents */
|
|
+
|
|
+ else op = OP_BRA;
|
|
+ }
|
|
+
|
|
+ /* Other types of node can be handled by a switch */
|
|
+
|
|
+ switch(op)
|
|
+ {
|
|
+ case OP_BRA: /* Non-capturing bracket: optimized */
|
|
+ DPRINTF(("start bracket 0\n"));
|
|
+ do
|
|
+ {
|
|
+ if (match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup))
|
|
+ return TRUE;
|
|
+ ecode += (ecode[1] << 8) + ecode[2];
|
|
+ }
|
|
+ while (*ecode == OP_ALT);
|
|
+ DPRINTF(("bracket 0 failed\n"));
|
|
+ return FALSE;
|
|
+
|
|
+ /* Conditional group: compilation checked that there are no more than
|
|
+ two branches. If the condition is false, skipping the first branch takes us
|
|
+ past the end if there is only one branch, but that's OK because that is
|
|
+ exactly what going to the ket would do. */
|
|
+
|
|
+ case OP_COND:
|
|
+ if (ecode[3] == OP_CREF) /* Condition is extraction test */
|
|
+ {
|
|
+ int offset = (ecode[4] << 9) | (ecode[5] << 1); /* Doubled ref number */
|
|
+ return match(eptr,
|
|
+ ecode + ((offset < offset_top && md->offset_vector[offset] >= 0)?
|
|
+ 6 : 3 + (ecode[1] << 8) + ecode[2]),
|
|
+ offset_top, md, ims, eptrb, match_isgroup);
|
|
+ }
|
|
+
|
|
+ /* The condition is an assertion. Call match() to evaluate it - setting
|
|
+ the final argument TRUE causes it to stop at the end of an assertion. */
|
|
+
|
|
+ else
|
|
+ {
|
|
+ if (match(eptr, ecode+3, offset_top, md, ims, NULL,
|
|
+ match_condassert | match_isgroup))
|
|
+ {
|
|
+ ecode += 3 + (ecode[4] << 8) + ecode[5];
|
|
+ while (*ecode == OP_ALT) ecode += (ecode[1] << 8) + ecode[2];
|
|
+ }
|
|
+ else ecode += (ecode[1] << 8) + ecode[2];
|
|
+ return match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup);
|
|
+ }
|
|
+ /* Control never reaches here */
|
|
+
|
|
+ /* Skip over conditional reference or large extraction number data if
|
|
+ encountered. */
|
|
+
|
|
+ case OP_CREF:
|
|
+ case OP_BRANUMBER:
|
|
+ ecode += 3;
|
|
+ break;
|
|
+
|
|
+ /* End of the pattern. If PCRE_NOTEMPTY is set, fail if we have matched
|
|
+ an empty string - recursion will then try other alternatives, if any. */
|
|
+
|
|
+ case OP_END:
|
|
+ if (md->notempty && eptr == md->start_match) return FALSE;
|
|
+ md->end_match_ptr = eptr; /* Record where we ended */
|
|
+ md->end_offset_top = offset_top; /* and how many extracts were taken */
|
|
+ return TRUE;
|
|
+
|
|
+ /* Change option settings */
|
|
+
|
|
+ case OP_OPT:
|
|
+ ims = ecode[1];
|
|
+ ecode += 2;
|
|
+ DPRINTF(("ims set to %02lx\n", ims));
|
|
+ break;
|
|
+
|
|
+ /* Assertion brackets. Check the alternative branches in turn - the
|
|
+ matching won't pass the KET for an assertion. If any one branch matches,
|
|
+ the assertion is true. Lookbehind assertions have an OP_REVERSE item at the
|
|
+ start of each branch to move the current point backwards, so the code at
|
|
+ this level is identical to the lookahead case. */
|
|
+
|
|
+ case OP_ASSERT:
|
|
+ case OP_ASSERTBACK:
|
|
+ do
|
|
+ {
|
|
+ if (match(eptr, ecode+3, offset_top, md, ims, NULL, match_isgroup)) break;
|
|
+ ecode += (ecode[1] << 8) + ecode[2];
|
|
+ }
|
|
+ while (*ecode == OP_ALT);
|
|
+ if (*ecode == OP_KET) return FALSE;
|
|
+
|
|
+ /* If checking an assertion for a condition, return TRUE. */
|
|
+
|
|
+ if ((flags & match_condassert) != 0) return TRUE;
|
|
+
|
|
+ /* Continue from after the assertion, updating the offsets high water
|
|
+ mark, since extracts may have been taken during the assertion. */
|
|
+
|
|
+ do ecode += (ecode[1] << 8) + ecode[2]; while (*ecode == OP_ALT);
|
|
+ ecode += 3;
|
|
+ offset_top = md->end_offset_top;
|
|
+ continue;
|
|
+
|
|
+ /* Negative assertion: all branches must fail to match */
|
|
+
|
|
+ case OP_ASSERT_NOT:
|
|
+ case OP_ASSERTBACK_NOT:
|
|
+ do
|
|
+ {
|
|
+ if (match(eptr, ecode+3, offset_top, md, ims, NULL, match_isgroup))
|
|
+ return FALSE;
|
|
+ ecode += (ecode[1] << 8) + ecode[2];
|
|
+ }
|
|
+ while (*ecode == OP_ALT);
|
|
+
|
|
+ if ((flags & match_condassert) != 0) return TRUE;
|
|
+
|
|
+ ecode += 3;
|
|
+ continue;
|
|
+
|
|
+ /* Move the subject pointer back. This occurs only at the start of
|
|
+ each branch of a lookbehind assertion. If we are too close to the start to
|
|
+ move back, this match function fails. When working with UTF-8 we move
|
|
+ back a number of characters, not bytes. */
|
|
+
|
|
+ case OP_REVERSE:
|
|
+#ifdef SUPPORT_UTF8
|
|
+ c = (ecode[1] << 8) + ecode[2];
|
|
+ for (i = 0; i < c; i++)
|
|
+ {
|
|
+ eptr--;
|
|
+ BACKCHAR(eptr)
|
|
+ }
|
|
+#else
|
|
+ eptr -= (ecode[1] << 8) + ecode[2];
|
|
+#endif
|
|
+
|
|
+ if (eptr < md->start_subject) return FALSE;
|
|
+ ecode += 3;
|
|
+ break;
|
|
+
|
|
+ /* Recursion matches the current regex, nested. If there are any capturing
|
|
+ brackets started but not finished, we have to save their starting points
|
|
+ and reinstate them after the recursion. However, we don't know how many
|
|
+ such there are (offset_top records the completed total) so we just have
|
|
+ to save all the potential data. There may be up to 99 such values, which
|
|
+ is a bit large to put on the stack, but using malloc for small numbers
|
|
+ seems expensive. As a compromise, the stack is used when there are fewer
|
|
+ than 16 values to store; otherwise malloc is used. A problem is what to do
|
|
+ if the malloc fails ... there is no way of returning to the top level with
|
|
+ an error. Save the top 15 values on the stack, and accept that the rest
|
|
+ may be wrong. */
|
|
+
|
|
+ case OP_RECURSE:
|
|
+ {
|
|
+ BOOL rc;
|
|
+ int *save;
|
|
+ int stacksave[15];
|
|
+
|
|
+ c = md->offset_max;
|
|
+
|
|
+ if (c < 16) save = stacksave; else
|
|
+ {
|
|
+ save = (int *)(pcre_malloc)((c+1) * sizeof(int));
|
|
+ if (save == NULL)
|
|
+ {
|
|
+ save = stacksave;
|
|
+ c = 15;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 1; i <= c; i++)
|
|
+ save[i] = md->offset_vector[md->offset_end - i];
|
|
+ rc = match(eptr, md->start_pattern, offset_top, md, ims, eptrb,
|
|
+ match_isgroup);
|
|
+ for (i = 1; i <= c; i++)
|
|
+ md->offset_vector[md->offset_end - i] = save[i];
|
|
+ if (save != stacksave) (pcre_free)(save);
|
|
+ if (!rc) return FALSE;
|
|
+
|
|
+ /* In case the recursion has set more capturing values, save the final
|
|
+ number, then move along the subject till after the recursive match,
|
|
+ and advance one byte in the pattern code. */
|
|
+
|
|
+ offset_top = md->end_offset_top;
|
|
+ eptr = md->end_match_ptr;
|
|
+ ecode++;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ /* "Once" brackets are like assertion brackets except that after a match,
|
|
+ the point in the subject string is not moved back. Thus there can never be
|
|
+ a move back into the brackets. Check the alternative branches in turn - the
|
|
+ matching won't pass the KET for this kind of subpattern. If any one branch
|
|
+ matches, we carry on as at the end of a normal bracket, leaving the subject
|
|
+ pointer. */
|
|
+
|
|
+ case OP_ONCE:
|
|
+ {
|
|
+ const uschar *prev = ecode;
|
|
+ const uschar *saved_eptr = eptr;
|
|
+
|
|
+ do
|
|
+ {
|
|
+ if (match(eptr, ecode+3, offset_top, md, ims, eptrb, match_isgroup))
|
|
+ break;
|
|
+ ecode += (ecode[1] << 8) + ecode[2];
|
|
+ }
|
|
+ while (*ecode == OP_ALT);
|
|
+
|
|
+ /* If hit the end of the group (which could be repeated), fail */
|
|
+
|
|
+ if (*ecode != OP_ONCE && *ecode != OP_ALT) return FALSE;
|
|
+
|
|
+ /* Continue as from after the assertion, updating the offsets high water
|
|
+ mark, since extracts may have been taken. */
|
|
+
|
|
+ do ecode += (ecode[1] << 8) + ecode[2]; while (*ecode == OP_ALT);
|
|
+
|
|
+ offset_top = md->end_offset_top;
|
|
+ eptr = md->end_match_ptr;
|
|
+
|
|
+ /* For a non-repeating ket, just continue at this level. This also
|
|
+ happens for a repeating ket if no characters were matched in the group.
|
|
+ This is the forcible breaking of infinite loops as implemented in Perl
|
|
+ 5.005. If there is an options reset, it will get obeyed in the normal
|
|
+ course of events. */
|
|
+
|
|
+ if (*ecode == OP_KET || eptr == saved_eptr)
|
|
+ {
|
|
+ ecode += 3;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* The repeating kets try the rest of the pattern or restart from the
|
|
+ preceding bracket, in the appropriate order. We need to reset any options
|
|
+ that changed within the bracket before re-running it, so check the next
|
|
+ opcode. */
|
|
+
|
|
+ if (ecode[3] == OP_OPT)
|
|
+ {
|
|
+ ims = (ims & ~PCRE_IMS) | ecode[4];
|
|
+ DPRINTF(("ims set to %02lx at group repeat\n", ims));
|
|
+ }
|
|
+
|
|
+ if (*ecode == OP_KETRMIN)
|
|
+ {
|
|
+ if (match(eptr, ecode+3, offset_top, md, ims, eptrb, 0) ||
|
|
+ match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup))
|
|
+ return TRUE;
|
|
+ }
|
|
+ else /* OP_KETRMAX */
|
|
+ {
|
|
+ if (match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup) ||
|
|
+ match(eptr, ecode+3, offset_top, md, ims, eptrb, 0)) return TRUE;
|
|
+ }
|
|
+ }
|
|
+ return FALSE;
|
|
+
|
|
+ /* An alternation is the end of a branch; scan along to find the end of the
|
|
+ bracketed group and go to there. */
|
|
+
|
|
+ case OP_ALT:
|
|
+ do ecode += (ecode[1] << 8) + ecode[2]; while (*ecode == OP_ALT);
|
|
+ break;
|
|
+
|
|
+ /* BRAZERO and BRAMINZERO occur just before a bracket group, indicating
|
|
+ that it may occur zero times. It may repeat infinitely, or not at all -
|
|
+ i.e. it could be ()* or ()? in the pattern. Brackets with fixed upper
|
|
+ repeat limits are compiled as a number of copies, with the optional ones
|
|
+ preceded by BRAZERO or BRAMINZERO. */
|
|
+
|
|
+ case OP_BRAZERO:
|
|
+ {
|
|
+ const uschar *next = ecode+1;
|
|
+ if (match(eptr, next, offset_top, md, ims, eptrb, match_isgroup))
|
|
+ return TRUE;
|
|
+ do next += (next[1] << 8) + next[2]; while (*next == OP_ALT);
|
|
+ ecode = next + 3;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case OP_BRAMINZERO:
|
|
+ {
|
|
+ const uschar *next = ecode+1;
|
|
+ do next += (next[1] << 8) + next[2]; while (*next == OP_ALT);
|
|
+ if (match(eptr, next+3, offset_top, md, ims, eptrb, match_isgroup))
|
|
+ return TRUE;
|
|
+ ecode++;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ /* End of a group, repeated or non-repeating. If we are at the end of
|
|
+ an assertion "group", stop matching and return TRUE, but record the
|
|
+ current high water mark for use by positive assertions. Do this also
|
|
+ for the "once" (not-backup up) groups. */
|
|
+
|
|
+ case OP_KET:
|
|
+ case OP_KETRMIN:
|
|
+ case OP_KETRMAX:
|
|
+ {
|
|
+ const uschar *prev = ecode - (ecode[1] << 8) - ecode[2];
|
|
+ const uschar *saved_eptr = eptrb->saved_eptr;
|
|
+
|
|
+ eptrb = eptrb->prev; /* Back up the stack of bracket start pointers */
|
|
+
|
|
+ if (*prev == OP_ASSERT || *prev == OP_ASSERT_NOT ||
|
|
+ *prev == OP_ASSERTBACK || *prev == OP_ASSERTBACK_NOT ||
|
|
+ *prev == OP_ONCE)
|
|
+ {
|
|
+ md->end_match_ptr = eptr; /* For ONCE */
|
|
+ md->end_offset_top = offset_top;
|
|
+ return TRUE;
|
|
+ }
|
|
+
|
|
+ /* In all other cases except a conditional group we have to check the
|
|
+ group number back at the start and if necessary complete handling an
|
|
+ extraction by setting the offsets and bumping the high water mark. */
|
|
+
|
|
+ if (*prev != OP_COND)
|
|
+ {
|
|
+ int offset;
|
|
+ int number = *prev - OP_BRA;
|
|
+
|
|
+ /* For extended extraction brackets (large number), we have to fish out
|
|
+ the number from a dummy opcode at the start. */
|
|
+
|
|
+ if (number > EXTRACT_BASIC_MAX) number = (prev[4] << 8) | prev[5];
|
|
+ offset = number << 1;
|
|
+
|
|
+#ifdef DEBUG
|
|
+ PCRE_PRINTF("end bracket %d", number);
|
|
+ PCRE_PRINTF("\n");
|
|
+#endif
|
|
+
|
|
+ if (number > 0)
|
|
+ {
|
|
+ if (offset >= md->offset_max) md->offset_overflow = TRUE; else
|
|
+ {
|
|
+ md->offset_vector[offset] =
|
|
+ md->offset_vector[md->offset_end - number];
|
|
+ md->offset_vector[offset+1] = eptr - md->start_subject;
|
|
+ if (offset_top <= offset) offset_top = offset + 2;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Reset the value of the ims flags, in case they got changed during
|
|
+ the group. */
|
|
+
|
|
+ ims = original_ims;
|
|
+ DPRINTF(("ims reset to %02lx\n", ims));
|
|
+
|
|
+ /* For a non-repeating ket, just continue at this level. This also
|
|
+ happens for a repeating ket if no characters were matched in the group.
|
|
+ This is the forcible breaking of infinite loops as implemented in Perl
|
|
+ 5.005. If there is an options reset, it will get obeyed in the normal
|
|
+ course of events. */
|
|
+
|
|
+ if (*ecode == OP_KET || eptr == saved_eptr)
|
|
+ {
|
|
+ ecode += 3;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* The repeating kets try the rest of the pattern or restart from the
|
|
+ preceding bracket, in the appropriate order. */
|
|
+
|
|
+ if (*ecode == OP_KETRMIN)
|
|
+ {
|
|
+ if (match(eptr, ecode+3, offset_top, md, ims, eptrb, 0) ||
|
|
+ match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup))
|
|
+ return TRUE;
|
|
+ }
|
|
+ else /* OP_KETRMAX */
|
|
+ {
|
|
+ if (match(eptr, prev, offset_top, md, ims, eptrb, match_isgroup) ||
|
|
+ match(eptr, ecode+3, offset_top, md, ims, eptrb, 0)) return TRUE;
|
|
+ }
|
|
+ }
|
|
+ return FALSE;
|
|
+
|
|
+ /* Start of subject unless notbol, or after internal newline if multiline */
|
|
+
|
|
+ case OP_CIRC:
|
|
+ if (md->notbol && eptr == md->start_subject) return FALSE;
|
|
+ if ((ims & PCRE_MULTILINE) != 0)
|
|
+ {
|
|
+ if (eptr != md->start_subject && eptr[-1] != NEWLINE) return FALSE;
|
|
+ ecode++;
|
|
+ break;
|
|
+ }
|
|
+ /* ... else fall through */
|
|
+
|
|
+ /* Start of subject assertion */
|
|
+
|
|
+ case OP_SOD:
|
|
+ if (eptr != md->start_subject) return FALSE;
|
|
+ ecode++;
|
|
+ break;
|
|
+
|
|
+ /* Assert before internal newline if multiline, or before a terminating
|
|
+ newline unless endonly is set, else end of subject unless noteol is set. */
|
|
+
|
|
+ case OP_DOLL:
|
|
+ if ((ims & PCRE_MULTILINE) != 0)
|
|
+ {
|
|
+ if (eptr < md->end_subject) { if (*eptr != NEWLINE) return FALSE; }
|
|
+ else { if (md->noteol) return FALSE; }
|
|
+ ecode++;
|
|
+ break;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ if (md->noteol) return FALSE;
|
|
+ if (!md->endonly)
|
|
+ {
|
|
+ if (eptr < md->end_subject - 1 ||
|
|
+ (eptr == md->end_subject - 1 && *eptr != NEWLINE)) return FALSE;
|
|
+
|
|
+ ecode++;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ /* ... else fall through */
|
|
+
|
|
+ /* End of subject assertion (\z) */
|
|
+
|
|
+ case OP_EOD:
|
|
+ if (eptr < md->end_subject) return FALSE;
|
|
+ ecode++;
|
|
+ break;
|
|
+
|
|
+ /* End of subject or ending \n assertion (\Z) */
|
|
+
|
|
+ case OP_EODN:
|
|
+ if (eptr < md->end_subject - 1 ||
|
|
+ (eptr == md->end_subject - 1 && *eptr != NEWLINE)) return FALSE;
|
|
+ ecode++;
|
|
+ break;
|
|
+
|
|
+ /* Word boundary assertions */
|
|
+
|
|
+ case OP_NOT_WORD_BOUNDARY:
|
|
+ case OP_WORD_BOUNDARY:
|
|
+ {
|
|
+ BOOL prev_is_word = (eptr != md->start_subject) &&
|
|
+ ((md->ctypes[eptr[-1]] & ctype_word) != 0);
|
|
+ BOOL cur_is_word = (eptr < md->end_subject) &&
|
|
+ ((md->ctypes[*eptr] & ctype_word) != 0);
|
|
+ if ((*ecode++ == OP_WORD_BOUNDARY)?
|
|
+ cur_is_word == prev_is_word : cur_is_word != prev_is_word)
|
|
+ return FALSE;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ /* Match a single character type; inline for speed */
|
|
+
|
|
+ case OP_ANY:
|
|
+ if ((ims & PCRE_DOTALL) == 0 && eptr < md->end_subject && *eptr == NEWLINE)
|
|
+ return FALSE;
|
|
+ if (eptr++ >= md->end_subject) return FALSE;
|
|
+#ifdef SUPPORT_UTF8
|
|
+ if (md->utf8)
|
|
+ while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
|
|
+#endif
|
|
+ ecode++;
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_DIGIT:
|
|
+ if (eptr >= md->end_subject ||
|
|
+ (md->ctypes[*eptr++] & ctype_digit) != 0)
|
|
+ return FALSE;
|
|
+ ecode++;
|
|
+ break;
|
|
+
|
|
+ case OP_DIGIT:
|
|
+ if (eptr >= md->end_subject ||
|
|
+ (md->ctypes[*eptr++] & ctype_digit) == 0)
|
|
+ return FALSE;
|
|
+ ecode++;
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_WHITESPACE:
|
|
+ if (eptr >= md->end_subject ||
|
|
+ (md->ctypes[*eptr++] & ctype_space) != 0)
|
|
+ return FALSE;
|
|
+ ecode++;
|
|
+ break;
|
|
+
|
|
+ case OP_WHITESPACE:
|
|
+ if (eptr >= md->end_subject ||
|
|
+ (md->ctypes[*eptr++] & ctype_space) == 0)
|
|
+ return FALSE;
|
|
+ ecode++;
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_WORDCHAR:
|
|
+ if (eptr >= md->end_subject ||
|
|
+ (md->ctypes[*eptr++] & ctype_word) != 0)
|
|
+ return FALSE;
|
|
+ ecode++;
|
|
+ break;
|
|
+
|
|
+ case OP_WORDCHAR:
|
|
+ if (eptr >= md->end_subject ||
|
|
+ (md->ctypes[*eptr++] & ctype_word) == 0)
|
|
+ return FALSE;
|
|
+ ecode++;
|
|
+ break;
|
|
+
|
|
+ /* Match a back reference, possibly repeatedly. Look past the end of the
|
|
+ item to see if there is repeat information following. The code is similar
|
|
+ to that for character classes, but repeated for efficiency. Then obey
|
|
+ similar code to character type repeats - written out again for speed.
|
|
+ However, if the referenced string is the empty string, always treat
|
|
+ it as matched, any number of times (otherwise there could be infinite
|
|
+ loops). */
|
|
+
|
|
+ case OP_REF:
|
|
+ {
|
|
+ int length;
|
|
+ int offset = (ecode[1] << 9) | (ecode[2] << 1); /* Doubled ref number */
|
|
+ ecode += 3; /* Advance past item */
|
|
+
|
|
+ /* If the reference is unset, set the length to be longer than the amount
|
|
+ of subject left; this ensures that every attempt at a match fails. We
|
|
+ can't just fail here, because of the possibility of quantifiers with zero
|
|
+ minima. */
|
|
+
|
|
+ length = (offset >= offset_top || md->offset_vector[offset] < 0)?
|
|
+ md->end_subject - eptr + 1 :
|
|
+ md->offset_vector[offset+1] - md->offset_vector[offset];
|
|
+
|
|
+ /* Set up for repetition, or handle the non-repeated case */
|
|
+
|
|
+ switch (*ecode)
|
|
+ {
|
|
+ case OP_CRSTAR:
|
|
+ case OP_CRMINSTAR:
|
|
+ case OP_CRPLUS:
|
|
+ case OP_CRMINPLUS:
|
|
+ case OP_CRQUERY:
|
|
+ case OP_CRMINQUERY:
|
|
+ c = *ecode++ - OP_CRSTAR;
|
|
+ minimize = (c & 1) != 0;
|
|
+ min = rep_min[c]; /* Pick up values from tables; */
|
|
+ max = rep_max[c]; /* zero for max => infinity */
|
|
+ if (max == 0) max = INT_MAX;
|
|
+ break;
|
|
+
|
|
+ case OP_CRRANGE:
|
|
+ case OP_CRMINRANGE:
|
|
+ minimize = (*ecode == OP_CRMINRANGE);
|
|
+ min = (ecode[1] << 8) + ecode[2];
|
|
+ max = (ecode[3] << 8) + ecode[4];
|
|
+ if (max == 0) max = INT_MAX;
|
|
+ ecode += 5;
|
|
+ break;
|
|
+
|
|
+ default: /* No repeat follows */
|
|
+ if (!match_ref(offset, eptr, length, md, ims)) return FALSE;
|
|
+ eptr += length;
|
|
+ continue; /* With the main loop */
|
|
+ }
|
|
+
|
|
+ /* If the length of the reference is zero, just continue with the
|
|
+ main loop. */
|
|
+
|
|
+ if (length == 0) continue;
|
|
+
|
|
+ /* First, ensure the minimum number of matches are present. We get back
|
|
+ the length of the reference string explicitly rather than passing the
|
|
+ address of eptr, so that eptr can be a register variable. */
|
|
+
|
|
+ for (i = 1; i <= min; i++)
|
|
+ {
|
|
+ if (!match_ref(offset, eptr, length, md, ims)) return FALSE;
|
|
+ eptr += length;
|
|
+ }
|
|
+
|
|
+ /* If min = max, continue at the same level without recursion.
|
|
+ They are not both allowed to be zero. */
|
|
+
|
|
+ if (min == max) continue;
|
|
+
|
|
+ /* If minimizing, keep trying and advancing the pointer */
|
|
+
|
|
+ if (minimize)
|
|
+ {
|
|
+ for (i = min;; i++)
|
|
+ {
|
|
+ if (match(eptr, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ if (i >= max || !match_ref(offset, eptr, length, md, ims))
|
|
+ return FALSE;
|
|
+ eptr += length;
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+ }
|
|
+
|
|
+ /* If maximizing, find the longest string and work backwards */
|
|
+
|
|
+ else
|
|
+ {
|
|
+ const uschar *pp = eptr;
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (!match_ref(offset, eptr, length, md, ims)) break;
|
|
+ eptr += length;
|
|
+ }
|
|
+ while (eptr >= pp)
|
|
+ {
|
|
+ if (match(eptr, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ eptr -= length;
|
|
+ }
|
|
+ return FALSE;
|
|
+ }
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+
|
|
+
|
|
+
|
|
+ /* Match a character class, possibly repeatedly. Look past the end of the
|
|
+ item to see if there is repeat information following. Then obey similar
|
|
+ code to character type repeats - written out again for speed. */
|
|
+
|
|
+ case OP_CLASS:
|
|
+ {
|
|
+ const uschar *data = ecode + 1; /* Save for matching */
|
|
+ ecode += 33; /* Advance past the item */
|
|
+
|
|
+ switch (*ecode)
|
|
+ {
|
|
+ case OP_CRSTAR:
|
|
+ case OP_CRMINSTAR:
|
|
+ case OP_CRPLUS:
|
|
+ case OP_CRMINPLUS:
|
|
+ case OP_CRQUERY:
|
|
+ case OP_CRMINQUERY:
|
|
+ c = *ecode++ - OP_CRSTAR;
|
|
+ minimize = (c & 1) != 0;
|
|
+ min = rep_min[c]; /* Pick up values from tables; */
|
|
+ max = rep_max[c]; /* zero for max => infinity */
|
|
+ if (max == 0) max = INT_MAX;
|
|
+ break;
|
|
+
|
|
+ case OP_CRRANGE:
|
|
+ case OP_CRMINRANGE:
|
|
+ minimize = (*ecode == OP_CRMINRANGE);
|
|
+ min = (ecode[1] << 8) + ecode[2];
|
|
+ max = (ecode[3] << 8) + ecode[4];
|
|
+ if (max == 0) max = INT_MAX;
|
|
+ ecode += 5;
|
|
+ break;
|
|
+
|
|
+ default: /* No repeat follows */
|
|
+ min = max = 1;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* First, ensure the minimum number of matches are present. */
|
|
+
|
|
+ for (i = 1; i <= min; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject) return FALSE;
|
|
+ GETCHARINC(c, eptr) /* Get character; increment eptr */
|
|
+
|
|
+#ifdef SUPPORT_UTF8
|
|
+ /* We do not yet support class members > 255 */
|
|
+ if (c > 255) return FALSE;
|
|
+#endif
|
|
+
|
|
+ if ((data[c/8] & (1 << (c&7))) != 0) continue;
|
|
+ return FALSE;
|
|
+ }
|
|
+
|
|
+ /* If max == min we can continue with the main loop without the
|
|
+ need to recurse. */
|
|
+
|
|
+ if (min == max) continue;
|
|
+
|
|
+ /* If minimizing, keep testing the rest of the expression and advancing
|
|
+ the pointer while it matches the class. */
|
|
+
|
|
+ if (minimize)
|
|
+ {
|
|
+ for (i = min;; i++)
|
|
+ {
|
|
+ if (match(eptr, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ if (i >= max || eptr >= md->end_subject) return FALSE;
|
|
+ GETCHARINC(c, eptr) /* Get character; increment eptr */
|
|
+
|
|
+#ifdef SUPPORT_UTF8
|
|
+ /* We do not yet support class members > 255 */
|
|
+ if (c > 255) return FALSE;
|
|
+#endif
|
|
+ if ((data[c/8] & (1 << (c&7))) != 0) continue;
|
|
+ return FALSE;
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+ }
|
|
+
|
|
+ /* If maximizing, find the longest possible run, then work backwards. */
|
|
+
|
|
+ else
|
|
+ {
|
|
+ const uschar *pp = eptr;
|
|
+ int len = 1;
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject) break;
|
|
+ GETCHARLEN(c, eptr, len) /* Get character, set length if UTF-8 */
|
|
+
|
|
+#ifdef SUPPORT_UTF8
|
|
+ /* We do not yet support class members > 255 */
|
|
+ if (c > 255) break;
|
|
+#endif
|
|
+ if ((data[c/8] & (1 << (c&7))) == 0) break;
|
|
+ eptr += len;
|
|
+ }
|
|
+
|
|
+ while (eptr >= pp)
|
|
+ {
|
|
+ if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+
|
|
+#ifdef SUPPORT_UTF8
|
|
+ BACKCHAR(eptr)
|
|
+#endif
|
|
+ }
|
|
+ return FALSE;
|
|
+ }
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+
|
|
+ /* Match a run of characters */
|
|
+
|
|
+ case OP_CHARS:
|
|
+ {
|
|
+ register int length = ecode[1];
|
|
+ ecode += 2;
|
|
+
|
|
+#ifdef DEBUG /* Sigh. Some compilers never learn. */
|
|
+ if (eptr >= md->end_subject)
|
|
+ PCRE_PRINTF("matching subject <null> against pattern ");
|
|
+ else
|
|
+ {
|
|
+ PCRE_PRINTF("matching subject ");
|
|
+ pchars(eptr, length, TRUE, md);
|
|
+ PCRE_PRINTF(" against pattern ");
|
|
+ }
|
|
+ pchars(ecode, length, FALSE, md);
|
|
+ PCRE_PRINTF("\n");
|
|
+#endif
|
|
+
|
|
+ if (length > md->end_subject - eptr) return FALSE;
|
|
+ if ((ims & PCRE_CASELESS) != 0)
|
|
+ {
|
|
+ while (length-- > 0)
|
|
+ if (md->lcc[*ecode++] != md->lcc[*eptr++])
|
|
+ return FALSE;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ while (length-- > 0) if (*ecode++ != *eptr++) return FALSE;
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ /* Match a single character repeatedly; different opcodes share code. */
|
|
+
|
|
+ case OP_EXACT:
|
|
+ min = max = (ecode[1] << 8) + ecode[2];
|
|
+ ecode += 3;
|
|
+ goto REPEATCHAR;
|
|
+
|
|
+ case OP_UPTO:
|
|
+ case OP_MINUPTO:
|
|
+ min = 0;
|
|
+ max = (ecode[1] << 8) + ecode[2];
|
|
+ minimize = *ecode == OP_MINUPTO;
|
|
+ ecode += 3;
|
|
+ goto REPEATCHAR;
|
|
+
|
|
+ case OP_STAR:
|
|
+ case OP_MINSTAR:
|
|
+ case OP_PLUS:
|
|
+ case OP_MINPLUS:
|
|
+ case OP_QUERY:
|
|
+ case OP_MINQUERY:
|
|
+ c = *ecode++ - OP_STAR;
|
|
+ minimize = (c & 1) != 0;
|
|
+ min = rep_min[c]; /* Pick up values from tables; */
|
|
+ max = rep_max[c]; /* zero for max => infinity */
|
|
+ if (max == 0) max = INT_MAX;
|
|
+
|
|
+ /* Common code for all repeated single-character matches. We can give
|
|
+ up quickly if there are fewer than the minimum number of characters left in
|
|
+ the subject. */
|
|
+
|
|
+ REPEATCHAR:
|
|
+ if (min > md->end_subject - eptr) return FALSE;
|
|
+ c = *ecode++;
|
|
+
|
|
+ /* The code is duplicated for the caseless and caseful cases, for speed,
|
|
+ since matching characters is likely to be quite common. First, ensure the
|
|
+ minimum number of matches are present. If min = max, continue at the same
|
|
+ level without recursing. Otherwise, if minimizing, keep trying the rest of
|
|
+ the expression and advancing one matching character if failing, up to the
|
|
+ maximum. Alternatively, if maximizing, find the maximum number of
|
|
+ characters and work backwards. */
|
|
+
|
|
+ DPRINTF(("matching %c{%d,%d} against subject %.*s\n", c, min, max,
|
|
+ max, eptr));
|
|
+
|
|
+ if ((ims & PCRE_CASELESS) != 0)
|
|
+ {
|
|
+ c = md->lcc[c];
|
|
+ for (i = 1; i <= min; i++)
|
|
+ if (c != md->lcc[*eptr++]) return FALSE;
|
|
+ if (min == max) continue;
|
|
+ if (minimize)
|
|
+ {
|
|
+ for (i = min;; i++)
|
|
+ {
|
|
+ if (match(eptr, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ if (i >= max || eptr >= md->end_subject ||
|
|
+ c != md->lcc[*eptr++])
|
|
+ return FALSE;
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ const uschar *pp = eptr;
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || c != md->lcc[*eptr]) break;
|
|
+ eptr++;
|
|
+ }
|
|
+ while (eptr >= pp)
|
|
+ if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ return FALSE;
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+ }
|
|
+
|
|
+ /* Caseful comparisons */
|
|
+
|
|
+ else
|
|
+ {
|
|
+ for (i = 1; i <= min; i++) if (c != *eptr++) return FALSE;
|
|
+ if (min == max) continue;
|
|
+ if (minimize)
|
|
+ {
|
|
+ for (i = min;; i++)
|
|
+ {
|
|
+ if (match(eptr, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ if (i >= max || eptr >= md->end_subject || c != *eptr++) return FALSE;
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ const uschar *pp = eptr;
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || c != *eptr) break;
|
|
+ eptr++;
|
|
+ }
|
|
+ while (eptr >= pp)
|
|
+ if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ return FALSE;
|
|
+ }
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+
|
|
+ /* Match a negated single character */
|
|
+
|
|
+ case OP_NOT:
|
|
+ if (eptr >= md->end_subject) return FALSE;
|
|
+ ecode++;
|
|
+ if ((ims & PCRE_CASELESS) != 0)
|
|
+ {
|
|
+ if (md->lcc[*ecode++] == md->lcc[*eptr++]) return FALSE;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ if (*ecode++ == *eptr++) return FALSE;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ /* Match a negated single character repeatedly. This is almost a repeat of
|
|
+ the code for a repeated single character, but I haven't found a nice way of
|
|
+ commoning these up that doesn't require a test of the positive/negative
|
|
+ option for each character match. Maybe that wouldn't add very much to the
|
|
+ time taken, but character matching *is* what this is all about... */
|
|
+
|
|
+ case OP_NOTEXACT:
|
|
+ min = max = (ecode[1] << 8) + ecode[2];
|
|
+ ecode += 3;
|
|
+ goto REPEATNOTCHAR;
|
|
+
|
|
+ case OP_NOTUPTO:
|
|
+ case OP_NOTMINUPTO:
|
|
+ min = 0;
|
|
+ max = (ecode[1] << 8) + ecode[2];
|
|
+ minimize = *ecode == OP_NOTMINUPTO;
|
|
+ ecode += 3;
|
|
+ goto REPEATNOTCHAR;
|
|
+
|
|
+ case OP_NOTSTAR:
|
|
+ case OP_NOTMINSTAR:
|
|
+ case OP_NOTPLUS:
|
|
+ case OP_NOTMINPLUS:
|
|
+ case OP_NOTQUERY:
|
|
+ case OP_NOTMINQUERY:
|
|
+ c = *ecode++ - OP_NOTSTAR;
|
|
+ minimize = (c & 1) != 0;
|
|
+ min = rep_min[c]; /* Pick up values from tables; */
|
|
+ max = rep_max[c]; /* zero for max => infinity */
|
|
+ if (max == 0) max = INT_MAX;
|
|
+
|
|
+ /* Common code for all repeated single-character matches. We can give
|
|
+ up quickly if there are fewer than the minimum number of characters left in
|
|
+ the subject. */
|
|
+
|
|
+ REPEATNOTCHAR:
|
|
+ if (min > md->end_subject - eptr) return FALSE;
|
|
+ c = *ecode++;
|
|
+
|
|
+ /* The code is duplicated for the caseless and caseful cases, for speed,
|
|
+ since matching characters is likely to be quite common. First, ensure the
|
|
+ minimum number of matches are present. If min = max, continue at the same
|
|
+ level without recursing. Otherwise, if minimizing, keep trying the rest of
|
|
+ the expression and advancing one matching character if failing, up to the
|
|
+ maximum. Alternatively, if maximizing, find the maximum number of
|
|
+ characters and work backwards. */
|
|
+
|
|
+ DPRINTF(("negative matching %c{%d,%d} against subject %.*s\n", c, min, max,
|
|
+ max, eptr));
|
|
+
|
|
+ if ((ims & PCRE_CASELESS) != 0)
|
|
+ {
|
|
+ c = md->lcc[c];
|
|
+ for (i = 1; i <= min; i++)
|
|
+ if (c == md->lcc[*eptr++]) return FALSE;
|
|
+ if (min == max) continue;
|
|
+ if (minimize)
|
|
+ {
|
|
+ for (i = min;; i++)
|
|
+ {
|
|
+ if (match(eptr, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ if (i >= max || eptr >= md->end_subject ||
|
|
+ c == md->lcc[*eptr++])
|
|
+ return FALSE;
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ const uschar *pp = eptr;
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || c == md->lcc[*eptr]) break;
|
|
+ eptr++;
|
|
+ }
|
|
+ while (eptr >= pp)
|
|
+ if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ return FALSE;
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+ }
|
|
+
|
|
+ /* Caseful comparisons */
|
|
+
|
|
+ else
|
|
+ {
|
|
+ for (i = 1; i <= min; i++) if (c == *eptr++) return FALSE;
|
|
+ if (min == max) continue;
|
|
+ if (minimize)
|
|
+ {
|
|
+ for (i = min;; i++)
|
|
+ {
|
|
+ if (match(eptr, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ if (i >= max || eptr >= md->end_subject || c == *eptr++) return FALSE;
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ const uschar *pp = eptr;
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || c == *eptr) break;
|
|
+ eptr++;
|
|
+ }
|
|
+ while (eptr >= pp)
|
|
+ if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+ return FALSE;
|
|
+ }
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+
|
|
+ /* Match a single character type repeatedly; several different opcodes
|
|
+ share code. This is very similar to the code for single characters, but we
|
|
+ repeat it in the interests of efficiency. */
|
|
+
|
|
+ case OP_TYPEEXACT:
|
|
+ min = max = (ecode[1] << 8) + ecode[2];
|
|
+ minimize = TRUE;
|
|
+ ecode += 3;
|
|
+ goto REPEATTYPE;
|
|
+
|
|
+ case OP_TYPEUPTO:
|
|
+ case OP_TYPEMINUPTO:
|
|
+ min = 0;
|
|
+ max = (ecode[1] << 8) + ecode[2];
|
|
+ minimize = *ecode == OP_TYPEMINUPTO;
|
|
+ ecode += 3;
|
|
+ goto REPEATTYPE;
|
|
+
|
|
+ case OP_TYPESTAR:
|
|
+ case OP_TYPEMINSTAR:
|
|
+ case OP_TYPEPLUS:
|
|
+ case OP_TYPEMINPLUS:
|
|
+ case OP_TYPEQUERY:
|
|
+ case OP_TYPEMINQUERY:
|
|
+ c = *ecode++ - OP_TYPESTAR;
|
|
+ minimize = (c & 1) != 0;
|
|
+ min = rep_min[c]; /* Pick up values from tables; */
|
|
+ max = rep_max[c]; /* zero for max => infinity */
|
|
+ if (max == 0) max = INT_MAX;
|
|
+
|
|
+ /* Common code for all repeated single character type matches */
|
|
+
|
|
+ REPEATTYPE:
|
|
+ ctype = *ecode++; /* Code for the character type */
|
|
+
|
|
+ /* First, ensure the minimum number of matches are present. Use inline
|
|
+ code for maximizing the speed, and do the type test once at the start
|
|
+ (i.e. keep it out of the loop). Also we can test that there are at least
|
|
+ the minimum number of bytes before we start, except when doing '.' in
|
|
+ UTF8 mode. Leave the test in in all cases; in the special case we have
|
|
+ to test after each character. */
|
|
+
|
|
+ if (min > md->end_subject - eptr) return FALSE;
|
|
+ if (min > 0) switch(ctype)
|
|
+ {
|
|
+ case OP_ANY:
|
|
+#ifdef SUPPORT_UTF8
|
|
+ if (md->utf8)
|
|
+ {
|
|
+ for (i = 1; i <= min; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject ||
|
|
+ (*eptr++ == NEWLINE && (ims & PCRE_DOTALL) == 0))
|
|
+ return FALSE;
|
|
+ while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+#endif
|
|
+ /* Non-UTF8 can be faster */
|
|
+ if ((ims & PCRE_DOTALL) == 0)
|
|
+ { for (i = 1; i <= min; i++) if (*eptr++ == NEWLINE) return FALSE; }
|
|
+ else eptr += min;
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_DIGIT:
|
|
+ for (i = 1; i <= min; i++)
|
|
+ if ((md->ctypes[*eptr++] & ctype_digit) != 0) return FALSE;
|
|
+ break;
|
|
+
|
|
+ case OP_DIGIT:
|
|
+ for (i = 1; i <= min; i++)
|
|
+ if ((md->ctypes[*eptr++] & ctype_digit) == 0) return FALSE;
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_WHITESPACE:
|
|
+ for (i = 1; i <= min; i++)
|
|
+ if ((md->ctypes[*eptr++] & ctype_space) != 0) return FALSE;
|
|
+ break;
|
|
+
|
|
+ case OP_WHITESPACE:
|
|
+ for (i = 1; i <= min; i++)
|
|
+ if ((md->ctypes[*eptr++] & ctype_space) == 0) return FALSE;
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_WORDCHAR:
|
|
+ for (i = 1; i <= min; i++)
|
|
+ if ((md->ctypes[*eptr++] & ctype_word) != 0)
|
|
+ return FALSE;
|
|
+ break;
|
|
+
|
|
+ case OP_WORDCHAR:
|
|
+ for (i = 1; i <= min; i++)
|
|
+ if ((md->ctypes[*eptr++] & ctype_word) == 0)
|
|
+ return FALSE;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* If min = max, continue at the same level without recursing */
|
|
+
|
|
+ if (min == max) continue;
|
|
+
|
|
+ /* If minimizing, we have to test the rest of the pattern before each
|
|
+ subsequent match. */
|
|
+
|
|
+ if (minimize)
|
|
+ {
|
|
+ for (i = min;; i++)
|
|
+ {
|
|
+ if (match(eptr, ecode, offset_top, md, ims, eptrb, 0)) return TRUE;
|
|
+ if (i >= max || eptr >= md->end_subject) return FALSE;
|
|
+
|
|
+ c = *eptr++;
|
|
+ switch(ctype)
|
|
+ {
|
|
+ case OP_ANY:
|
|
+ if ((ims & PCRE_DOTALL) == 0 && c == NEWLINE) return FALSE;
|
|
+#ifdef SUPPORT_UTF8
|
|
+ if (md->utf8)
|
|
+ while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
|
|
+#endif
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_DIGIT:
|
|
+ if ((md->ctypes[c] & ctype_digit) != 0) return FALSE;
|
|
+ break;
|
|
+
|
|
+ case OP_DIGIT:
|
|
+ if ((md->ctypes[c] & ctype_digit) == 0) return FALSE;
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_WHITESPACE:
|
|
+ if ((md->ctypes[c] & ctype_space) != 0) return FALSE;
|
|
+ break;
|
|
+
|
|
+ case OP_WHITESPACE:
|
|
+ if ((md->ctypes[c] & ctype_space) == 0) return FALSE;
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_WORDCHAR:
|
|
+ if ((md->ctypes[c] & ctype_word) != 0) return FALSE;
|
|
+ break;
|
|
+
|
|
+ case OP_WORDCHAR:
|
|
+ if ((md->ctypes[c] & ctype_word) == 0) return FALSE;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+ }
|
|
+
|
|
+ /* If maximizing it is worth using inline code for speed, doing the type
|
|
+ test once at the start (i.e. keep it out of the loop). */
|
|
+
|
|
+ else
|
|
+ {
|
|
+ const uschar *pp = eptr;
|
|
+ switch(ctype)
|
|
+ {
|
|
+ case OP_ANY:
|
|
+
|
|
+ /* Special code is required for UTF8, but when the maximum is unlimited
|
|
+ we don't need it. */
|
|
+
|
|
+#ifdef SUPPORT_UTF8
|
|
+ if (md->utf8 && max < INT_MAX)
|
|
+ {
|
|
+ if ((ims & PCRE_DOTALL) == 0)
|
|
+ {
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || *eptr++ == NEWLINE) break;
|
|
+ while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
|
|
+ }
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ eptr++;
|
|
+ while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+#endif
|
|
+ /* Non-UTF8 can be faster */
|
|
+ if ((ims & PCRE_DOTALL) == 0)
|
|
+ {
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || *eptr == NEWLINE) break;
|
|
+ eptr++;
|
|
+ }
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ c = max - min;
|
|
+ if (c > md->end_subject - eptr) c = md->end_subject - eptr;
|
|
+ eptr += c;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_DIGIT:
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_digit) != 0)
|
|
+ break;
|
|
+ eptr++;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case OP_DIGIT:
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_digit) == 0)
|
|
+ break;
|
|
+ eptr++;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_WHITESPACE:
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_space) != 0)
|
|
+ break;
|
|
+ eptr++;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case OP_WHITESPACE:
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_space) == 0)
|
|
+ break;
|
|
+ eptr++;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case OP_NOT_WORDCHAR:
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_word) != 0)
|
|
+ break;
|
|
+ eptr++;
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case OP_WORDCHAR:
|
|
+ for (i = min; i < max; i++)
|
|
+ {
|
|
+ if (eptr >= md->end_subject || (md->ctypes[*eptr] & ctype_word) == 0)
|
|
+ break;
|
|
+ eptr++;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ while (eptr >= pp)
|
|
+ {
|
|
+ if (match(eptr--, ecode, offset_top, md, ims, eptrb, 0))
|
|
+ return TRUE;
|
|
+#ifdef SUPPORT_UTF8
|
|
+ if (md->utf8)
|
|
+ while (eptr > pp && (*eptr & 0xc0) == 0x80) eptr--;
|
|
+#endif
|
|
+ }
|
|
+ return FALSE;
|
|
+ }
|
|
+ /* Control never gets here */
|
|
+
|
|
+ /* There's been some horrible disaster. */
|
|
+
|
|
+ default:
|
|
+ DPRINTF(("Unknown opcode %d\n", *ecode));
|
|
+ md->errorcode = PCRE_ERROR_UNKNOWN_NODE;
|
|
+ return FALSE;
|
|
+ }
|
|
+
|
|
+ /* Do not stick any code in here without much thought; it is assumed
|
|
+ that "continue" in the code above comes out to here to repeat the main
|
|
+ loop. */
|
|
+
|
|
+ } /* End of main loop */
|
|
+/* Control never reaches here */
|
|
+}
|
|
+
|
|
+
|
|
+/*************************************************
|
|
+* Execute a Regular Expression *
|
|
+*************************************************/
|
|
+
|
|
+/* This function applies a compiled re to a subject string and picks out
|
|
+portions of the string if it matches. Two elements in the vector are set for
|
|
+each substring: the offsets to the start and end of the substring.
|
|
+
|
|
+Arguments:
|
|
+ external_re points to the compiled expression
|
|
+ external_extra points to "hints" from pcre_study() or is NULL
|
|
+ subject points to the subject string
|
|
+ length length of subject string (may contain binary zeros)
|
|
+ start_offset where to start in the subject string
|
|
+ options option bits
|
|
+ offsets points to a vector of ints to be filled in with offsets
|
|
+ offsetcount the number of elements in the vector
|
|
+
|
|
+Returns: > 0 => success; value is the number of elements filled in
|
|
+ = 0 => success, but offsets is not big enough
|
|
+ -1 => failed to match
|
|
+ < -1 => some kind of unexpected problem
|
|
+*/
|
|
+
|
|
+int
|
|
+pcre_exec(const pcre *external_re, const pcre_extra *external_extra,
|
|
+ const char *subject, int length, int start_offset, int options, int *offsets,
|
|
+ int offsetcount)
|
|
+{
|
|
+int resetcount, ocount;
|
|
+int first_char = -1;
|
|
+int req_char = -1;
|
|
+int req_char2 = -1;
|
|
+unsigned long int ims = 0;
|
|
+match_data match_block;
|
|
+const uschar *start_bits = NULL;
|
|
+const uschar *start_match = (const uschar *)subject + start_offset;
|
|
+const uschar *end_subject;
|
|
+const uschar *req_char_ptr = start_match - 1;
|
|
+const real_pcre *re = (const real_pcre *)external_re;
|
|
+const real_pcre_extra *extra = (const real_pcre_extra *)external_extra;
|
|
+BOOL using_temporary_offsets = FALSE;
|
|
+BOOL anchored;
|
|
+BOOL startline;
|
|
+
|
|
+if ((options & ~PUBLIC_EXEC_OPTIONS) != 0) return PCRE_ERROR_BADOPTION;
|
|
+
|
|
+if (re == NULL || subject == NULL ||
|
|
+ (offsets == NULL && offsetcount > 0)) return PCRE_ERROR_NULL;
|
|
+if (re->magic_number != MAGIC_NUMBER) return PCRE_ERROR_BADMAGIC;
|
|
+
|
|
+anchored = ((re->options | options) & PCRE_ANCHORED) != 0;
|
|
+startline = (re->options & PCRE_STARTLINE) != 0;
|
|
+
|
|
+match_block.start_pattern = re->code;
|
|
+match_block.start_subject = (const uschar *)subject;
|
|
+match_block.end_subject = match_block.start_subject + length;
|
|
+end_subject = match_block.end_subject;
|
|
+
|
|
+match_block.endonly = (re->options & PCRE_DOLLAR_ENDONLY) != 0;
|
|
+match_block.utf8 = (re->options & PCRE_UTF8) != 0;
|
|
+
|
|
+match_block.notbol = (options & PCRE_NOTBOL) != 0;
|
|
+match_block.noteol = (options & PCRE_NOTEOL) != 0;
|
|
+match_block.notempty = (options & PCRE_NOTEMPTY) != 0;
|
|
+
|
|
+match_block.errorcode = PCRE_ERROR_NOMATCH; /* Default error */
|
|
+
|
|
+match_block.lcc = re->tables + lcc_offset;
|
|
+match_block.ctypes = re->tables + ctypes_offset;
|
|
+
|
|
+/* The ims options can vary during the matching as a result of the presence
|
|
+of (?ims) items in the pattern. They are kept in a local variable so that
|
|
+restoring at the exit of a group is easy. */
|
|
+
|
|
+ims = re->options & (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL);
|
|
+
|
|
+/* If the expression has got more back references than the offsets supplied can
|
|
+hold, we get a temporary bit of working store to use during the matching.
|
|
+Otherwise, we can use the vector supplied, rounding down its size to a multiple
|
|
+of 3. */
|
|
+
|
|
+ocount = offsetcount - (offsetcount % 3);
|
|
+
|
|
+if (re->top_backref > 0 && re->top_backref >= ocount/3)
|
|
+ {
|
|
+ ocount = re->top_backref * 3 + 3;
|
|
+ match_block.offset_vector = (int *)(pcre_malloc)(ocount * sizeof(int));
|
|
+ if (match_block.offset_vector == NULL) return PCRE_ERROR_NOMEMORY;
|
|
+ using_temporary_offsets = TRUE;
|
|
+ DPRINTF(("Got memory to hold back references\n"));
|
|
+ }
|
|
+else match_block.offset_vector = offsets;
|
|
+
|
|
+match_block.offset_end = ocount;
|
|
+match_block.offset_max = (2*ocount)/3;
|
|
+match_block.offset_overflow = FALSE;
|
|
+
|
|
+/* Compute the minimum number of offsets that we need to reset each time. Doing
|
|
+this makes a huge difference to execution time when there aren't many brackets
|
|
+in the pattern. */
|
|
+
|
|
+resetcount = 2 + re->top_bracket * 2;
|
|
+if (resetcount > offsetcount) resetcount = ocount;
|
|
+
|
|
+/* Reset the working variable associated with each extraction. These should
|
|
+never be used unless previously set, but they get saved and restored, and so we
|
|
+initialize them to avoid reading uninitialized locations. */
|
|
+
|
|
+if (match_block.offset_vector != NULL)
|
|
+ {
|
|
+ register int *iptr = match_block.offset_vector + ocount;
|
|
+ register int *iend = iptr - resetcount/2 + 1;
|
|
+ while (--iptr >= iend) *iptr = -1;
|
|
+ }
|
|
+
|
|
+/* Set up the first character to match, if available. The first_char value is
|
|
+never set for an anchored regular expression, but the anchoring may be forced
|
|
+at run time, so we have to test for anchoring. The first char may be unset for
|
|
+an unanchored pattern, of course. If there's no first char and the pattern was
|
|
+studied, there may be a bitmap of possible first characters. */
|
|
+
|
|
+if (!anchored)
|
|
+ {
|
|
+ if ((re->options & PCRE_FIRSTSET) != 0)
|
|
+ {
|
|
+ first_char = re->first_char;
|
|
+ if ((ims & PCRE_CASELESS) != 0) first_char = match_block.lcc[first_char];
|
|
+ }
|
|
+ else
|
|
+ if (!startline && extra != NULL &&
|
|
+ (extra->options & PCRE_STUDY_MAPPED) != 0)
|
|
+ start_bits = extra->start_bits;
|
|
+ }
|
|
+
|
|
+/* For anchored or unanchored matches, there may be a "last known required
|
|
+character" set. If the PCRE_CASELESS is set, implying that the match starts
|
|
+caselessly, or if there are any changes of this flag within the regex, set up
|
|
+both cases of the character. Otherwise set the two values the same, which will
|
|
+avoid duplicate testing (which takes significant time). This covers the vast
|
|
+majority of cases. It will be suboptimal when the case flag changes in a regex
|
|
+and the required character in fact is caseful. */
|
|
+
|
|
+if ((re->options & PCRE_REQCHSET) != 0)
|
|
+ {
|
|
+ req_char = re->req_char;
|
|
+ req_char2 = ((re->options & (PCRE_CASELESS | PCRE_ICHANGED)) != 0)?
|
|
+ (re->tables + fcc_offset)[req_char] : req_char;
|
|
+ }
|
|
+
|
|
+/* Loop for handling unanchored repeated matching attempts; for anchored regexs
|
|
+the loop runs just once. */
|
|
+
|
|
+do
|
|
+ {
|
|
+ int rc;
|
|
+ register int *iptr = match_block.offset_vector;
|
|
+ register int *iend = iptr + resetcount;
|
|
+
|
|
+ /* Reset the maximum number of extractions we might see. */
|
|
+
|
|
+ while (iptr < iend) *iptr++ = -1;
|
|
+
|
|
+ /* Advance to a unique first char if possible */
|
|
+
|
|
+ if (first_char >= 0)
|
|
+ {
|
|
+ if ((ims & PCRE_CASELESS) != 0)
|
|
+ while (start_match < end_subject &&
|
|
+ match_block.lcc[*start_match] != first_char)
|
|
+ start_match++;
|
|
+ else
|
|
+ while (start_match < end_subject && *start_match != first_char)
|
|
+ start_match++;
|
|
+ }
|
|
+
|
|
+ /* Or to just after \n for a multiline match if possible */
|
|
+
|
|
+ else if (startline)
|
|
+ {
|
|
+ if (start_match > match_block.start_subject + start_offset)
|
|
+ {
|
|
+ while (start_match < end_subject && start_match[-1] != NEWLINE)
|
|
+ start_match++;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Or to a non-unique first char after study */
|
|
+
|
|
+ else if (start_bits != NULL)
|
|
+ {
|
|
+ while (start_match < end_subject)
|
|
+ {
|
|
+ register int c = *start_match;
|
|
+ if ((start_bits[c/8] & (1 << (c&7))) == 0) start_match++; else break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+#ifdef DEBUG /* Sigh. Some compilers never learn. */
|
|
+ PCRE_PRINTF(">>>> Match against: ");
|
|
+ pchars(start_match, end_subject - start_match, TRUE, &match_block);
|
|
+ PCRE_PRINTF("\n");
|
|
+#endif
|
|
+
|
|
+ /* If req_char is set, we know that that character must appear in the subject
|
|
+ for the match to succeed. If the first character is set, req_char must be
|
|
+ later in the subject; otherwise the test starts at the match point. This
|
|
+ optimization can save a huge amount of backtracking in patterns with nested
|
|
+ unlimited repeats that aren't going to match. We don't know what the state of
|
|
+ case matching may be when this character is hit, so test for it in both its
|
|
+ cases if necessary. However, the different cased versions will not be set up
|
|
+ unless PCRE_CASELESS was given or the casing state changes within the regex.
|
|
+ Writing separate code makes it go faster, as does using an autoincrement and
|
|
+ backing off on a match. */
|
|
+
|
|
+ if (req_char >= 0)
|
|
+ {
|
|
+ register const uschar *p = start_match + ((first_char >= 0)? 1 : 0);
|
|
+
|
|
+ /* We don't need to repeat the search if we haven't yet reached the
|
|
+ place we found it at last time. */
|
|
+
|
|
+ if (p > req_char_ptr)
|
|
+ {
|
|
+ /* Do a single test if no case difference is set up */
|
|
+
|
|
+ if (req_char == req_char2)
|
|
+ {
|
|
+ while (p < end_subject)
|
|
+ {
|
|
+ if (*p++ == req_char) { p--; break; }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Otherwise test for either case */
|
|
+
|
|
+ else
|
|
+ {
|
|
+ while (p < end_subject)
|
|
+ {
|
|
+ register int pp = *p++;
|
|
+ if (pp == req_char || pp == req_char2) { p--; break; }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* If we can't find the required character, break the matching loop */
|
|
+
|
|
+ if (p >= end_subject) break;
|
|
+
|
|
+ /* If we have found the required character, save the point where we
|
|
+ found it, so that we don't search again next time round the loop if
|
|
+ the start hasn't passed this character yet. */
|
|
+
|
|
+ req_char_ptr = p;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* When a match occurs, substrings will be set for all internal extractions;
|
|
+ we just need to set up the whole thing as substring 0 before returning. If
|
|
+ there were too many extractions, set the return code to zero. In the case
|
|
+ where we had to get some local store to hold offsets for backreferences, copy
|
|
+ those back references that we can. In this case there need not be overflow
|
|
+ if certain parts of the pattern were not used. */
|
|
+
|
|
+ match_block.start_match = start_match;
|
|
+ if (!match(start_match, re->code, 2, &match_block, ims, NULL, match_isgroup))
|
|
+ continue;
|
|
+
|
|
+ /* Copy the offset information from temporary store if necessary */
|
|
+
|
|
+ if (using_temporary_offsets)
|
|
+ {
|
|
+ if (offsetcount >= 4)
|
|
+ {
|
|
+ memcpy(offsets + 2, match_block.offset_vector + 2,
|
|
+ (offsetcount - 2) * sizeof(int));
|
|
+ DPRINTF(("Copied offsets from temporary memory\n"));
|
|
+ }
|
|
+ if (match_block.end_offset_top > offsetcount)
|
|
+ match_block.offset_overflow = TRUE;
|
|
+
|
|
+ DPRINTF(("Freeing temporary memory\n"));
|
|
+ (pcre_free)(match_block.offset_vector);
|
|
+ }
|
|
+
|
|
+ rc = match_block.offset_overflow? 0 : match_block.end_offset_top/2;
|
|
+
|
|
+ if (offsetcount < 2) rc = 0; else
|
|
+ {
|
|
+ offsets[0] = start_match - match_block.start_subject;
|
|
+ offsets[1] = match_block.end_match_ptr - match_block.start_subject;
|
|
+ }
|
|
+
|
|
+ DPRINTF((">>>> returning %d\n", rc));
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+/* This "while" is the end of the "do" above */
|
|
+
|
|
+while (!anchored &&
|
|
+ match_block.errorcode == PCRE_ERROR_NOMATCH &&
|
|
+ start_match++ < end_subject);
|
|
+
|
|
+if (using_temporary_offsets)
|
|
+ {
|
|
+ DPRINTF(("Freeing temporary memory\n"));
|
|
+ (pcre_free)(match_block.offset_vector);
|
|
+ }
|
|
+
|
|
+DPRINTF((">>>> returning %d\n", match_block.errorcode));
|
|
+
|
|
+return match_block.errorcode;
|
|
+}
|
|
+
|
|
+/* End of pcre.c */
|
|
Index: linux-2.6.19/security/apparmor/match/pcre_exec.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/match/pcre_exec.h
|
|
@@ -0,0 +1,308 @@
|
|
+/*
|
|
+ * This is a modified header file containing the definitions from
|
|
+ * pcre.h and internal.h required to support pcre_exec()
|
|
+ */
|
|
+
|
|
+
|
|
+/*************************************************
|
|
+* Perl-Compatible Regular Expressions *
|
|
+*************************************************/
|
|
+
|
|
+/* Copyright (c) 1997-2001 University of Cambridge */
|
|
+
|
|
+#ifndef _PCRE_H
|
|
+#define _PCRE_H
|
|
+
|
|
+/* ----- CODE ADDED ---- */
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+#include <linux/slab.h> // for kmalloc/kfree
|
|
+#endif
|
|
+
|
|
+#ifdef __KERNEL__
|
|
+#define PCRE_PRINTF printk
|
|
+#define isprint(x) ((unsigned char)(x) >= 128 && (unsigned char)(x) <= 255)
|
|
+#else
|
|
+#define PCRE_PRINTF printf
|
|
+#endif
|
|
+
|
|
+/* The value of NEWLINE determines the newline character. The default is to
|
|
+ * leave it up to the compiler, but some sites want to force a particular value.
|
|
+ * On Unix systems, "configure" can be used to override this default. */
|
|
+
|
|
+#ifndef NEWLINE
|
|
+#define NEWLINE '\n'
|
|
+#endif
|
|
+
|
|
+/* ---- CODE DELETED ---- */
|
|
+
|
|
+/* Options */
|
|
+
|
|
+#define PCRE_CASELESS 0x0001
|
|
+#define PCRE_MULTILINE 0x0002
|
|
+#define PCRE_DOTALL 0x0004
|
|
+#define PCRE_EXTENDED 0x0008
|
|
+#define PCRE_ANCHORED 0x0010
|
|
+#define PCRE_DOLLAR_ENDONLY 0x0020
|
|
+#define PCRE_EXTRA 0x0040
|
|
+#define PCRE_NOTBOL 0x0080
|
|
+#define PCRE_NOTEOL 0x0100
|
|
+#define PCRE_UNGREEDY 0x0200
|
|
+#define PCRE_NOTEMPTY 0x0400
|
|
+#define PCRE_UTF8 0x0800
|
|
+
|
|
+/* Exec-time and get-time error codes */
|
|
+
|
|
+#define PCRE_ERROR_NOMATCH (-1)
|
|
+#define PCRE_ERROR_NULL (-2)
|
|
+#define PCRE_ERROR_BADOPTION (-3)
|
|
+#define PCRE_ERROR_BADMAGIC (-4)
|
|
+#define PCRE_ERROR_UNKNOWN_NODE (-5)
|
|
+#define PCRE_ERROR_NOMEMORY (-6)
|
|
+#define PCRE_ERROR_NOSUBSTRING (-7)
|
|
+
|
|
+/* ---- CODE DELETED ---- */
|
|
+
|
|
+/* Types */
|
|
+
|
|
+struct real_pcre; /* declaration; the definition is private */
|
|
+struct real_pcre_extra; /* declaration; the definition is private */
|
|
+
|
|
+typedef struct real_pcre pcre;
|
|
+typedef struct real_pcre_extra pcre_extra;
|
|
+
|
|
+/* ---- CODE DELETED ---- */
|
|
+
|
|
+extern int pcre_exec(const pcre *, const pcre_extra *,
|
|
+ const char *, int, int, int, int *,
|
|
+ int);
|
|
+
|
|
+/* ---- CODE ADDED (from internal.h) ---- */
|
|
+
|
|
+/* These are the public options that can change during matching. */
|
|
+
|
|
+#define PCRE_IMS (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL)
|
|
+
|
|
+/* Private options flags start at the most significant end of the four bytes,
|
|
+but skip the top bit so we can use ints for convenience without getting tangled
|
|
+with negative values. The public options defined in pcre.h start at the least
|
|
+significant end. Make sure they don't overlap, though now that we have expanded
|
|
+to four bytes there is plenty of space. */
|
|
+
|
|
+#define PCRE_FIRSTSET 0x40000000 /* first_char is set */
|
|
+#define PCRE_REQCHSET 0x20000000 /* req_char is set */
|
|
+#define PCRE_STARTLINE 0x10000000 /* start after \n for multiline */
|
|
+#define PCRE_ICHANGED 0x04000000 /* i option changes within regex */
|
|
+
|
|
+/* Options for the "extra" block produced by pcre_study(). */
|
|
+
|
|
+#define PCRE_STUDY_MAPPED 0x01 /* a map of starting chars exists */
|
|
+
|
|
+/* Masks for identifying the public options which are permitted at compile
|
|
+time, run time or study time, respectively. */
|
|
+
|
|
+#define PUBLIC_EXEC_OPTIONS \
|
|
+ (PCRE_ANCHORED|PCRE_NOTBOL|PCRE_NOTEOL|PCRE_NOTEMPTY)
|
|
+
|
|
+/* Magic number to provide a small check against being handed junk. */
|
|
+
|
|
+#define MAGIC_NUMBER 0x50435245UL /* 'PCRE' */
|
|
+
|
|
+typedef int BOOL;
|
|
+
|
|
+#define FALSE 0
|
|
+#define TRUE 1
|
|
+
|
|
+/* Opcode table: OP_BRA must be last, as all values >= it are used for brackets
|
|
+that extract substrings. Starting from 1 (i.e. after OP_END), the values up to
|
|
+OP_EOD must correspond in order to the list of escapes immediately above. */
|
|
+
|
|
+enum {
|
|
+ OP_END, /* End of pattern */
|
|
+
|
|
+ /* Values corresponding to backslashed metacharacters */
|
|
+
|
|
+ OP_SOD, /* Start of data: \A */
|
|
+ OP_NOT_WORD_BOUNDARY, /* \B */
|
|
+ OP_WORD_BOUNDARY, /* \b */
|
|
+ OP_NOT_DIGIT, /* \D */
|
|
+ OP_DIGIT, /* \d */
|
|
+ OP_NOT_WHITESPACE, /* \S */
|
|
+ OP_WHITESPACE, /* \s */
|
|
+ OP_NOT_WORDCHAR, /* \W */
|
|
+ OP_WORDCHAR, /* \w */
|
|
+ OP_EODN, /* End of data or \n at end of data: \Z. */
|
|
+ OP_EOD, /* End of data: \z */
|
|
+
|
|
+ OP_OPT, /* Set runtime options */
|
|
+ OP_CIRC, /* Start of line - varies with multiline switch */
|
|
+ OP_DOLL, /* End of line - varies with multiline switch */
|
|
+ OP_ANY, /* Match any character */
|
|
+ OP_CHARS, /* Match string of characters */
|
|
+ OP_NOT, /* Match anything but the following char */
|
|
+
|
|
+ OP_STAR, /* The maximizing and minimizing versions of */
|
|
+ OP_MINSTAR, /* all these opcodes must come in pairs, with */
|
|
+ OP_PLUS, /* the minimizing one second. */
|
|
+ OP_MINPLUS, /* This first set applies to single characters */
|
|
+ OP_QUERY,
|
|
+ OP_MINQUERY,
|
|
+ OP_UPTO, /* From 0 to n matches */
|
|
+ OP_MINUPTO,
|
|
+ OP_EXACT, /* Exactly n matches */
|
|
+
|
|
+ OP_NOTSTAR, /* The maximizing and minimizing versions of */
|
|
+ OP_NOTMINSTAR, /* all these opcodes must come in pairs, with */
|
|
+ OP_NOTPLUS, /* the minimizing one second. */
|
|
+ OP_NOTMINPLUS, /* This first set applies to "not" single characters */
|
|
+ OP_NOTQUERY,
|
|
+ OP_NOTMINQUERY,
|
|
+ OP_NOTUPTO, /* From 0 to n matches */
|
|
+ OP_NOTMINUPTO,
|
|
+ OP_NOTEXACT, /* Exactly n matches */
|
|
+
|
|
+ OP_TYPESTAR, /* The maximizing and minimizing versions of */
|
|
+ OP_TYPEMINSTAR, /* all these opcodes must come in pairs, with */
|
|
+ OP_TYPEPLUS, /* the minimizing one second. These codes must */
|
|
+ OP_TYPEMINPLUS, /* be in exactly the same order as those above. */
|
|
+ OP_TYPEQUERY, /* This set applies to character types such as \d */
|
|
+ OP_TYPEMINQUERY,
|
|
+ OP_TYPEUPTO, /* From 0 to n matches */
|
|
+ OP_TYPEMINUPTO,
|
|
+ OP_TYPEEXACT, /* Exactly n matches */
|
|
+
|
|
+ OP_CRSTAR, /* The maximizing and minimizing versions of */
|
|
+ OP_CRMINSTAR, /* all these opcodes must come in pairs, with */
|
|
+ OP_CRPLUS, /* the minimizing one second. These codes must */
|
|
+ OP_CRMINPLUS, /* be in exactly the same order as those above. */
|
|
+ OP_CRQUERY, /* These are for character classes and back refs */
|
|
+ OP_CRMINQUERY,
|
|
+ OP_CRRANGE, /* These are different to the three seta above. */
|
|
+ OP_CRMINRANGE,
|
|
+
|
|
+ OP_CLASS, /* Match a character class */
|
|
+ OP_REF, /* Match a back reference */
|
|
+ OP_RECURSE, /* Match this pattern recursively */
|
|
+
|
|
+ OP_ALT, /* Start of alternation */
|
|
+ OP_KET, /* End of group that doesn't have an unbounded repeat */
|
|
+ OP_KETRMAX, /* These two must remain together and in this */
|
|
+ OP_KETRMIN, /* order. They are for groups the repeat for ever. */
|
|
+
|
|
+ /* The assertions must come before ONCE and COND */
|
|
+
|
|
+ OP_ASSERT, /* Positive lookahead */
|
|
+ OP_ASSERT_NOT, /* Negative lookahead */
|
|
+ OP_ASSERTBACK, /* Positive lookbehind */
|
|
+ OP_ASSERTBACK_NOT, /* Negative lookbehind */
|
|
+ OP_REVERSE, /* Move pointer back - used in lookbehind assertions */
|
|
+
|
|
+ /* ONCE and COND must come after the assertions, with ONCE first, as there's
|
|
+ a test for >= ONCE for a subpattern that isn't an assertion. */
|
|
+
|
|
+ OP_ONCE, /* Once matched, don't back up into the subpattern */
|
|
+ OP_COND, /* Conditional group */
|
|
+ OP_CREF, /* Used to hold an extraction string number (cond ref) */
|
|
+
|
|
+ OP_BRAZERO, /* These two must remain together and in this */
|
|
+ OP_BRAMINZERO, /* order. */
|
|
+
|
|
+ OP_BRANUMBER, /* Used for extracting brackets whose number is greater
|
|
+ than can fit into an opcode. */
|
|
+
|
|
+ OP_BRA /* This and greater values are used for brackets that
|
|
+ extract substrings up to a basic limit. After that,
|
|
+ use is made of OP_BRANUMBER. */
|
|
+};
|
|
+
|
|
+/* The highest extraction number before we have to start using additional
|
|
+bytes. (Originally PCRE didn't have support for extraction counts highter than
|
|
+this number.) The value is limited by the number of opcodes left after OP_BRA,
|
|
+i.e. 255 - OP_BRA. We actually set it a bit lower to leave room for additional
|
|
+opcodes. */
|
|
+
|
|
+#define EXTRACT_BASIC_MAX 150
|
|
+
|
|
+/* All character handling must be done as unsigned characters. Otherwise there
|
|
+are problems with top-bit-set characters and functions such as isspace().
|
|
+However, we leave the interface to the outside world as char *, because that
|
|
+should make things easier for callers. We define a short type for unsigned char
|
|
+to save lots of typing. I tried "uchar", but it causes problems on Digital
|
|
+Unix, where it is defined in sys/types, so use "uschar" instead. */
|
|
+
|
|
+typedef unsigned char uschar;
|
|
+
|
|
+/* The real format of the start of the pcre block; the actual code vector
|
|
+runs on as long as necessary after the end. */
|
|
+
|
|
+typedef struct real_pcre {
|
|
+ unsigned long int magic_number;
|
|
+ size_t size;
|
|
+ const unsigned char *tables;
|
|
+ unsigned long int options;
|
|
+ unsigned short int top_bracket;
|
|
+ unsigned short int top_backref;
|
|
+ uschar first_char;
|
|
+ uschar req_char;
|
|
+ uschar code[1];
|
|
+} real_pcre;
|
|
+
|
|
+/* The real format of the extra block returned by pcre_study(). */
|
|
+
|
|
+typedef struct real_pcre_extra {
|
|
+ uschar options;
|
|
+ uschar start_bits[32];
|
|
+} real_pcre_extra;
|
|
+
|
|
+/* Structure for passing "static" information around between the functions
|
|
+doing the matching, so that they are thread-safe. */
|
|
+
|
|
+typedef struct match_data {
|
|
+ int errorcode; /* As it says */
|
|
+ int *offset_vector; /* Offset vector */
|
|
+ int offset_end; /* One past the end */
|
|
+ int offset_max; /* The maximum usable for return data */
|
|
+ const uschar *lcc; /* Points to lower casing table */
|
|
+ const uschar *ctypes; /* Points to table of type maps */
|
|
+ BOOL offset_overflow; /* Set if too many extractions */
|
|
+ BOOL notbol; /* NOTBOL flag */
|
|
+ BOOL noteol; /* NOTEOL flag */
|
|
+ BOOL utf8; /* UTF8 flag */
|
|
+ BOOL endonly; /* Dollar not before final \n */
|
|
+ BOOL notempty; /* Empty string match not wanted */
|
|
+ const uschar *start_pattern; /* For use when recursing */
|
|
+ const uschar *start_subject; /* Start of the subject string */
|
|
+ const uschar *end_subject; /* End of the subject string */
|
|
+ const uschar *start_match; /* Start of this match attempt */
|
|
+ const uschar *end_match_ptr; /* Subject position at end match */
|
|
+ int end_offset_top; /* Highwater mark at end of match */
|
|
+} match_data;
|
|
+
|
|
+/* Bit definitions for entries in the pcre_ctypes table. */
|
|
+
|
|
+#define ctype_space 0x01
|
|
+#define ctype_letter 0x02
|
|
+#define ctype_digit 0x04
|
|
+#define ctype_xdigit 0x08
|
|
+#define ctype_word 0x10 /* alphameric or '_' */
|
|
+#define ctype_meta 0x80 /* regexp meta char or zero (end pattern) */
|
|
+
|
|
+/* Offsets for the bitmap tables in pcre_cbits. Each table contains a set
|
|
+of bits for a class map. Some classes are built by combining these tables. */
|
|
+
|
|
+#define cbit_length 320 /* Length of the cbits table */
|
|
+
|
|
+/* Offsets of the various tables from the base tables pointer, and
|
|
+total length. */
|
|
+
|
|
+#define lcc_offset 0
|
|
+#define fcc_offset 256
|
|
+
|
|
+#define fcc_offset 256
|
|
+#define cbits_offset 512
|
|
+#define ctypes_offset (cbits_offset + cbit_length)
|
|
+
|
|
+/* ----- CODE ADDED ---- */
|
|
+
|
|
+#endif // _PCRE_H
|
|
+ /* End of pcre.h */
|
|
Index: linux-2.6.19/security/apparmor/match/pcre_tables.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.19/security/apparmor/match/pcre_tables.h
|
|
@@ -0,0 +1,184 @@
|
|
+
|
|
+/*************************************************
|
|
+* Perl-Compatible Regular Expressions *
|
|
+*************************************************/
|
|
+
|
|
+/* This file is automatically written by the dftables auxiliary
|
|
+program. If you edit it by hand, you might like to edit the Makefile to
|
|
+prevent its ever being regenerated.
|
|
+
|
|
+This file is #included in the compilation of pcre.c to build the default
|
|
+character tables which are used when no tables are passed to the compile
|
|
+function. */
|
|
+
|
|
+static unsigned char pcre_default_tables[] = {
|
|
+
|
|
+/* This table is a lower casing table. */
|
|
+
|
|
+ 0, 1, 2, 3, 4, 5, 6, 7,
|
|
+ 8, 9, 10, 11, 12, 13, 14, 15,
|
|
+ 16, 17, 18, 19, 20, 21, 22, 23,
|
|
+ 24, 25, 26, 27, 28, 29, 30, 31,
|
|
+ 32, 33, 34, 35, 36, 37, 38, 39,
|
|
+ 40, 41, 42, 43, 44, 45, 46, 47,
|
|
+ 48, 49, 50, 51, 52, 53, 54, 55,
|
|
+ 56, 57, 58, 59, 60, 61, 62, 63,
|
|
+ 64, 97, 98, 99,100,101,102,103,
|
|
+ 104,105,106,107,108,109,110,111,
|
|
+ 112,113,114,115,116,117,118,119,
|
|
+ 120,121,122, 91, 92, 93, 94, 95,
|
|
+ 96, 97, 98, 99,100,101,102,103,
|
|
+ 104,105,106,107,108,109,110,111,
|
|
+ 112,113,114,115,116,117,118,119,
|
|
+ 120,121,122,123,124,125,126,127,
|
|
+ 128,129,130,131,132,133,134,135,
|
|
+ 136,137,138,139,140,141,142,143,
|
|
+ 144,145,146,147,148,149,150,151,
|
|
+ 152,153,154,155,156,157,158,159,
|
|
+ 160,161,162,163,164,165,166,167,
|
|
+ 168,169,170,171,172,173,174,175,
|
|
+ 176,177,178,179,180,181,182,183,
|
|
+ 184,185,186,187,188,189,190,191,
|
|
+ 192,193,194,195,196,197,198,199,
|
|
+ 200,201,202,203,204,205,206,207,
|
|
+ 208,209,210,211,212,213,214,215,
|
|
+ 216,217,218,219,220,221,222,223,
|
|
+ 224,225,226,227,228,229,230,231,
|
|
+ 232,233,234,235,236,237,238,239,
|
|
+ 240,241,242,243,244,245,246,247,
|
|
+ 248,249,250,251,252,253,254,255,
|
|
+
|
|
+/* This table is a case flipping table. */
|
|
+
|
|
+ 0, 1, 2, 3, 4, 5, 6, 7,
|
|
+ 8, 9, 10, 11, 12, 13, 14, 15,
|
|
+ 16, 17, 18, 19, 20, 21, 22, 23,
|
|
+ 24, 25, 26, 27, 28, 29, 30, 31,
|
|
+ 32, 33, 34, 35, 36, 37, 38, 39,
|
|
+ 40, 41, 42, 43, 44, 45, 46, 47,
|
|
+ 48, 49, 50, 51, 52, 53, 54, 55,
|
|
+ 56, 57, 58, 59, 60, 61, 62, 63,
|
|
+ 64, 97, 98, 99,100,101,102,103,
|
|
+ 104,105,106,107,108,109,110,111,
|
|
+ 112,113,114,115,116,117,118,119,
|
|
+ 120,121,122, 91, 92, 93, 94, 95,
|
|
+ 96, 65, 66, 67, 68, 69, 70, 71,
|
|
+ 72, 73, 74, 75, 76, 77, 78, 79,
|
|
+ 80, 81, 82, 83, 84, 85, 86, 87,
|
|
+ 88, 89, 90,123,124,125,126,127,
|
|
+ 128,129,130,131,132,133,134,135,
|
|
+ 136,137,138,139,140,141,142,143,
|
|
+ 144,145,146,147,148,149,150,151,
|
|
+ 152,153,154,155,156,157,158,159,
|
|
+ 160,161,162,163,164,165,166,167,
|
|
+ 168,169,170,171,172,173,174,175,
|
|
+ 176,177,178,179,180,181,182,183,
|
|
+ 184,185,186,187,188,189,190,191,
|
|
+ 192,193,194,195,196,197,198,199,
|
|
+ 200,201,202,203,204,205,206,207,
|
|
+ 208,209,210,211,212,213,214,215,
|
|
+ 216,217,218,219,220,221,222,223,
|
|
+ 224,225,226,227,228,229,230,231,
|
|
+ 232,233,234,235,236,237,238,239,
|
|
+ 240,241,242,243,244,245,246,247,
|
|
+ 248,249,250,251,252,253,254,255,
|
|
+
|
|
+/* This table contains bit maps for various character classes.
|
|
+Each map is 32 bytes long and the bits run from the least
|
|
+significant end of each byte. The classes that have their own
|
|
+maps are: space, xdigit, digit, upper, lower, word, graph
|
|
+print, punct, and cntrl. Other classes are built from combinations. */
|
|
+
|
|
+ 0x00,0x3e,0x00,0x00,0x01,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03,
|
|
+ 0x7e,0x00,0x00,0x00,0x7e,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0xfe,0xff,0xff,0x07,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03,
|
|
+ 0xfe,0xff,0xff,0x87,0xfe,0xff,0xff,0x07,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+
|
|
+ 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,
|
|
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+
|
|
+ 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,
|
|
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+
|
|
+ 0x00,0x00,0x00,0x00,0xfe,0xff,0x00,0xfc,
|
|
+ 0x01,0x00,0x00,0xf8,0x01,0x00,0x00,0x78,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+
|
|
+ 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
+
|
|
+/* This table identifies various classes of character by individual bits:
|
|
+ 0x01 white space character
|
|
+ 0x02 letter
|
|
+ 0x04 decimal digit
|
|
+ 0x08 hexadecimal digit
|
|
+ 0x10 alphanumeric or '_'
|
|
+ 0x80 regular expression metacharacter or binary zero
|
|
+*/
|
|
+
|
|
+ 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */
|
|
+ 0x00,0x01,0x01,0x01,0x01,0x01,0x00,0x00, /* 8- 15 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */
|
|
+ 0x01,0x00,0x00,0x00,0x80,0x00,0x00,0x00, /* - ' */
|
|
+ 0x80,0x80,0x80,0x80,0x00,0x00,0x80,0x00, /* ( - / */
|
|
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */
|
|
+ 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x80, /* 8 - ? */
|
|
+ 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* @ - G */
|
|
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* H - O */
|
|
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* P - W */
|
|
+ 0x12,0x12,0x12,0x80,0x00,0x00,0x80,0x10, /* X - _ */
|
|
+ 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* ` - g */
|
|
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* h - o */
|
|
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* p - w */
|
|
+ 0x12,0x12,0x12,0x80,0x80,0x00,0x00,0x00, /* x -127 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */
|
|
+
|
|
+/* End of chartables.c */
|