apparmor-2.8.95~2430/0000755000175000017500000000000012311706715014040 5ustar sarnoldsarnoldapparmor-2.8.95~2430/kernel-patches/0000755000175000017500000000000012311706677016754 5ustar sarnoldsarnoldapparmor-2.8.95~2430/kernel-patches/3.8/0000755000175000017500000000000012311706711017251 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.8/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patchapparmor-2.8.95~2430/kernel-patches/3.8/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.pa0000644000175000017500000006474012234002577031414 0ustar sarnoldsarnoldFrom ab9e3accebaaf0a6d123d73bd7387d4b81df92ef Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 May 2012 10:58:05 -0700 Subject: [PATCH 4/4] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount Add the ability for apparmor to do mediation of mount operations. Mount rules require an updated apparmor_parser (2.8 series) for policy compilation. The basic form of the rules are. [audit] [deny] mount [conds]* [device] [ -> [conds] path], [audit] [deny] remount [conds]* [path], [audit] [deny] umount [conds]* [path], [audit] [deny] pivotroot [oldroot=] remount is just a short cut for mount options=remount where [conds] can be fstype= options= Example mount commands mount, # allow all mounts, but not umount or pivotroot mount fstype=procfs, # allow mounting procfs anywhere mount options=(bind, ro) /foo -> /bar, # readonly bind mount mount /dev/sda -> /mnt, mount /dev/sd** -> /mnt/**, mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ umount, umount /m*, See the apparmor userspace for full documentation Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 13 + security/apparmor/audit.c | 4 + security/apparmor/domain.c | 2 +- security/apparmor/include/apparmor.h | 3 +- security/apparmor/include/audit.h | 11 + security/apparmor/include/domain.h | 2 + security/apparmor/include/mount.h | 54 +++ security/apparmor/lsm.c | 59 ++++ security/apparmor/mount.c | 620 +++++++++++++++++++++++++++++++++++ 10 files changed, 767 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/mount.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index e270692..9b44e1a 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o net.o + resource.o sid.o file.o net.o mount.o clean-files := capability_names.h rlim_names.h net_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 114fb23..ee77ec9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -426,10 +426,23 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { { } }; +static struct aa_fs_entry aa_fs_entry_mount[] = { + AA_FS_FILE_STRING("mask", "mount umount"), + { } +}; + +static struct aa_fs_entry aa_fs_entry_namespaces[] = { + AA_FS_FILE_BOOLEAN("profile", 1), + AA_FS_FILE_BOOLEAN("pivot_root", 1), + { } +}; + static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_DIR("mount", aa_fs_entry_mount), + AA_FS_DIR("namespaces", aa_fs_entry_namespaces), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 3ae28db..e267963 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -44,6 +44,10 @@ const char *const op_table[] = { "file_mmap", "file_mprotect", + "pivotroot", + "mount", + "umount", + "create", "post_create", "bind", diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 60f0c76..4625a28 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -242,7 +242,7 @@ static const char *next_name(int xtype, const char *name) * * Returns: refcounted profile, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) { struct aa_profile *new_profile = NULL; struct aa_namespace *ns = profile->ns; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 40aedd9..e243d96 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -29,8 +29,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_MOUNT 7 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_MOUNT /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 4af6523..ada004d 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -73,6 +73,10 @@ enum aa_ops { OP_FMMAP, OP_FMPROT, + OP_PIVOTROOT, + OP_MOUNT, + OP_UMOUNT, + OP_CREATE, OP_POST_CREATE, OP_BIND, @@ -122,6 +126,13 @@ struct apparmor_audit_data { unsigned long max; } rlim; struct { + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { const char *target; u32 request; u32 denied; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464..a3f70c5 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,6 +23,8 @@ struct aa_domain { char **table; }; +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm); diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 0000000..bc17a53 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 Canonical Ltd. + * + * 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 __AA_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags); + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *old_name); + +int aa_new_mount(struct aa_profile *profile, const char *dev_name, + struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path); + +#endif /* __AA_MOUNT_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index dcb578e..1989066 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -36,6 +36,7 @@ #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" +#include "include/mount.h" /* Flag indicating whether initialization completed */ int apparmor_initialized __initdata; @@ -504,6 +505,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + int error = 0; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + profile = __aa_current_profile(); + if (!unconfined(profile)) { + if (flags & MS_REMOUNT) + error = aa_remount(profile, path, flags, data); + else if (flags & MS_BIND) + error = aa_bind_mount(profile, path, dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(profile, path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount(profile, path, dev_name); + else + error = aa_new_mount(profile, dev_name, path, type, + flags, data); + } + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_umount(profile, mnt, flags); + + return error; +} + +static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_pivotroot(profile, old_path, new_path); + + return error; +} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { @@ -721,6 +776,10 @@ static struct security_operations apparmor_ops = { .capget = apparmor_capget, .capable = apparmor_capable, + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_sb_umount, + .sb_pivotroot = apparmor_sb_pivotroot, + .path_link = apparmor_path_link, .path_unlink = apparmor_path_unlink, .path_symlink = apparmor_path_symlink, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 0000000..478aa4d --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,620 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (sa->aad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, sa->aad->mnt.type); + } + if (sa->aad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, sa->aad->mnt.src_name); + } + if (sa->aad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, sa->aad->mnt.trans); + } + if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, sa->aad->mnt.flags); + audit_log_format(ab, "\""); + } + if (sa->aad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, sa->aad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @profile: the profile being enforced (NOT NULL) + * @gfp: allocation flags + * @op: operation being mediated (NOT NULL) + * @name: name of object being mediated (MAYBE NULL) + * @src_name: src_name of object being mediated (MAYBE_NULL) + * @type: type of filesystem (MAYBE_NULL) + * @trans: name of trans (MAYBE NULL) + * @flags: filesystem idependent mount flags + * @data: filesystem mount flags + * @request: permissions requested + * @perms: the permissions computed for the request (NOT NULL) + * @info: extra information message (MAYBE NULL) + * @error: 0 if operation allowed else failure error code + * + * Returns: %0 or error on failure + */ +static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct file_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa = { }; + struct apparmor_audit_data aad = { }; + + if (likely(!error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + request &= mask; + + if (likely(!request)) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + request = request & ~perms->allow; + + if (request & perms->kill) + audit_type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + request &= ~perms->quiet; + + if (!request) + return COMPLAIN_MODE(profile) ? + complain_error(error) : error; + } + + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + sa.aad->op = op; + sa.aad->name = name; + sa.aad->mnt.src_name = src_name; + sa.aad->mnt.type = type; + sa.aad->mnt.trans = trans; + sa.aad->mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + sa.aad->mnt.data = data; + sa.aad->info = info; + sa.aad->error = error; + + return aa_audit(audit_type, profile, gfp, &sa, audit_cb); +} + +/** + * match_mnt_flags - Do an ordered match on mount flags + * @dfa: dfa to match against + * @state: state to start in + * @flags: mount flags to match against + * + * Mount flags are encoded as an ordered match. This is done instead of + * checking against a simple bitmask, to allow for logical operations + * on the flags. + * + * Returns: next state after flags match + */ +static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, + unsigned long flags) +{ + unsigned int i; + + for (i = 0; i <= 31 ; ++i) { + if ((1 << i) & flags) + state = aa_dfa_next(dfa, state, i + 1); + } + + return state; +} + +/** + * compute_mnt_perms - compute mount permission associated with @state + * @dfa: dfa to match against (NOT NULL) + * @state: state match finished in + * + * Returns: mount permissions + */ +static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, + unsigned int state) +{ + struct file_perms perms; + + perms.kill = 0; + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + perms.xindex = dfa_user_xindex(dfa, state); + + return perms; +} + +static const char const *mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match" +}; + +/* + * Returns 0 on success else element that match failed in, this is the + * index into the mnt_info_table above + */ +static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct file_perms *perms) +{ + unsigned int state; + + state = aa_dfa_match(dfa, start, mntpnt); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(dfa, state, devname); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(dfa, state, type); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(dfa, state, flags); + if (!state) + return 4; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + + /* only match data if not binary and the DFA flags data is expected */ + if (data && !binary && (perms->allow & AA_CONT_MATCH)) { + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(dfa, state, data); + if (!state) + return 5; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at end of flags match */ + return 4; +} + +/** + * match_mnt - handle path matching for mount + * @profile: the confining profile + * @mntpnt: string for the mntpnt (NOT NULL) + * @devname: string for the devname/src_name (MAYBE NULL) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @perms: Returns: permission found by the match + * @info: Returns: infomation string about the match for logging + * + * Returns: 0 on success else error + */ +static int match_mnt(struct aa_profile *profile, const char *mntpnt, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + struct file_perms *perms, const char **info) +{ + int pos; + + if (!profile->policy.dfa) + return -EACCES; + + pos = do_match_mnt(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + mntpnt, devname, type, flags, data, binary, perms); + if (pos) { + *info = mnt_info_table[pos]; + return -EACCES; + } + + return 0; +} + +static int path_flags(struct aa_profile *profile, struct path *path) +{ + return profile->path_flags | + S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; +} + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data) +{ + struct file_perms perms = { }; + const char *name, *info = NULL; + char *buffer = NULL; + int binary, error; + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *dev_name, unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, + &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *orig_name) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, + struct path *path, const char *type, unsigned long flags, + void *data) +{ + struct file_perms perms = { }; + char *buffer = NULL, *dev_buffer = NULL; + const char *name = NULL, *dev_name = NULL, *info = NULL; + int binary = 1; + int error; + + dev_name = orig_dev_name; + if (type) { + int requires_dev; + struct file_system_type *fstype = get_fs_type(type); + if (!fstype) + return -ENODEV; + + binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; + requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; + put_filesystem(fstype); + + if (requires_dev) { + struct path dev_path; + + if (!dev_name || !*dev_name) { + error = -ENOENT; + goto out; + } + + error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); + if (error) + goto audit; + + error = aa_path_name(&dev_path, + path_flags(profile, &dev_path), + &dev_buffer, &dev_name, &info); + path_put(&dev_path); + if (error) + goto audit; + } + } + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, dev_name, type, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, + type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + kfree(dev_buffer); + +out: + return error; + +} + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + struct path path = { mnt, mnt->mnt_root }; + error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, + &info); + if (error) + goto audit; + + if (!error && profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); + kfree(buffer); + + return error; +} + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path) +{ + struct file_perms perms = { }; + struct aa_profile *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL; + const char *old_name, *new_name = NULL, *info = NULL; + int error; + + error = aa_path_name(old_path, path_flags(profile, old_path), + &old_buffer, &old_name, &info); + if (error) + goto audit; + + error = aa_path_name(new_path, path_flags(profile, new_path), + &new_buffer, &new_name, &info); + if (error) + goto audit; + + if (profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(profile->policy.dfa, state); + state = aa_dfa_match(profile->policy.dfa, state, old_name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_PIVOTROOT & perms.allow) { + if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + target = x_table_lookup(profile, perms.xindex); + if (!target) + error = -ENOENT; + else + error = aa_replace_current_profile(target); + } + } else + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, + old_name, NULL, target ? target->base.name : NULL, + 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); + aa_put_profile(target); + kfree(old_buffer); + kfree(new_buffer); + + return error; +} -- 1.8.3.2 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.8/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.patchapparmor-2.8.95~2430/kernel-patches/3.8/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.pa0000644000175000017500000001744412234002577031524 0ustar sarnoldsarnoldFrom 301639739c56c36bcbe90000934fedb416a65019 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 1/4] UBUNTU: SAUCE: AppArmor: Add profile introspection file to interface Add the dynamic profiles file to the interace, to allow load policy introspection. Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Kconfig | 9 ++ security/apparmor/apparmorfs.c | 231 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16c15ec..42b7c9f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,6 +182,234 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; +#endif /* CONFIG_SECURITY_APPARMOR_COMPAT_24 */ + /** Base file system setup **/ static struct aa_fs_entry aa_fs_entry_file[] = { @@ -210,6 +438,9 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = { AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), +#endif AA_FS_DIR("features", aa_fs_entry_features), { } }; -- 1.8.3.2 apparmor-2.8.95~2430/kernel-patches/3.8/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch0000644000175000017500000004305112234002577030652 0ustar sarnoldsarnoldFrom 7e975d9b537f9aeadbf13bede10c6b5e5fb47a1d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 2/4] UBUNTU: SAUCE: AppArmor: basic networking rules Base support for network mediation. Signed-off-by: John Johansen --- security/apparmor/.gitignore | 1 + security/apparmor/Makefile | 42 +++++++++- security/apparmor/apparmorfs.c | 1 + security/apparmor/include/audit.h | 4 + security/apparmor/include/net.h | 44 ++++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ security/apparmor/net.c | 162 +++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 46 +++++++++++ 10 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 9cdec70..d5b291e 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,5 +1,6 @@ # # Generated include files # +net_names.h capability_names.h rlim_names.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 5706b74..e270692 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h net_names.h # Build a lower case string table of capability names @@ -20,6 +20,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ +# Build a lower case string table of address family names +# Transform lines from +# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [1] = "local", +# [2] = "inet", +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# #define AA_FS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ ;\ + echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ + sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ + $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +# Build a lower case string table of sock type names +# Transform lines from +# SOCK_STREAM = 1, +# to +# [1] = "stream", +quiet_cmd_make-sock = GEN $@ +cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ + sed $^ >>$@ -r -n \ + -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ # Build a lower case string table of rlimit names. # Transforms lines from @@ -56,6 +88,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(src)/Makefile @@ -63,3 +96,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) +$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ + $(srctree)/include/linux/net.h \ + $(src)/Makefile + $(call cmd,make-af) + $(call cmd,make-sock) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 42b7c9f..114fb23 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -429,6 +429,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 69d8cae..4af6523 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -127,6 +127,10 @@ struct apparmor_audit_data { u32 denied; kuid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; }; diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..cb8a121 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,44 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +#include "apparmorfs.h" + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern struct aa_fs_entry aa_fs_entry_network[]; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index bda4569..eb13a73 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *const profile_mode_names[]; @@ -157,6 +158,7 @@ struct aa_policydb { * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -194,6 +196,7 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8c2a7f6..dcb578e 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -614,6 +615,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -646,6 +745,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..003dd18 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,162 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "net_names.h" + +struct aa_fs_entry aa_fs_entry_network[] = { + AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), + { } +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net->family]) { + audit_log_string(ab, address_family_names[sa->u.net->family]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); + } + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad->net.type]) { + audit_log_string(ab, sock_type_names[sa->aad->net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); + } + audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + struct lsm_network_audit net = { }; + if (sk) { + sa.type = LSM_AUDIT_DATA_NET; + } else { + sa.type = LSM_AUDIT_DATA_NONE; + } + /* todo fill in socket addr info */ + sa.aad = &aad; + sa.u.net = &net; + sa.aad->op = op, + sa.u.net->family = family; + sa.u.net->sk = sk; + sa.aad->net.type = type; + sa.aad->net.protocol = protocol; + sa.aad->error = error; + + if (likely(!sa.aad->error)) { + u16 audit_mask = profile->net.audit[sa.u.net->family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad->net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 8132003..56e5304 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -747,6 +747,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 329b1fd..1b90dfa 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -193,6 +193,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -471,6 +484,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; + size_t size = 0; int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -564,6 +578,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + } + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ profile->policy.dfa = unpack_dfa(e); -- 1.8.3.2 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.8/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.patchapparmor-2.8.95~2430/kernel-patches/3.8/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.pa0000644000175000017500000000277412234002577032223 0ustar sarnoldsarnoldFrom c7208a008f899194674c77b424ea8386ec2fb413 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jun 2012 17:34:00 -0700 Subject: [PATCH 3/4] apparmor: Fix quieting of audit messages for network mediation If a profile specified a quieting of network denials for a given rule by either the quiet or deny rule qualifiers, the resultant quiet mask for denied requests was applied incorrectly, resulting in two potential bugs. 1. The misapplied quiet mask would prevent denials from being correctly tested against the kill mask/mode. Thus network access requests that should have resulted in the application being killed did not. 2. The actual quieting of the denied network request was not being applied. This would result in network rejections always being logged even when they had been specifically marked as quieted. Signed-off-by: John Johansen --- security/apparmor/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 003dd18..6e6e5c9 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -88,7 +88,7 @@ static int audit_net(struct aa_profile *profile, int op, u16 family, int type, } else { u16 quiet_mask = profile->net.quiet[sa.u.net->family]; u16 kill_mask = 0; - u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + u16 denied = (1 << sa.aad->net.type); if (denied & kill_mask) audit_type = AUDIT_APPARMOR_KILL; -- 1.8.3.2 apparmor-2.8.95~2430/kernel-patches/3.6/0000755000175000017500000000000012311706722017251 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.6/0006-apparmor-fix-IRQ-stack-overflow-during-free_profile.patchapparmor-2.8.95~2430/kernel-patches/3.6/0006-apparmor-fix-IRQ-stack-overflow-during-free_profile.pat0000644000175000017500000000457712053023750032302 0ustar sarnoldsarnoldFrom 663d5bbe6197bf990721c37ec877ea8ba5840202 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 24 Oct 2012 06:27:32 -0700 Subject: [PATCH 6/6] apparmor: fix IRQ stack overflow during free_profile BugLink: http://bugs.launchpad.net/bugs/1056078 Profile replacement can cause long chains of profiles to build up when the profile being replaced is pinned. When the pinned profile is finally freed, it puts the reference to its replacement, which may in turn nest another call to free_profile on the stack. Because this may happen for each profile in the replacedby chain this can result in a recusion that causes the stack to overflow. Break this nesting by directly walking the chain of replacedby profiles (ie. use iteration instead of recursion to free the list). This results in at most 2 levels of free_profile being called, while freeing a replacedby chain. Signed-off-by: John Johansen Signed-off-by: James Morris --- security/apparmor/policy.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 27c8161..56e5304 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -724,6 +724,8 @@ fail: */ static void free_profile(struct aa_profile *profile) { + struct aa_profile *p; + AA_DEBUG("%s(%p)\n", __func__, profile); if (!profile) @@ -752,7 +754,27 @@ static void free_profile(struct aa_profile *profile) aa_put_dfa(profile->xmatch); aa_put_dfa(profile->policy.dfa); - aa_put_profile(profile->replacedby); + /* put the profile reference for replacedby, but not via + * put_profile(kref_put). + * replacedby can form a long chain that can result in cascading + * frees that blows the stack because kref_put makes a nested fn + * call (it looks like recursion, with free_profile calling + * free_profile) for each profile in the chain lp#1056078. + */ + for (p = profile->replacedby; p; ) { + if (atomic_dec_and_test(&p->base.count.refcount)) { + /* no more refs on p, grab its replacedby */ + struct aa_profile *next = p->replacedby; + /* break the chain */ + p->replacedby = NULL; + /* now free p, chain is broken */ + free_profile(p); + + /* follow up with next profile in the chain */ + p = next; + } else + break; + } kzfree(profile); } -- 1.7.10.4 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.6/0005-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patchapparmor-2.8.95~2430/kernel-patches/3.6/0005-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.pa0000644000175000017500000006475312053023750031412 0ustar sarnoldsarnoldFrom f5e962d77f98deab3461404567abd4759f5445a7 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 May 2012 10:58:05 -0700 Subject: [PATCH 5/6] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount Add the ability for apparmor to do mediation of mount operations. Mount rules require an updated apparmor_parser (2.8 series) for policy compilation. The basic form of the rules are. [audit] [deny] mount [conds]* [device] [ -> [conds] path], [audit] [deny] remount [conds]* [path], [audit] [deny] umount [conds]* [path], [audit] [deny] pivotroot [oldroot=] remount is just a short cut for mount options=remount where [conds] can be fstype= options= Example mount commands mount, # allow all mounts, but not umount or pivotroot mount fstype=procfs, # allow mounting procfs anywhere mount options=(bind, ro) /foo -> /bar, # readonly bind mount mount /dev/sda -> /mnt, mount /dev/sd** -> /mnt/**, mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ umount, umount /m*, See the apparmor userspace for full documentation Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 13 + security/apparmor/audit.c | 4 + security/apparmor/domain.c | 2 +- security/apparmor/include/apparmor.h | 3 +- security/apparmor/include/audit.h | 11 + security/apparmor/include/domain.h | 2 + security/apparmor/include/mount.h | 54 +++ security/apparmor/lsm.c | 59 ++++ security/apparmor/mount.c | 620 ++++++++++++++++++++++++++++++++++ 10 files changed, 767 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/mount.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 19daa85..63e0a4c 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o net.o + resource.o sid.o file.o net.o mount.o clean-files := capability_names.h rlim_names.h net_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 114fb23..ee77ec9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -426,10 +426,23 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { { } }; +static struct aa_fs_entry aa_fs_entry_mount[] = { + AA_FS_FILE_STRING("mask", "mount umount"), + { } +}; + +static struct aa_fs_entry aa_fs_entry_namespaces[] = { + AA_FS_FILE_BOOLEAN("profile", 1), + AA_FS_FILE_BOOLEAN("pivot_root", 1), + { } +}; + static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_DIR("mount", aa_fs_entry_mount), + AA_FS_DIR("namespaces", aa_fs_entry_namespaces), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 3ae28db..e267963 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -44,6 +44,10 @@ const char *const op_table[] = { "file_mmap", "file_mprotect", + "pivotroot", + "mount", + "umount", + "create", "post_create", "bind", diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index b81ea10..afa8671 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -242,7 +242,7 @@ static const char *next_name(int xtype, const char *name) * * Returns: refcounted profile, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) { struct aa_profile *new_profile = NULL; struct aa_namespace *ns = profile->ns; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 40aedd9..e243d96 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -29,8 +29,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_MOUNT 7 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_MOUNT /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 17734f9..66a738c 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -73,6 +73,10 @@ enum aa_ops { OP_FMMAP, OP_FMPROT, + OP_PIVOTROOT, + OP_MOUNT, + OP_UMOUNT, + OP_CREATE, OP_POST_CREATE, OP_BIND, @@ -122,6 +126,13 @@ struct apparmor_audit_data { unsigned long max; } rlim; struct { + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { const char *target; u32 request; u32 denied; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464..a3f70c5 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,6 +23,8 @@ struct aa_domain { char **table; }; +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm); diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 0000000..bc17a53 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 Canonical Ltd. + * + * 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 __AA_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags); + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *old_name); + +int aa_new_mount(struct aa_profile *profile, const char *dev_name, + struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path); + +#endif /* __AA_MOUNT_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index a172d01..5da8af9 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -36,6 +36,7 @@ #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" +#include "include/mount.h" /* Flag indicating whether initialization completed */ int apparmor_initialized __initdata; @@ -504,6 +505,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + int error = 0; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + profile = __aa_current_profile(); + if (!unconfined(profile)) { + if (flags & MS_REMOUNT) + error = aa_remount(profile, path, flags, data); + else if (flags & MS_BIND) + error = aa_bind_mount(profile, path, dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(profile, path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount(profile, path, dev_name); + else + error = aa_new_mount(profile, dev_name, path, type, + flags, data); + } + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_umount(profile, mnt, flags); + + return error; +} + +static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_pivotroot(profile, old_path, new_path); + + return error; +} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { @@ -737,6 +792,10 @@ static struct security_operations apparmor_ops = { .capget = apparmor_capget, .capable = apparmor_capable, + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_sb_umount, + .sb_pivotroot = apparmor_sb_pivotroot, + .path_link = apparmor_path_link, .path_unlink = apparmor_path_unlink, .path_symlink = apparmor_path_symlink, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 0000000..478aa4d --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,620 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (sa->aad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, sa->aad->mnt.type); + } + if (sa->aad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, sa->aad->mnt.src_name); + } + if (sa->aad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, sa->aad->mnt.trans); + } + if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, sa->aad->mnt.flags); + audit_log_format(ab, "\""); + } + if (sa->aad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, sa->aad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @profile: the profile being enforced (NOT NULL) + * @gfp: allocation flags + * @op: operation being mediated (NOT NULL) + * @name: name of object being mediated (MAYBE NULL) + * @src_name: src_name of object being mediated (MAYBE_NULL) + * @type: type of filesystem (MAYBE_NULL) + * @trans: name of trans (MAYBE NULL) + * @flags: filesystem idependent mount flags + * @data: filesystem mount flags + * @request: permissions requested + * @perms: the permissions computed for the request (NOT NULL) + * @info: extra information message (MAYBE NULL) + * @error: 0 if operation allowed else failure error code + * + * Returns: %0 or error on failure + */ +static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct file_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa = { }; + struct apparmor_audit_data aad = { }; + + if (likely(!error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + request &= mask; + + if (likely(!request)) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + request = request & ~perms->allow; + + if (request & perms->kill) + audit_type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + request &= ~perms->quiet; + + if (!request) + return COMPLAIN_MODE(profile) ? + complain_error(error) : error; + } + + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + sa.aad->op = op; + sa.aad->name = name; + sa.aad->mnt.src_name = src_name; + sa.aad->mnt.type = type; + sa.aad->mnt.trans = trans; + sa.aad->mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + sa.aad->mnt.data = data; + sa.aad->info = info; + sa.aad->error = error; + + return aa_audit(audit_type, profile, gfp, &sa, audit_cb); +} + +/** + * match_mnt_flags - Do an ordered match on mount flags + * @dfa: dfa to match against + * @state: state to start in + * @flags: mount flags to match against + * + * Mount flags are encoded as an ordered match. This is done instead of + * checking against a simple bitmask, to allow for logical operations + * on the flags. + * + * Returns: next state after flags match + */ +static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, + unsigned long flags) +{ + unsigned int i; + + for (i = 0; i <= 31 ; ++i) { + if ((1 << i) & flags) + state = aa_dfa_next(dfa, state, i + 1); + } + + return state; +} + +/** + * compute_mnt_perms - compute mount permission associated with @state + * @dfa: dfa to match against (NOT NULL) + * @state: state match finished in + * + * Returns: mount permissions + */ +static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, + unsigned int state) +{ + struct file_perms perms; + + perms.kill = 0; + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + perms.xindex = dfa_user_xindex(dfa, state); + + return perms; +} + +static const char const *mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match" +}; + +/* + * Returns 0 on success else element that match failed in, this is the + * index into the mnt_info_table above + */ +static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct file_perms *perms) +{ + unsigned int state; + + state = aa_dfa_match(dfa, start, mntpnt); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(dfa, state, devname); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(dfa, state, type); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(dfa, state, flags); + if (!state) + return 4; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + + /* only match data if not binary and the DFA flags data is expected */ + if (data && !binary && (perms->allow & AA_CONT_MATCH)) { + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(dfa, state, data); + if (!state) + return 5; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at end of flags match */ + return 4; +} + +/** + * match_mnt - handle path matching for mount + * @profile: the confining profile + * @mntpnt: string for the mntpnt (NOT NULL) + * @devname: string for the devname/src_name (MAYBE NULL) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @perms: Returns: permission found by the match + * @info: Returns: infomation string about the match for logging + * + * Returns: 0 on success else error + */ +static int match_mnt(struct aa_profile *profile, const char *mntpnt, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + struct file_perms *perms, const char **info) +{ + int pos; + + if (!profile->policy.dfa) + return -EACCES; + + pos = do_match_mnt(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + mntpnt, devname, type, flags, data, binary, perms); + if (pos) { + *info = mnt_info_table[pos]; + return -EACCES; + } + + return 0; +} + +static int path_flags(struct aa_profile *profile, struct path *path) +{ + return profile->path_flags | + S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; +} + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data) +{ + struct file_perms perms = { }; + const char *name, *info = NULL; + char *buffer = NULL; + int binary, error; + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *dev_name, unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, + &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *orig_name) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, + struct path *path, const char *type, unsigned long flags, + void *data) +{ + struct file_perms perms = { }; + char *buffer = NULL, *dev_buffer = NULL; + const char *name = NULL, *dev_name = NULL, *info = NULL; + int binary = 1; + int error; + + dev_name = orig_dev_name; + if (type) { + int requires_dev; + struct file_system_type *fstype = get_fs_type(type); + if (!fstype) + return -ENODEV; + + binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; + requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; + put_filesystem(fstype); + + if (requires_dev) { + struct path dev_path; + + if (!dev_name || !*dev_name) { + error = -ENOENT; + goto out; + } + + error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); + if (error) + goto audit; + + error = aa_path_name(&dev_path, + path_flags(profile, &dev_path), + &dev_buffer, &dev_name, &info); + path_put(&dev_path); + if (error) + goto audit; + } + } + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, dev_name, type, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, + type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + kfree(dev_buffer); + +out: + return error; + +} + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + struct path path = { mnt, mnt->mnt_root }; + error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, + &info); + if (error) + goto audit; + + if (!error && profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); + kfree(buffer); + + return error; +} + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path) +{ + struct file_perms perms = { }; + struct aa_profile *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL; + const char *old_name, *new_name = NULL, *info = NULL; + int error; + + error = aa_path_name(old_path, path_flags(profile, old_path), + &old_buffer, &old_name, &info); + if (error) + goto audit; + + error = aa_path_name(new_path, path_flags(profile, new_path), + &new_buffer, &new_name, &info); + if (error) + goto audit; + + if (profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(profile->policy.dfa, state); + state = aa_dfa_match(profile->policy.dfa, state, old_name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_PIVOTROOT & perms.allow) { + if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + target = x_table_lookup(profile, perms.xindex); + if (!target) + error = -ENOENT; + else + error = aa_replace_current_profile(target); + } + } else + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, + old_name, NULL, target ? target->base.name : NULL, + 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); + aa_put_profile(target); + kfree(old_buffer); + kfree(new_buffer); + + return error; +} -- 1.7.10.4 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.6/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.patchapparmor-2.8.95~2430/kernel-patches/3.6/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.pa0000644000175000017500000001744612053023750031517 0ustar sarnoldsarnoldFrom 259cf7251194d81a4a3c4e6d76c2cf9e38d5647d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 1/6] UBUNTU: SAUCE: AppArmor: Add profile introspection file to interface Add the dynamic profiles file to the interace, to allow load policy introspection. Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Kconfig | 9 ++ security/apparmor/apparmorfs.c | 231 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16c15ec..42b7c9f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,6 +182,234 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; +#endif /* CONFIG_SECURITY_APPARMOR_COMPAT_24 */ + /** Base file system setup **/ static struct aa_fs_entry aa_fs_entry_file[] = { @@ -210,6 +438,9 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = { AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), +#endif AA_FS_DIR("features", aa_fs_entry_features), { } }; -- 1.7.10.4 apparmor-2.8.95~2430/kernel-patches/3.6/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch0000644000175000017500000004305712053023750030651 0ustar sarnoldsarnoldFrom 0317e6ba6aa4adc71f645b7da5318f4caa69267e Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 2/6] UBUNTU: SAUCE: AppArmor: basic networking rules Base support for network mediation. Signed-off-by: John Johansen --- security/apparmor/.gitignore | 2 +- security/apparmor/Makefile | 42 +++++++++- security/apparmor/apparmorfs.c | 1 + security/apparmor/include/audit.h | 4 + security/apparmor/include/net.h | 44 ++++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ security/apparmor/net.c | 162 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 46 ++++++++++ 10 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 4d995ae..d5b291e 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,6 +1,6 @@ # # Generated include files # -af_names.h +net_names.h capability_names.h rlim_names.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 806bd19..19daa85 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h net_names.h # Build a lower case string table of capability names @@ -20,6 +20,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ +# Build a lower case string table of address family names +# Transform lines from +# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [1] = "local", +# [2] = "inet", +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# #define AA_FS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ ;\ + echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ + sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ + $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +# Build a lower case string table of sock type names +# Transform lines from +# SOCK_STREAM = 1, +# to +# [1] = "stream", +quiet_cmd_make-sock = GEN $@ +cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ + sed $^ >>$@ -r -n \ + -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ # Build a lower case string table of rlimit names. # Transforms lines from @@ -56,6 +88,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ $(src)/Makefile @@ -63,3 +96,8 @@ $(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ $(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) +$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ + $(srctree)/include/linux/net.h \ + $(src)/Makefile + $(call cmd,make-af) + $(call cmd,make-sock) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 42b7c9f..114fb23 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -429,6 +429,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 4b7e189..17734f9 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -127,6 +127,10 @@ struct apparmor_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; }; diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..cb8a121 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,44 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +#include "apparmorfs.h" + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern struct aa_fs_entry aa_fs_entry_network[]; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index bda4569..eb13a73 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *const profile_mode_names[]; @@ -157,6 +158,7 @@ struct aa_policydb { * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -194,6 +196,7 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8ea39aa..f628734 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -614,6 +615,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -646,6 +745,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..003dd18 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,162 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "net_names.h" + +struct aa_fs_entry aa_fs_entry_network[] = { + AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), + { } +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net->family]) { + audit_log_string(ab, address_family_names[sa->u.net->family]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); + } + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad->net.type]) { + audit_log_string(ab, sock_type_names[sa->aad->net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); + } + audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + struct lsm_network_audit net = { }; + if (sk) { + sa.type = LSM_AUDIT_DATA_NET; + } else { + sa.type = LSM_AUDIT_DATA_NONE; + } + /* todo fill in socket addr info */ + sa.aad = &aad; + sa.u.net = &net; + sa.aad->op = op, + sa.u.net->family = family; + sa.u.net->sk = sk; + sa.aad->net.type = type; + sa.aad->net.protocol = protocol; + sa.aad->error = error; + + if (likely(!sa.aad->error)) { + u16 audit_mask = profile->net.audit[sa.u.net->family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad->net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index cf5fd22..27c8161 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 329b1fd..1b90dfa 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -193,6 +193,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -471,6 +484,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; + size_t size = 0; int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -564,6 +578,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + } + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ profile->policy.dfa = unpack_dfa(e); -- 1.7.10.4 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.6/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.patchapparmor-2.8.95~2430/kernel-patches/3.6/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.pa0000644000175000017500000000300012053023750032173 0ustar sarnoldsarnoldFrom b1cb9d1b4f0d585c271c584da954d9eb2e347b40 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jun 2012 17:34:00 -0700 Subject: [PATCH 3/6] apparmor: Fix quieting of audit messages for network mediation If a profile specified a quieting of network denials for a given rule by either the quiet or deny rule qualifiers, the resultant quiet mask for denied requests was applied incorrectly, resulting in two potential bugs. 1. The misapplied quiet mask would prevent denials from being correctly tested against the kill mask/mode. Thus network access requests that should have resulted in the application being killed did not. 2. The actual quieting of the denied network request was not being applied. This would result in network rejections always being logged even when they had been specifically marked as quieted. Signed-off-by: John Johansen --- security/apparmor/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 003dd18..6e6e5c9 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -88,7 +88,7 @@ static int audit_net(struct aa_profile *profile, int op, u16 family, int type, } else { u16 quiet_mask = profile->net.quiet[sa.u.net->family]; u16 kill_mask = 0; - u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + u16 denied = (1 << sa.aad->net.type); if (denied & kill_mask) audit_type = AUDIT_APPARMOR_KILL; -- 1.7.10.4 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.6/0004-apparmor-Ensure-apparmor-does-not-mediate-kernel-bas.patchapparmor-2.8.95~2430/kernel-patches/3.6/0004-apparmor-Ensure-apparmor-does-not-mediate-kernel-bas.pa0000644000175000017500000000643312053023750032232 0ustar sarnoldsarnoldFrom f284c9554341aded2d599e9355574cac36c2dd23 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jun 2012 17:34:01 -0700 Subject: [PATCH 4/6] apparmor: Ensure apparmor does not mediate kernel based sockets Currently apparmor makes the assumption that kernel sockets are unmediated because mediation is only done against tasks that have a profile attached. Ensure we never get in a situation where a kernel socket is being mediated by tagging the sk_security field for kernel sockets. Signed-off-by: John Johansen --- security/apparmor/include/net.h | 2 ++ security/apparmor/lsm.c | 18 ++++++++++++++++++ security/apparmor/net.c | 3 +++ 3 files changed, 23 insertions(+) diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index cb8a121..bc8198b 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -19,6 +19,8 @@ #include "apparmorfs.h" +#define AA_SOCK_KERN 0xAA + /* struct aa_net - network confinement data * @allowed: basic network families permissions * @audit_network: which network permissions to force audit diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index f628734..a172d01 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -630,6 +630,16 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern) return error; } +static int apparmor_socket_post_create(struct socket *sock, int family, + int type, int protocol, int kern) +{ + if (kern) + /* tag kernel sockets so we don't mediate them later */ + sock->sk->sk_security = (void *) AA_SOCK_KERN; + + return 0; +} + static int apparmor_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) { @@ -713,6 +723,12 @@ static int apparmor_socket_shutdown(struct socket *sock, int how) return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); } +static void apparmor_sk_clone_security(const struct sock *sk, + struct sock *newsk) +{ + newsk->sk_security = sk->sk_security; +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -746,6 +762,7 @@ static struct security_operations apparmor_ops = { .setprocattr = apparmor_setprocattr, .socket_create = apparmor_socket_create, + .socket_post_create = apparmor_socket_post_create, .socket_bind = apparmor_socket_bind, .socket_connect = apparmor_socket_connect, .socket_listen = apparmor_socket_listen, @@ -757,6 +774,7 @@ static struct security_operations apparmor_ops = { .socket_getsockopt = apparmor_socket_getsockopt, .socket_setsockopt = apparmor_socket_setsockopt, .socket_shutdown = apparmor_socket_shutdown, + .sk_clone_security = apparmor_sk_clone_security, .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 6e6e5c9..baa4df1 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -153,6 +153,9 @@ int aa_revalidate_sk(int op, struct sock *sk) if (in_interrupt()) return 0; + if (sk->sk_security == (void *) AA_SOCK_KERN) + return 0; + profile = __aa_current_profile(); if (!unconfined(profile)) error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, -- 1.7.10.4 apparmor-2.8.95~2430/kernel-patches/3.5/0000755000175000017500000000000012311706722017250 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.5/0006-apparmor-fix-IRQ-stack-overflow-during-free_profile.patchapparmor-2.8.95~2430/kernel-patches/3.5/0006-apparmor-fix-IRQ-stack-overflow-during-free_profile.pat0000644000175000017500000000457712053023750032301 0ustar sarnoldsarnoldFrom f58c91bc1871d604f88d0056099dc34f8ce3ae21 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 24 Oct 2012 06:27:32 -0700 Subject: [PATCH 6/6] apparmor: fix IRQ stack overflow during free_profile BugLink: http://bugs.launchpad.net/bugs/1056078 Profile replacement can cause long chains of profiles to build up when the profile being replaced is pinned. When the pinned profile is finally freed, it puts the reference to its replacement, which may in turn nest another call to free_profile on the stack. Because this may happen for each profile in the replacedby chain this can result in a recusion that causes the stack to overflow. Break this nesting by directly walking the chain of replacedby profiles (ie. use iteration instead of recursion to free the list). This results in at most 2 levels of free_profile being called, while freeing a replacedby chain. Signed-off-by: John Johansen Signed-off-by: James Morris --- security/apparmor/policy.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 27c8161..56e5304 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -724,6 +724,8 @@ fail: */ static void free_profile(struct aa_profile *profile) { + struct aa_profile *p; + AA_DEBUG("%s(%p)\n", __func__, profile); if (!profile) @@ -752,7 +754,27 @@ static void free_profile(struct aa_profile *profile) aa_put_dfa(profile->xmatch); aa_put_dfa(profile->policy.dfa); - aa_put_profile(profile->replacedby); + /* put the profile reference for replacedby, but not via + * put_profile(kref_put). + * replacedby can form a long chain that can result in cascading + * frees that blows the stack because kref_put makes a nested fn + * call (it looks like recursion, with free_profile calling + * free_profile) for each profile in the chain lp#1056078. + */ + for (p = profile->replacedby; p; ) { + if (atomic_dec_and_test(&p->base.count.refcount)) { + /* no more refs on p, grab its replacedby */ + struct aa_profile *next = p->replacedby; + /* break the chain */ + p->replacedby = NULL; + /* now free p, chain is broken */ + free_profile(p); + + /* follow up with next profile in the chain */ + p = next; + } else + break; + } kzfree(profile); } -- 1.7.10.4 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.5/0005-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patchapparmor-2.8.95~2430/kernel-patches/3.5/0005-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.pa0000644000175000017500000006475312053023750031411 0ustar sarnoldsarnoldFrom 272431fc90fab50ea9593d969d3ab8d98f03627c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 May 2012 10:58:05 -0700 Subject: [PATCH 5/6] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount Add the ability for apparmor to do mediation of mount operations. Mount rules require an updated apparmor_parser (2.8 series) for policy compilation. The basic form of the rules are. [audit] [deny] mount [conds]* [device] [ -> [conds] path], [audit] [deny] remount [conds]* [path], [audit] [deny] umount [conds]* [path], [audit] [deny] pivotroot [oldroot=] remount is just a short cut for mount options=remount where [conds] can be fstype= options= Example mount commands mount, # allow all mounts, but not umount or pivotroot mount fstype=procfs, # allow mounting procfs anywhere mount options=(bind, ro) /foo -> /bar, # readonly bind mount mount /dev/sda -> /mnt, mount /dev/sd** -> /mnt/**, mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ umount, umount /m*, See the apparmor userspace for full documentation Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 13 + security/apparmor/audit.c | 4 + security/apparmor/domain.c | 2 +- security/apparmor/include/apparmor.h | 3 +- security/apparmor/include/audit.h | 11 + security/apparmor/include/domain.h | 2 + security/apparmor/include/mount.h | 54 +++ security/apparmor/lsm.c | 59 ++++ security/apparmor/mount.c | 620 ++++++++++++++++++++++++++++++++++ 10 files changed, 767 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/mount.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 19daa85..63e0a4c 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o net.o + resource.o sid.o file.o net.o mount.o clean-files := capability_names.h rlim_names.h net_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 114fb23..ee77ec9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -426,10 +426,23 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { { } }; +static struct aa_fs_entry aa_fs_entry_mount[] = { + AA_FS_FILE_STRING("mask", "mount umount"), + { } +}; + +static struct aa_fs_entry aa_fs_entry_namespaces[] = { + AA_FS_FILE_BOOLEAN("profile", 1), + AA_FS_FILE_BOOLEAN("pivot_root", 1), + { } +}; + static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_DIR("mount", aa_fs_entry_mount), + AA_FS_DIR("namespaces", aa_fs_entry_namespaces), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 3ae28db..e267963 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -44,6 +44,10 @@ const char *const op_table[] = { "file_mmap", "file_mprotect", + "pivotroot", + "mount", + "umount", + "create", "post_create", "bind", diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index b81ea10..afa8671 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -242,7 +242,7 @@ static const char *next_name(int xtype, const char *name) * * Returns: refcounted profile, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) { struct aa_profile *new_profile = NULL; struct aa_namespace *ns = profile->ns; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 40aedd9..e243d96 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -29,8 +29,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_MOUNT 7 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_MOUNT /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 17734f9..66a738c 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -73,6 +73,10 @@ enum aa_ops { OP_FMMAP, OP_FMPROT, + OP_PIVOTROOT, + OP_MOUNT, + OP_UMOUNT, + OP_CREATE, OP_POST_CREATE, OP_BIND, @@ -122,6 +126,13 @@ struct apparmor_audit_data { unsigned long max; } rlim; struct { + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { const char *target; u32 request; u32 denied; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464..a3f70c5 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,6 +23,8 @@ struct aa_domain { char **table; }; +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm); diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 0000000..bc17a53 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 Canonical Ltd. + * + * 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 __AA_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags); + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *old_name); + +int aa_new_mount(struct aa_profile *profile, const char *dev_name, + struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path); + +#endif /* __AA_MOUNT_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index a172d01..5da8af9 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -36,6 +36,7 @@ #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" +#include "include/mount.h" /* Flag indicating whether initialization completed */ int apparmor_initialized __initdata; @@ -504,6 +505,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + int error = 0; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + profile = __aa_current_profile(); + if (!unconfined(profile)) { + if (flags & MS_REMOUNT) + error = aa_remount(profile, path, flags, data); + else if (flags & MS_BIND) + error = aa_bind_mount(profile, path, dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(profile, path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount(profile, path, dev_name); + else + error = aa_new_mount(profile, dev_name, path, type, + flags, data); + } + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_umount(profile, mnt, flags); + + return error; +} + +static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_pivotroot(profile, old_path, new_path); + + return error; +} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { @@ -737,6 +792,10 @@ static struct security_operations apparmor_ops = { .capget = apparmor_capget, .capable = apparmor_capable, + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_sb_umount, + .sb_pivotroot = apparmor_sb_pivotroot, + .path_link = apparmor_path_link, .path_unlink = apparmor_path_unlink, .path_symlink = apparmor_path_symlink, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 0000000..478aa4d --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,620 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (sa->aad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, sa->aad->mnt.type); + } + if (sa->aad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, sa->aad->mnt.src_name); + } + if (sa->aad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, sa->aad->mnt.trans); + } + if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, sa->aad->mnt.flags); + audit_log_format(ab, "\""); + } + if (sa->aad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, sa->aad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @profile: the profile being enforced (NOT NULL) + * @gfp: allocation flags + * @op: operation being mediated (NOT NULL) + * @name: name of object being mediated (MAYBE NULL) + * @src_name: src_name of object being mediated (MAYBE_NULL) + * @type: type of filesystem (MAYBE_NULL) + * @trans: name of trans (MAYBE NULL) + * @flags: filesystem idependent mount flags + * @data: filesystem mount flags + * @request: permissions requested + * @perms: the permissions computed for the request (NOT NULL) + * @info: extra information message (MAYBE NULL) + * @error: 0 if operation allowed else failure error code + * + * Returns: %0 or error on failure + */ +static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct file_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa = { }; + struct apparmor_audit_data aad = { }; + + if (likely(!error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + request &= mask; + + if (likely(!request)) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + request = request & ~perms->allow; + + if (request & perms->kill) + audit_type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + request &= ~perms->quiet; + + if (!request) + return COMPLAIN_MODE(profile) ? + complain_error(error) : error; + } + + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + sa.aad->op = op; + sa.aad->name = name; + sa.aad->mnt.src_name = src_name; + sa.aad->mnt.type = type; + sa.aad->mnt.trans = trans; + sa.aad->mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + sa.aad->mnt.data = data; + sa.aad->info = info; + sa.aad->error = error; + + return aa_audit(audit_type, profile, gfp, &sa, audit_cb); +} + +/** + * match_mnt_flags - Do an ordered match on mount flags + * @dfa: dfa to match against + * @state: state to start in + * @flags: mount flags to match against + * + * Mount flags are encoded as an ordered match. This is done instead of + * checking against a simple bitmask, to allow for logical operations + * on the flags. + * + * Returns: next state after flags match + */ +static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, + unsigned long flags) +{ + unsigned int i; + + for (i = 0; i <= 31 ; ++i) { + if ((1 << i) & flags) + state = aa_dfa_next(dfa, state, i + 1); + } + + return state; +} + +/** + * compute_mnt_perms - compute mount permission associated with @state + * @dfa: dfa to match against (NOT NULL) + * @state: state match finished in + * + * Returns: mount permissions + */ +static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, + unsigned int state) +{ + struct file_perms perms; + + perms.kill = 0; + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + perms.xindex = dfa_user_xindex(dfa, state); + + return perms; +} + +static const char const *mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match" +}; + +/* + * Returns 0 on success else element that match failed in, this is the + * index into the mnt_info_table above + */ +static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct file_perms *perms) +{ + unsigned int state; + + state = aa_dfa_match(dfa, start, mntpnt); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(dfa, state, devname); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(dfa, state, type); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(dfa, state, flags); + if (!state) + return 4; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + + /* only match data if not binary and the DFA flags data is expected */ + if (data && !binary && (perms->allow & AA_CONT_MATCH)) { + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(dfa, state, data); + if (!state) + return 5; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at end of flags match */ + return 4; +} + +/** + * match_mnt - handle path matching for mount + * @profile: the confining profile + * @mntpnt: string for the mntpnt (NOT NULL) + * @devname: string for the devname/src_name (MAYBE NULL) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @perms: Returns: permission found by the match + * @info: Returns: infomation string about the match for logging + * + * Returns: 0 on success else error + */ +static int match_mnt(struct aa_profile *profile, const char *mntpnt, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + struct file_perms *perms, const char **info) +{ + int pos; + + if (!profile->policy.dfa) + return -EACCES; + + pos = do_match_mnt(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + mntpnt, devname, type, flags, data, binary, perms); + if (pos) { + *info = mnt_info_table[pos]; + return -EACCES; + } + + return 0; +} + +static int path_flags(struct aa_profile *profile, struct path *path) +{ + return profile->path_flags | + S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; +} + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data) +{ + struct file_perms perms = { }; + const char *name, *info = NULL; + char *buffer = NULL; + int binary, error; + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *dev_name, unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, + &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *orig_name) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, + struct path *path, const char *type, unsigned long flags, + void *data) +{ + struct file_perms perms = { }; + char *buffer = NULL, *dev_buffer = NULL; + const char *name = NULL, *dev_name = NULL, *info = NULL; + int binary = 1; + int error; + + dev_name = orig_dev_name; + if (type) { + int requires_dev; + struct file_system_type *fstype = get_fs_type(type); + if (!fstype) + return -ENODEV; + + binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; + requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; + put_filesystem(fstype); + + if (requires_dev) { + struct path dev_path; + + if (!dev_name || !*dev_name) { + error = -ENOENT; + goto out; + } + + error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); + if (error) + goto audit; + + error = aa_path_name(&dev_path, + path_flags(profile, &dev_path), + &dev_buffer, &dev_name, &info); + path_put(&dev_path); + if (error) + goto audit; + } + } + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, dev_name, type, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, + type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + kfree(dev_buffer); + +out: + return error; + +} + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + struct path path = { mnt, mnt->mnt_root }; + error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, + &info); + if (error) + goto audit; + + if (!error && profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); + kfree(buffer); + + return error; +} + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path) +{ + struct file_perms perms = { }; + struct aa_profile *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL; + const char *old_name, *new_name = NULL, *info = NULL; + int error; + + error = aa_path_name(old_path, path_flags(profile, old_path), + &old_buffer, &old_name, &info); + if (error) + goto audit; + + error = aa_path_name(new_path, path_flags(profile, new_path), + &new_buffer, &new_name, &info); + if (error) + goto audit; + + if (profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(profile->policy.dfa, state); + state = aa_dfa_match(profile->policy.dfa, state, old_name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_PIVOTROOT & perms.allow) { + if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + target = x_table_lookup(profile, perms.xindex); + if (!target) + error = -ENOENT; + else + error = aa_replace_current_profile(target); + } + } else + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, + old_name, NULL, target ? target->base.name : NULL, + 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); + aa_put_profile(target); + kfree(old_buffer); + kfree(new_buffer); + + return error; +} -- 1.7.10.4 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.5/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.patchapparmor-2.8.95~2430/kernel-patches/3.5/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.pa0000644000175000017500000001744612053023750031516 0ustar sarnoldsarnoldFrom 05bf1eb7276886a3eda0588a8e012b558b693e96 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 1/6] UBUNTU: SAUCE: AppArmor: Add profile introspection file to interface Add the dynamic profiles file to the interace, to allow load policy introspection. Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Kconfig | 9 ++ security/apparmor/apparmorfs.c | 231 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16c15ec..42b7c9f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,6 +182,234 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; +#endif /* CONFIG_SECURITY_APPARMOR_COMPAT_24 */ + /** Base file system setup **/ static struct aa_fs_entry aa_fs_entry_file[] = { @@ -210,6 +438,9 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = { AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), +#endif AA_FS_DIR("features", aa_fs_entry_features), { } }; -- 1.7.10.4 apparmor-2.8.95~2430/kernel-patches/3.5/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch0000644000175000017500000004305712053023750030650 0ustar sarnoldsarnoldFrom 4facdf9db37c12ff655c91270d9030e2ed805ca2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 2/6] UBUNTU: SAUCE: AppArmor: basic networking rules Base support for network mediation. Signed-off-by: John Johansen --- security/apparmor/.gitignore | 2 +- security/apparmor/Makefile | 42 +++++++++- security/apparmor/apparmorfs.c | 1 + security/apparmor/include/audit.h | 4 + security/apparmor/include/net.h | 44 ++++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ security/apparmor/net.c | 162 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 46 ++++++++++ 10 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 4d995ae..d5b291e 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,6 +1,6 @@ # # Generated include files # -af_names.h +net_names.h capability_names.h rlim_names.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 806bd19..19daa85 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h net_names.h # Build a lower case string table of capability names @@ -20,6 +20,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ +# Build a lower case string table of address family names +# Transform lines from +# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [1] = "local", +# [2] = "inet", +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# #define AA_FS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ ;\ + echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ + sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ + $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +# Build a lower case string table of sock type names +# Transform lines from +# SOCK_STREAM = 1, +# to +# [1] = "stream", +quiet_cmd_make-sock = GEN $@ +cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ + sed $^ >>$@ -r -n \ + -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ # Build a lower case string table of rlimit names. # Transforms lines from @@ -56,6 +88,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ $(src)/Makefile @@ -63,3 +96,8 @@ $(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ $(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) +$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ + $(srctree)/include/linux/net.h \ + $(src)/Makefile + $(call cmd,make-af) + $(call cmd,make-sock) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 42b7c9f..114fb23 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -429,6 +429,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 4b7e189..17734f9 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -127,6 +127,10 @@ struct apparmor_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; }; diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..cb8a121 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,44 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +#include "apparmorfs.h" + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern struct aa_fs_entry aa_fs_entry_network[]; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index bda4569..eb13a73 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *const profile_mode_names[]; @@ -157,6 +158,7 @@ struct aa_policydb { * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -194,6 +196,7 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8ea39aa..f628734 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -614,6 +615,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -646,6 +745,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..003dd18 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,162 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "net_names.h" + +struct aa_fs_entry aa_fs_entry_network[] = { + AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), + { } +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net->family]) { + audit_log_string(ab, address_family_names[sa->u.net->family]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); + } + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad->net.type]) { + audit_log_string(ab, sock_type_names[sa->aad->net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); + } + audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + struct lsm_network_audit net = { }; + if (sk) { + sa.type = LSM_AUDIT_DATA_NET; + } else { + sa.type = LSM_AUDIT_DATA_NONE; + } + /* todo fill in socket addr info */ + sa.aad = &aad; + sa.u.net = &net; + sa.aad->op = op, + sa.u.net->family = family; + sa.u.net->sk = sk; + sa.aad->net.type = type; + sa.aad->net.protocol = protocol; + sa.aad->error = error; + + if (likely(!sa.aad->error)) { + u16 audit_mask = profile->net.audit[sa.u.net->family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad->net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index cf5fd22..27c8161 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 329b1fd..1b90dfa 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -193,6 +193,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -471,6 +484,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; + size_t size = 0; int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -564,6 +578,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + } + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ profile->policy.dfa = unpack_dfa(e); -- 1.7.10.4 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.5/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.patchapparmor-2.8.95~2430/kernel-patches/3.5/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.pa0000644000175000017500000000300012053023750032172 0ustar sarnoldsarnoldFrom 4b25e62dc1e8d81d80f778e1e57b7c38ba4fd901 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jun 2012 17:34:00 -0700 Subject: [PATCH 3/6] apparmor: Fix quieting of audit messages for network mediation If a profile specified a quieting of network denials for a given rule by either the quiet or deny rule qualifiers, the resultant quiet mask for denied requests was applied incorrectly, resulting in two potential bugs. 1. The misapplied quiet mask would prevent denials from being correctly tested against the kill mask/mode. Thus network access requests that should have resulted in the application being killed did not. 2. The actual quieting of the denied network request was not being applied. This would result in network rejections always being logged even when they had been specifically marked as quieted. Signed-off-by: John Johansen --- security/apparmor/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 003dd18..6e6e5c9 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -88,7 +88,7 @@ static int audit_net(struct aa_profile *profile, int op, u16 family, int type, } else { u16 quiet_mask = profile->net.quiet[sa.u.net->family]; u16 kill_mask = 0; - u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + u16 denied = (1 << sa.aad->net.type); if (denied & kill_mask) audit_type = AUDIT_APPARMOR_KILL; -- 1.7.10.4 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.5/0004-apparmor-Ensure-apparmor-does-not-mediate-kernel-bas.patchapparmor-2.8.95~2430/kernel-patches/3.5/0004-apparmor-Ensure-apparmor-does-not-mediate-kernel-bas.pa0000644000175000017500000000643312053023750032231 0ustar sarnoldsarnoldFrom e2d745442133f625e715f713c0441f0f2a7ea6ad Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jun 2012 17:34:01 -0700 Subject: [PATCH 4/6] apparmor: Ensure apparmor does not mediate kernel based sockets Currently apparmor makes the assumption that kernel sockets are unmediated because mediation is only done against tasks that have a profile attached. Ensure we never get in a situation where a kernel socket is being mediated by tagging the sk_security field for kernel sockets. Signed-off-by: John Johansen --- security/apparmor/include/net.h | 2 ++ security/apparmor/lsm.c | 18 ++++++++++++++++++ security/apparmor/net.c | 3 +++ 3 files changed, 23 insertions(+) diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index cb8a121..bc8198b 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -19,6 +19,8 @@ #include "apparmorfs.h" +#define AA_SOCK_KERN 0xAA + /* struct aa_net - network confinement data * @allowed: basic network families permissions * @audit_network: which network permissions to force audit diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index f628734..a172d01 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -630,6 +630,16 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern) return error; } +static int apparmor_socket_post_create(struct socket *sock, int family, + int type, int protocol, int kern) +{ + if (kern) + /* tag kernel sockets so we don't mediate them later */ + sock->sk->sk_security = (void *) AA_SOCK_KERN; + + return 0; +} + static int apparmor_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) { @@ -713,6 +723,12 @@ static int apparmor_socket_shutdown(struct socket *sock, int how) return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); } +static void apparmor_sk_clone_security(const struct sock *sk, + struct sock *newsk) +{ + newsk->sk_security = sk->sk_security; +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -746,6 +762,7 @@ static struct security_operations apparmor_ops = { .setprocattr = apparmor_setprocattr, .socket_create = apparmor_socket_create, + .socket_post_create = apparmor_socket_post_create, .socket_bind = apparmor_socket_bind, .socket_connect = apparmor_socket_connect, .socket_listen = apparmor_socket_listen, @@ -757,6 +774,7 @@ static struct security_operations apparmor_ops = { .socket_getsockopt = apparmor_socket_getsockopt, .socket_setsockopt = apparmor_socket_setsockopt, .socket_shutdown = apparmor_socket_shutdown, + .sk_clone_security = apparmor_sk_clone_security, .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 6e6e5c9..baa4df1 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -153,6 +153,9 @@ int aa_revalidate_sk(int op, struct sock *sk) if (in_interrupt()) return 0; + if (sk->sk_security == (void *) AA_SOCK_KERN) + return 0; + profile = __aa_current_profile(); if (!unconfined(profile)) error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, -- 1.7.10.4 apparmor-2.8.95~2430/kernel-patches/3.0/0000755000175000017500000000000012311706720017241 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.0/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.patchapparmor-2.8.95~2430/kernel-patches/3.0/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.pa0000644000175000017500000000507311626242242032173 0ustar sarnoldsarnoldFrom 7a10d093f9779f42cb8d6affcb6a4436d3ebd6d3 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:41 -0700 Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken userspace The apparmor_parser when compiling policy could generate invalid dfas that did not have sufficient padding to avoid invalid references, when used by the kernel. The kernels check to verify the next/check table size was broken meaning invalid dfas were being created by userspace and not caught. To remain compatible with old tools that are not fixed, pad the loaded dfas next/check table. The dfa's themselves are valid except for the high padding for potentially invalid transitions (high bounds error), which have a maximimum is 256 entries. So just allocate an extra null filled 256 entries for the next/check tables. This will guarentee all bounds are good and invalid transitions go to the null (0) state. Signed-off-by: John Johansen --- security/apparmor/match.c | 17 +++++++++++++++++ 1 files changed, 17 insertions(+), 0 deletions(-) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 94de6b4..081491e 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize) if (bsize < tsize) goto out; + /* Pad table allocation for next/check by 256 entries to remain + * backwards compatible with old (buggy) tools and remain safe without + * run time checks + */ + if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK) + tsize += 256 * th.td_flags; + table = kvmalloc(tsize); if (table) { + /* ensure the pad is clear, else there will be errors */ + memset(table, 0, tsize); *table = th; if (th.td_flags == YYTD_DATA8) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, @@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags) goto out; if (flags & DFA_FLAG_VERIFY_STATES) { + int warning = 0; for (i = 0; i < state_count; i++) { if (DEFAULT_TABLE(dfa)[i] >= state_count) goto out; /* TODO: do check that DEF state recursion terminates */ if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { + if (warning) + continue; + printk(KERN_WARNING "AppArmor DFA next/check " + "upper bounds error fixed, upgrade " + "user space tools \n"); + warning = 1; + } else if (BASE_TABLE(dfa)[i] >= trans_count) { printk(KERN_ERR "AppArmor DFA next/check upper " "bounds error\n"); goto out; -- 1.7.5.4 apparmor-2.8.95~2430/kernel-patches/3.0/0002-AppArmor-compatibility-patch-for-v5-interface.patch0000644000175000017500000002577211626242242031417 0ustar sarnoldsarnoldFrom a2515f25ad5a7833ddc5a032d34eee6a5ddee3a2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:40 -0700 Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface Signed-off-by: John Johansen --- security/apparmor/Kconfig | 9 + security/apparmor/Makefile | 1 + security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++ security/apparmor/apparmorfs.c | 18 ++- security/apparmor/include/apparmorfs.h | 6 + 5 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/apparmorfs-24.c diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 7cefef9..0bb604b 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o sid.o file.o net.o +apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o clean-files := capability_names.h rlim_names.h af_names.h diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c new file mode 100644 index 0000000..dc8c744 --- /dev/null +++ b/security/apparmor/apparmorfs-24.c @@ -0,0 +1,287 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + * + * + * This file contain functions providing an interface for <= AppArmor 2.4 + * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24 + * being set (see Makefile). + */ + +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/policy.h" + + +/* apparmor/matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char matching[] = "pattern=aadfa audit perms=crwxamlk/ " + "user::other"; + + return simple_read_from_buffer(buf, size, ppos, matching, + sizeof(matching) - 1); +} + +const struct file_operations aa_fs_matching_fops = { + .read = aa_matching_read, +}; + +/* apparmor/features */ +static ssize_t aa_features_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char features[] = "file=3.1 capability=2.0 network=1.0 " + "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1"; + + return simple_read_from_buffer(buf, size, ppos, features, + sizeof(features) - 1); +} + +const struct file_operations aa_fs_features_fops = { + .read = aa_features_read, +}; + +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 0848292..28c52ac 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -187,7 +187,11 @@ void __init aa_destroy_aafs(void) aafs_remove(".remove"); aafs_remove(".replace"); aafs_remove(".load"); - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + aafs_remove("profiles"); + aafs_remove("matching"); + aafs_remove("features"); +#endif securityfs_remove(aa_fs_dentry); aa_fs_dentry = NULL; } @@ -218,7 +222,17 @@ int __init aa_create_aafs(void) aa_fs_dentry = NULL; goto error; } - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + error = aafs_create("matching", 0444, &aa_fs_matching_fops); + if (error) + goto error; + error = aafs_create("features", 0444, &aa_fs_features_fops); + if (error) + goto error; +#endif + error = aafs_create("profiles", 0440, &aa_fs_profiles_fops); + if (error) + goto error; error = aafs_create(".load", 0640, &aa_fs_profile_load); if (error) goto error; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index cb1e93a..14f955c 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -17,4 +17,10 @@ extern void __init aa_destroy_aafs(void); +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +extern const struct file_operations aa_fs_matching_fops; +extern const struct file_operations aa_fs_features_fops; +extern const struct file_operations aa_fs_profiles_fops; +#endif + #endif /* __AA_APPARMORFS_H */ -- 1.7.5.4 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.0/0001-AppArmor-compatibility-patch-for-v5-network-controll.patchapparmor-2.8.95~2430/kernel-patches/3.0/0001-AppArmor-compatibility-patch-for-v5-network-controll.pa0000644000175000017500000003651311626242242032275 0ustar sarnoldsarnoldFrom dc13dec93dbd04bfa7a9ba67df1b8ed3431d8d48 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:39 -0700 Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll Add compatibility for v5 network rules. Signed-off-by: John Johansen --- include/linux/lsm_audit.h | 4 + security/apparmor/Makefile | 19 ++++- security/apparmor/include/net.h | 40 +++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++ security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 48 ++++++++++- 8 files changed, 394 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 88e78de..c63979a 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -124,6 +124,10 @@ struct common_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; } apparmor_audit_data; #endif diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 2dafe50..7cefef9 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h af_names.h # Build a lower case string table of capability names @@ -44,9 +44,24 @@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\ sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\ echo "};" >> $@ +# Build a lower case string table of address family names. +# Transform lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [2] = "inet", +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >> $@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+).*/[\2] = "\L\1",/p';\ + echo "};" >> $@ + + $(obj)/capability.o : $(obj)/capability_names.h $(obj)/resource.o : $(obj)/rlim_names.h +$(obj)/net.o : $(obj)/af_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h $(call cmd,make-caps) $(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h $(call cmd,make-rlim) +$(obj)/af_names.h : $(srctree)/include/linux/socket.h + $(call cmd,make-af) \ No newline at end of file diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..3c7d599 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,40 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index aeda5cf..6776929 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *profile_mode_names[]; @@ -145,6 +146,7 @@ struct aa_namespace { * @size: the memory consumed by this profiles rules * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -181,6 +183,7 @@ struct aa_profile { struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 3d2fd14..aa293ae 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -621,6 +622,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -652,6 +751,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..1765901 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,170 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "af_names.h" + +static const char *sock_type_names[] = { + "unknown(0)", + "stream", + "dgram", + "raw", + "rdm", + "seqpacket", + "dccp", + "unknown(7)", + "unknown(8)", + "unknown(9)", + "packet", +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net.family]) { + audit_log_string(ab, address_family_names[sa->u.net.family]); + } else { + audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family); + } + + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad.net.type]) { + audit_log_string(ab, sock_type_names[sa->aad.net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type); + } + + audit_log_format(ab, " protocol=%d", sa->aad.net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + if (sk) { + COMMON_AUDIT_DATA_INIT(&sa, NET); + } else { + COMMON_AUDIT_DATA_INIT(&sa, NONE); + } + /* todo fill in socket addr info */ + + sa.aad.op = op, + sa.u.net.family = family; + sa.u.net.sk = sk; + sa.aad.net.type = type; + sa.aad.net.protocol = protocol; + sa.aad.error = error; + + if (likely(!sa.aad.error)) { + u16 audit_mask = profile->net.audit[sa.u.net.family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad.net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net.family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad.net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 4f0eade..4d5ce13 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index d6d9a57..f4874c4 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -190,6 +190,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; - int error = -EPROTO; + size_t size = 0; + int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + } + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + /* get file rules */ profile->file.dfa = unpack_dfa(e); if (IS_ERR(profile->file.dfa)) { -- 1.7.5.4 apparmor-2.8.95~2430/kernel-patches/3.2/0000755000175000017500000000000012311706704017245 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.2/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.patchapparmor-2.8.95~2430/kernel-patches/3.2/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.pa0000644000175000017500000000505211756603243032200 0ustar sarnoldsarnoldFrom e5d90918aa31f948ecec2f3c088567dbab30c90b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:41 -0700 Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken userspace The apparmor_parser when compiling policy could generate invalid dfas that did not have sufficient padding to avoid invalid references, when used by the kernel. The kernels check to verify the next/check table size was broken meaning invalid dfas were being created by userspace and not caught. To remain compatible with old tools that are not fixed, pad the loaded dfas next/check table. The dfa's themselves are valid except for the high padding for potentially invalid transitions (high bounds error), which have a maximimum is 256 entries. So just allocate an extra null filled 256 entries for the next/check tables. This will guarentee all bounds are good and invalid transitions go to the null (0) state. Signed-off-by: John Johansen --- security/apparmor/match.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 94de6b4..081491e 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize) if (bsize < tsize) goto out; + /* Pad table allocation for next/check by 256 entries to remain + * backwards compatible with old (buggy) tools and remain safe without + * run time checks + */ + if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK) + tsize += 256 * th.td_flags; + table = kvmalloc(tsize); if (table) { + /* ensure the pad is clear, else there will be errors */ + memset(table, 0, tsize); *table = th; if (th.td_flags == YYTD_DATA8) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, @@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags) goto out; if (flags & DFA_FLAG_VERIFY_STATES) { + int warning = 0; for (i = 0; i < state_count; i++) { if (DEFAULT_TABLE(dfa)[i] >= state_count) goto out; /* TODO: do check that DEF state recursion terminates */ if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { + if (warning) + continue; + printk(KERN_WARNING "AppArmor DFA next/check " + "upper bounds error fixed, upgrade " + "user space tools \n"); + warning = 1; + } else if (BASE_TABLE(dfa)[i] >= trans_count) { printk(KERN_ERR "AppArmor DFA next/check upper " "bounds error\n"); goto out; -- 1.7.9.5 apparmor-2.8.95~2430/kernel-patches/3.2/0002-AppArmor-compatibility-patch-for-v5-interface.patch0000644000175000017500000002600011756603243031410 0ustar sarnoldsarnoldFrom 004192fb5223c7b81a949e36a080a5da56132826 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:40 -0700 Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface Signed-off-by: John Johansen --- security/apparmor/Kconfig | 9 + security/apparmor/Makefile | 1 + security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++ security/apparmor/apparmorfs.c | 18 +- security/apparmor/include/apparmorfs.h | 6 + 5 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/apparmorfs-24.c diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 7cefef9..0bb604b 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o sid.o file.o net.o +apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o clean-files := capability_names.h rlim_names.h af_names.h diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c new file mode 100644 index 0000000..dc8c744 --- /dev/null +++ b/security/apparmor/apparmorfs-24.c @@ -0,0 +1,287 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + * + * + * This file contain functions providing an interface for <= AppArmor 2.4 + * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24 + * being set (see Makefile). + */ + +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/policy.h" + + +/* apparmor/matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char matching[] = "pattern=aadfa audit perms=crwxamlk/ " + "user::other"; + + return simple_read_from_buffer(buf, size, ppos, matching, + sizeof(matching) - 1); +} + +const struct file_operations aa_fs_matching_fops = { + .read = aa_matching_read, +}; + +/* apparmor/features */ +static ssize_t aa_features_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char features[] = "file=3.1 capability=2.0 network=1.0 " + "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1"; + + return simple_read_from_buffer(buf, size, ppos, features, + sizeof(features) - 1); +} + +const struct file_operations aa_fs_features_fops = { + .read = aa_features_read, +}; + +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 69ddb47..867995c 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -187,7 +187,11 @@ void __init aa_destroy_aafs(void) aafs_remove(".remove"); aafs_remove(".replace"); aafs_remove(".load"); - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + aafs_remove("profiles"); + aafs_remove("matching"); + aafs_remove("features"); +#endif securityfs_remove(aa_fs_dentry); aa_fs_dentry = NULL; } @@ -218,7 +222,17 @@ static int __init aa_create_aafs(void) aa_fs_dentry = NULL; goto error; } - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + error = aafs_create("matching", 0444, &aa_fs_matching_fops); + if (error) + goto error; + error = aafs_create("features", 0444, &aa_fs_features_fops); + if (error) + goto error; +#endif + error = aafs_create("profiles", 0440, &aa_fs_profiles_fops); + if (error) + goto error; error = aafs_create(".load", 0640, &aa_fs_profile_load); if (error) goto error; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index cb1e93a..14f955c 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -17,4 +17,10 @@ extern void __init aa_destroy_aafs(void); +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +extern const struct file_operations aa_fs_matching_fops; +extern const struct file_operations aa_fs_features_fops; +extern const struct file_operations aa_fs_profiles_fops; +#endif + #endif /* __AA_APPARMORFS_H */ -- 1.7.9.5 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.2/0001-AppArmor-compatibility-patch-for-v5-network-controll.patchapparmor-2.8.95~2430/kernel-patches/3.2/0001-AppArmor-compatibility-patch-for-v5-network-controll.pa0000644000175000017500000003651211756603243032304 0ustar sarnoldsarnoldFrom 125fccb600288968aa3395883c0a394c47176fcd Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:39 -0700 Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll Add compatibility for v5 network rules. Signed-off-by: John Johansen --- include/linux/lsm_audit.h | 4 + security/apparmor/Makefile | 19 +++- security/apparmor/include/net.h | 40 +++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 ++++++++++++++++++++++++ security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 48 +++++++++- 8 files changed, 394 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 88e78de..c63979a 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -124,6 +124,10 @@ struct common_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; } apparmor_audit_data; #endif diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 2dafe50..7cefef9 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h af_names.h # Build a lower case string table of capability names @@ -44,9 +44,24 @@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\ sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\ echo "};" >> $@ +# Build a lower case string table of address family names. +# Transform lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [2] = "inet", +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >> $@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+).*/[\2] = "\L\1",/p';\ + echo "};" >> $@ + + $(obj)/capability.o : $(obj)/capability_names.h $(obj)/resource.o : $(obj)/rlim_names.h +$(obj)/net.o : $(obj)/af_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h $(call cmd,make-caps) $(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h $(call cmd,make-rlim) +$(obj)/af_names.h : $(srctree)/include/linux/socket.h + $(call cmd,make-af) \ No newline at end of file diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..3c7d599 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,40 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index aeda5cf..6776929 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *profile_mode_names[]; @@ -145,6 +146,7 @@ struct aa_namespace { * @size: the memory consumed by this profiles rules * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -181,6 +183,7 @@ struct aa_profile { struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 3783202..7459547 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -621,6 +622,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -652,6 +751,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..1765901 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,170 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "af_names.h" + +static const char *sock_type_names[] = { + "unknown(0)", + "stream", + "dgram", + "raw", + "rdm", + "seqpacket", + "dccp", + "unknown(7)", + "unknown(8)", + "unknown(9)", + "packet", +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net.family]) { + audit_log_string(ab, address_family_names[sa->u.net.family]); + } else { + audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family); + } + + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad.net.type]) { + audit_log_string(ab, sock_type_names[sa->aad.net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type); + } + + audit_log_format(ab, " protocol=%d", sa->aad.net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + if (sk) { + COMMON_AUDIT_DATA_INIT(&sa, NET); + } else { + COMMON_AUDIT_DATA_INIT(&sa, NONE); + } + /* todo fill in socket addr info */ + + sa.aad.op = op, + sa.u.net.family = family; + sa.u.net.sk = sk; + sa.aad.net.type = type; + sa.aad.net.protocol = protocol; + sa.aad.error = error; + + if (likely(!sa.aad.error)) { + u16 audit_mask = profile->net.audit[sa.u.net.family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad.net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net.family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad.net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 4f0eade..4d5ce13 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 741dd13..ee8043e 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -190,6 +190,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; - int error = -EPROTO; + size_t size = 0; + int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + } + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + /* get file rules */ profile->file.dfa = unpack_dfa(e); if (IS_ERR(profile->file.dfa)) { -- 1.7.9.5 apparmor-2.8.95~2430/kernel-patches/3.4/0000755000175000017500000000000012311706704017247 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.4/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patchapparmor-2.8.95~2430/kernel-patches/3.4/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.pa0000644000175000017500000006475111756603243031416 0ustar sarnoldsarnoldFrom a94d5e11c0484af59e5feebf144cc48c186892ad Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 May 2012 10:58:05 -0700 Subject: [PATCH 3/3] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount Add the ability for apparmor to do mediation of mount operations. Mount rules require an updated apparmor_parser (2.8 series) for policy compilation. The basic form of the rules are. [audit] [deny] mount [conds]* [device] [ -> [conds] path], [audit] [deny] remount [conds]* [path], [audit] [deny] umount [conds]* [path], [audit] [deny] pivotroot [oldroot=] remount is just a short cut for mount options=remount where [conds] can be fstype= options= Example mount commands mount, # allow all mounts, but not umount or pivotroot mount fstype=procfs, # allow mounting procfs anywhere mount options=(bind, ro) /foo -> /bar, # readonly bind mount mount /dev/sda -> /mnt, mount /dev/sd** -> /mnt/**, mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ umount, umount /m*, See the apparmor userspace for full documentation Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 13 + security/apparmor/audit.c | 4 + security/apparmor/domain.c | 2 +- security/apparmor/include/apparmor.h | 3 +- security/apparmor/include/audit.h | 11 + security/apparmor/include/domain.h | 2 + security/apparmor/include/mount.h | 54 +++ security/apparmor/lsm.c | 59 ++++ security/apparmor/mount.c | 620 ++++++++++++++++++++++++++++++++++ 10 files changed, 767 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/mount.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 19daa85..63e0a4c 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o net.o + resource.o sid.o file.o net.o mount.o clean-files := capability_names.h rlim_names.h net_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index c66315d..ff19009 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -424,10 +424,23 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { { } }; +static struct aa_fs_entry aa_fs_entry_mount[] = { + AA_FS_FILE_STRING("mask", "mount umount"), + { } +}; + +static struct aa_fs_entry aa_fs_entry_namespaces[] = { + AA_FS_FILE_BOOLEAN("profile", 1), + AA_FS_FILE_BOOLEAN("pivot_root", 1), + { } +}; + static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_DIR("mount", aa_fs_entry_mount), + AA_FS_DIR("namespaces", aa_fs_entry_namespaces), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index cc3520d..b9f5ee9 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -44,6 +44,10 @@ const char *const op_table[] = { "file_mmap", "file_mprotect", + "pivotroot", + "mount", + "umount", + "create", "post_create", "bind", diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 6327685..dfdc47b 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -242,7 +242,7 @@ static const char *next_name(int xtype, const char *name) * * Returns: refcounted profile, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) { struct aa_profile *new_profile = NULL; struct aa_namespace *ns = profile->ns; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 40aedd9..e243d96 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -29,8 +29,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_MOUNT 7 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_MOUNT /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index c1ff09c..7b90900c 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -73,6 +73,10 @@ enum aa_ops { OP_FMMAP, OP_FMPROT, + OP_PIVOTROOT, + OP_MOUNT, + OP_UMOUNT, + OP_CREATE, OP_POST_CREATE, OP_BIND, @@ -121,6 +125,13 @@ struct apparmor_audit_data { unsigned long max; } rlim; struct { + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { const char *target; u32 request; u32 denied; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464..a3f70c5 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,6 +23,8 @@ struct aa_domain { char **table; }; +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm); diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 0000000..bc17a53 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 Canonical Ltd. + * + * 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 __AA_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags); + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *old_name); + +int aa_new_mount(struct aa_profile *profile, const char *dev_name, + struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path); + +#endif /* __AA_MOUNT_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 3cde194..4512cc6 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -36,6 +36,7 @@ #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" +#include "include/mount.h" /* Flag indicating whether initialization completed */ int apparmor_initialized __initdata; @@ -512,6 +513,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + int error = 0; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + profile = __aa_current_profile(); + if (!unconfined(profile)) { + if (flags & MS_REMOUNT) + error = aa_remount(profile, path, flags, data); + else if (flags & MS_BIND) + error = aa_bind_mount(profile, path, dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(profile, path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount(profile, path, dev_name); + else + error = aa_new_mount(profile, dev_name, path, type, + flags, data); + } + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_umount(profile, mnt, flags); + + return error; +} + +static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_pivotroot(profile, old_path, new_path); + + return error; +} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { @@ -729,6 +784,10 @@ static struct security_operations apparmor_ops = { .capget = apparmor_capget, .capable = apparmor_capable, + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_sb_umount, + .sb_pivotroot = apparmor_sb_pivotroot, + .path_link = apparmor_path_link, .path_unlink = apparmor_path_unlink, .path_symlink = apparmor_path_symlink, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 0000000..63d8493 --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,620 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (sa->aad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, sa->aad->mnt.type); + } + if (sa->aad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, sa->aad->mnt.src_name); + } + if (sa->aad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, sa->aad->mnt.trans); + } + if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, sa->aad->mnt.flags); + audit_log_format(ab, "\""); + } + if (sa->aad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, sa->aad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @profile: the profile being enforced (NOT NULL) + * @gfp: allocation flags + * @op: operation being mediated (NOT NULL) + * @name: name of object being mediated (MAYBE NULL) + * @src_name: src_name of object being mediated (MAYBE_NULL) + * @type: type of filesystem (MAYBE_NULL) + * @trans: name of trans (MAYBE NULL) + * @flags: filesystem idependent mount flags + * @data: filesystem mount flags + * @request: permissions requested + * @perms: the permissions computed for the request (NOT NULL) + * @info: extra information message (MAYBE NULL) + * @error: 0 if operation allowed else failure error code + * + * Returns: %0 or error on failure + */ +static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct file_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + + if (likely(!error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + request &= mask; + + if (likely(!request)) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + request = request & ~perms->allow; + + if (request & perms->kill) + audit_type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + request &= ~perms->quiet; + + if (!request) + return COMPLAIN_MODE(profile) ? + complain_error(error) : error; + } + + COMMON_AUDIT_DATA_INIT(&sa, NONE); + sa.aad = &aad; + sa.aad->op = op; + sa.aad->name = name; + sa.aad->mnt.src_name = src_name; + sa.aad->mnt.type = type; + sa.aad->mnt.trans = trans; + sa.aad->mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + sa.aad->mnt.data = data; + sa.aad->info = info; + sa.aad->error = error; + + return aa_audit(audit_type, profile, gfp, &sa, audit_cb); +} + +/** + * match_mnt_flags - Do an ordered match on mount flags + * @dfa: dfa to match against + * @state: state to start in + * @flags: mount flags to match against + * + * Mount flags are encoded as an ordered match. This is done instead of + * checking against a simple bitmask, to allow for logical operations + * on the flags. + * + * Returns: next state after flags match + */ +static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, + unsigned long flags) +{ + unsigned int i; + + for (i = 0; i <= 31 ; ++i) { + if ((1 << i) & flags) + state = aa_dfa_next(dfa, state, i + 1); + } + + return state; +} + +/** + * compute_mnt_perms - compute mount permission associated with @state + * @dfa: dfa to match against (NOT NULL) + * @state: state match finished in + * + * Returns: mount permissions + */ +static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, + unsigned int state) +{ + struct file_perms perms; + + perms.kill = 0; + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + perms.xindex = dfa_user_xindex(dfa, state); + + return perms; +} + +static const char const *mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match" +}; + +/* + * Returns 0 on success else element that match failed in, this is the + * index into the mnt_info_table above + */ +static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct file_perms *perms) +{ + unsigned int state; + + state = aa_dfa_match(dfa, start, mntpnt); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(dfa, state, devname); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(dfa, state, type); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(dfa, state, flags); + if (!state) + return 4; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + + /* only match data if not binary and the DFA flags data is expected */ + if (data && !binary && (perms->allow & AA_CONT_MATCH)) { + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(dfa, state, data); + if (!state) + return 5; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at end of flags match */ + return 4; +} + +/** + * match_mnt - handle path matching for mount + * @profile: the confining profile + * @mntpnt: string for the mntpnt (NOT NULL) + * @devname: string for the devname/src_name (MAYBE NULL) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @perms: Returns: permission found by the match + * @info: Returns: infomation string about the match for logging + * + * Returns: 0 on success else error + */ +static int match_mnt(struct aa_profile *profile, const char *mntpnt, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + struct file_perms *perms, const char **info) +{ + int pos; + + if (!profile->policy.dfa) + return -EACCES; + + pos = do_match_mnt(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + mntpnt, devname, type, flags, data, binary, perms); + if (pos) { + *info = mnt_info_table[pos]; + return -EACCES; + } + + return 0; +} + +static int path_flags(struct aa_profile *profile, struct path *path) +{ + return profile->path_flags | + S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; +} + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data) +{ + struct file_perms perms = { }; + const char *name, *info = NULL; + char *buffer = NULL; + int binary, error; + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *dev_name, unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, + &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *orig_name) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, + struct path *path, const char *type, unsigned long flags, + void *data) +{ + struct file_perms perms = { }; + char *buffer = NULL, *dev_buffer = NULL; + const char *name = NULL, *dev_name = NULL, *info = NULL; + int binary = 1; + int error; + + dev_name = orig_dev_name; + if (type) { + int requires_dev; + struct file_system_type *fstype = get_fs_type(type); + if (!fstype) + return -ENODEV; + + binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; + requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; + put_filesystem(fstype); + + if (requires_dev) { + struct path dev_path; + + if (!dev_name || !*dev_name) { + error = -ENOENT; + goto out; + } + + error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); + if (error) + goto audit; + + error = aa_path_name(&dev_path, + path_flags(profile, &dev_path), + &dev_buffer, &dev_name, &info); + path_put(&dev_path); + if (error) + goto audit; + } + } + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, dev_name, type, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, + type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + kfree(dev_buffer); + +out: + return error; + +} + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + struct path path = { mnt, mnt->mnt_root }; + error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, + &info); + if (error) + goto audit; + + if (!error && profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); + kfree(buffer); + + return error; +} + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path) +{ + struct file_perms perms = { }; + struct aa_profile *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL; + const char *old_name, *new_name = NULL, *info = NULL; + int error; + + error = aa_path_name(old_path, path_flags(profile, old_path), + &old_buffer, &old_name, &info); + if (error) + goto audit; + + error = aa_path_name(new_path, path_flags(profile, new_path), + &new_buffer, &new_name, &info); + if (error) + goto audit; + + if (profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(profile->policy.dfa, state); + state = aa_dfa_match(profile->policy.dfa, state, old_name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_PIVOTROOT & perms.allow) { + if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + target = x_table_lookup(profile, perms.xindex); + if (!target) + error = -ENOENT; + else + error = aa_replace_current_profile(target); + } + } else + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, + old_name, NULL, target ? target->base.name : NULL, + 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); + aa_put_profile(target); + kfree(old_buffer); + kfree(new_buffer); + + return error; +} -- 1.7.9.5 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.4/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.patchapparmor-2.8.95~2430/kernel-patches/3.4/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.pa0000644000175000017500000001613211756603243031516 0ustar sarnoldsarnoldFrom 8de755e4dfdbc40bfcaca848ae6b5aeaf0ede0e8 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 1/3] UBUNTU: SAUCE: AppArmor: Add profile introspection file to interface Add the dynamic profiles file to the interace, to allow load policy introspection. Signed-off-by: John Johansen Acked-by: Kees Cook Signed-off-by: Tim Gardner --- security/apparmor/apparmorfs.c | 227 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16c15ec..89bdc62 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,6 +182,232 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; + /** Base file system setup **/ static struct aa_fs_entry aa_fs_entry_file[] = { @@ -210,6 +436,7 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = { AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), + AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), AA_FS_DIR("features", aa_fs_entry_features), { } }; -- 1.7.9.5 apparmor-2.8.95~2430/kernel-patches/3.4/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch0000644000175000017500000004306611756603243030661 0ustar sarnoldsarnoldFrom 423e2cb454d75d6185eecd0c1b5cf6ccc2d8482d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 2/3] UBUNTU: SAUCE: AppArmor: basic networking rules Base support for network mediation. Signed-off-by: John Johansen --- security/apparmor/.gitignore | 2 +- security/apparmor/Makefile | 42 +++++++++- security/apparmor/apparmorfs.c | 1 + security/apparmor/include/audit.h | 4 + security/apparmor/include/net.h | 44 ++++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ security/apparmor/net.c | 162 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 46 ++++++++++ 10 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 4d995ae..d5b291e 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,6 +1,6 @@ # # Generated include files # -af_names.h +net_names.h capability_names.h rlim_names.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 806bd19..19daa85 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h net_names.h # Build a lower case string table of capability names @@ -20,6 +20,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ +# Build a lower case string table of address family names +# Transform lines from +# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [1] = "local", +# [2] = "inet", +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# #define AA_FS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ ;\ + echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ + sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ + $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +# Build a lower case string table of sock type names +# Transform lines from +# SOCK_STREAM = 1, +# to +# [1] = "stream", +quiet_cmd_make-sock = GEN $@ +cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ + sed $^ >>$@ -r -n \ + -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ # Build a lower case string table of rlimit names. # Transforms lines from @@ -56,6 +88,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ $(src)/Makefile @@ -63,3 +96,8 @@ $(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ $(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) +$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ + $(srctree)/include/linux/net.h \ + $(src)/Makefile + $(call cmd,make-af) + $(call cmd,make-sock) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 89bdc62..c66315d 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -427,6 +427,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 3868b1e..c1ff09c 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -126,6 +126,10 @@ struct apparmor_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; }; diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..cb8a121 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,44 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +#include "apparmorfs.h" + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern struct aa_fs_entry aa_fs_entry_network[]; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index bda4569..eb13a73 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *const profile_mode_names[]; @@ -157,6 +158,7 @@ struct aa_policydb { * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -194,6 +196,7 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index ad05d39..3cde194 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -622,6 +623,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -653,6 +752,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..084232b --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,162 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "net_names.h" + +struct aa_fs_entry aa_fs_entry_network[] = { + AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), + { } +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net->family]) { + audit_log_string(ab, address_family_names[sa->u.net->family]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); + } + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad->net.type]) { + audit_log_string(ab, sock_type_names[sa->aad->net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); + } + audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + struct lsm_network_audit net = { }; + if (sk) { + COMMON_AUDIT_DATA_INIT(&sa, NET); + } else { + COMMON_AUDIT_DATA_INIT(&sa, NONE); + } + /* todo fill in socket addr info */ + sa.aad = &aad; + sa.u.net = &net; + sa.aad->op = op, + sa.u.net->family = family; + sa.u.net->sk = sk; + sa.aad->net.type = type; + sa.aad->net.protocol = protocol; + sa.aad->error = error; + + if (likely(!sa.aad->error)) { + u16 audit_mask = profile->net.audit[sa.u.net->family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad->net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index f1f7506..b8100a7 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index deab7c7..8f8e9c1 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -193,6 +193,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -471,6 +484,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; + size_t size = 0; int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -564,6 +578,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + } + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ profile->policy.dfa = unpack_dfa(e); -- 1.7.9.5 apparmor-2.8.95~2430/kernel-patches/3.1/0000755000175000017500000000000012311706720017242 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.1/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.patchapparmor-2.8.95~2430/kernel-patches/3.1/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.pa0000644000175000017500000000507311626246005032175 0ustar sarnoldsarnoldFrom 7a10d093f9779f42cb8d6affcb6a4436d3ebd6d3 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:41 -0700 Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken userspace The apparmor_parser when compiling policy could generate invalid dfas that did not have sufficient padding to avoid invalid references, when used by the kernel. The kernels check to verify the next/check table size was broken meaning invalid dfas were being created by userspace and not caught. To remain compatible with old tools that are not fixed, pad the loaded dfas next/check table. The dfa's themselves are valid except for the high padding for potentially invalid transitions (high bounds error), which have a maximimum is 256 entries. So just allocate an extra null filled 256 entries for the next/check tables. This will guarentee all bounds are good and invalid transitions go to the null (0) state. Signed-off-by: John Johansen --- security/apparmor/match.c | 17 +++++++++++++++++ 1 files changed, 17 insertions(+), 0 deletions(-) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 94de6b4..081491e 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize) if (bsize < tsize) goto out; + /* Pad table allocation for next/check by 256 entries to remain + * backwards compatible with old (buggy) tools and remain safe without + * run time checks + */ + if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK) + tsize += 256 * th.td_flags; + table = kvmalloc(tsize); if (table) { + /* ensure the pad is clear, else there will be errors */ + memset(table, 0, tsize); *table = th; if (th.td_flags == YYTD_DATA8) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, @@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags) goto out; if (flags & DFA_FLAG_VERIFY_STATES) { + int warning = 0; for (i = 0; i < state_count; i++) { if (DEFAULT_TABLE(dfa)[i] >= state_count) goto out; /* TODO: do check that DEF state recursion terminates */ if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { + if (warning) + continue; + printk(KERN_WARNING "AppArmor DFA next/check " + "upper bounds error fixed, upgrade " + "user space tools \n"); + warning = 1; + } else if (BASE_TABLE(dfa)[i] >= trans_count) { printk(KERN_ERR "AppArmor DFA next/check upper " "bounds error\n"); goto out; -- 1.7.5.4 apparmor-2.8.95~2430/kernel-patches/3.1/0002-AppArmor-compatibility-patch-for-v5-interface.patch0000644000175000017500000002577211626246005031421 0ustar sarnoldsarnoldFrom a2515f25ad5a7833ddc5a032d34eee6a5ddee3a2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:40 -0700 Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface Signed-off-by: John Johansen --- security/apparmor/Kconfig | 9 + security/apparmor/Makefile | 1 + security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++ security/apparmor/apparmorfs.c | 18 ++- security/apparmor/include/apparmorfs.h | 6 + 5 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/apparmorfs-24.c diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 7cefef9..0bb604b 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o sid.o file.o net.o +apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o clean-files := capability_names.h rlim_names.h af_names.h diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c new file mode 100644 index 0000000..dc8c744 --- /dev/null +++ b/security/apparmor/apparmorfs-24.c @@ -0,0 +1,287 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + * + * + * This file contain functions providing an interface for <= AppArmor 2.4 + * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24 + * being set (see Makefile). + */ + +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/policy.h" + + +/* apparmor/matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char matching[] = "pattern=aadfa audit perms=crwxamlk/ " + "user::other"; + + return simple_read_from_buffer(buf, size, ppos, matching, + sizeof(matching) - 1); +} + +const struct file_operations aa_fs_matching_fops = { + .read = aa_matching_read, +}; + +/* apparmor/features */ +static ssize_t aa_features_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char features[] = "file=3.1 capability=2.0 network=1.0 " + "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1"; + + return simple_read_from_buffer(buf, size, ppos, features, + sizeof(features) - 1); +} + +const struct file_operations aa_fs_features_fops = { + .read = aa_features_read, +}; + +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 0848292..28c52ac 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -187,7 +187,11 @@ void __init aa_destroy_aafs(void) aafs_remove(".remove"); aafs_remove(".replace"); aafs_remove(".load"); - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + aafs_remove("profiles"); + aafs_remove("matching"); + aafs_remove("features"); +#endif securityfs_remove(aa_fs_dentry); aa_fs_dentry = NULL; } @@ -218,7 +222,17 @@ int __init aa_create_aafs(void) aa_fs_dentry = NULL; goto error; } - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + error = aafs_create("matching", 0444, &aa_fs_matching_fops); + if (error) + goto error; + error = aafs_create("features", 0444, &aa_fs_features_fops); + if (error) + goto error; +#endif + error = aafs_create("profiles", 0440, &aa_fs_profiles_fops); + if (error) + goto error; error = aafs_create(".load", 0640, &aa_fs_profile_load); if (error) goto error; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index cb1e93a..14f955c 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -17,4 +17,10 @@ extern void __init aa_destroy_aafs(void); +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +extern const struct file_operations aa_fs_matching_fops; +extern const struct file_operations aa_fs_features_fops; +extern const struct file_operations aa_fs_profiles_fops; +#endif + #endif /* __AA_APPARMORFS_H */ -- 1.7.5.4 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.1/0001-AppArmor-compatibility-patch-for-v5-network-controll.patchapparmor-2.8.95~2430/kernel-patches/3.1/0001-AppArmor-compatibility-patch-for-v5-network-controll.pa0000644000175000017500000003651311626246005032277 0ustar sarnoldsarnoldFrom dc13dec93dbd04bfa7a9ba67df1b8ed3431d8d48 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:39 -0700 Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll Add compatibility for v5 network rules. Signed-off-by: John Johansen --- include/linux/lsm_audit.h | 4 + security/apparmor/Makefile | 19 ++++- security/apparmor/include/net.h | 40 +++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++ security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 48 ++++++++++- 8 files changed, 394 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 88e78de..c63979a 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -124,6 +124,10 @@ struct common_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; } apparmor_audit_data; #endif diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 2dafe50..7cefef9 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h af_names.h # Build a lower case string table of capability names @@ -44,9 +44,24 @@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\ sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\ echo "};" >> $@ +# Build a lower case string table of address family names. +# Transform lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [2] = "inet", +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >> $@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+).*/[\2] = "\L\1",/p';\ + echo "};" >> $@ + + $(obj)/capability.o : $(obj)/capability_names.h $(obj)/resource.o : $(obj)/rlim_names.h +$(obj)/net.o : $(obj)/af_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h $(call cmd,make-caps) $(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h $(call cmd,make-rlim) +$(obj)/af_names.h : $(srctree)/include/linux/socket.h + $(call cmd,make-af) \ No newline at end of file diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..3c7d599 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,40 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index aeda5cf..6776929 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *profile_mode_names[]; @@ -145,6 +146,7 @@ struct aa_namespace { * @size: the memory consumed by this profiles rules * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -181,6 +183,7 @@ struct aa_profile { struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 3d2fd14..aa293ae 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -621,6 +622,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -652,6 +751,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..1765901 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,170 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "af_names.h" + +static const char *sock_type_names[] = { + "unknown(0)", + "stream", + "dgram", + "raw", + "rdm", + "seqpacket", + "dccp", + "unknown(7)", + "unknown(8)", + "unknown(9)", + "packet", +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net.family]) { + audit_log_string(ab, address_family_names[sa->u.net.family]); + } else { + audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family); + } + + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad.net.type]) { + audit_log_string(ab, sock_type_names[sa->aad.net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type); + } + + audit_log_format(ab, " protocol=%d", sa->aad.net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + if (sk) { + COMMON_AUDIT_DATA_INIT(&sa, NET); + } else { + COMMON_AUDIT_DATA_INIT(&sa, NONE); + } + /* todo fill in socket addr info */ + + sa.aad.op = op, + sa.u.net.family = family; + sa.u.net.sk = sk; + sa.aad.net.type = type; + sa.aad.net.protocol = protocol; + sa.aad.error = error; + + if (likely(!sa.aad.error)) { + u16 audit_mask = profile->net.audit[sa.u.net.family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad.net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net.family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad.net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 4f0eade..4d5ce13 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index d6d9a57..f4874c4 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -190,6 +190,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; - int error = -EPROTO; + size_t size = 0; + int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + } + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + /* get file rules */ profile->file.dfa = unpack_dfa(e); if (IS_ERR(profile->file.dfa)) { -- 1.7.5.4 apparmor-2.8.95~2430/kernel-patches/2.6.37/0000755000175000017500000000000012311706720017476 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/2.6.37/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.patchapparmor-2.8.95~2430/kernel-patches/2.6.37/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke0000644000175000017500000000507011512511427032024 0ustar sarnoldsarnoldFrom dd6eaf697f4deb510ecbfba12ac0d5221e4c6829 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 20 Jul 2010 06:57:08 -0700 Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken userspace The apparmor_parser when compiling policy could generate invalid dfas that did not have sufficient padding to avoid invalid references, when used by the kernel. The kernels check to verify the next/check table size was broken meaning invalid dfas were being created by userspace and not caught. To remain compatible with old tools that are not fixed, pad the loaded dfas next/check table. The dfa's themselves are valid except for the high padding for potentially invalid transitions (high bounds error), which have a maximimum is 256 entries. So just allocate an extra null filled 256 entries for the next/check tables. This will guarentee all bounds are good and invalid transitions go to the null (0) state. Signed-off-by: John Johansen --- security/apparmor/match.c | 17 +++++++++++++++++ 1 files changed, 17 insertions(+), 0 deletions(-) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 5cb4dc1..0248bb3 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize) if (bsize < tsize) goto out; + /* Pad table allocation for next/check by 256 entries to remain + * backwards compatible with old (buggy) tools and remain safe without + * run time checks + */ + if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK) + tsize += 256 * th.td_flags; + table = kvmalloc(tsize); if (table) { + /* ensure the pad is clear, else there will be errors */ + memset(table, 0, tsize); *table = th; if (th.td_flags == YYTD_DATA8) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, @@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags) goto out; if (flags & DFA_FLAG_VERIFY_STATES) { + int warning = 0; for (i = 0; i < state_count; i++) { if (DEFAULT_TABLE(dfa)[i] >= state_count) goto out; /* TODO: do check that DEF state recursion terminates */ if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { + if (warning) + continue; + printk(KERN_WARNING "AppArmor DFA next/check " + "upper bounds error fixed, upgrade " + "user space tools \n"); + warning = 1; + } else if (BASE_TABLE(dfa)[i] >= trans_count) { printk(KERN_ERR "AppArmor DFA next/check upper " "bounds error\n"); goto out; -- 1.7.1 apparmor-2.8.95~2430/kernel-patches/2.6.37/0002-AppArmor-compatibility-patch-for-v5-interface.patch0000644000175000017500000002573611512511427031652 0ustar sarnoldsarnoldFrom a89f0bb2dfd82445e58f5f6bfceb40ab8bee543f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface Signed-off-by: John Johansen --- security/apparmor/Kconfig | 9 + security/apparmor/Makefile | 2 + security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++ security/apparmor/apparmorfs.c | 18 ++- security/apparmor/include/apparmorfs.h | 6 + 5 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/apparmorfs-24.c diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index a9a1db0..e5e8968 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -6,6 +6,8 @@ apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o sid.o file.o net.o +apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o + clean-files: capability_names.h af_names.h quiet_cmd_make-caps = GEN $@ diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c new file mode 100644 index 0000000..dc8c744 --- /dev/null +++ b/security/apparmor/apparmorfs-24.c @@ -0,0 +1,287 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + * + * + * This file contain functions providing an interface for <= AppArmor 2.4 + * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24 + * being set (see Makefile). + */ + +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/policy.h" + + +/* apparmor/matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char matching[] = "pattern=aadfa audit perms=crwxamlk/ " + "user::other"; + + return simple_read_from_buffer(buf, size, ppos, matching, + sizeof(matching) - 1); +} + +const struct file_operations aa_fs_matching_fops = { + .read = aa_matching_read, +}; + +/* apparmor/features */ +static ssize_t aa_features_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char features[] = "file=3.1 capability=2.0 network=1.0 " + "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1"; + + return simple_read_from_buffer(buf, size, ppos, features, + sizeof(features) - 1); +} + +const struct file_operations aa_fs_features_fops = { + .read = aa_features_read, +}; + +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 0848292..28c52ac 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -187,7 +187,11 @@ void __init aa_destroy_aafs(void) aafs_remove(".remove"); aafs_remove(".replace"); aafs_remove(".load"); - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + aafs_remove("profiles"); + aafs_remove("matching"); + aafs_remove("features"); +#endif securityfs_remove(aa_fs_dentry); aa_fs_dentry = NULL; } @@ -218,7 +222,17 @@ int __init aa_create_aafs(void) aa_fs_dentry = NULL; goto error; } - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + error = aafs_create("matching", 0444, &aa_fs_matching_fops); + if (error) + goto error; + error = aafs_create("features", 0444, &aa_fs_features_fops); + if (error) + goto error; +#endif + error = aafs_create("profiles", 0440, &aa_fs_profiles_fops); + if (error) + goto error; error = aafs_create(".load", 0640, &aa_fs_profile_load); if (error) goto error; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index cb1e93a..14f955c 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -17,4 +17,10 @@ extern void __init aa_destroy_aafs(void); +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +extern const struct file_operations aa_fs_matching_fops; +extern const struct file_operations aa_fs_features_fops; +extern const struct file_operations aa_fs_profiles_fops; +#endif + #endif /* __AA_APPARMORFS_H */ -- 1.7.1 ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/2.6.37/0001-AppArmor-compatibility-patch-for-v5-network-controll.patchapparmor-2.8.95~2430/kernel-patches/2.6.37/0001-AppArmor-compatibility-patch-for-v5-network-controll0000644000175000017500000003667211512511427032137 0ustar sarnoldsarnoldFrom 333f21a5004bc386f217801549514b689f3430cd Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll Add compatibility for v5 network rules. Signed-off-by: John Johansen --- include/linux/lsm_audit.h | 4 + security/apparmor/Makefile | 6 +- security/apparmor/include/net.h | 40 +++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++ security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 48 ++++++++++- 8 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 112a550..d5f3dd7 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -123,6 +123,10 @@ struct common_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; } apparmor_audit_data; #endif diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index f204869..a9a1db0 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,17 +4,21 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o clean-files: capability_names.h af_names.h quiet_cmd_make-caps = GEN $@ cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ; sed -n -e "/CAP_FS_MASK/d" -e "s/^\#define[ \\t]\\+CAP_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ; sed -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "s/^\#define[ \\t]\\+AF_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ + quiet_cmd_make-rlim = GEN $@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ; sed -n --e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+RLIMIT_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ ; echo "static const int rlim_map[] = {" >> $@ ; sed -n -e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+\\(RLIMIT_[A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/\\1,/p" $< >> $@ ; echo "};" >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/af_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h $(call cmd,make-caps) diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..3c7d599 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,40 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index aeda5cf..6776929 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *profile_mode_names[]; @@ -145,6 +146,7 @@ struct aa_namespace { * @size: the memory consumed by this profiles rules * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -181,6 +183,7 @@ struct aa_profile { struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index b7106f1..fa778a7 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -31,6 +31,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -619,6 +620,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -650,6 +749,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..1765901 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,170 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "af_names.h" + +static const char *sock_type_names[] = { + "unknown(0)", + "stream", + "dgram", + "raw", + "rdm", + "seqpacket", + "dccp", + "unknown(7)", + "unknown(8)", + "unknown(9)", + "packet", +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net.family]) { + audit_log_string(ab, address_family_names[sa->u.net.family]); + } else { + audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family); + } + + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad.net.type]) { + audit_log_string(ab, sock_type_names[sa->aad.net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type); + } + + audit_log_format(ab, " protocol=%d", sa->aad.net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + if (sk) { + COMMON_AUDIT_DATA_INIT(&sa, NET); + } else { + COMMON_AUDIT_DATA_INIT(&sa, NONE); + } + /* todo fill in socket addr info */ + + sa.aad.op = op, + sa.u.net.family = family; + sa.u.net.sk = sk; + sa.aad.net.type = type; + sa.aad.net.protocol = protocol; + sa.aad.error = error; + + if (likely(!sa.aad.error)) { + u16 audit_mask = profile->net.audit[sa.u.net.family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad.net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net.family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad.net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 4f0eade..4d5ce13 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index eb3700e..c2b6225 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -190,6 +190,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; - int error = -EPROTO; + size_t size = 0; + int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i > AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + } + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + /* get file rules */ profile->file.dfa = unpack_dfa(e); if (IS_ERR(profile->file.dfa)) { -- 1.7.1 apparmor-2.8.95~2430/kernel-patches/3.12/0000755000175000017500000000000012311706711017324 5ustar sarnoldsarnoldapparmor-2.8.95~2430/kernel-patches/3.12/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch0000644000175000017500000004334412234002577030731 0ustar sarnoldsarnoldFrom d29d73fa5d7b5d016f9c17236fff2a741acea247 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 1/3] UBUNTU: SAUCE: AppArmor: basic networking rules Base support for network mediation. Signed-off-by: John Johansen Conflicts: security/apparmor/Makefile security/apparmor/policy.c --- security/apparmor/.gitignore | 1 + security/apparmor/Makefile | 42 +++++++++- security/apparmor/apparmorfs.c | 1 + security/apparmor/include/audit.h | 4 + security/apparmor/include/net.h | 44 ++++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ security/apparmor/net.c | 162 +++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 46 +++++++++++ 10 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 9cdec70..d5b291e 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,5 +1,6 @@ # # Generated include files # +net_names.h capability_names.h rlim_names.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index d693df8..5dbb72f 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,10 +4,10 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h net_names.h # Build a lower case string table of capability names @@ -25,6 +25,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ +# Build a lower case string table of address family names +# Transform lines from +# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [1] = "local", +# [2] = "inet", +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# #define AA_FS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ ;\ + echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ + sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ + $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +# Build a lower case string table of sock type names +# Transform lines from +# SOCK_STREAM = 1, +# to +# [1] = "stream", +quiet_cmd_make-sock = GEN $@ +cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ + sed $^ >>$@ -r -n \ + -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ # Build a lower case string table of rlimit names. # Transforms lines from @@ -61,6 +93,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(src)/Makefile @@ -68,3 +101,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) +$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ + $(srctree)/include/linux/net.h \ + $(src)/Makefile + $(call cmd,make-af) + $(call cmd,make-sock) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 7db9954..18fc02c 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -806,6 +806,7 @@ static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("policy", aa_fs_entry_policy), AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), AA_FS_DIR("caps", aa_fs_entry_caps), diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 30e8d76..61abec5 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -126,6 +126,10 @@ struct apparmor_audit_data { u32 denied; kuid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; }; diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..cb8a121 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,44 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +#include "apparmorfs.h" + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern struct aa_fs_entry aa_fs_entry_network[]; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index c28b0f2..b524d88 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *const aa_profile_mode_names[]; @@ -176,6 +177,7 @@ struct aa_replacedby { * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * @dents: dentries for the profiles file entries in apparmorfs @@ -217,6 +219,7 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; unsigned char *hash; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index fb99e18..de55a7f 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -615,6 +616,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -647,6 +746,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..003dd18 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,162 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "net_names.h" + +struct aa_fs_entry aa_fs_entry_network[] = { + AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), + { } +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net->family]) { + audit_log_string(ab, address_family_names[sa->u.net->family]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); + } + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad->net.type]) { + audit_log_string(ab, sock_type_names[sa->aad->net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); + } + audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + struct lsm_network_audit net = { }; + if (sk) { + sa.type = LSM_AUDIT_DATA_NET; + } else { + sa.type = LSM_AUDIT_DATA_NONE; + } + /* todo fill in socket addr info */ + sa.aad = &aad; + sa.u.net = &net; + sa.aad->op = op, + sa.u.net->family = family; + sa.u.net->sk = sk; + sa.aad->net.type = type; + sa.aad->net.protocol = protocol; + sa.aad->error = error; + + if (likely(!sa.aad->error)) { + u16 audit_mask = profile->net.audit[sa.u.net->family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad->net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 705c287..e2afe29 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -603,6 +603,7 @@ void aa_free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); kzfree(profile->dirname); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index a689f10..1a35e6b 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -193,6 +193,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -476,6 +489,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; + size_t size = 0; int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -576,6 +590,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + } + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ profile->policy.dfa = unpack_dfa(e); -- 1.8.3.2 ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.12/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patchapparmor-2.8.95~2430/kernel-patches/3.12/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.p0000644000175000017500000006546212234002577031327 0ustar sarnoldsarnoldFrom 0f113c1f052be315f5097d8b7294a620b0adda87 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 May 2012 10:58:05 -0700 Subject: [PATCH 3/3] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount Add the ability for apparmor to do mediation of mount operations. Mount rules require an updated apparmor_parser (2.8 series) for policy compilation. The basic form of the rules are. [audit] [deny] mount [conds]* [device] [ -> [conds] path], [audit] [deny] remount [conds]* [path], [audit] [deny] umount [conds]* [path], [audit] [deny] pivotroot [oldroot=] remount is just a short cut for mount options=remount where [conds] can be fstype= options= Example mount commands mount, # allow all mounts, but not umount or pivotroot mount fstype=procfs, # allow mounting procfs anywhere mount options=(bind, ro) /foo -> /bar, # readonly bind mount mount /dev/sda -> /mnt, mount /dev/sd** -> /mnt/**, mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ umount, umount /m*, See the apparmor userspace for full documentation Signed-off-by: John Johansen Acked-by: Kees Cook Conflicts: security/apparmor/Makefile security/apparmor/apparmorfs.c --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 15 +- security/apparmor/audit.c | 4 + security/apparmor/domain.c | 2 +- security/apparmor/include/apparmor.h | 3 +- security/apparmor/include/audit.h | 11 + security/apparmor/include/domain.h | 2 + security/apparmor/include/mount.h | 54 +++ security/apparmor/lsm.c | 59 ++++ security/apparmor/mount.c | 620 +++++++++++++++++++++++++++++++++++ 10 files changed, 768 insertions(+), 4 deletions(-) create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/mount.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 5dbb72f..89b3445 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o net.o + resource.o sid.o file.o net.o mount.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o clean-files := capability_names.h rlim_names.h net_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 18fc02c..e709030 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -799,7 +799,18 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_policy[] = { AA_FS_FILE_BOOLEAN("set_load", 1), - {} + { } +}; + +static struct aa_fs_entry aa_fs_entry_mount[] = { + AA_FS_FILE_STRING("mask", "mount umount"), + { } +}; + +static struct aa_fs_entry aa_fs_entry_namespaces[] = { + AA_FS_FILE_BOOLEAN("profile", 1), + AA_FS_FILE_BOOLEAN("pivot_root", 1), + { } }; static struct aa_fs_entry aa_fs_entry_features[] = { @@ -807,6 +818,8 @@ static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_DIR("mount", aa_fs_entry_mount), + AA_FS_DIR("namespaces", aa_fs_entry_namespaces), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), AA_FS_DIR("caps", aa_fs_entry_caps), diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 031d2d9..02d804c 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -44,6 +44,10 @@ const char *const op_table[] = { "file_mmap", "file_mprotect", + "pivotroot", + "mount", + "umount", + "create", "post_create", "bind", diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 26c607c..23936c5 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -238,7 +238,7 @@ static const char *next_name(int xtype, const char *name) * * Returns: refcounted profile, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) { struct aa_profile *new_profile = NULL; struct aa_namespace *ns = profile->ns; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 8fb1488..22b172c 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -30,8 +30,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_MOUNT 7 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_MOUNT /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 61abec5..a9835c3 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -72,6 +72,10 @@ enum aa_ops { OP_FMMAP, OP_FMPROT, + OP_PIVOTROOT, + OP_MOUNT, + OP_UMOUNT, + OP_CREATE, OP_POST_CREATE, OP_BIND, @@ -121,6 +125,13 @@ struct apparmor_audit_data { unsigned long max; } rlim; struct { + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { const char *target; u32 request; u32 denied; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464..a3f70c5 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,6 +23,8 @@ struct aa_domain { char **table; }; +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm); diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 0000000..bc17a53 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 Canonical Ltd. + * + * 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 __AA_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags); + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *old_name); + +int aa_new_mount(struct aa_profile *profile, const char *dev_name, + struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path); + +#endif /* __AA_MOUNT_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index de55a7f..e0dd95f 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -36,6 +36,7 @@ #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" +#include "include/mount.h" /* Flag indicating whether initialization completed */ int apparmor_initialized __initdata; @@ -502,6 +503,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + int error = 0; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + profile = __aa_current_profile(); + if (!unconfined(profile)) { + if (flags & MS_REMOUNT) + error = aa_remount(profile, path, flags, data); + else if (flags & MS_BIND) + error = aa_bind_mount(profile, path, dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(profile, path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount(profile, path, dev_name); + else + error = aa_new_mount(profile, dev_name, path, type, + flags, data); + } + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_umount(profile, mnt, flags); + + return error; +} + +static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_pivotroot(profile, old_path, new_path); + + return error; +} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { @@ -722,6 +777,10 @@ static struct security_operations apparmor_ops = { .capget = apparmor_capget, .capable = apparmor_capable, + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_sb_umount, + .sb_pivotroot = apparmor_sb_pivotroot, + .path_link = apparmor_path_link, .path_unlink = apparmor_path_unlink, .path_symlink = apparmor_path_symlink, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 0000000..478aa4d --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,620 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (sa->aad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, sa->aad->mnt.type); + } + if (sa->aad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, sa->aad->mnt.src_name); + } + if (sa->aad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, sa->aad->mnt.trans); + } + if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, sa->aad->mnt.flags); + audit_log_format(ab, "\""); + } + if (sa->aad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, sa->aad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @profile: the profile being enforced (NOT NULL) + * @gfp: allocation flags + * @op: operation being mediated (NOT NULL) + * @name: name of object being mediated (MAYBE NULL) + * @src_name: src_name of object being mediated (MAYBE_NULL) + * @type: type of filesystem (MAYBE_NULL) + * @trans: name of trans (MAYBE NULL) + * @flags: filesystem idependent mount flags + * @data: filesystem mount flags + * @request: permissions requested + * @perms: the permissions computed for the request (NOT NULL) + * @info: extra information message (MAYBE NULL) + * @error: 0 if operation allowed else failure error code + * + * Returns: %0 or error on failure + */ +static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct file_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa = { }; + struct apparmor_audit_data aad = { }; + + if (likely(!error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + request &= mask; + + if (likely(!request)) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + request = request & ~perms->allow; + + if (request & perms->kill) + audit_type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + request &= ~perms->quiet; + + if (!request) + return COMPLAIN_MODE(profile) ? + complain_error(error) : error; + } + + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + sa.aad->op = op; + sa.aad->name = name; + sa.aad->mnt.src_name = src_name; + sa.aad->mnt.type = type; + sa.aad->mnt.trans = trans; + sa.aad->mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + sa.aad->mnt.data = data; + sa.aad->info = info; + sa.aad->error = error; + + return aa_audit(audit_type, profile, gfp, &sa, audit_cb); +} + +/** + * match_mnt_flags - Do an ordered match on mount flags + * @dfa: dfa to match against + * @state: state to start in + * @flags: mount flags to match against + * + * Mount flags are encoded as an ordered match. This is done instead of + * checking against a simple bitmask, to allow for logical operations + * on the flags. + * + * Returns: next state after flags match + */ +static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, + unsigned long flags) +{ + unsigned int i; + + for (i = 0; i <= 31 ; ++i) { + if ((1 << i) & flags) + state = aa_dfa_next(dfa, state, i + 1); + } + + return state; +} + +/** + * compute_mnt_perms - compute mount permission associated with @state + * @dfa: dfa to match against (NOT NULL) + * @state: state match finished in + * + * Returns: mount permissions + */ +static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, + unsigned int state) +{ + struct file_perms perms; + + perms.kill = 0; + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + perms.xindex = dfa_user_xindex(dfa, state); + + return perms; +} + +static const char const *mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match" +}; + +/* + * Returns 0 on success else element that match failed in, this is the + * index into the mnt_info_table above + */ +static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct file_perms *perms) +{ + unsigned int state; + + state = aa_dfa_match(dfa, start, mntpnt); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(dfa, state, devname); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(dfa, state, type); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(dfa, state, flags); + if (!state) + return 4; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + + /* only match data if not binary and the DFA flags data is expected */ + if (data && !binary && (perms->allow & AA_CONT_MATCH)) { + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(dfa, state, data); + if (!state) + return 5; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at end of flags match */ + return 4; +} + +/** + * match_mnt - handle path matching for mount + * @profile: the confining profile + * @mntpnt: string for the mntpnt (NOT NULL) + * @devname: string for the devname/src_name (MAYBE NULL) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @perms: Returns: permission found by the match + * @info: Returns: infomation string about the match for logging + * + * Returns: 0 on success else error + */ +static int match_mnt(struct aa_profile *profile, const char *mntpnt, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + struct file_perms *perms, const char **info) +{ + int pos; + + if (!profile->policy.dfa) + return -EACCES; + + pos = do_match_mnt(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + mntpnt, devname, type, flags, data, binary, perms); + if (pos) { + *info = mnt_info_table[pos]; + return -EACCES; + } + + return 0; +} + +static int path_flags(struct aa_profile *profile, struct path *path) +{ + return profile->path_flags | + S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; +} + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data) +{ + struct file_perms perms = { }; + const char *name, *info = NULL; + char *buffer = NULL; + int binary, error; + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *dev_name, unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, + &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *orig_name) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, + struct path *path, const char *type, unsigned long flags, + void *data) +{ + struct file_perms perms = { }; + char *buffer = NULL, *dev_buffer = NULL; + const char *name = NULL, *dev_name = NULL, *info = NULL; + int binary = 1; + int error; + + dev_name = orig_dev_name; + if (type) { + int requires_dev; + struct file_system_type *fstype = get_fs_type(type); + if (!fstype) + return -ENODEV; + + binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; + requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; + put_filesystem(fstype); + + if (requires_dev) { + struct path dev_path; + + if (!dev_name || !*dev_name) { + error = -ENOENT; + goto out; + } + + error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); + if (error) + goto audit; + + error = aa_path_name(&dev_path, + path_flags(profile, &dev_path), + &dev_buffer, &dev_name, &info); + path_put(&dev_path); + if (error) + goto audit; + } + } + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, dev_name, type, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, + type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + kfree(dev_buffer); + +out: + return error; + +} + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + struct path path = { mnt, mnt->mnt_root }; + error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, + &info); + if (error) + goto audit; + + if (!error && profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); + kfree(buffer); + + return error; +} + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path) +{ + struct file_perms perms = { }; + struct aa_profile *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL; + const char *old_name, *new_name = NULL, *info = NULL; + int error; + + error = aa_path_name(old_path, path_flags(profile, old_path), + &old_buffer, &old_name, &info); + if (error) + goto audit; + + error = aa_path_name(new_path, path_flags(profile, new_path), + &new_buffer, &new_name, &info); + if (error) + goto audit; + + if (profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(profile->policy.dfa, state); + state = aa_dfa_match(profile->policy.dfa, state, old_name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_PIVOTROOT & perms.allow) { + if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + target = x_table_lookup(profile, perms.xindex); + if (!target) + error = -ENOENT; + else + error = aa_replace_current_profile(target); + } + } else + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, + old_name, NULL, target ? target->base.name : NULL, + 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); + aa_put_profile(target); + kfree(old_buffer); + kfree(new_buffer); + + return error; +} -- 1.8.3.2 ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.12/0002-apparmor-Fix-quieting-of-audit-messages-for-network-.patchapparmor-2.8.95~2430/kernel-patches/3.12/0002-apparmor-Fix-quieting-of-audit-messages-for-network-.p0000644000175000017500000000277412234002577032134 0ustar sarnoldsarnoldFrom b452a37e97af826ba6c7548230e07c95bd13d9c4 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jun 2012 17:34:00 -0700 Subject: [PATCH 2/3] apparmor: Fix quieting of audit messages for network mediation If a profile specified a quieting of network denials for a given rule by either the quiet or deny rule qualifiers, the resultant quiet mask for denied requests was applied incorrectly, resulting in two potential bugs. 1. The misapplied quiet mask would prevent denials from being correctly tested against the kill mask/mode. Thus network access requests that should have resulted in the application being killed did not. 2. The actual quieting of the denied network request was not being applied. This would result in network rejections always being logged even when they had been specifically marked as quieted. Signed-off-by: John Johansen --- security/apparmor/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 003dd18..6e6e5c9 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -88,7 +88,7 @@ static int audit_net(struct aa_profile *profile, int op, u16 family, int type, } else { u16 quiet_mask = profile->net.quiet[sa.u.net->family]; u16 kill_mask = 0; - u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + u16 denied = (1 << sa.aad->net.type); if (denied & kill_mask) audit_type = AUDIT_APPARMOR_KILL; -- 1.8.3.2 apparmor-2.8.95~2430/kernel-patches/3.9/0000755000175000017500000000000012311706711017252 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.9/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patchapparmor-2.8.95~2430/kernel-patches/3.9/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.pa0000644000175000017500000006474012234002577031415 0ustar sarnoldsarnoldFrom 883574b10ad78766ca48a7eb11082dc21597c583 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 May 2012 10:58:05 -0700 Subject: [PATCH 4/4] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount Add the ability for apparmor to do mediation of mount operations. Mount rules require an updated apparmor_parser (2.8 series) for policy compilation. The basic form of the rules are. [audit] [deny] mount [conds]* [device] [ -> [conds] path], [audit] [deny] remount [conds]* [path], [audit] [deny] umount [conds]* [path], [audit] [deny] pivotroot [oldroot=] remount is just a short cut for mount options=remount where [conds] can be fstype= options= Example mount commands mount, # allow all mounts, but not umount or pivotroot mount fstype=procfs, # allow mounting procfs anywhere mount options=(bind, ro) /foo -> /bar, # readonly bind mount mount /dev/sda -> /mnt, mount /dev/sd** -> /mnt/**, mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ umount, umount /m*, See the apparmor userspace for full documentation Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 13 + security/apparmor/audit.c | 4 + security/apparmor/domain.c | 2 +- security/apparmor/include/apparmor.h | 3 +- security/apparmor/include/audit.h | 11 + security/apparmor/include/domain.h | 2 + security/apparmor/include/mount.h | 54 +++ security/apparmor/lsm.c | 59 ++++ security/apparmor/mount.c | 620 +++++++++++++++++++++++++++++++++++ 10 files changed, 767 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/mount.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index e270692..9b44e1a 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o net.o + resource.o sid.o file.o net.o mount.o clean-files := capability_names.h rlim_names.h net_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 114fb23..ee77ec9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -426,10 +426,23 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { { } }; +static struct aa_fs_entry aa_fs_entry_mount[] = { + AA_FS_FILE_STRING("mask", "mount umount"), + { } +}; + +static struct aa_fs_entry aa_fs_entry_namespaces[] = { + AA_FS_FILE_BOOLEAN("profile", 1), + AA_FS_FILE_BOOLEAN("pivot_root", 1), + { } +}; + static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_DIR("mount", aa_fs_entry_mount), + AA_FS_DIR("namespaces", aa_fs_entry_namespaces), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 3ae28db..e267963 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -44,6 +44,10 @@ const char *const op_table[] = { "file_mmap", "file_mprotect", + "pivotroot", + "mount", + "umount", + "create", "post_create", "bind", diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 859abda..3fee1fe 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -242,7 +242,7 @@ static const char *next_name(int xtype, const char *name) * * Returns: refcounted profile, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) { struct aa_profile *new_profile = NULL; struct aa_namespace *ns = profile->ns; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 40aedd9..e243d96 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -29,8 +29,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_MOUNT 7 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_MOUNT /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 4af6523..ada004d 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -73,6 +73,10 @@ enum aa_ops { OP_FMMAP, OP_FMPROT, + OP_PIVOTROOT, + OP_MOUNT, + OP_UMOUNT, + OP_CREATE, OP_POST_CREATE, OP_BIND, @@ -122,6 +126,13 @@ struct apparmor_audit_data { unsigned long max; } rlim; struct { + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { const char *target; u32 request; u32 denied; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464..a3f70c5 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,6 +23,8 @@ struct aa_domain { char **table; }; +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm); diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 0000000..bc17a53 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 Canonical Ltd. + * + * 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 __AA_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags); + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *old_name); + +int aa_new_mount(struct aa_profile *profile, const char *dev_name, + struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path); + +#endif /* __AA_MOUNT_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 1bce440..6750673 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -36,6 +36,7 @@ #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" +#include "include/mount.h" /* Flag indicating whether initialization completed */ int apparmor_initialized __initdata; @@ -504,6 +505,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + int error = 0; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + profile = __aa_current_profile(); + if (!unconfined(profile)) { + if (flags & MS_REMOUNT) + error = aa_remount(profile, path, flags, data); + else if (flags & MS_BIND) + error = aa_bind_mount(profile, path, dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(profile, path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount(profile, path, dev_name); + else + error = aa_new_mount(profile, dev_name, path, type, + flags, data); + } + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_umount(profile, mnt, flags); + + return error; +} + +static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_pivotroot(profile, old_path, new_path); + + return error; +} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { @@ -721,6 +776,10 @@ static struct security_operations apparmor_ops = { .capget = apparmor_capget, .capable = apparmor_capable, + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_sb_umount, + .sb_pivotroot = apparmor_sb_pivotroot, + .path_link = apparmor_path_link, .path_unlink = apparmor_path_unlink, .path_symlink = apparmor_path_symlink, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 0000000..478aa4d --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,620 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (sa->aad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, sa->aad->mnt.type); + } + if (sa->aad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, sa->aad->mnt.src_name); + } + if (sa->aad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, sa->aad->mnt.trans); + } + if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, sa->aad->mnt.flags); + audit_log_format(ab, "\""); + } + if (sa->aad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, sa->aad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @profile: the profile being enforced (NOT NULL) + * @gfp: allocation flags + * @op: operation being mediated (NOT NULL) + * @name: name of object being mediated (MAYBE NULL) + * @src_name: src_name of object being mediated (MAYBE_NULL) + * @type: type of filesystem (MAYBE_NULL) + * @trans: name of trans (MAYBE NULL) + * @flags: filesystem idependent mount flags + * @data: filesystem mount flags + * @request: permissions requested + * @perms: the permissions computed for the request (NOT NULL) + * @info: extra information message (MAYBE NULL) + * @error: 0 if operation allowed else failure error code + * + * Returns: %0 or error on failure + */ +static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct file_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa = { }; + struct apparmor_audit_data aad = { }; + + if (likely(!error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + request &= mask; + + if (likely(!request)) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + request = request & ~perms->allow; + + if (request & perms->kill) + audit_type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + request &= ~perms->quiet; + + if (!request) + return COMPLAIN_MODE(profile) ? + complain_error(error) : error; + } + + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + sa.aad->op = op; + sa.aad->name = name; + sa.aad->mnt.src_name = src_name; + sa.aad->mnt.type = type; + sa.aad->mnt.trans = trans; + sa.aad->mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + sa.aad->mnt.data = data; + sa.aad->info = info; + sa.aad->error = error; + + return aa_audit(audit_type, profile, gfp, &sa, audit_cb); +} + +/** + * match_mnt_flags - Do an ordered match on mount flags + * @dfa: dfa to match against + * @state: state to start in + * @flags: mount flags to match against + * + * Mount flags are encoded as an ordered match. This is done instead of + * checking against a simple bitmask, to allow for logical operations + * on the flags. + * + * Returns: next state after flags match + */ +static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, + unsigned long flags) +{ + unsigned int i; + + for (i = 0; i <= 31 ; ++i) { + if ((1 << i) & flags) + state = aa_dfa_next(dfa, state, i + 1); + } + + return state; +} + +/** + * compute_mnt_perms - compute mount permission associated with @state + * @dfa: dfa to match against (NOT NULL) + * @state: state match finished in + * + * Returns: mount permissions + */ +static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, + unsigned int state) +{ + struct file_perms perms; + + perms.kill = 0; + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + perms.xindex = dfa_user_xindex(dfa, state); + + return perms; +} + +static const char const *mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match" +}; + +/* + * Returns 0 on success else element that match failed in, this is the + * index into the mnt_info_table above + */ +static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct file_perms *perms) +{ + unsigned int state; + + state = aa_dfa_match(dfa, start, mntpnt); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(dfa, state, devname); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(dfa, state, type); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(dfa, state, flags); + if (!state) + return 4; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + + /* only match data if not binary and the DFA flags data is expected */ + if (data && !binary && (perms->allow & AA_CONT_MATCH)) { + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(dfa, state, data); + if (!state) + return 5; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at end of flags match */ + return 4; +} + +/** + * match_mnt - handle path matching for mount + * @profile: the confining profile + * @mntpnt: string for the mntpnt (NOT NULL) + * @devname: string for the devname/src_name (MAYBE NULL) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @perms: Returns: permission found by the match + * @info: Returns: infomation string about the match for logging + * + * Returns: 0 on success else error + */ +static int match_mnt(struct aa_profile *profile, const char *mntpnt, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + struct file_perms *perms, const char **info) +{ + int pos; + + if (!profile->policy.dfa) + return -EACCES; + + pos = do_match_mnt(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + mntpnt, devname, type, flags, data, binary, perms); + if (pos) { + *info = mnt_info_table[pos]; + return -EACCES; + } + + return 0; +} + +static int path_flags(struct aa_profile *profile, struct path *path) +{ + return profile->path_flags | + S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; +} + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data) +{ + struct file_perms perms = { }; + const char *name, *info = NULL; + char *buffer = NULL; + int binary, error; + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *dev_name, unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, + &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *orig_name) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, + struct path *path, const char *type, unsigned long flags, + void *data) +{ + struct file_perms perms = { }; + char *buffer = NULL, *dev_buffer = NULL; + const char *name = NULL, *dev_name = NULL, *info = NULL; + int binary = 1; + int error; + + dev_name = orig_dev_name; + if (type) { + int requires_dev; + struct file_system_type *fstype = get_fs_type(type); + if (!fstype) + return -ENODEV; + + binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; + requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; + put_filesystem(fstype); + + if (requires_dev) { + struct path dev_path; + + if (!dev_name || !*dev_name) { + error = -ENOENT; + goto out; + } + + error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); + if (error) + goto audit; + + error = aa_path_name(&dev_path, + path_flags(profile, &dev_path), + &dev_buffer, &dev_name, &info); + path_put(&dev_path); + if (error) + goto audit; + } + } + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, dev_name, type, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, + type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + kfree(dev_buffer); + +out: + return error; + +} + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + struct path path = { mnt, mnt->mnt_root }; + error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, + &info); + if (error) + goto audit; + + if (!error && profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); + kfree(buffer); + + return error; +} + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path) +{ + struct file_perms perms = { }; + struct aa_profile *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL; + const char *old_name, *new_name = NULL, *info = NULL; + int error; + + error = aa_path_name(old_path, path_flags(profile, old_path), + &old_buffer, &old_name, &info); + if (error) + goto audit; + + error = aa_path_name(new_path, path_flags(profile, new_path), + &new_buffer, &new_name, &info); + if (error) + goto audit; + + if (profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(profile->policy.dfa, state); + state = aa_dfa_match(profile->policy.dfa, state, old_name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_PIVOTROOT & perms.allow) { + if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + target = x_table_lookup(profile, perms.xindex); + if (!target) + error = -ENOENT; + else + error = aa_replace_current_profile(target); + } + } else + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, + old_name, NULL, target ? target->base.name : NULL, + 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); + aa_put_profile(target); + kfree(old_buffer); + kfree(new_buffer); + + return error; +} -- 1.8.3.2 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.9/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.patchapparmor-2.8.95~2430/kernel-patches/3.9/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.pa0000644000175000017500000001744412234002577031525 0ustar sarnoldsarnoldFrom 9b9e0d69288c5f83f1d8b3799d6163a7d97f8e5c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 1/4] UBUNTU: SAUCE: AppArmor: Add profile introspection file to interface Add the dynamic profiles file to the interace, to allow load policy introspection. Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Kconfig | 9 ++ security/apparmor/apparmorfs.c | 231 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16c15ec..42b7c9f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,6 +182,234 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; +#endif /* CONFIG_SECURITY_APPARMOR_COMPAT_24 */ + /** Base file system setup **/ static struct aa_fs_entry aa_fs_entry_file[] = { @@ -210,6 +438,9 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = { AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), +#endif AA_FS_DIR("features", aa_fs_entry_features), { } }; -- 1.8.3.2 apparmor-2.8.95~2430/kernel-patches/3.9/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch0000644000175000017500000004305112234002577030653 0ustar sarnoldsarnoldFrom b50585bdf248fa83c60cf5df33019e46b1051553 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 2/4] UBUNTU: SAUCE: AppArmor: basic networking rules Base support for network mediation. Signed-off-by: John Johansen --- security/apparmor/.gitignore | 1 + security/apparmor/Makefile | 42 +++++++++- security/apparmor/apparmorfs.c | 1 + security/apparmor/include/audit.h | 4 + security/apparmor/include/net.h | 44 ++++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ security/apparmor/net.c | 162 +++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 46 +++++++++++ 10 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 9cdec70..d5b291e 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,5 +1,6 @@ # # Generated include files # +net_names.h capability_names.h rlim_names.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 5706b74..e270692 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h net_names.h # Build a lower case string table of capability names @@ -20,6 +20,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ +# Build a lower case string table of address family names +# Transform lines from +# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [1] = "local", +# [2] = "inet", +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# #define AA_FS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ ;\ + echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ + sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ + $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +# Build a lower case string table of sock type names +# Transform lines from +# SOCK_STREAM = 1, +# to +# [1] = "stream", +quiet_cmd_make-sock = GEN $@ +cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ + sed $^ >>$@ -r -n \ + -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ # Build a lower case string table of rlimit names. # Transforms lines from @@ -56,6 +88,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(src)/Makefile @@ -63,3 +96,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) +$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ + $(srctree)/include/linux/net.h \ + $(src)/Makefile + $(call cmd,make-af) + $(call cmd,make-sock) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 42b7c9f..114fb23 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -429,6 +429,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 69d8cae..4af6523 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -127,6 +127,10 @@ struct apparmor_audit_data { u32 denied; kuid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; }; diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..cb8a121 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,44 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +#include "apparmorfs.h" + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern struct aa_fs_entry aa_fs_entry_network[]; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index bda4569..eb13a73 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *const profile_mode_names[]; @@ -157,6 +158,7 @@ struct aa_policydb { * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -194,6 +196,7 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index b21830e..1bce440 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -614,6 +615,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -646,6 +745,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..003dd18 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,162 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "net_names.h" + +struct aa_fs_entry aa_fs_entry_network[] = { + AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), + { } +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net->family]) { + audit_log_string(ab, address_family_names[sa->u.net->family]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); + } + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad->net.type]) { + audit_log_string(ab, sock_type_names[sa->aad->net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); + } + audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + struct lsm_network_audit net = { }; + if (sk) { + sa.type = LSM_AUDIT_DATA_NET; + } else { + sa.type = LSM_AUDIT_DATA_NONE; + } + /* todo fill in socket addr info */ + sa.aad = &aad; + sa.u.net = &net; + sa.aad->op = op, + sa.u.net->family = family; + sa.u.net->sk = sk; + sa.aad->net.type = type; + sa.aad->net.protocol = protocol; + sa.aad->error = error; + + if (likely(!sa.aad->error)) { + u16 audit_mask = profile->net.audit[sa.u.net->family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad->net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 8132003..56e5304 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -747,6 +747,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 329b1fd..1b90dfa 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -193,6 +193,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -471,6 +484,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; + size_t size = 0; int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -564,6 +578,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + } + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ profile->policy.dfa = unpack_dfa(e); -- 1.8.3.2 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.9/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.patchapparmor-2.8.95~2430/kernel-patches/3.9/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.pa0000644000175000017500000000277412234002577032224 0ustar sarnoldsarnoldFrom 0937b6c0fd45917de9debc8ec5be9cb1a447a6f9 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jun 2012 17:34:00 -0700 Subject: [PATCH 3/4] apparmor: Fix quieting of audit messages for network mediation If a profile specified a quieting of network denials for a given rule by either the quiet or deny rule qualifiers, the resultant quiet mask for denied requests was applied incorrectly, resulting in two potential bugs. 1. The misapplied quiet mask would prevent denials from being correctly tested against the kill mask/mode. Thus network access requests that should have resulted in the application being killed did not. 2. The actual quieting of the denied network request was not being applied. This would result in network rejections always being logged even when they had been specifically marked as quieted. Signed-off-by: John Johansen --- security/apparmor/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 003dd18..6e6e5c9 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -88,7 +88,7 @@ static int audit_net(struct aa_profile *profile, int op, u16 family, int type, } else { u16 quiet_mask = profile->net.quiet[sa.u.net->family]; u16 kill_mask = 0; - u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + u16 denied = (1 << sa.aad->net.type); if (denied & kill_mask) audit_type = AUDIT_APPARMOR_KILL; -- 1.8.3.2 apparmor-2.8.95~2430/kernel-patches/2.6.39/0000755000175000017500000000000012311706720017500 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/2.6.39/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.patchapparmor-2.8.95~2430/kernel-patches/2.6.39/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke0000644000175000017500000000507211565255000032030 0ustar sarnoldsarnoldFrom f17b28f64b963c47e76737f7bb7f58ce3a7c5249 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 20 Jul 2010 06:57:08 -0700 Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken userspace The apparmor_parser when compiling policy could generate invalid dfas that did not have sufficient padding to avoid invalid references, when used by the kernel. The kernels check to verify the next/check table size was broken meaning invalid dfas were being created by userspace and not caught. To remain compatible with old tools that are not fixed, pad the loaded dfas next/check table. The dfa's themselves are valid except for the high padding for potentially invalid transitions (high bounds error), which have a maximimum is 256 entries. So just allocate an extra null filled 256 entries for the next/check tables. This will guarentee all bounds are good and invalid transitions go to the null (0) state. Signed-off-by: John Johansen --- security/apparmor/match.c | 17 +++++++++++++++++ 1 files changed, 17 insertions(+), 0 deletions(-) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 06d764c..cf92856 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize) if (bsize < tsize) goto out; + /* Pad table allocation for next/check by 256 entries to remain + * backwards compatible with old (buggy) tools and remain safe without + * run time checks + */ + if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK) + tsize += 256 * th.td_flags; + table = kvmalloc(tsize); if (table) { + /* ensure the pad is clear, else there will be errors */ + memset(table, 0, tsize); *table = th; if (th.td_flags == YYTD_DATA8) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, @@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags) goto out; if (flags & DFA_FLAG_VERIFY_STATES) { + int warning = 0; for (i = 0; i < state_count; i++) { if (DEFAULT_TABLE(dfa)[i] >= state_count) goto out; /* TODO: do check that DEF state recursion terminates */ if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { + if (warning) + continue; + printk(KERN_WARNING "AppArmor DFA next/check " + "upper bounds error fixed, upgrade " + "user space tools \n"); + warning = 1; + } else if (BASE_TABLE(dfa)[i] >= trans_count) { printk(KERN_ERR "AppArmor DFA next/check upper " "bounds error\n"); goto out; -- 1.7.0.4 apparmor-2.8.95~2430/kernel-patches/2.6.39/0002-AppArmor-compatibility-patch-for-v5-interface.patch0000644000175000017500000002577211565255000031654 0ustar sarnoldsarnoldFrom cdc6b35345e5bcfe92bb2b52ef003f94ceedd40d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface Signed-off-by: John Johansen --- security/apparmor/Kconfig | 9 + security/apparmor/Makefile | 1 + security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++ security/apparmor/apparmorfs.c | 18 ++- security/apparmor/include/apparmorfs.h | 6 + 5 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/apparmorfs-24.c diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 7cefef9..0bb604b 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o sid.o file.o net.o +apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o clean-files := capability_names.h rlim_names.h af_names.h diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c new file mode 100644 index 0000000..dc8c744 --- /dev/null +++ b/security/apparmor/apparmorfs-24.c @@ -0,0 +1,287 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + * + * + * This file contain functions providing an interface for <= AppArmor 2.4 + * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24 + * being set (see Makefile). + */ + +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/policy.h" + + +/* apparmor/matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char matching[] = "pattern=aadfa audit perms=crwxamlk/ " + "user::other"; + + return simple_read_from_buffer(buf, size, ppos, matching, + sizeof(matching) - 1); +} + +const struct file_operations aa_fs_matching_fops = { + .read = aa_matching_read, +}; + +/* apparmor/features */ +static ssize_t aa_features_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char features[] = "file=3.1 capability=2.0 network=1.0 " + "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1"; + + return simple_read_from_buffer(buf, size, ppos, features, + sizeof(features) - 1); +} + +const struct file_operations aa_fs_features_fops = { + .read = aa_features_read, +}; + +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 0848292..28c52ac 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -187,7 +187,11 @@ void __init aa_destroy_aafs(void) aafs_remove(".remove"); aafs_remove(".replace"); aafs_remove(".load"); - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + aafs_remove("profiles"); + aafs_remove("matching"); + aafs_remove("features"); +#endif securityfs_remove(aa_fs_dentry); aa_fs_dentry = NULL; } @@ -218,7 +222,17 @@ int __init aa_create_aafs(void) aa_fs_dentry = NULL; goto error; } - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + error = aafs_create("matching", 0444, &aa_fs_matching_fops); + if (error) + goto error; + error = aafs_create("features", 0444, &aa_fs_features_fops); + if (error) + goto error; +#endif + error = aafs_create("profiles", 0440, &aa_fs_profiles_fops); + if (error) + goto error; error = aafs_create(".load", 0640, &aa_fs_profile_load); if (error) goto error; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index cb1e93a..14f955c 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -17,4 +17,10 @@ extern void __init aa_destroy_aafs(void); +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +extern const struct file_operations aa_fs_matching_fops; +extern const struct file_operations aa_fs_features_fops; +extern const struct file_operations aa_fs_profiles_fops; +#endif + #endif /* __AA_APPARMORFS_H */ -- 1.7.0.4 ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/2.6.39/0001-AppArmor-compatibility-patch-for-v5-network-controll.patchapparmor-2.8.95~2430/kernel-patches/2.6.39/0001-AppArmor-compatibility-patch-for-v5-network-controll0000644000175000017500000003651111565255000032131 0ustar sarnoldsarnoldFrom 0ae314bc92d8b22250f04f85e4bd36ee9ed30890 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll Add compatibility for v5 network rules. Signed-off-by: John Johansen --- include/linux/lsm_audit.h | 4 + security/apparmor/Makefile | 19 ++++- security/apparmor/include/net.h | 40 +++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++ security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 48 ++++++++++- 8 files changed, 394 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 112a550..d5f3dd7 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -123,6 +123,10 @@ struct common_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; } apparmor_audit_data; #endif diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 2dafe50..7cefef9 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h af_names.h # Build a lower case string table of capability names @@ -44,9 +44,24 @@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\ sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\ echo "};" >> $@ +# Build a lower case string table of address family names. +# Transform lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [2] = "inet", +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >> $@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+).*/[\2] = "\L\1",/p';\ + echo "};" >> $@ + + $(obj)/capability.o : $(obj)/capability_names.h $(obj)/resource.o : $(obj)/rlim_names.h +$(obj)/net.o : $(obj)/af_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h $(call cmd,make-caps) $(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h $(call cmd,make-rlim) +$(obj)/af_names.h : $(srctree)/include/linux/socket.h + $(call cmd,make-af) \ No newline at end of file diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..3c7d599 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,40 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index aeda5cf..6776929 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *profile_mode_names[]; @@ -145,6 +146,7 @@ struct aa_namespace { * @size: the memory consumed by this profiles rules * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -181,6 +183,7 @@ struct aa_profile { struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index ae3a698..05c018b 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -620,6 +621,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -651,6 +750,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..1765901 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,170 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "af_names.h" + +static const char *sock_type_names[] = { + "unknown(0)", + "stream", + "dgram", + "raw", + "rdm", + "seqpacket", + "dccp", + "unknown(7)", + "unknown(8)", + "unknown(9)", + "packet", +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net.family]) { + audit_log_string(ab, address_family_names[sa->u.net.family]); + } else { + audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family); + } + + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad.net.type]) { + audit_log_string(ab, sock_type_names[sa->aad.net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type); + } + + audit_log_format(ab, " protocol=%d", sa->aad.net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + if (sk) { + COMMON_AUDIT_DATA_INIT(&sa, NET); + } else { + COMMON_AUDIT_DATA_INIT(&sa, NONE); + } + /* todo fill in socket addr info */ + + sa.aad.op = op, + sa.u.net.family = family; + sa.u.net.sk = sk; + sa.aad.net.type = type; + sa.aad.net.protocol = protocol; + sa.aad.error = error; + + if (likely(!sa.aad.error)) { + u16 audit_mask = profile->net.audit[sa.u.net.family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad.net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net.family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad.net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 4f0eade..4d5ce13 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index e33aaf7..fa3f1b4 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -190,6 +190,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; - int error = -EPROTO; + size_t size = 0; + int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i > AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + } + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + /* get file rules */ profile->file.dfa = unpack_dfa(e); if (IS_ERR(profile->file.dfa)) { -- 1.7.0.4 apparmor-2.8.95~2430/kernel-patches/3.10/0000755000175000017500000000000012311706711017322 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.10/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patchapparmor-2.8.95~2430/kernel-patches/3.10/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.p0000644000175000017500000006474012234002577031324 0ustar sarnoldsarnoldFrom 7a445944525ad3b2a3f292ddf0d491ae6ed947c1 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 May 2012 10:58:05 -0700 Subject: [PATCH 4/4] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount Add the ability for apparmor to do mediation of mount operations. Mount rules require an updated apparmor_parser (2.8 series) for policy compilation. The basic form of the rules are. [audit] [deny] mount [conds]* [device] [ -> [conds] path], [audit] [deny] remount [conds]* [path], [audit] [deny] umount [conds]* [path], [audit] [deny] pivotroot [oldroot=] remount is just a short cut for mount options=remount where [conds] can be fstype= options= Example mount commands mount, # allow all mounts, but not umount or pivotroot mount fstype=procfs, # allow mounting procfs anywhere mount options=(bind, ro) /foo -> /bar, # readonly bind mount mount /dev/sda -> /mnt, mount /dev/sd** -> /mnt/**, mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ umount, umount /m*, See the apparmor userspace for full documentation Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 13 + security/apparmor/audit.c | 4 + security/apparmor/domain.c | 2 +- security/apparmor/include/apparmor.h | 3 +- security/apparmor/include/audit.h | 11 + security/apparmor/include/domain.h | 2 + security/apparmor/include/mount.h | 54 +++ security/apparmor/lsm.c | 59 ++++ security/apparmor/mount.c | 620 +++++++++++++++++++++++++++++++++++ 10 files changed, 767 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/mount.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index e270692..9b44e1a 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o net.o + resource.o sid.o file.o net.o mount.o clean-files := capability_names.h rlim_names.h net_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 114fb23..ee77ec9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -426,10 +426,23 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { { } }; +static struct aa_fs_entry aa_fs_entry_mount[] = { + AA_FS_FILE_STRING("mask", "mount umount"), + { } +}; + +static struct aa_fs_entry aa_fs_entry_namespaces[] = { + AA_FS_FILE_BOOLEAN("profile", 1), + AA_FS_FILE_BOOLEAN("pivot_root", 1), + { } +}; + static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_DIR("mount", aa_fs_entry_mount), + AA_FS_DIR("namespaces", aa_fs_entry_namespaces), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 3ae28db..e267963 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -44,6 +44,10 @@ const char *const op_table[] = { "file_mmap", "file_mprotect", + "pivotroot", + "mount", + "umount", + "create", "post_create", "bind", diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 859abda..3fee1fe 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -242,7 +242,7 @@ static const char *next_name(int xtype, const char *name) * * Returns: refcounted profile, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) { struct aa_profile *new_profile = NULL; struct aa_namespace *ns = profile->ns; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 40aedd9..e243d96 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -29,8 +29,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_MOUNT 7 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_MOUNT /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 4af6523..ada004d 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -73,6 +73,10 @@ enum aa_ops { OP_FMMAP, OP_FMPROT, + OP_PIVOTROOT, + OP_MOUNT, + OP_UMOUNT, + OP_CREATE, OP_POST_CREATE, OP_BIND, @@ -122,6 +126,13 @@ struct apparmor_audit_data { unsigned long max; } rlim; struct { + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { const char *target; u32 request; u32 denied; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464..a3f70c5 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,6 +23,8 @@ struct aa_domain { char **table; }; +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm); diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 0000000..bc17a53 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 Canonical Ltd. + * + * 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 __AA_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags); + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *old_name); + +int aa_new_mount(struct aa_profile *profile, const char *dev_name, + struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path); + +#endif /* __AA_MOUNT_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 1bce440..6750673 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -36,6 +36,7 @@ #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" +#include "include/mount.h" /* Flag indicating whether initialization completed */ int apparmor_initialized __initdata; @@ -504,6 +505,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + int error = 0; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + profile = __aa_current_profile(); + if (!unconfined(profile)) { + if (flags & MS_REMOUNT) + error = aa_remount(profile, path, flags, data); + else if (flags & MS_BIND) + error = aa_bind_mount(profile, path, dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(profile, path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount(profile, path, dev_name); + else + error = aa_new_mount(profile, dev_name, path, type, + flags, data); + } + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_umount(profile, mnt, flags); + + return error; +} + +static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_pivotroot(profile, old_path, new_path); + + return error; +} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { @@ -721,6 +776,10 @@ static struct security_operations apparmor_ops = { .capget = apparmor_capget, .capable = apparmor_capable, + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_sb_umount, + .sb_pivotroot = apparmor_sb_pivotroot, + .path_link = apparmor_path_link, .path_unlink = apparmor_path_unlink, .path_symlink = apparmor_path_symlink, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 0000000..478aa4d --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,620 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (sa->aad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, sa->aad->mnt.type); + } + if (sa->aad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, sa->aad->mnt.src_name); + } + if (sa->aad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, sa->aad->mnt.trans); + } + if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, sa->aad->mnt.flags); + audit_log_format(ab, "\""); + } + if (sa->aad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, sa->aad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @profile: the profile being enforced (NOT NULL) + * @gfp: allocation flags + * @op: operation being mediated (NOT NULL) + * @name: name of object being mediated (MAYBE NULL) + * @src_name: src_name of object being mediated (MAYBE_NULL) + * @type: type of filesystem (MAYBE_NULL) + * @trans: name of trans (MAYBE NULL) + * @flags: filesystem idependent mount flags + * @data: filesystem mount flags + * @request: permissions requested + * @perms: the permissions computed for the request (NOT NULL) + * @info: extra information message (MAYBE NULL) + * @error: 0 if operation allowed else failure error code + * + * Returns: %0 or error on failure + */ +static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct file_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa = { }; + struct apparmor_audit_data aad = { }; + + if (likely(!error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + request &= mask; + + if (likely(!request)) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + request = request & ~perms->allow; + + if (request & perms->kill) + audit_type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + request &= ~perms->quiet; + + if (!request) + return COMPLAIN_MODE(profile) ? + complain_error(error) : error; + } + + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + sa.aad->op = op; + sa.aad->name = name; + sa.aad->mnt.src_name = src_name; + sa.aad->mnt.type = type; + sa.aad->mnt.trans = trans; + sa.aad->mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + sa.aad->mnt.data = data; + sa.aad->info = info; + sa.aad->error = error; + + return aa_audit(audit_type, profile, gfp, &sa, audit_cb); +} + +/** + * match_mnt_flags - Do an ordered match on mount flags + * @dfa: dfa to match against + * @state: state to start in + * @flags: mount flags to match against + * + * Mount flags are encoded as an ordered match. This is done instead of + * checking against a simple bitmask, to allow for logical operations + * on the flags. + * + * Returns: next state after flags match + */ +static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, + unsigned long flags) +{ + unsigned int i; + + for (i = 0; i <= 31 ; ++i) { + if ((1 << i) & flags) + state = aa_dfa_next(dfa, state, i + 1); + } + + return state; +} + +/** + * compute_mnt_perms - compute mount permission associated with @state + * @dfa: dfa to match against (NOT NULL) + * @state: state match finished in + * + * Returns: mount permissions + */ +static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, + unsigned int state) +{ + struct file_perms perms; + + perms.kill = 0; + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + perms.xindex = dfa_user_xindex(dfa, state); + + return perms; +} + +static const char const *mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match" +}; + +/* + * Returns 0 on success else element that match failed in, this is the + * index into the mnt_info_table above + */ +static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct file_perms *perms) +{ + unsigned int state; + + state = aa_dfa_match(dfa, start, mntpnt); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(dfa, state, devname); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(dfa, state, type); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(dfa, state, flags); + if (!state) + return 4; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + + /* only match data if not binary and the DFA flags data is expected */ + if (data && !binary && (perms->allow & AA_CONT_MATCH)) { + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(dfa, state, data); + if (!state) + return 5; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at end of flags match */ + return 4; +} + +/** + * match_mnt - handle path matching for mount + * @profile: the confining profile + * @mntpnt: string for the mntpnt (NOT NULL) + * @devname: string for the devname/src_name (MAYBE NULL) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @perms: Returns: permission found by the match + * @info: Returns: infomation string about the match for logging + * + * Returns: 0 on success else error + */ +static int match_mnt(struct aa_profile *profile, const char *mntpnt, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + struct file_perms *perms, const char **info) +{ + int pos; + + if (!profile->policy.dfa) + return -EACCES; + + pos = do_match_mnt(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + mntpnt, devname, type, flags, data, binary, perms); + if (pos) { + *info = mnt_info_table[pos]; + return -EACCES; + } + + return 0; +} + +static int path_flags(struct aa_profile *profile, struct path *path) +{ + return profile->path_flags | + S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; +} + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data) +{ + struct file_perms perms = { }; + const char *name, *info = NULL; + char *buffer = NULL; + int binary, error; + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *dev_name, unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, + &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *orig_name) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, + struct path *path, const char *type, unsigned long flags, + void *data) +{ + struct file_perms perms = { }; + char *buffer = NULL, *dev_buffer = NULL; + const char *name = NULL, *dev_name = NULL, *info = NULL; + int binary = 1; + int error; + + dev_name = orig_dev_name; + if (type) { + int requires_dev; + struct file_system_type *fstype = get_fs_type(type); + if (!fstype) + return -ENODEV; + + binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; + requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; + put_filesystem(fstype); + + if (requires_dev) { + struct path dev_path; + + if (!dev_name || !*dev_name) { + error = -ENOENT; + goto out; + } + + error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); + if (error) + goto audit; + + error = aa_path_name(&dev_path, + path_flags(profile, &dev_path), + &dev_buffer, &dev_name, &info); + path_put(&dev_path); + if (error) + goto audit; + } + } + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, dev_name, type, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, + type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + kfree(dev_buffer); + +out: + return error; + +} + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + struct path path = { mnt, mnt->mnt_root }; + error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, + &info); + if (error) + goto audit; + + if (!error && profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); + kfree(buffer); + + return error; +} + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path) +{ + struct file_perms perms = { }; + struct aa_profile *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL; + const char *old_name, *new_name = NULL, *info = NULL; + int error; + + error = aa_path_name(old_path, path_flags(profile, old_path), + &old_buffer, &old_name, &info); + if (error) + goto audit; + + error = aa_path_name(new_path, path_flags(profile, new_path), + &new_buffer, &new_name, &info); + if (error) + goto audit; + + if (profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(profile->policy.dfa, state); + state = aa_dfa_match(profile->policy.dfa, state, old_name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_PIVOTROOT & perms.allow) { + if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + target = x_table_lookup(profile, perms.xindex); + if (!target) + error = -ENOENT; + else + error = aa_replace_current_profile(target); + } + } else + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, + old_name, NULL, target ? target->base.name : NULL, + 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); + aa_put_profile(target); + kfree(old_buffer); + kfree(new_buffer); + + return error; +} -- 1.8.3.2 ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.10/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.patchapparmor-2.8.95~2430/kernel-patches/3.10/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.p0000644000175000017500000001744412234002577031434 0ustar sarnoldsarnoldFrom 1b5e29dcf1c18938e62e23f24e9a19c01b861561 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 1/4] UBUNTU: SAUCE: AppArmor: Add profile introspection file to interface Add the dynamic profiles file to the interace, to allow load policy introspection. Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Kconfig | 9 ++ security/apparmor/apparmorfs.c | 231 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16c15ec..42b7c9f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,6 +182,234 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; +#endif /* CONFIG_SECURITY_APPARMOR_COMPAT_24 */ + /** Base file system setup **/ static struct aa_fs_entry aa_fs_entry_file[] = { @@ -210,6 +438,9 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = { AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), +#endif AA_FS_DIR("features", aa_fs_entry_features), { } }; -- 1.8.3.2 apparmor-2.8.95~2430/kernel-patches/3.10/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch0000644000175000017500000004305112234002577030723 0ustar sarnoldsarnoldFrom e83b058f391e96a2a640fb2e2812d50ef67e0f43 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 2/4] UBUNTU: SAUCE: AppArmor: basic networking rules Base support for network mediation. Signed-off-by: John Johansen --- security/apparmor/.gitignore | 1 + security/apparmor/Makefile | 42 +++++++++- security/apparmor/apparmorfs.c | 1 + security/apparmor/include/audit.h | 4 + security/apparmor/include/net.h | 44 ++++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ security/apparmor/net.c | 162 +++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 46 +++++++++++ 10 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 9cdec70..d5b291e 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,5 +1,6 @@ # # Generated include files # +net_names.h capability_names.h rlim_names.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 5706b74..e270692 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h net_names.h # Build a lower case string table of capability names @@ -20,6 +20,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ +# Build a lower case string table of address family names +# Transform lines from +# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [1] = "local", +# [2] = "inet", +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# #define AA_FS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ ;\ + echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ + sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ + $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +# Build a lower case string table of sock type names +# Transform lines from +# SOCK_STREAM = 1, +# to +# [1] = "stream", +quiet_cmd_make-sock = GEN $@ +cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ + sed $^ >>$@ -r -n \ + -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ # Build a lower case string table of rlimit names. # Transforms lines from @@ -56,6 +88,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(src)/Makefile @@ -63,3 +96,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) +$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ + $(srctree)/include/linux/net.h \ + $(src)/Makefile + $(call cmd,make-af) + $(call cmd,make-sock) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 42b7c9f..114fb23 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -429,6 +429,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 69d8cae..4af6523 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -127,6 +127,10 @@ struct apparmor_audit_data { u32 denied; kuid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; }; diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..cb8a121 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,44 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +#include "apparmorfs.h" + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern struct aa_fs_entry aa_fs_entry_network[]; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index bda4569..eb13a73 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *const profile_mode_names[]; @@ -157,6 +158,7 @@ struct aa_policydb { * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -194,6 +196,7 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index b21830e..1bce440 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -614,6 +615,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -646,6 +745,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..003dd18 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,162 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "net_names.h" + +struct aa_fs_entry aa_fs_entry_network[] = { + AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), + { } +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net->family]) { + audit_log_string(ab, address_family_names[sa->u.net->family]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); + } + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad->net.type]) { + audit_log_string(ab, sock_type_names[sa->aad->net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); + } + audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + struct lsm_network_audit net = { }; + if (sk) { + sa.type = LSM_AUDIT_DATA_NET; + } else { + sa.type = LSM_AUDIT_DATA_NONE; + } + /* todo fill in socket addr info */ + sa.aad = &aad; + sa.u.net = &net; + sa.aad->op = op, + sa.u.net->family = family; + sa.u.net->sk = sk; + sa.aad->net.type = type; + sa.aad->net.protocol = protocol; + sa.aad->error = error; + + if (likely(!sa.aad->error)) { + u16 audit_mask = profile->net.audit[sa.u.net->family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad->net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 8132003..56e5304 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -747,6 +747,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 329b1fd..1b90dfa 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -193,6 +193,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -471,6 +484,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; + size_t size = 0; int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -564,6 +578,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + } + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ profile->policy.dfa = unpack_dfa(e); -- 1.8.3.2 ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.10/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.patchapparmor-2.8.95~2430/kernel-patches/3.10/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.p0000644000175000017500000000277412234002577032133 0ustar sarnoldsarnoldFrom ee0073a1e7b0ec172273a6211a3b117d024e5949 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jun 2012 17:34:00 -0700 Subject: [PATCH 3/4] apparmor: Fix quieting of audit messages for network mediation If a profile specified a quieting of network denials for a given rule by either the quiet or deny rule qualifiers, the resultant quiet mask for denied requests was applied incorrectly, resulting in two potential bugs. 1. The misapplied quiet mask would prevent denials from being correctly tested against the kill mask/mode. Thus network access requests that should have resulted in the application being killed did not. 2. The actual quieting of the denied network request was not being applied. This would result in network rejections always being logged even when they had been specifically marked as quieted. Signed-off-by: John Johansen --- security/apparmor/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 003dd18..6e6e5c9 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -88,7 +88,7 @@ static int audit_net(struct aa_profile *profile, int op, u16 family, int type, } else { u16 quiet_mask = profile->net.quiet[sa.u.net->family]; u16 kill_mask = 0; - u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + u16 denied = (1 << sa.aad->net.type); if (denied & kill_mask) audit_type = AUDIT_APPARMOR_KILL; -- 1.8.3.2 apparmor-2.8.95~2430/kernel-patches/2.6.36/0000755000175000017500000000000012311706720017475 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/2.6.36/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.patchapparmor-2.8.95~2430/kernel-patches/2.6.36/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke0000644000175000017500000000507011460077472032034 0ustar sarnoldsarnoldFrom 5d5b58edb77c2e2746395a3818239c6b7d17315d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 20 Jul 2010 06:57:08 -0700 Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken userspace The apparmor_parser when compiling policy could generate invalid dfas that did not have sufficient padding to avoid invalid references, when used by the kernel. The kernels check to verify the next/check table size was broken meaning invalid dfas were being created by userspace and not caught. To remain compatible with old tools that are not fixed, pad the loaded dfas next/check table. The dfa's themselves are valid except for the high padding for potentially invalid transitions (high bounds error), which have a maximimum is 256 entries. So just allocate an extra null filled 256 entries for the next/check tables. This will guarentee all bounds are good and invalid transitions go to the null (0) state. Signed-off-by: John Johansen --- security/apparmor/match.c | 17 +++++++++++++++++ 1 files changed, 17 insertions(+), 0 deletions(-) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 5cb4dc1..0248bb3 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize) if (bsize < tsize) goto out; + /* Pad table allocation for next/check by 256 entries to remain + * backwards compatible with old (buggy) tools and remain safe without + * run time checks + */ + if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK) + tsize += 256 * th.td_flags; + table = kvmalloc(tsize); if (table) { + /* ensure the pad is clear, else there will be errors */ + memset(table, 0, tsize); *table = th; if (th.td_flags == YYTD_DATA8) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, @@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags) goto out; if (flags & DFA_FLAG_VERIFY_STATES) { + int warning = 0; for (i = 0; i < state_count; i++) { if (DEFAULT_TABLE(dfa)[i] >= state_count) goto out; /* TODO: do check that DEF state recursion terminates */ if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { + if (warning) + continue; + printk(KERN_WARNING "AppArmor DFA next/check " + "upper bounds error fixed, upgrade " + "user space tools \n"); + warning = 1; + } else if (BASE_TABLE(dfa)[i] >= trans_count) { printk(KERN_ERR "AppArmor DFA next/check upper " "bounds error\n"); goto out; -- 1.7.1 apparmor-2.8.95~2430/kernel-patches/2.6.36/0002-AppArmor-compatibility-patch-for-v5-interface.patch0000644000175000017500000002573611460077472031662 0ustar sarnoldsarnoldFrom 5f034900aa447abea213c434d6d262d28fd168e7 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface Signed-off-by: John Johansen --- security/apparmor/Kconfig | 9 + security/apparmor/Makefile | 2 + security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++ security/apparmor/apparmorfs.c | 18 ++- security/apparmor/include/apparmorfs.h | 6 + 5 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/apparmorfs-24.c diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index a9a1db0..e5e8968 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -6,6 +6,8 @@ apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o sid.o file.o net.o +apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o + clean-files: capability_names.h af_names.h quiet_cmd_make-caps = GEN $@ diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c new file mode 100644 index 0000000..dc8c744 --- /dev/null +++ b/security/apparmor/apparmorfs-24.c @@ -0,0 +1,287 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + * + * + * This file contain functions providing an interface for <= AppArmor 2.4 + * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24 + * being set (see Makefile). + */ + +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/policy.h" + + +/* apparmor/matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char matching[] = "pattern=aadfa audit perms=crwxamlk/ " + "user::other"; + + return simple_read_from_buffer(buf, size, ppos, matching, + sizeof(matching) - 1); +} + +const struct file_operations aa_fs_matching_fops = { + .read = aa_matching_read, +}; + +/* apparmor/features */ +static ssize_t aa_features_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char features[] = "file=3.1 capability=2.0 network=1.0 " + "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1"; + + return simple_read_from_buffer(buf, size, ppos, features, + sizeof(features) - 1); +} + +const struct file_operations aa_fs_features_fops = { + .read = aa_features_read, +}; + +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 7320331..0e27449 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,7 +182,11 @@ void __init aa_destroy_aafs(void) aafs_remove(".remove"); aafs_remove(".replace"); aafs_remove(".load"); - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + aafs_remove("profiles"); + aafs_remove("matching"); + aafs_remove("features"); +#endif securityfs_remove(aa_fs_dentry); aa_fs_dentry = NULL; } @@ -213,7 +217,17 @@ int __init aa_create_aafs(void) aa_fs_dentry = NULL; goto error; } - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + error = aafs_create("matching", 0444, &aa_fs_matching_fops); + if (error) + goto error; + error = aafs_create("features", 0444, &aa_fs_features_fops); + if (error) + goto error; +#endif + error = aafs_create("profiles", 0440, &aa_fs_profiles_fops); + if (error) + goto error; error = aafs_create(".load", 0640, &aa_fs_profile_load); if (error) goto error; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index cb1e93a..14f955c 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -17,4 +17,10 @@ extern void __init aa_destroy_aafs(void); +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +extern const struct file_operations aa_fs_matching_fops; +extern const struct file_operations aa_fs_features_fops; +extern const struct file_operations aa_fs_profiles_fops; +#endif + #endif /* __AA_APPARMORFS_H */ -- 1.7.1 ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/2.6.36/0001-AppArmor-compatibility-patch-for-v5-network-controll.patchapparmor-2.8.95~2430/kernel-patches/2.6.36/0001-AppArmor-compatibility-patch-for-v5-network-controll0000644000175000017500000003667211460077472032147 0ustar sarnoldsarnoldFrom 6ab924a333c81d552eb92900509113bdf2fccb2e Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll Add compatibility for v5 network rules. Signed-off-by: John Johansen --- include/linux/lsm_audit.h | 4 + security/apparmor/Makefile | 6 +- security/apparmor/include/net.h | 40 +++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++ security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 48 ++++++++++- 8 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 112a550..d5f3dd7 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -123,6 +123,10 @@ struct common_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; } apparmor_audit_data; #endif diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index f204869..a9a1db0 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,17 +4,21 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o clean-files: capability_names.h af_names.h quiet_cmd_make-caps = GEN $@ cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ; sed -n -e "/CAP_FS_MASK/d" -e "s/^\#define[ \\t]\\+CAP_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ; sed -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "s/^\#define[ \\t]\\+AF_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ + quiet_cmd_make-rlim = GEN $@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ; sed -n --e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+RLIMIT_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ ; echo "static const int rlim_map[] = {" >> $@ ; sed -n -e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+\\(RLIMIT_[A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/\\1,/p" $< >> $@ ; echo "};" >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/af_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h $(call cmd,make-caps) diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..3c7d599 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,40 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index aeda5cf..6776929 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *profile_mode_names[]; @@ -145,6 +146,7 @@ struct aa_namespace { * @size: the memory consumed by this profiles rules * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -181,6 +183,7 @@ struct aa_profile { struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index cf1de44..324ab91 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -31,6 +31,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -619,6 +620,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -650,6 +749,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..1765901 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,170 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "af_names.h" + +static const char *sock_type_names[] = { + "unknown(0)", + "stream", + "dgram", + "raw", + "rdm", + "seqpacket", + "dccp", + "unknown(7)", + "unknown(8)", + "unknown(9)", + "packet", +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net.family]) { + audit_log_string(ab, address_family_names[sa->u.net.family]); + } else { + audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family); + } + + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad.net.type]) { + audit_log_string(ab, sock_type_names[sa->aad.net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type); + } + + audit_log_format(ab, " protocol=%d", sa->aad.net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + if (sk) { + COMMON_AUDIT_DATA_INIT(&sa, NET); + } else { + COMMON_AUDIT_DATA_INIT(&sa, NONE); + } + /* todo fill in socket addr info */ + + sa.aad.op = op, + sa.u.net.family = family; + sa.u.net.sk = sk; + sa.aad.net.type = type; + sa.aad.net.protocol = protocol; + sa.aad.error = error; + + if (likely(!sa.aad.error)) { + u16 audit_mask = profile->net.audit[sa.u.net.family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad.net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net.family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad.net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 52cc865..3b5da44 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index eb3700e..c2b6225 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -190,6 +190,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; - int error = -EPROTO; + size_t size = 0; + int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i > AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + } + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + /* get file rules */ profile->file.dfa = unpack_dfa(e); if (IS_ERR(profile->file.dfa)) { -- 1.7.1 apparmor-2.8.95~2430/kernel-patches/3.3/0000755000175000017500000000000012311706704017246 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.3/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.patchapparmor-2.8.95~2430/kernel-patches/3.3/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.pa0000644000175000017500000000505211756603243032201 0ustar sarnoldsarnoldFrom 5d05f2909c12f6f03581bca9c1fa52dafa10fb0f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:41 -0700 Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken userspace The apparmor_parser when compiling policy could generate invalid dfas that did not have sufficient padding to avoid invalid references, when used by the kernel. The kernels check to verify the next/check table size was broken meaning invalid dfas were being created by userspace and not caught. To remain compatible with old tools that are not fixed, pad the loaded dfas next/check table. The dfa's themselves are valid except for the high padding for potentially invalid transitions (high bounds error), which have a maximimum is 256 entries. So just allocate an extra null filled 256 entries for the next/check tables. This will guarentee all bounds are good and invalid transitions go to the null (0) state. Signed-off-by: John Johansen --- security/apparmor/match.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 94de6b4..081491e 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize) if (bsize < tsize) goto out; + /* Pad table allocation for next/check by 256 entries to remain + * backwards compatible with old (buggy) tools and remain safe without + * run time checks + */ + if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK) + tsize += 256 * th.td_flags; + table = kvmalloc(tsize); if (table) { + /* ensure the pad is clear, else there will be errors */ + memset(table, 0, tsize); *table = th; if (th.td_flags == YYTD_DATA8) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, @@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags) goto out; if (flags & DFA_FLAG_VERIFY_STATES) { + int warning = 0; for (i = 0; i < state_count; i++) { if (DEFAULT_TABLE(dfa)[i] >= state_count) goto out; /* TODO: do check that DEF state recursion terminates */ if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { + if (warning) + continue; + printk(KERN_WARNING "AppArmor DFA next/check " + "upper bounds error fixed, upgrade " + "user space tools \n"); + warning = 1; + } else if (BASE_TABLE(dfa)[i] >= trans_count) { printk(KERN_ERR "AppArmor DFA next/check upper " "bounds error\n"); goto out; -- 1.7.9.5 apparmor-2.8.95~2430/kernel-patches/3.3/0002-AppArmor-compatibility-patch-for-v5-interface.patch0000644000175000017500000002600011756603243031411 0ustar sarnoldsarnoldFrom da1ce2265ebb70860b9c137a542e48b170e4606b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:40 -0700 Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface Signed-off-by: John Johansen --- security/apparmor/Kconfig | 9 + security/apparmor/Makefile | 1 + security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++ security/apparmor/apparmorfs.c | 18 +- security/apparmor/include/apparmorfs.h | 6 + 5 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/apparmorfs-24.c diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 7cefef9..0bb604b 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o sid.o file.o net.o +apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o clean-files := capability_names.h rlim_names.h af_names.h diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c new file mode 100644 index 0000000..dc8c744 --- /dev/null +++ b/security/apparmor/apparmorfs-24.c @@ -0,0 +1,287 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + * + * + * This file contain functions providing an interface for <= AppArmor 2.4 + * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24 + * being set (see Makefile). + */ + +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/policy.h" + + +/* apparmor/matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char matching[] = "pattern=aadfa audit perms=crwxamlk/ " + "user::other"; + + return simple_read_from_buffer(buf, size, ppos, matching, + sizeof(matching) - 1); +} + +const struct file_operations aa_fs_matching_fops = { + .read = aa_matching_read, +}; + +/* apparmor/features */ +static ssize_t aa_features_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char features[] = "file=3.1 capability=2.0 network=1.0 " + "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1"; + + return simple_read_from_buffer(buf, size, ppos, features, + sizeof(features) - 1); +} + +const struct file_operations aa_fs_features_fops = { + .read = aa_features_read, +}; + +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index e39df6d..235e9fa 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -187,7 +187,11 @@ void __init aa_destroy_aafs(void) aafs_remove(".remove"); aafs_remove(".replace"); aafs_remove(".load"); - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + aafs_remove("profiles"); + aafs_remove("matching"); + aafs_remove("features"); +#endif securityfs_remove(aa_fs_dentry); aa_fs_dentry = NULL; } @@ -218,7 +222,17 @@ static int __init aa_create_aafs(void) aa_fs_dentry = NULL; goto error; } - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + error = aafs_create("matching", 0444, &aa_fs_matching_fops); + if (error) + goto error; + error = aafs_create("features", 0444, &aa_fs_features_fops); + if (error) + goto error; +#endif + error = aafs_create("profiles", 0440, &aa_fs_profiles_fops); + if (error) + goto error; error = aafs_create(".load", 0640, &aa_fs_profile_load); if (error) goto error; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index cb1e93a..14f955c 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -17,4 +17,10 @@ extern void __init aa_destroy_aafs(void); +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +extern const struct file_operations aa_fs_matching_fops; +extern const struct file_operations aa_fs_features_fops; +extern const struct file_operations aa_fs_profiles_fops; +#endif + #endif /* __AA_APPARMORFS_H */ -- 1.7.9.5 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.3/0001-AppArmor-compatibility-patch-for-v5-network-controll.patchapparmor-2.8.95~2430/kernel-patches/3.3/0001-AppArmor-compatibility-patch-for-v5-network-controll.pa0000644000175000017500000003651211756603243032305 0ustar sarnoldsarnoldFrom 1023c7c2f9d9c5707147479104312c4c3d1a2c2b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Aug 2011 22:02:39 -0700 Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll Add compatibility for v5 network rules. Signed-off-by: John Johansen --- include/linux/lsm_audit.h | 4 + security/apparmor/Makefile | 19 +++- security/apparmor/include/net.h | 40 +++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 ++++++++++++++++++++++++ security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 48 +++++++++- 8 files changed, 394 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 88e78de..c63979a 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -124,6 +124,10 @@ struct common_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; } apparmor_audit_data; #endif diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 2dafe50..7cefef9 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h af_names.h # Build a lower case string table of capability names @@ -44,9 +44,24 @@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\ sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\ echo "};" >> $@ +# Build a lower case string table of address family names. +# Transform lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [2] = "inet", +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >> $@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+).*/[\2] = "\L\1",/p';\ + echo "};" >> $@ + + $(obj)/capability.o : $(obj)/capability_names.h $(obj)/resource.o : $(obj)/rlim_names.h +$(obj)/net.o : $(obj)/af_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h $(call cmd,make-caps) $(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h $(call cmd,make-rlim) +$(obj)/af_names.h : $(srctree)/include/linux/socket.h + $(call cmd,make-af) \ No newline at end of file diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..3c7d599 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,40 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index aeda5cf..6776929 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *profile_mode_names[]; @@ -145,6 +146,7 @@ struct aa_namespace { * @size: the memory consumed by this profiles rules * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -181,6 +183,7 @@ struct aa_profile { struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 97ce8fa..a54adbc 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -620,6 +621,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -651,6 +750,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..1765901 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,170 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "af_names.h" + +static const char *sock_type_names[] = { + "unknown(0)", + "stream", + "dgram", + "raw", + "rdm", + "seqpacket", + "dccp", + "unknown(7)", + "unknown(8)", + "unknown(9)", + "packet", +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net.family]) { + audit_log_string(ab, address_family_names[sa->u.net.family]); + } else { + audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family); + } + + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad.net.type]) { + audit_log_string(ab, sock_type_names[sa->aad.net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type); + } + + audit_log_format(ab, " protocol=%d", sa->aad.net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + if (sk) { + COMMON_AUDIT_DATA_INIT(&sa, NET); + } else { + COMMON_AUDIT_DATA_INIT(&sa, NONE); + } + /* todo fill in socket addr info */ + + sa.aad.op = op, + sa.u.net.family = family; + sa.u.net.sk = sk; + sa.aad.net.type = type; + sa.aad.net.protocol = protocol; + sa.aad.error = error; + + if (likely(!sa.aad.error)) { + u16 audit_mask = profile->net.audit[sa.u.net.family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad.net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net.family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad.net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 4f0eade..4d5ce13 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 741dd13..ee8043e 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -190,6 +190,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; - int error = -EPROTO; + size_t size = 0; + int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + } + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + /* get file rules */ profile->file.dfa = unpack_dfa(e); if (IS_ERR(profile->file.dfa)) { -- 1.7.9.5 apparmor-2.8.95~2430/kernel-patches/3.11/0000755000175000017500000000000012311706711017323 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.11/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patchapparmor-2.8.95~2430/kernel-patches/3.11/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.p0000644000175000017500000006474012234002577031325 0ustar sarnoldsarnoldFrom 3dce616a157eafb2963bbd684dd556deb5cea6c6 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 May 2012 10:58:05 -0700 Subject: [PATCH 4/4] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount Add the ability for apparmor to do mediation of mount operations. Mount rules require an updated apparmor_parser (2.8 series) for policy compilation. The basic form of the rules are. [audit] [deny] mount [conds]* [device] [ -> [conds] path], [audit] [deny] remount [conds]* [path], [audit] [deny] umount [conds]* [path], [audit] [deny] pivotroot [oldroot=] remount is just a short cut for mount options=remount where [conds] can be fstype= options= Example mount commands mount, # allow all mounts, but not umount or pivotroot mount fstype=procfs, # allow mounting procfs anywhere mount options=(bind, ro) /foo -> /bar, # readonly bind mount mount /dev/sda -> /mnt, mount /dev/sd** -> /mnt/**, mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ umount, umount /m*, See the apparmor userspace for full documentation Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 13 + security/apparmor/audit.c | 4 + security/apparmor/domain.c | 2 +- security/apparmor/include/apparmor.h | 3 +- security/apparmor/include/audit.h | 11 + security/apparmor/include/domain.h | 2 + security/apparmor/include/mount.h | 54 +++ security/apparmor/lsm.c | 59 ++++ security/apparmor/mount.c | 620 +++++++++++++++++++++++++++++++++++ 10 files changed, 767 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/mount.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index e270692..9b44e1a 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o net.o + resource.o sid.o file.o net.o mount.o clean-files := capability_names.h rlim_names.h net_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 114fb23..ee77ec9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -426,10 +426,23 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { { } }; +static struct aa_fs_entry aa_fs_entry_mount[] = { + AA_FS_FILE_STRING("mask", "mount umount"), + { } +}; + +static struct aa_fs_entry aa_fs_entry_namespaces[] = { + AA_FS_FILE_BOOLEAN("profile", 1), + AA_FS_FILE_BOOLEAN("pivot_root", 1), + { } +}; + static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_DIR("mount", aa_fs_entry_mount), + AA_FS_DIR("namespaces", aa_fs_entry_namespaces), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 031d2d9..02d804c 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -44,6 +44,10 @@ const char *const op_table[] = { "file_mmap", "file_mprotect", + "pivotroot", + "mount", + "umount", + "create", "post_create", "bind", diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 01b7bd6..abd613c 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -238,7 +238,7 @@ static const char *next_name(int xtype, const char *name) * * Returns: refcounted profile, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) { struct aa_profile *new_profile = NULL; struct aa_namespace *ns = profile->ns; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 1ba2ca5..d1b145b 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -30,8 +30,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_MOUNT 7 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_MOUNT /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 4af6523..ada004d 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -73,6 +73,10 @@ enum aa_ops { OP_FMMAP, OP_FMPROT, + OP_PIVOTROOT, + OP_MOUNT, + OP_UMOUNT, + OP_CREATE, OP_POST_CREATE, OP_BIND, @@ -122,6 +126,13 @@ struct apparmor_audit_data { unsigned long max; } rlim; struct { + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { const char *target; u32 request; u32 denied; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464..a3f70c5 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,6 +23,8 @@ struct aa_domain { char **table; }; +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm); diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 0000000..bc17a53 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 Canonical Ltd. + * + * 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 __AA_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags); + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *old_name); + +int aa_new_mount(struct aa_profile *profile, const char *dev_name, + struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path); + +#endif /* __AA_MOUNT_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8610d6f..daab74e 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -36,6 +36,7 @@ #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" +#include "include/mount.h" /* Flag indicating whether initialization completed */ int apparmor_initialized __initdata; @@ -502,6 +503,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + int error = 0; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + profile = __aa_current_profile(); + if (!unconfined(profile)) { + if (flags & MS_REMOUNT) + error = aa_remount(profile, path, flags, data); + else if (flags & MS_BIND) + error = aa_bind_mount(profile, path, dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(profile, path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount(profile, path, dev_name); + else + error = aa_new_mount(profile, dev_name, path, type, + flags, data); + } + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_umount(profile, mnt, flags); + + return error; +} + +static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_pivotroot(profile, old_path, new_path); + + return error; +} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { @@ -720,6 +775,10 @@ static struct security_operations apparmor_ops = { .capget = apparmor_capget, .capable = apparmor_capable, + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_sb_umount, + .sb_pivotroot = apparmor_sb_pivotroot, + .path_link = apparmor_path_link, .path_unlink = apparmor_path_unlink, .path_symlink = apparmor_path_symlink, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 0000000..478aa4d --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,620 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (sa->aad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, sa->aad->mnt.type); + } + if (sa->aad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, sa->aad->mnt.src_name); + } + if (sa->aad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, sa->aad->mnt.trans); + } + if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, sa->aad->mnt.flags); + audit_log_format(ab, "\""); + } + if (sa->aad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, sa->aad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @profile: the profile being enforced (NOT NULL) + * @gfp: allocation flags + * @op: operation being mediated (NOT NULL) + * @name: name of object being mediated (MAYBE NULL) + * @src_name: src_name of object being mediated (MAYBE_NULL) + * @type: type of filesystem (MAYBE_NULL) + * @trans: name of trans (MAYBE NULL) + * @flags: filesystem idependent mount flags + * @data: filesystem mount flags + * @request: permissions requested + * @perms: the permissions computed for the request (NOT NULL) + * @info: extra information message (MAYBE NULL) + * @error: 0 if operation allowed else failure error code + * + * Returns: %0 or error on failure + */ +static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct file_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa = { }; + struct apparmor_audit_data aad = { }; + + if (likely(!error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + request &= mask; + + if (likely(!request)) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + request = request & ~perms->allow; + + if (request & perms->kill) + audit_type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + request &= ~perms->quiet; + + if (!request) + return COMPLAIN_MODE(profile) ? + complain_error(error) : error; + } + + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + sa.aad->op = op; + sa.aad->name = name; + sa.aad->mnt.src_name = src_name; + sa.aad->mnt.type = type; + sa.aad->mnt.trans = trans; + sa.aad->mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + sa.aad->mnt.data = data; + sa.aad->info = info; + sa.aad->error = error; + + return aa_audit(audit_type, profile, gfp, &sa, audit_cb); +} + +/** + * match_mnt_flags - Do an ordered match on mount flags + * @dfa: dfa to match against + * @state: state to start in + * @flags: mount flags to match against + * + * Mount flags are encoded as an ordered match. This is done instead of + * checking against a simple bitmask, to allow for logical operations + * on the flags. + * + * Returns: next state after flags match + */ +static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, + unsigned long flags) +{ + unsigned int i; + + for (i = 0; i <= 31 ; ++i) { + if ((1 << i) & flags) + state = aa_dfa_next(dfa, state, i + 1); + } + + return state; +} + +/** + * compute_mnt_perms - compute mount permission associated with @state + * @dfa: dfa to match against (NOT NULL) + * @state: state match finished in + * + * Returns: mount permissions + */ +static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, + unsigned int state) +{ + struct file_perms perms; + + perms.kill = 0; + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + perms.xindex = dfa_user_xindex(dfa, state); + + return perms; +} + +static const char const *mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match" +}; + +/* + * Returns 0 on success else element that match failed in, this is the + * index into the mnt_info_table above + */ +static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct file_perms *perms) +{ + unsigned int state; + + state = aa_dfa_match(dfa, start, mntpnt); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(dfa, state, devname); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(dfa, state, type); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(dfa, state, flags); + if (!state) + return 4; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + + /* only match data if not binary and the DFA flags data is expected */ + if (data && !binary && (perms->allow & AA_CONT_MATCH)) { + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(dfa, state, data); + if (!state) + return 5; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at end of flags match */ + return 4; +} + +/** + * match_mnt - handle path matching for mount + * @profile: the confining profile + * @mntpnt: string for the mntpnt (NOT NULL) + * @devname: string for the devname/src_name (MAYBE NULL) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @perms: Returns: permission found by the match + * @info: Returns: infomation string about the match for logging + * + * Returns: 0 on success else error + */ +static int match_mnt(struct aa_profile *profile, const char *mntpnt, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + struct file_perms *perms, const char **info) +{ + int pos; + + if (!profile->policy.dfa) + return -EACCES; + + pos = do_match_mnt(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + mntpnt, devname, type, flags, data, binary, perms); + if (pos) { + *info = mnt_info_table[pos]; + return -EACCES; + } + + return 0; +} + +static int path_flags(struct aa_profile *profile, struct path *path) +{ + return profile->path_flags | + S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; +} + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data) +{ + struct file_perms perms = { }; + const char *name, *info = NULL; + char *buffer = NULL; + int binary, error; + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *dev_name, unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, + &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *orig_name) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, + struct path *path, const char *type, unsigned long flags, + void *data) +{ + struct file_perms perms = { }; + char *buffer = NULL, *dev_buffer = NULL; + const char *name = NULL, *dev_name = NULL, *info = NULL; + int binary = 1; + int error; + + dev_name = orig_dev_name; + if (type) { + int requires_dev; + struct file_system_type *fstype = get_fs_type(type); + if (!fstype) + return -ENODEV; + + binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; + requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; + put_filesystem(fstype); + + if (requires_dev) { + struct path dev_path; + + if (!dev_name || !*dev_name) { + error = -ENOENT; + goto out; + } + + error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); + if (error) + goto audit; + + error = aa_path_name(&dev_path, + path_flags(profile, &dev_path), + &dev_buffer, &dev_name, &info); + path_put(&dev_path); + if (error) + goto audit; + } + } + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, dev_name, type, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, + type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + kfree(dev_buffer); + +out: + return error; + +} + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + struct path path = { mnt, mnt->mnt_root }; + error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, + &info); + if (error) + goto audit; + + if (!error && profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); + kfree(buffer); + + return error; +} + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path) +{ + struct file_perms perms = { }; + struct aa_profile *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL; + const char *old_name, *new_name = NULL, *info = NULL; + int error; + + error = aa_path_name(old_path, path_flags(profile, old_path), + &old_buffer, &old_name, &info); + if (error) + goto audit; + + error = aa_path_name(new_path, path_flags(profile, new_path), + &new_buffer, &new_name, &info); + if (error) + goto audit; + + if (profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(profile->policy.dfa, state); + state = aa_dfa_match(profile->policy.dfa, state, old_name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_PIVOTROOT & perms.allow) { + if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + target = x_table_lookup(profile, perms.xindex); + if (!target) + error = -ENOENT; + else + error = aa_replace_current_profile(target); + } + } else + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, + old_name, NULL, target ? target->base.name : NULL, + 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); + aa_put_profile(target); + kfree(old_buffer); + kfree(new_buffer); + + return error; +} -- 1.8.3.2 ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.11/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.patchapparmor-2.8.95~2430/kernel-patches/3.11/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.p0000644000175000017500000001744412234002577031435 0ustar sarnoldsarnoldFrom b69a30ebeca7c4a021a2465a2c66eb422609c65d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 1/4] UBUNTU: SAUCE: AppArmor: Add profile introspection file to interface Add the dynamic profiles file to the interace, to allow load policy introspection. Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Kconfig | 9 ++ security/apparmor/apparmorfs.c | 231 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16c15ec..42b7c9f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,6 +182,234 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; +#endif /* CONFIG_SECURITY_APPARMOR_COMPAT_24 */ + /** Base file system setup **/ static struct aa_fs_entry aa_fs_entry_file[] = { @@ -210,6 +438,9 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = { AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), +#endif AA_FS_DIR("features", aa_fs_entry_features), { } }; -- 1.8.3.2 apparmor-2.8.95~2430/kernel-patches/3.11/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch0000644000175000017500000004305312234002577030726 0ustar sarnoldsarnoldFrom 187d55e41a0c81061500334c0493b7a7bf560eb1 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 2/4] UBUNTU: SAUCE: AppArmor: basic networking rules Base support for network mediation. Signed-off-by: John Johansen --- security/apparmor/.gitignore | 1 + security/apparmor/Makefile | 42 +++++++++- security/apparmor/apparmorfs.c | 1 + security/apparmor/include/audit.h | 4 + security/apparmor/include/net.h | 44 ++++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ security/apparmor/net.c | 162 +++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 46 +++++++++++ 10 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 9cdec70..d5b291e 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,5 +1,6 @@ # # Generated include files # +net_names.h capability_names.h rlim_names.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 5706b74..e270692 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h net_names.h # Build a lower case string table of capability names @@ -20,6 +20,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ +# Build a lower case string table of address family names +# Transform lines from +# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [1] = "local", +# [2] = "inet", +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# #define AA_FS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ ;\ + echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ + sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ + $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +# Build a lower case string table of sock type names +# Transform lines from +# SOCK_STREAM = 1, +# to +# [1] = "stream", +quiet_cmd_make-sock = GEN $@ +cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ + sed $^ >>$@ -r -n \ + -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ # Build a lower case string table of rlimit names. # Transforms lines from @@ -56,6 +88,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(src)/Makefile @@ -63,3 +96,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) +$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ + $(srctree)/include/linux/net.h \ + $(src)/Makefile + $(call cmd,make-af) + $(call cmd,make-sock) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 42b7c9f..114fb23 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -429,6 +429,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 69d8cae..4af6523 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -127,6 +127,10 @@ struct apparmor_audit_data { u32 denied; kuid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; }; diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..cb8a121 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,44 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +#include "apparmorfs.h" + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern struct aa_fs_entry aa_fs_entry_network[]; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index b25491a..2aed6de 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *const profile_mode_names[]; @@ -158,6 +159,7 @@ struct aa_policydb { * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -194,6 +196,7 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 2e2a0dd..8610d6f 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -613,6 +614,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -645,6 +744,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..003dd18 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,162 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "net_names.h" + +struct aa_fs_entry aa_fs_entry_network[] = { + AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), + { } +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net->family]) { + audit_log_string(ab, address_family_names[sa->u.net->family]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); + } + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad->net.type]) { + audit_log_string(ab, sock_type_names[sa->aad->net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); + } + audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + struct lsm_network_audit net = { }; + if (sk) { + sa.type = LSM_AUDIT_DATA_NET; + } else { + sa.type = LSM_AUDIT_DATA_NONE; + } + /* todo fill in socket addr info */ + sa.aad = &aad; + sa.u.net = &net; + sa.aad->op = op, + sa.u.net->family = family; + sa.u.net->sk = sk; + sa.aad->net.type = type; + sa.aad->net.protocol = protocol; + sa.aad->error = error; + + if (likely(!sa.aad->error)) { + u16 audit_mask = profile->net.audit[sa.u.net->family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad->net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 0f345c4..6e5853a 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -669,6 +669,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_put_dfa(profile->xmatch); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 6dac7d7..c6380c0 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -192,6 +192,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -473,6 +486,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; + size_t size = 0; int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -566,6 +580,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + } + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ profile->policy.dfa = unpack_dfa(e); -- 1.8.3.2 ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.11/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.patchapparmor-2.8.95~2430/kernel-patches/3.11/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.p0000644000175000017500000000277412234002577032134 0ustar sarnoldsarnoldFrom 920dd3917d665e57fb1c317bcf0b07b5cb8b7640 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jun 2012 17:34:00 -0700 Subject: [PATCH 3/4] apparmor: Fix quieting of audit messages for network mediation If a profile specified a quieting of network denials for a given rule by either the quiet or deny rule qualifiers, the resultant quiet mask for denied requests was applied incorrectly, resulting in two potential bugs. 1. The misapplied quiet mask would prevent denials from being correctly tested against the kill mask/mode. Thus network access requests that should have resulted in the application being killed did not. 2. The actual quieting of the denied network request was not being applied. This would result in network rejections always being logged even when they had been specifically marked as quieted. Signed-off-by: John Johansen --- security/apparmor/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 003dd18..6e6e5c9 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -88,7 +88,7 @@ static int audit_net(struct aa_profile *profile, int op, u16 family, int type, } else { u16 quiet_mask = profile->net.quiet[sa.u.net->family]; u16 kill_mask = 0; - u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + u16 denied = (1 << sa.aad->net.type); if (denied & kill_mask) audit_type = AUDIT_APPARMOR_KILL; -- 1.8.3.2 apparmor-2.8.95~2430/kernel-patches/2.6.36.2/0000755000175000017500000000000012311706720017635 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/2.6.36.2/0003-AppArmor-Allow-dfa-backward-compatibility-with-broke.patchapparmor-2.8.95~2430/kernel-patches/2.6.36.2/0003-AppArmor-Allow-dfa-backward-compatibility-with-bro0000644000175000017500000000507011512511427031643 0ustar sarnoldsarnoldFrom 484d99eaaa89c3bfd707128b4c508d4a70a18eea Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 20 Jul 2010 06:57:08 -0700 Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken userspace The apparmor_parser when compiling policy could generate invalid dfas that did not have sufficient padding to avoid invalid references, when used by the kernel. The kernels check to verify the next/check table size was broken meaning invalid dfas were being created by userspace and not caught. To remain compatible with old tools that are not fixed, pad the loaded dfas next/check table. The dfa's themselves are valid except for the high padding for potentially invalid transitions (high bounds error), which have a maximimum is 256 entries. So just allocate an extra null filled 256 entries for the next/check tables. This will guarentee all bounds are good and invalid transitions go to the null (0) state. Signed-off-by: John Johansen --- security/apparmor/match.c | 17 +++++++++++++++++ 1 files changed, 17 insertions(+), 0 deletions(-) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 5cb4dc1..0248bb3 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize) if (bsize < tsize) goto out; + /* Pad table allocation for next/check by 256 entries to remain + * backwards compatible with old (buggy) tools and remain safe without + * run time checks + */ + if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK) + tsize += 256 * th.td_flags; + table = kvmalloc(tsize); if (table) { + /* ensure the pad is clear, else there will be errors */ + memset(table, 0, tsize); *table = th; if (th.td_flags == YYTD_DATA8) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, @@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags) goto out; if (flags & DFA_FLAG_VERIFY_STATES) { + int warning = 0; for (i = 0; i < state_count; i++) { if (DEFAULT_TABLE(dfa)[i] >= state_count) goto out; /* TODO: do check that DEF state recursion terminates */ if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { + if (warning) + continue; + printk(KERN_WARNING "AppArmor DFA next/check " + "upper bounds error fixed, upgrade " + "user space tools \n"); + warning = 1; + } else if (BASE_TABLE(dfa)[i] >= trans_count) { printk(KERN_ERR "AppArmor DFA next/check upper " "bounds error\n"); goto out; -- 1.7.1 ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/2.6.36.2/0002-AppArmor-compatibility-patch-for-v5-interface.patchapparmor-2.8.95~2430/kernel-patches/2.6.36.2/0002-AppArmor-compatibility-patch-for-v5-interface.patc0000644000175000017500000002573611512511427031641 0ustar sarnoldsarnoldFrom 287eaf29269e7692c0fe510fe3838f286a2984e1 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface Signed-off-by: John Johansen --- security/apparmor/Kconfig | 9 + security/apparmor/Makefile | 2 + security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++ security/apparmor/apparmorfs.c | 18 ++- security/apparmor/include/apparmorfs.h | 6 + 5 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/apparmorfs-24.c diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index a9a1db0..e5e8968 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -6,6 +6,8 @@ apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o sid.o file.o net.o +apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o + clean-files: capability_names.h af_names.h quiet_cmd_make-caps = GEN $@ diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c new file mode 100644 index 0000000..dc8c744 --- /dev/null +++ b/security/apparmor/apparmorfs-24.c @@ -0,0 +1,287 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + * + * + * This file contain functions providing an interface for <= AppArmor 2.4 + * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24 + * being set (see Makefile). + */ + +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/policy.h" + + +/* apparmor/matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char matching[] = "pattern=aadfa audit perms=crwxamlk/ " + "user::other"; + + return simple_read_from_buffer(buf, size, ppos, matching, + sizeof(matching) - 1); +} + +const struct file_operations aa_fs_matching_fops = { + .read = aa_matching_read, +}; + +/* apparmor/features */ +static ssize_t aa_features_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char features[] = "file=3.1 capability=2.0 network=1.0 " + "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1"; + + return simple_read_from_buffer(buf, size, ppos, features, + sizeof(features) - 1); +} + +const struct file_operations aa_fs_features_fops = { + .read = aa_features_read, +}; + +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 7320331..0e27449 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,7 +182,11 @@ void __init aa_destroy_aafs(void) aafs_remove(".remove"); aafs_remove(".replace"); aafs_remove(".load"); - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + aafs_remove("profiles"); + aafs_remove("matching"); + aafs_remove("features"); +#endif securityfs_remove(aa_fs_dentry); aa_fs_dentry = NULL; } @@ -213,7 +217,17 @@ int __init aa_create_aafs(void) aa_fs_dentry = NULL; goto error; } - +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + error = aafs_create("matching", 0444, &aa_fs_matching_fops); + if (error) + goto error; + error = aafs_create("features", 0444, &aa_fs_features_fops); + if (error) + goto error; +#endif + error = aafs_create("profiles", 0440, &aa_fs_profiles_fops); + if (error) + goto error; error = aafs_create(".load", 0640, &aa_fs_profile_load); if (error) goto error; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index cb1e93a..14f955c 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -17,4 +17,10 @@ extern void __init aa_destroy_aafs(void); +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +extern const struct file_operations aa_fs_matching_fops; +extern const struct file_operations aa_fs_features_fops; +extern const struct file_operations aa_fs_profiles_fops; +#endif + #endif /* __AA_APPARMORFS_H */ -- 1.7.1 ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/2.6.36.2/0001-AppArmor-compatibility-patch-for-v5-network-controll.patchapparmor-2.8.95~2430/kernel-patches/2.6.36.2/0001-AppArmor-compatibility-patch-for-v5-network-contro0000644000175000017500000003667211512511427031746 0ustar sarnoldsarnoldFrom 729ccc6e522199ace96d9344b941e4530b7a0e64 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll Add compatibility for v5 network rules. Signed-off-by: John Johansen --- include/linux/lsm_audit.h | 4 + security/apparmor/Makefile | 6 +- security/apparmor/include/net.h | 40 +++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++ security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 48 ++++++++++- 8 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 112a550..d5f3dd7 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -123,6 +123,10 @@ struct common_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; } apparmor_audit_data; #endif diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index f204869..a9a1db0 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,17 +4,21 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o clean-files: capability_names.h af_names.h quiet_cmd_make-caps = GEN $@ cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ; sed -n -e "/CAP_FS_MASK/d" -e "s/^\#define[ \\t]\\+CAP_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ; sed -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "s/^\#define[ \\t]\\+AF_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ + quiet_cmd_make-rlim = GEN $@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ; sed -n --e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+RLIMIT_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ ; echo "static const int rlim_map[] = {" >> $@ ; sed -n -e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+\\(RLIMIT_[A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/\\1,/p" $< >> $@ ; echo "};" >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/af_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h $(call cmd,make-caps) diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..3c7d599 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,40 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index aeda5cf..6776929 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *profile_mode_names[]; @@ -145,6 +146,7 @@ struct aa_namespace { * @size: the memory consumed by this profiles rules * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -181,6 +183,7 @@ struct aa_profile { struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index cf1de44..324ab91 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -31,6 +31,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -619,6 +620,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -650,6 +749,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..1765901 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,170 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "af_names.h" + +static const char *sock_type_names[] = { + "unknown(0)", + "stream", + "dgram", + "raw", + "rdm", + "seqpacket", + "dccp", + "unknown(7)", + "unknown(8)", + "unknown(9)", + "packet", +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net.family]) { + audit_log_string(ab, address_family_names[sa->u.net.family]); + } else { + audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family); + } + + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad.net.type]) { + audit_log_string(ab, sock_type_names[sa->aad.net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type); + } + + audit_log_format(ab, " protocol=%d", sa->aad.net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + if (sk) { + COMMON_AUDIT_DATA_INIT(&sa, NET); + } else { + COMMON_AUDIT_DATA_INIT(&sa, NONE); + } + /* todo fill in socket addr info */ + + sa.aad.op = op, + sa.u.net.family = family; + sa.u.net.sk = sk; + sa.aad.net.type = type; + sa.aad.net.protocol = protocol; + sa.aad.error = error; + + if (likely(!sa.aad.error)) { + u16 audit_mask = profile->net.audit[sa.u.net.family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad.net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net.family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad.net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 52cc865..3b5da44 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index eb3700e..c2b6225 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -190,6 +190,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; - int error = -EPROTO; + size_t size = 0; + int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i > AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + } + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + /* get file rules */ profile->file.dfa = unpack_dfa(e); if (IS_ERR(profile->file.dfa)) { -- 1.7.1 apparmor-2.8.95~2430/kernel-patches/3.7/0000755000175000017500000000000012311706711017250 5ustar sarnoldsarnold././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.7/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patchapparmor-2.8.95~2430/kernel-patches/3.7/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.pa0000644000175000017500000006474012234002577031413 0ustar sarnoldsarnoldFrom 4c06a31907ae49bc33adb983946456cc2f9409ab Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 May 2012 10:58:05 -0700 Subject: [PATCH 4/4] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount Add the ability for apparmor to do mediation of mount operations. Mount rules require an updated apparmor_parser (2.8 series) for policy compilation. The basic form of the rules are. [audit] [deny] mount [conds]* [device] [ -> [conds] path], [audit] [deny] remount [conds]* [path], [audit] [deny] umount [conds]* [path], [audit] [deny] pivotroot [oldroot=] remount is just a short cut for mount options=remount where [conds] can be fstype= options= Example mount commands mount, # allow all mounts, but not umount or pivotroot mount fstype=procfs, # allow mounting procfs anywhere mount options=(bind, ro) /foo -> /bar, # readonly bind mount mount /dev/sda -> /mnt, mount /dev/sd** -> /mnt/**, mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ umount, umount /m*, See the apparmor userspace for full documentation Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 13 + security/apparmor/audit.c | 4 + security/apparmor/domain.c | 2 +- security/apparmor/include/apparmor.h | 3 +- security/apparmor/include/audit.h | 11 + security/apparmor/include/domain.h | 2 + security/apparmor/include/mount.h | 54 +++ security/apparmor/lsm.c | 59 ++++ security/apparmor/mount.c | 620 +++++++++++++++++++++++++++++++++++ 10 files changed, 767 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/mount.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index e270692..9b44e1a 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o net.o + resource.o sid.o file.o net.o mount.o clean-files := capability_names.h rlim_names.h net_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 114fb23..ee77ec9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -426,10 +426,23 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { { } }; +static struct aa_fs_entry aa_fs_entry_mount[] = { + AA_FS_FILE_STRING("mask", "mount umount"), + { } +}; + +static struct aa_fs_entry aa_fs_entry_namespaces[] = { + AA_FS_FILE_BOOLEAN("profile", 1), + AA_FS_FILE_BOOLEAN("pivot_root", 1), + { } +}; + static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_DIR("mount", aa_fs_entry_mount), + AA_FS_DIR("namespaces", aa_fs_entry_namespaces), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 3ae28db..e267963 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -44,6 +44,10 @@ const char *const op_table[] = { "file_mmap", "file_mprotect", + "pivotroot", + "mount", + "umount", + "create", "post_create", "bind", diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 60f0c76..4625a28 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -242,7 +242,7 @@ static const char *next_name(int xtype, const char *name) * * Returns: refcounted profile, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) { struct aa_profile *new_profile = NULL; struct aa_namespace *ns = profile->ns; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 40aedd9..e243d96 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -29,8 +29,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_MOUNT 7 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_MOUNT /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 4af6523..ada004d 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -73,6 +73,10 @@ enum aa_ops { OP_FMMAP, OP_FMPROT, + OP_PIVOTROOT, + OP_MOUNT, + OP_UMOUNT, + OP_CREATE, OP_POST_CREATE, OP_BIND, @@ -122,6 +126,13 @@ struct apparmor_audit_data { unsigned long max; } rlim; struct { + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { const char *target; u32 request; u32 denied; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464..a3f70c5 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,6 +23,8 @@ struct aa_domain { char **table; }; +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm); diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 0000000..bc17a53 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 Canonical Ltd. + * + * 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 __AA_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags); + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *old_name); + +int aa_new_mount(struct aa_profile *profile, const char *dev_name, + struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path); + +#endif /* __AA_MOUNT_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index dcb578e..1989066 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -36,6 +36,7 @@ #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" +#include "include/mount.h" /* Flag indicating whether initialization completed */ int apparmor_initialized __initdata; @@ -504,6 +505,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + int error = 0; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + profile = __aa_current_profile(); + if (!unconfined(profile)) { + if (flags & MS_REMOUNT) + error = aa_remount(profile, path, flags, data); + else if (flags & MS_BIND) + error = aa_bind_mount(profile, path, dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(profile, path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount(profile, path, dev_name); + else + error = aa_new_mount(profile, dev_name, path, type, + flags, data); + } + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_umount(profile, mnt, flags); + + return error; +} + +static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_pivotroot(profile, old_path, new_path); + + return error; +} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { @@ -721,6 +776,10 @@ static struct security_operations apparmor_ops = { .capget = apparmor_capget, .capable = apparmor_capable, + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_sb_umount, + .sb_pivotroot = apparmor_sb_pivotroot, + .path_link = apparmor_path_link, .path_unlink = apparmor_path_unlink, .path_symlink = apparmor_path_symlink, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 0000000..478aa4d --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,620 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (sa->aad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, sa->aad->mnt.type); + } + if (sa->aad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, sa->aad->mnt.src_name); + } + if (sa->aad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, sa->aad->mnt.trans); + } + if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, sa->aad->mnt.flags); + audit_log_format(ab, "\""); + } + if (sa->aad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, sa->aad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @profile: the profile being enforced (NOT NULL) + * @gfp: allocation flags + * @op: operation being mediated (NOT NULL) + * @name: name of object being mediated (MAYBE NULL) + * @src_name: src_name of object being mediated (MAYBE_NULL) + * @type: type of filesystem (MAYBE_NULL) + * @trans: name of trans (MAYBE NULL) + * @flags: filesystem idependent mount flags + * @data: filesystem mount flags + * @request: permissions requested + * @perms: the permissions computed for the request (NOT NULL) + * @info: extra information message (MAYBE NULL) + * @error: 0 if operation allowed else failure error code + * + * Returns: %0 or error on failure + */ +static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct file_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa = { }; + struct apparmor_audit_data aad = { }; + + if (likely(!error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + request &= mask; + + if (likely(!request)) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + request = request & ~perms->allow; + + if (request & perms->kill) + audit_type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + request &= ~perms->quiet; + + if (!request) + return COMPLAIN_MODE(profile) ? + complain_error(error) : error; + } + + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + sa.aad->op = op; + sa.aad->name = name; + sa.aad->mnt.src_name = src_name; + sa.aad->mnt.type = type; + sa.aad->mnt.trans = trans; + sa.aad->mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + sa.aad->mnt.data = data; + sa.aad->info = info; + sa.aad->error = error; + + return aa_audit(audit_type, profile, gfp, &sa, audit_cb); +} + +/** + * match_mnt_flags - Do an ordered match on mount flags + * @dfa: dfa to match against + * @state: state to start in + * @flags: mount flags to match against + * + * Mount flags are encoded as an ordered match. This is done instead of + * checking against a simple bitmask, to allow for logical operations + * on the flags. + * + * Returns: next state after flags match + */ +static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, + unsigned long flags) +{ + unsigned int i; + + for (i = 0; i <= 31 ; ++i) { + if ((1 << i) & flags) + state = aa_dfa_next(dfa, state, i + 1); + } + + return state; +} + +/** + * compute_mnt_perms - compute mount permission associated with @state + * @dfa: dfa to match against (NOT NULL) + * @state: state match finished in + * + * Returns: mount permissions + */ +static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, + unsigned int state) +{ + struct file_perms perms; + + perms.kill = 0; + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + perms.xindex = dfa_user_xindex(dfa, state); + + return perms; +} + +static const char const *mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match" +}; + +/* + * Returns 0 on success else element that match failed in, this is the + * index into the mnt_info_table above + */ +static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct file_perms *perms) +{ + unsigned int state; + + state = aa_dfa_match(dfa, start, mntpnt); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(dfa, state, devname); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(dfa, state, type); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(dfa, state, flags); + if (!state) + return 4; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + + /* only match data if not binary and the DFA flags data is expected */ + if (data && !binary && (perms->allow & AA_CONT_MATCH)) { + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(dfa, state, data); + if (!state) + return 5; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at end of flags match */ + return 4; +} + +/** + * match_mnt - handle path matching for mount + * @profile: the confining profile + * @mntpnt: string for the mntpnt (NOT NULL) + * @devname: string for the devname/src_name (MAYBE NULL) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @perms: Returns: permission found by the match + * @info: Returns: infomation string about the match for logging + * + * Returns: 0 on success else error + */ +static int match_mnt(struct aa_profile *profile, const char *mntpnt, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + struct file_perms *perms, const char **info) +{ + int pos; + + if (!profile->policy.dfa) + return -EACCES; + + pos = do_match_mnt(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + mntpnt, devname, type, flags, data, binary, perms); + if (pos) { + *info = mnt_info_table[pos]; + return -EACCES; + } + + return 0; +} + +static int path_flags(struct aa_profile *profile, struct path *path) +{ + return profile->path_flags | + S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; +} + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data) +{ + struct file_perms perms = { }; + const char *name, *info = NULL; + char *buffer = NULL; + int binary, error; + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *dev_name, unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, + &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *orig_name) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, + struct path *path, const char *type, unsigned long flags, + void *data) +{ + struct file_perms perms = { }; + char *buffer = NULL, *dev_buffer = NULL; + const char *name = NULL, *dev_name = NULL, *info = NULL; + int binary = 1; + int error; + + dev_name = orig_dev_name; + if (type) { + int requires_dev; + struct file_system_type *fstype = get_fs_type(type); + if (!fstype) + return -ENODEV; + + binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; + requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; + put_filesystem(fstype); + + if (requires_dev) { + struct path dev_path; + + if (!dev_name || !*dev_name) { + error = -ENOENT; + goto out; + } + + error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); + if (error) + goto audit; + + error = aa_path_name(&dev_path, + path_flags(profile, &dev_path), + &dev_buffer, &dev_name, &info); + path_put(&dev_path); + if (error) + goto audit; + } + } + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, dev_name, type, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, + type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + kfree(dev_buffer); + +out: + return error; + +} + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + struct path path = { mnt, mnt->mnt_root }; + error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, + &info); + if (error) + goto audit; + + if (!error && profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); + kfree(buffer); + + return error; +} + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path) +{ + struct file_perms perms = { }; + struct aa_profile *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL; + const char *old_name, *new_name = NULL, *info = NULL; + int error; + + error = aa_path_name(old_path, path_flags(profile, old_path), + &old_buffer, &old_name, &info); + if (error) + goto audit; + + error = aa_path_name(new_path, path_flags(profile, new_path), + &new_buffer, &new_name, &info); + if (error) + goto audit; + + if (profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(profile->policy.dfa, state); + state = aa_dfa_match(profile->policy.dfa, state, old_name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_PIVOTROOT & perms.allow) { + if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + target = x_table_lookup(profile, perms.xindex); + if (!target) + error = -ENOENT; + else + error = aa_replace_current_profile(target); + } + } else + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, + old_name, NULL, target ? target->base.name : NULL, + 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); + aa_put_profile(target); + kfree(old_buffer); + kfree(new_buffer); + + return error; +} -- 1.8.3.2 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.7/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.patchapparmor-2.8.95~2430/kernel-patches/3.7/0001-UBUNTU-SAUCE-AppArmor-Add-profile-introspection-file.pa0000644000175000017500000001744412234002577031523 0ustar sarnoldsarnoldFrom f799dd0857774850de17901bf2f1bacd823036c4 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH 1/4] UBUNTU: SAUCE: AppArmor: Add profile introspection file to interface Add the dynamic profiles file to the interace, to allow load policy introspection. Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Kconfig | 9 ++ security/apparmor/apparmorfs.c | 231 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16c15ec..42b7c9f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,6 +182,234 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; +#endif /* CONFIG_SECURITY_APPARMOR_COMPAT_24 */ + /** Base file system setup **/ static struct aa_fs_entry aa_fs_entry_file[] = { @@ -210,6 +438,9 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = { AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), +#endif AA_FS_DIR("features", aa_fs_entry_features), { } }; -- 1.8.3.2 apparmor-2.8.95~2430/kernel-patches/3.7/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch0000644000175000017500000004305112234002577030651 0ustar sarnoldsarnoldFrom a1bfb457f0b8a5f5783bfc8efe127830f13a4c70 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH 2/4] UBUNTU: SAUCE: AppArmor: basic networking rules Base support for network mediation. Signed-off-by: John Johansen --- security/apparmor/.gitignore | 1 + security/apparmor/Makefile | 42 +++++++++- security/apparmor/apparmorfs.c | 1 + security/apparmor/include/audit.h | 4 + security/apparmor/include/net.h | 44 ++++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ security/apparmor/net.c | 162 +++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 46 +++++++++++ 10 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 9cdec70..d5b291e 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,5 +1,6 @@ # # Generated include files # +net_names.h capability_names.h rlim_names.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 5706b74..e270692 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h net_names.h # Build a lower case string table of capability names @@ -20,6 +20,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ +# Build a lower case string table of address family names +# Transform lines from +# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [1] = "local", +# [2] = "inet", +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# #define AA_FS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ ;\ + echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ + sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ + $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +# Build a lower case string table of sock type names +# Transform lines from +# SOCK_STREAM = 1, +# to +# [1] = "stream", +quiet_cmd_make-sock = GEN $@ +cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ + sed $^ >>$@ -r -n \ + -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ # Build a lower case string table of rlimit names. # Transforms lines from @@ -56,6 +88,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(src)/Makefile @@ -63,3 +96,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) +$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ + $(srctree)/include/linux/net.h \ + $(src)/Makefile + $(call cmd,make-af) + $(call cmd,make-sock) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 42b7c9f..114fb23 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -429,6 +429,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 69d8cae..4af6523 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -127,6 +127,10 @@ struct apparmor_audit_data { u32 denied; kuid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; }; diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..cb8a121 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,44 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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 __AA_NET_H +#define __AA_NET_H + +#include + +#include "apparmorfs.h" + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern struct aa_fs_entry aa_fs_entry_network[]; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index bda4569..eb13a73 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *const profile_mode_names[]; @@ -157,6 +158,7 @@ struct aa_policydb { * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions * @caps: capabilities for the profile + * @net: network controls for the profile * @rlimits: rlimits for the profile * * The AppArmor profile contains the basic confinement data. Each profile @@ -194,6 +196,7 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8c2a7f6..dcb578e 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #include "include/context.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" @@ -614,6 +615,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -646,6 +745,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..003dd18 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,162 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 Canonical Ltd. + * + * 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. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "net_names.h" + +struct aa_fs_entry aa_fs_entry_network[] = { + AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), + { } +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net->family]) { + audit_log_string(ab, address_family_names[sa->u.net->family]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); + } + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad->net.type]) { + audit_log_string(ab, sock_type_names[sa->aad->net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); + } + audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + struct lsm_network_audit net = { }; + if (sk) { + sa.type = LSM_AUDIT_DATA_NET; + } else { + sa.type = LSM_AUDIT_DATA_NONE; + } + /* todo fill in socket addr info */ + sa.aad = &aad; + sa.u.net = &net; + sa.aad->op = op, + sa.u.net->family = family; + sa.u.net->sk = sk; + sa.aad->net.type = type; + sa.aad->net.protocol = protocol; + sa.aad->error = error; + + if (likely(!sa.aad->error)) { + u16 audit_mask = profile->net.audit[sa.u.net->family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad->net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 8132003..56e5304 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -747,6 +747,7 @@ static void free_profile(struct aa_profile *profile) aa_free_file_rules(&profile->file); aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); aa_free_rlimit_rules(&profile->rlimits); aa_free_sid(profile->sid); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 329b1fd..1b90dfa 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -193,6 +193,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -471,6 +484,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; + size_t size = 0; int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -564,6 +578,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + } + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ profile->policy.dfa = unpack_dfa(e); -- 1.8.3.2 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootapparmor-2.8.95~2430/kernel-patches/3.7/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.patchapparmor-2.8.95~2430/kernel-patches/3.7/0003-apparmor-Fix-quieting-of-audit-messages-for-network-.pa0000644000175000017500000000277412234002577032222 0ustar sarnoldsarnoldFrom 3ffe7266aea607c1cdad484b141c5b244e384de0 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 29 Jun 2012 17:34:00 -0700 Subject: [PATCH 3/4] apparmor: Fix quieting of audit messages for network mediation If a profile specified a quieting of network denials for a given rule by either the quiet or deny rule qualifiers, the resultant quiet mask for denied requests was applied incorrectly, resulting in two potential bugs. 1. The misapplied quiet mask would prevent denials from being correctly tested against the kill mask/mode. Thus network access requests that should have resulted in the application being killed did not. 2. The actual quieting of the denied network request was not being applied. This would result in network rejections always being logged even when they had been specifically marked as quieted. Signed-off-by: John Johansen --- security/apparmor/net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 003dd18..6e6e5c9 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -88,7 +88,7 @@ static int audit_net(struct aa_profile *profile, int op, u16 family, int type, } else { u16 quiet_mask = profile->net.quiet[sa.u.net->family]; u16 kill_mask = 0; - u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + u16 denied = (1 << sa.aad->net.type); if (denied & kill_mask) audit_type = AUDIT_APPARMOR_KILL; -- 1.8.3.2 apparmor-2.8.95~2430/utils/0000755000175000017500000000000012311706722015176 5ustar sarnoldsarnoldapparmor-2.8.95~2430/utils/aa-genprof0000755000175000017500000001422712306136342017150 0ustar sarnoldsarnold#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import atexit import os import re import subprocess import sys import apparmor.aa as apparmor import apparmor.ui as aaui # setup module translations from apparmor.translations import init_translation _ = init_translation() def sysctl_read(path): value = None with open(path, 'r') as f_in: value = int(f_in.readline()) return value def sysctl_write(path, value): if not value: return with open(path, 'w') as f_out: f_out.write(str(value)) def last_audit_entry_time(): out = subprocess.check_output(['tail', '-1', '/var/log/audit/audit.log'], shell=True) logmark = None if re.search('^*msg\=audit\((\d+\.\d+\:\d+).*\).*$', out): logmark = re.search('^*msg\=audit\((\d+\.\d+\:\d+).*\).*$', out).groups()[0] else: logmark = '' return logmark def restore_ratelimit(): sysctl_write(ratelimit_sysctl, ratelimit_saved) parser = argparse.ArgumentParser(description=_('Generate profile for the given program')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-f', '--file', type=str, help=_('path to logfile')) parser.add_argument('program', type=str, help=_('name of program to profile')) args = parser.parse_args() profiling = args.program profiledir = args.dir filename = args.file if filename: if not os.path.isfile(filename): raise apparmor.AppArmorException(_('The logfile %s does not exist. Please check the path') % filename) else: apparmor.filename = filename aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) if profiledir: apparmor.profile_dir = apparmor.get_full_path(profiledir) if not os.path.isdir(apparmor.profile_dir): raise apparmor.AppArmorException(_("%s is not a directory.") %profiledir) program = None #if os.path.exists(apparmor.which(profiling.strip())): if os.path.exists(profiling): program = apparmor.get_full_path(profiling) else: if '/' not in profiling: which = apparmor.which(profiling) if which: program = apparmor.get_full_path(which) if not program or not os.path.exists(program): if '/' not in profiling: raise apparmor.AppArmorException(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") %(profiling, profiling)) else: raise apparmor.AppArmorException(_('%s does not exists, please double-check the path.') %profiling) # Check if the program has been marked as not allowed to have a profile apparmor.check_qualifiers(program) apparmor.loadincludes() profile_filename = apparmor.get_profile_filename(program) if os.path.exists(profile_filename): apparmor.helpers[program] = apparmor.get_profile_flags(profile_filename, program) else: apparmor.autodep(program) apparmor.helpers[program] = 'enforce' if apparmor.helpers[program] == 'enforce': apparmor.complain(program) apparmor.reload(program) # When reading from syslog, it is possible to hit the default kernel # printk ratelimit. This will result in audit entries getting skipped, # making profile generation inaccurate. When using genprof, disable # the printk ratelimit, and restore it on exit. ratelimit_sysctl = '/proc/sys/kernel/printk_ratelimit' ratelimit_saved = sysctl_read(ratelimit_sysctl) sysctl_write(ratelimit_sysctl, 0) atexit.register(restore_ratelimit) aaui.UI_Info(_('\nBefore you begin, you may wish to check if a\nprofile already exists for the application you\nwish to confine. See the following wiki page for\nmore information:')+'\nhttp://wiki.apparmor.net/index.php/Profiles') aaui.UI_Important(_('Please start the application to be profiled in\nanother window and exercise its functionality now.\n\nOnce completed, select the "Scan" option below in \norder to scan the system logs for AppArmor events. \n\nFor each AppArmor event, you will be given the \nopportunity to choose whether the access should be \nallowed or denied.')) syslog = True logmark = '' done_profiling = False if os.path.exists('/var/log/audit/audit.log'): syslog = False passno = 0 while not done_profiling: if syslog: logmark = subprocess.check_output(['date | md5sum'], shell=True) logmark = logmark.decode('ascii').strip() logmark = re.search('^([0-9a-f]+)', logmark).groups()[0] t=subprocess.call("%s -p kern.warn 'GenProf: %s'"%(apparmor.logger, logmark), shell=True) else: logmark = last_audit_entry_time() q=apparmor.hasher() q['headers'] = [_('Profiling'), program] q['functions'] = ['CMD_SCAN', 'CMD_FINISHED'] q['default'] = 'CMD_SCAN' ans, arg = aaui.UI_PromptUser(q, 'noexit') if ans == 'CMD_SCAN': lp_ret = apparmor.do_logprof_pass(logmark, passno) passno += 1 if lp_ret == 'FINISHED': done_profiling = True else: done_profiling = True for p in sorted(apparmor.helpers.keys()): if apparmor.helpers[p] == 'enforce': apparmor.enforce(p) apparmor.reload(p) aaui.UI_Info(_('\nReloaded AppArmor profiles in enforce mode.')) aaui.UI_Info(_('\nPlease consider contributing your new profile!\nSee the following wiki page for more information:')+'\nhttp://wiki.apparmor.net/index.php/Profiles\n') aaui.UI_Info(_('Finished generating profile for %s.')%program) sys.exit(0) apparmor-2.8.95~2430/utils/aa-genprof.pod0000644000175000017500000000606112277004630017724 0ustar sarnoldsarnold# This publication is intellectual property of Novell Inc. and Canonical # Ltd. Its contents can be duplicated, either in part or in whole, provided # that a copyright label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators # shall be held liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. SUSE LINUX GmbH # and Canonical Ltd. essentially adhere to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-genprof - profile generation utility for AppArmor =head1 SYNOPSIS BexecutableE> [I<-d /path/to/profiles>] [I<-f /path/to/logfile>]> =head1 OPTIONS B<-d --dir /path/to/profiles> Specifies where to look for the AppArmor security profile set. Defaults to /etc/apparmor.d. B<-f --file /path/to/logfile> Specifies the location of logfile. Default locations are read from F. Typical defaults are: /var/log/audit/audit.log /var/log/syslog /var/log/messages =head1 DESCRIPTION When running aa-genprof, you must specify a program to profile. If the specified program is not a fully-qualified path, aa-genprof will search $PATH in order to find the program. If a profile does not exist for the program, aa-genprof will create one using aa-autodep(1). Genprof will then: - set the profile to complain mode - write a mark to the system log - instruct the user to start the application to be profiled in another window and exercise its functionality It then presents the user with two options, (S)can system log for entries to add to profile and (F)inish. If the user selects (S)can or hits return, aa-genprof will parse the complain mode logs and iterate through generated violations using aa-logprof(1). After the user finishes selecting profile entries based on violations that were detected during the program execution, aa-genprof will reload the updated profiles in complain mode and again prompt the user for (S)can and (F)inish. This cycle can then be repeated as necessary until all application functionality has been exercised without generating access violations. When the user eventually hits (F)inish, aa-genprof will set the main profile, and any other profiles that were generated, into enforce mode and exit. =head1 BUGS If you find any bugs, please report them at L. =head1 SEE ALSO apparmor(7), apparmor.d(5), aa-enforce(1), aa-complain(1), aa-disable(1), aa_change_hat(2), aa-logprof(1), logprof.conf(5), and L. =cut apparmor-2.8.95~2430/utils/aa-complain.pod0000644000175000017500000000340412306150644020064 0ustar sarnoldsarnold# This publication is intellectual property of Novell Inc. and Canonical # Ltd. Its contents can be duplicated, either in part or in whole, provided # that a copyright label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators # shall be held liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. SUSE LINUX GmbH # and Canonical Ltd. essentially adhere to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-complain - set an AppArmor security profile to I mode. =head1 SYNOPSIS BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] =head1 OPTIONS B<-d --dir /path/to/profiles> Specifies where to look for the AppArmor security profile set. Defaults to /etc/apparmor.d. =head1 DESCRIPTION B is used to set the enforcement mode for one or more profiles to I mode. In this mode security policy is not enforced but rather access violations are logged to the system log. =head1 BUGS If you find any bugs, please report them at L. =head1 SEE ALSO apparmor(7), apparmor.d(5), aa-enforce(1), aa-disable(1), aa_change_hat(2), and L. =cut apparmor-2.8.95~2430/utils/aa-status0000755000175000017500000001466412277515713017052 0ustar sarnoldsarnold#! /usr/bin/env python # ------------------------------------------------------------------ # # Copyright (C) 2005-2006 Novell/SUSE # Copyright (C) 2011 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ import re, os, sys def cmd_enabled(): '''Returns error code if AppArmor is not enabled''' if get_profiles() == {}: sys.exit(2) def cmd_profiled(): '''Prints the number of loaded profiles''' profiles = get_profiles() sys.stdout.write("%d\n" % len(profiles)) if profiles == {}: sys.exit(2) def cmd_enforced(): '''Prints the number of loaded enforcing profiles''' profiles = get_profiles() sys.stdout.write("%d\n" % len(filter_profiles(profiles, 'enforce'))) if profiles == {}: sys.exit(2) def cmd_complaining(): '''Prints the number of loaded non-enforcing profiles''' profiles = get_profiles() sys.stdout.write("%d\n" % len(filter_profiles(profiles, 'complain'))) if profiles == {}: sys.exit(2) def cmd_verbose(): '''Displays multiple data points about loaded profile set''' global verbose verbose = True profiles = get_profiles() processes = get_processes(profiles) stdmsg("%d profiles are loaded." % len(profiles)) for status in ('enforce', 'complain'): filtered_profiles = filter_profiles(profiles, status) stdmsg("%d profiles are in %s mode." % (len(filtered_profiles), status)) for item in filtered_profiles: stdmsg(" %s" % item) stdmsg("%d processes have profiles defined." % len(processes)) for status in ('enforce', 'complain', 'unconfined'): filtered_processes = filter_processes(processes, status) if status == 'unconfined': stdmsg("%d processes are unconfined but have a profile defined." % len(filtered_processes)) else: stdmsg("%d processes are in %s mode." % (len(filtered_processes), status)) # Sort by name, and then by pid filtered_processes.sort(key=lambda x: int(x[0])) filtered_processes.sort(key=lambda x: x[1]) for (pid, process) in filtered_processes: stdmsg(" %s (%s) " % (process, pid)) if profiles == {}: sys.exit(2) def get_profiles(): '''Fetch loaded profiles''' profiles = {} if os.path.exists("/sys/module/apparmor"): stdmsg("apparmor module is loaded.") else: errormsg("apparmor module is not loaded.") sys.exit(1) apparmorfs = find_apparmorfs() if not apparmorfs: errormsg("apparmor filesystem is not mounted.") sys.exit(3) apparmor_profiles = os.path.join(apparmorfs, "profiles") if not os.access(apparmor_profiles, os.R_OK): errormsg("You do not have enough privilege to read the profile set.") sys.exit(4) for p in open(apparmor_profiles).readlines(): match = re.search("^([^\(]+)\s+\((\w+)\)$", p) profiles[match.group(1)] = match.group(2) return profiles def get_processes(profiles): '''Fetch process list''' processes = {} contents = os.listdir("/proc") for filename in contents: if filename.isdigit(): try: for p in open("/proc/%s/attr/current" % filename).readlines(): match = re.search("^([^\(]+)\s+\((\w+)\)$", p) if match: processes[filename] = { 'profile' : match.group(1), \ 'mode' : match.group(2) } elif os.path.realpath("/proc/%s/exe" % filename) in profiles: # keep only unconfined processes that have a profile defined processes[filename] = { 'profile' : os.path.realpath("/proc/%s/exe" % filename), \ 'mode' : 'unconfined' } except: pass return processes def filter_profiles(profiles, status): '''Return a list of profiles that have a particular status''' filtered = [] for key, value in list(profiles.items()): if value == status: filtered.append(key) filtered.sort() return filtered def filter_processes(processes, status): '''Return a list of processes that have a particular status''' filtered = [] for key, value in list(processes.items()): if value['mode'] == status: filtered.append([key, value['profile']]) return filtered def find_apparmorfs(): '''Finds AppArmor mount point''' for p in open("/proc/mounts").readlines(): if p.split()[2] == "securityfs" and \ os.path.exists(os.path.join(p.split()[1], "apparmor")): return os.path.join(p.split()[1], "apparmor") return False def errormsg(message): '''Prints to stderr if verbose mode is on''' global verbose if verbose: sys.stderr.write(message + "\n") def stdmsg(message): '''Prints to stdout if verbose mode is on''' global verbose if verbose: sys.stdout.write(message + "\n") def print_usage(): '''Print usage information''' sys.stdout.write('''Usage: %s [OPTIONS] Displays various information about the currently loaded AppArmor policy. OPTIONS (one only): --enabled returns error code if AppArmor not enabled --profiled prints the number of loaded policies --enforced prints the number of loaded enforcing policies --complaining prints the number of loaded non-enforcing policies --verbose (default) displays multiple data points about loaded policy set --help this message ''' % sys.argv[0]) # Main global verbose verbose = False if len(sys.argv) > 2: sys.stderr.write("Error: Too many options.\n") print_usage() sys.exit(1) elif len(sys.argv) == 2: cmd = sys.argv.pop(1) else: cmd = '--verbose' # Command dispatch: commands = { '--enabled' : cmd_enabled, '--profiled' : cmd_profiled, '--enforced' : cmd_enforced, '--complaining' : cmd_complaining, '--verbose' : cmd_verbose, '-v' : cmd_verbose, '--help' : print_usage, '-h' : print_usage } if cmd in commands: commands[cmd]() sys.exit(0) else: sys.stderr.write("Error: Invalid command.\n") print_usage() sys.exit(1) apparmor-2.8.95~2430/utils/check_po.pl0000755000175000017500000000276310660651401017317 0ustar sarnoldsarnold#!/usr/bin/perl # Look in the po directory and check the files for missing shortcuts - where the # msgid has a string "(B)lah" and the msgstr contains no character surrounded by # parens use Data::Dumper; my $dir = "./po"; opendir(PODIR, $dir) || die "Can't open directory $dir."; my $errors = {}; for my $file (grep { /.*\.po$/ && -f "$dir/$_" } readdir(PODIR)) { #print STDOUT "Processing $file\n"; check_po_for_shortcuts( "$dir/$file", $errors ); } my $msg = Data::Dumper->Dump([$errors], [qw(*ERRORS)]); my $count = 0; for my $f ( keys %$errors ) { for my $err ( keys %{$errors->{$f}} ) { $count++; } } print STDOUT "$count missing shortcuts in the po files\n.$msg\n"; closedir(PODIR); sub check_po_for_shortcuts { my ($filename, $errors) = @_; my $line = 0; if (open(PO, "$filename")) { while () { $line++; chomp; if ( /^.*msgid.*\(\w{1}?\)*/ ) { $looking_for_msgstr = 1; $msgid = $_; } if ( /^.*msgstr*/ && $looking_for_msgstr ) { unless (/^.*msgstr.*\(\w{1}?\)*/) { $errors->{$filename}{$line} = { "msgid" => $msgid, "msgstr" => $_, }; } $msgid = ""; $looking_for_msgstr = 0; } } close(PO); } return $config; } apparmor-2.8.95~2430/utils/severity.db0000644000175000017500000002435512012516362017365 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2002-2005 Novell/SUSE # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ # Allow this process to 0wn the machine: CAP_SYS_ADMIN 10 CAP_SYS_CHROOT 10 CAP_SYS_MODULE 10 CAP_SYS_PTRACE 10 CAP_SYS_RAWIO 10 CAP_MAC_ADMIN 10 CAP_MAC_OVERRIDE 10 # Allow other processes to 0wn the machine: CAP_SETPCAP 9 CAP_SETFCAP 9 CAP_CHOWN 9 CAP_FSETID 9 CAP_MKNOD 9 CAP_LINUX_IMMUTABLE 9 CAP_DAC_OVERRIDE 9 CAP_SETGID 9 CAP_SETUID 9 CAP_FOWNER 9 # Denial of service, bypass audit controls, information leak CAP_SYS_TIME 8 CAP_NET_ADMIN 8 CAP_SYS_RESOURCE 8 CAP_KILL 8 CAP_IPC_OWNER 8 CAP_SYS_PACCT 8 CAP_SYS_BOOT 8 CAP_NET_BIND_SERVICE 8 CAP_NET_RAW 8 CAP_SYS_NICE 8 CAP_LEASE 8 CAP_IPC_LOCK 8 CAP_SYS_TTY_CONFIG 8 CAP_AUDIT_CONTROL 8 CAP_AUDIT_WRITE 8 CAP_SYSLOG 8 CAP_WAKE_ALARM 8 CAP_BLOCK_SUSPEND 8 CAP_DAC_READ_SEARCH 7 # unused CAP_NET_BROADCAST 0 # filename r w x # 'hard drives' are generally 4 10 0 /**/lost+found/** 5 5 0 /boot/** 7 10 0 /etc/passwd* 4 8 0 /etc/group* 4 8 0 /etc/shadow* 7 9 0 /etc/shadow* 7 9 0 /home/*/.ssh/** 7 9 0 /home/*/.gnupg/** 5 7 0 /home/** 4 6 0 /srv/** 4 6 0 /proc/** 6 9 0 /proc/sys/kernel/hotplug 2 10 0 /proc/sys/kernel/modprobe 2 10 0 /proc/kallsyms 7 0 0 /sys/** 4 8 0 /sys/power/state 2 8 0 /sys/firmware/** 2 10 0 /dev/pts/* 8 9 0 /dev/ptmx 8 9 0 /dev/pty* 8 9 0 /dev/null 0 0 0 /dev/adbmouse 3 8 0 /dev/ataraid 9 10 0 /dev/zero 0 0 0 /dev/agpgart* 8 10 0 /dev/aio 3 3 0 /dev/cbd/* 5 5 0 /dev/cciss/* 4 10 0 /dev/capi* 4 6 0 /dev/cfs0 4 10 0 /dev/compaq/* 4 10 0 /dev/cdouble* 4 8 0 /dev/cpu** 5 5 0 /dev/cpu**microcode 1 10 0 /dev/double* 4 8 0 /dev/hd* 4 10 0 /dev/sd* 4 10 0 /dev/ida/* 4 10 0 /dev/input/* 4 8 0 /dev/mapper/control 4 10 0 /dev/*mem 8 10 0 /dev/loop* 4 10 0 /dev/lp* 0 4 0 /dev/md* 4 10 0 /dev/msr 4 10 0 /dev/nb* 4 10 0 /dev/ram* 8 10 0 /dev/rd/* 4 10 0 /dev/*random 3 1 0 /dev/sbpcd* 4 0 0 /dev/rtc 6 0 0 /dev/sd* 4 10 0 /dev/sc* 4 10 0 /dev/sg* 4 10 0 /dev/st* 4 10 0 /dev/snd/* 3 8 0 /dev/usb/mouse* 4 6 0 /dev/usb/hid* 4 6 0 /dev/usb/tty* 4 6 0 /dev/tty* 8 9 0 /dev/stderr 0 0 0 /dev/stdin 0 0 0 /dev/stdout 0 0 0 /dev/ubd* 4 10 0 /dev/usbmouse* 4 6 0 /dev/userdma 8 10 0 /dev/vcs* 8 9 0 /dev/xta* 4 10 0 /dev/zero 0 0 0 /dev/inittcl 8 10 0 /dev/log 5 7 0 /etc/fstab 3 8 0 /etc/mtab 3 5 0 /etc/SuSEconfig/* 1 8 0 /etc/X11/* 2 7 0 /etc/X11/xinit/* 2 8 0 /etc/SuSE-release 1 5 0 /etc/issue* 1 3 0 /etc/motd 1 3 0 /etc/aliases.d/* 1 7 0 /etc/cron* 1 9 0 /etc/cups/* 2 7 0 /etc/default/* 3 8 0 /etc/init.d/* 1 10 0 /etc/permissions.d/* 1 8 0 /etc/ppp/* 2 6 0 /etc/ppp/*secrets 8 6 0 /etc/profile.d/* 1 8 0 /etc/skel/* 0 7 0 /etc/sysconfig/* 4 10 0 /etc/xinetd.d/* 1 9 0 /etc/termcap/* 1 4 0 /etc/ld.so.* 1 9 0 /etc/pam.d/* 3 9 0 /etc/udev/* 3 9 0 /etc/insserv.conf 3 6 0 /etc/security/* 1 9 0 /etc/securetty 0 7 0 /etc/sudoers 4 9 0 /etc/hotplug/* 2 10 0 /etc/xinitd.conf 1 9 0 /etc/gpm/* 2 10 0 /etc/ssl/** 2 7 0 /etc/shadow* 5 9 0 /etc/bash.bashrc 1 9 0 /etc/csh.cshrc 1 9 0 /etc/csh.login 1 9 0 /etc/inittab 1 10 0 /etc/profile* 1 9 0 /etc/shells 1 5 0 /etc/alternatives 1 6 0 /etc/sysctl.conf 3 7 0 /etc/dev.d/* 1 8 0 /etc/manpath.config 1 6 0 /etc/permissions* 1 8 0 /etc/evms.conf 3 8 0 /etc/exports 3 8 0 /etc/samba/* 5 8 0 /etc/ssh/* 3 8 0 /etc/ssh/ssh_host_*key 8 8 0 /etc/krb5.conf 4 8 0 /etc/ntp.conf 3 8 0 /etc/auto.* 3 8 0 /etc/postfix/* 3 7 0 /etc/postfix/*passwd* 6 7 0 /etc/postfix/*cert* 6 7 0 /etc/foomatic/* 3 5 0 /etc/printcap 3 5 0 /etc/youservers 4 9 0 /etc/grub.conf 7 10 0 /etc/modules.conf 4 10 0 /etc/resolv.conf 2 7 0 /etc/apache2/** 3 7 0 /etc/apache2/**ssl** 7 7 0 /etc/subdomain.d/** 6 10 0 /etc/apparmor.d/** 6 10 0 /etc/apparmor/** 6 10 0 /var/log/** 3 8 0 /var/adm/SuSEconfig/** 3 8 0 /var/adm/** 3 7 0 /var/lib/rpm/** 4 8 0 /var/run/nscd/* 3 3 0 /var/run/.nscd_socket 3 3 0 /usr/share/doc/** 1 1 0 /usr/share/man/** 3 5 0 /usr/X11/man/** 3 5 0 /usr/share/info/** 2 4 0 /usr/share/java/** 2 5 0 /usr/share/locale/** 2 4 0 /usr/share/sgml/** 2 4 0 /usr/share/YaST2/** 3 9 0 /usr/share/ghostscript/** 3 5 0 /usr/share/terminfo/** 1 8 0 /usr/share/latex2html/** 2 4 0 /usr/share/cups/** 5 6 0 /usr/share/susehelp/** 2 6 0 /usr/share/susehelp/cgi-bin/** 3 7 7 /usr/share/zoneinfo/** 2 7 0 /usr/share/zsh/** 3 6 0 /usr/share/vim/** 3 8 0 /usr/share/groff/** 3 7 0 /usr/share/vnc/** 3 8 0 /usr/share/wallpapers/** 2 4 0 /usr/X11** 3 8 5 /usr/X11*/bin/XFree86 3 8 8 /usr/X11*/bin/Xorg 3 8 8 /usr/X11*/bin/sux 3 8 8 /usr/X11*/bin/xconsole 3 7 7 /usr/X11*/bin/xhost 3 7 7 /usr/X11*/bin/xauth 3 7 7 /usr/X11*/bin/ethereal 3 6 8 /usr/lib/ooo-** 3 6 5 /usr/lib/lsb/** 2 8 8 /usr/lib/pt_chwon 2 8 5 /usr/lib/tcl** 2 5 3 /usr/lib/lib*so* 3 8 4 /usr/lib/iptables/* 2 8 2 /usr/lib/perl5/** 4 10 6 /usr/lib/gconv/* 4 7 4 /usr/lib/locale/** 4 8 0 /usr/lib/jvm/** 5 7 5 /usr/lib/sasl*/** 5 8 4 /usr/lib/jvm-exports/** 5 7 5 /usr/lib/jvm-private/** 5 7 5 /usr/lib/python*/** 5 7 5 /usr/lib/libkrb5* 4 8 4 /usr/lib/postfix/* 4 7 4 /usr/lib/rpm/** 4 8 6 /usr/lib/rpm/gnupg/** 4 9 0 /usr/lib/apache2** 4 7 4 /usr/lib/mailman/** 4 6 4 /usr/bin/ldd 1 7 4 /usr/bin/netcat 5 7 8 /usr/bin/clear 2 6 3 /usr/bin/reset 2 6 3 /usr/bin/tput 2 6 3 /usr/bin/tset 2 6 3 /usr/bin/file 2 6 3 /usr/bin/ftp 3 7 5 /usr/bin/busybox 4 8 6 /usr/bin/rbash 4 8 5 /usr/bin/screen 3 6 5 /usr/bin/getfacl 3 7 4 /usr/bin/setfacl 3 7 9 /usr/bin/*awk* 3 7 7 /usr/bin/sudo 2 9 10 /usr/bin/lsattr 2 6 5 /usr/bin/chattr 2 7 8 /usr/bin/sed 3 7 6 /usr/bin/grep 2 7 2 /usr/bin/chroot 2 6 10 /usr/bin/dircolors 2 9 3 /usr/bin/cut 2 7 2 /usr/bin/du 2 7 3 /usr/bin/env 2 7 2 /usr/bin/head 2 7 2 /usr/bin/tail 2 7 2 /usr/bin/install 2 8 4 /usr/bin/link 2 6 4 /usr/bin/logname 2 6 2 /usr/bin/md5sum 2 8 3 /usr/bin/mkfifo 2 6 10 /usr/bin/nice 2 7 7 /usr/bin/nohup 2 7 7 /usr/bin/printf 2 7 1 /usr/bin/readlink 2 7 3 /usr/bin/seq 2 7 1 /usr/bin/sha1sum 2 8 3 /usr/bin/shred 2 7 3 /usr/bin/sort 2 7 3 /usr/bin/split 2 7 3 /usr/bin/stat 2 7 4 /usr/bin/sum 2 8 3 /usr/bin/tac 2 7 3 /usr/bin/tail 3 8 4 /usr/bin/tee 2 7 3 /usr/bin/test 2 8 4 /usr/bin/touch 2 7 3 /usr/bin/tr 2 8 3 /usr/bin/tsort 2 7 3 /usr/bin/tty 2 7 3 /usr/bin/unexpand 2 7 3 /usr/bin/uniq 2 7 3 /usr/bin/unlink 2 8 4 /usr/bin/uptime 2 7 3 /usr/bin/users 2 8 4 /usr/bin/vdir 2 8 4 /usr/bin/wc 2 7 3 /usr/bin/who 2 8 4 /usr/bin/whoami 2 8 4 /usr/bin/yes 1 6 1 /usr/bin/ed 2 7 5 /usr/bin/red 2 7 4 /usr/bin/find 2 8 5 /usr/bin/xargs 2 7 5 /usr/bin/ispell 2 7 4 /usr/bin/a2p 2 7 5 /usr/bin/perlcc 2 7 5 /usr/bin/perldoc 2 7 5 /usr/bin/pod2* 2 7 5 /usr/bin/prove 2 7 5 /usr/bin/perl 2 10 7 /usr/bin/perl* 2 10 7 /usr/bin/suidperl 2 8 8 /usr/bin/csh 2 8 8 /usr/bin/tcsh 2 8 8 /usr/bin/tree 2 6 5 /usr/bin/last 2 7 5 /usr/bin/lastb 2 7 5 /usr/bin/utmpdump 2 6 5 /usr/bin/alsamixer 2 6 8 /usr/bin/amixer 2 6 8 /usr/bin/amidi 2 6 8 /usr/bin/aoss 2 6 8 /usr/bin/aplay 2 6 8 /usr/bin/aplaymidi 2 6 8 /usr/bin/arecord 2 6 8 /usr/bin/arecordmidi 2 6 8 /usr/bin/aseqnet 2 6 8 /usr/bin/aserver 2 6 8 /usr/bin/iecset 2 6 8 /usr/bin/rview 2 6 5 /usr/bin/ex 2 7 5 /usr/bin/enscript 2 6 5 /usr/bin/genscript 2 6 5 /usr/bin/xdelta 2 6 5 /usr/bin/edit 2 6 5 /usr/bin/vimtutor 2 6 5 /usr/bin/rvim 2 6 5 /usr/bin/vim 2 8 7 /usr/bin/vimdiff 2 8 7 /usr/bin/aspell 2 6 5 /usr/bin/xxd 2 6 5 /usr/bin/spell 2 6 5 /usr/bin/eqn 2 6 5 /usr/bin/eqn2graph 2 6 5 /usr/bin/word-list-compress 2 6 4 /usr/bin/afmtodit 2 6 4 /usr/bin/hpf2dit 2 6 4 /usr/bin/geqn 2 6 4 /usr/bin/grn 2 6 4 /usr/bin/grodvi 2 6 4 /usr/bin/groff 2 6 5 /usr/bin/groffer 2 6 4 /usr/bin/grolj4 2 6 4 /usr/bin/grotty 2 6 4 /usr/bin/gtbl 2 6 4 /usr/bin/pic2graph 2 6 4 /usr/bin/indxbib 2 6 4 /usr/bin/lkbib 2 6 4 /usr/bin/lookbib 2 6 4 /usr/bin/mmroff 2 6 4 /usr/bin/neqn 2 6 4 /usr/bin/pfbtops 2 6 4 /usr/bin/pic 2 6 4 /usr/bin/tfmtodit 2 6 4 /usr/bin/tbl 2 6 4 /usr/bin/post-grohtml 2 6 4 /usr/bin/pre-grohtml 2 6 4 /usr/bin/refer 2 6 4 /usr/bin/soelim 2 6 4 /usr/bin/disable-paste 2 6 6 /usr/bin/troff 2 6 4 /usr/bin/strace-graph 2 6 4 /usr/bin/gpm-root 2 6 7 /usr/bin/hltest 2 6 7 /usr/bin/mev 2 6 6 /usr/bin/mouse-test 2 6 6 /usr/bin/strace 2 8 9 /usr/bin/scsiformat 2 7 10 /usr/bin/lsscsi 2 7 7 /usr/bin/scsiinfo 2 7 7 /usr/bin/sg_* 2 7 7 /usr/bin/build-classpath 2 6 6 /usr/bin/build-classpath-directory 2 6 6 /usr/bin/build-jar-repository 2 6 6 /usr/bin/diff-jars 2 6 6 /usr/bin/jvmjar 2 6 6 /usr/bin/rebuild-jar-repository 2 6 6 /usr/bin/scriptreplay 2 6 5 /usr/bin/cal 2 6 3 /usr/bin/chkdupexe 2 6 5 /usr/bin/col 2 6 4 /usr/bin/colcrt 2 6 4 /usr/bin/colrm 2 6 3 /usr/bin/column 2 6 4 /usr/bin/cytune 2 6 6 /usr/bin/ddate 2 6 3 /usr/bin/fdformat 2 6 6 /usr/bin/getopt 2 8 6 /usr/bin/hexdump 2 6 4 /usr/bin/hostid 2 6 4 /usr/bin/ipcrm 2 7 7 /usr/bin/ipcs 2 7 6 /usr/bin/isosize 2 6 4 /usr/bin/line 2 6 4 /usr/bin/look 2 6 5 /usr/bin/mcookie 2 7 5 /usr/bin/mesg 2 6 4 /usr/bin/namei 2 6 5 /usr/bin/rename 2 6 5 /usr/bin/renice 2 6 7 /usr/bin/rev 2 6 5 /usr/bin/script 2 6 6 /usr/bin/ChangeSymlinks 2 8 8 /usr/bin/setfdprm 2 6 7 /usr/bin/setsid 2 6 3 /usr/bin/setterm 2 6 5 /usr/bin/tailf 2 6 4 /usr/bin/time 2 6 4 /usr/bin/ul 2 6 4 /usr/bin/wall 2 6 5 /usr/bin/whereis 2 6 4 /usr/bin/which 2 6 3 /usr/bin/c_rehash 2 7 6 /usr/bin/openssl 2 8 6 /usr/bin/lsdev 2 6 5 /usr/bin/procinfo 2 6 5 /usr/bin/socklist 2 6 5 /usr/bin/filesize 2 6 3 /usr/bin/linkto 2 6 3 /usr/bin/mkinfodir 2 6 5 /usr/bin/old 2 6 4 /usr/bin/rpmlocate 2 6 5 /usr/bin/safe-rm 2 8 6 /usr/bin/safe-rmdir 2 8 6 /usr/bin/setJava 2 6 1 /usr/bin/vmstat 2 6 4 /usr/bin/top 2 6 6 /usr/bin/pinentry* 2 7 6 /usr/bin/free 2 8 4 /usr/bin/pmap 2 6 5 /usr/bin/slabtop 2 6 4 /usr/bin/tload 2 6 4 /usr/bin/watch 2 6 3 /usr/bin/w 2 6 4 /usr/bin/pstree.x11 2 6 4 /usr/bin/pstree 2 6 4 /usr/bin/snice 2 6 6 /usr/bin/skill 2 6 7 /usr/bin/pgrep 2 6 4 /usr/bin/killall 2 6 7 /usr/bin/curl 2 7 7 /usr/bin/slptool 2 7 8 /usr/bin/ldap* 2 7 7 /usr/bin/whatis 2 7 5 apparmor-2.8.95~2430/utils/aa-autodep.pod0000644000175000017500000000456312277004630017732 0ustar sarnoldsarnold# This publication is intellectual property of Novell Inc. and Canonical # Ltd. Its contents can be duplicated, either in part or in whole, provided # that a copyright label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators # shall be held liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. SUSE LINUX GmbH # and Canonical Ltd. essentially adhere to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-autodep - guess basic AppArmor profile requirements =head1 SYNOPSIS BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] [I<-f>]> =head1 OPTIONS B<-d --dir /path/to/profiles> Specifies where to look for the AppArmor security profile set. Defaults to /etc/apparmor.d. B<-f --force> Overwrites any existing AppArmor profile for the executable with the generated minimal AppArmor profile. =head1 DESCRIPTION B is used to generate a minimal AppArmor profile for a set of executables. This program will generate a profile for binary executable as well as interpreted script programs. At a minimum aa-autodep will provide a base profile containing a base include directive which includes basic profile entries needed by most programs. The profile is generated by recursively calling ldd(1) on the executables listed on the command line. The I<--force> option will overwrite any existing profile for the executable with the newly generated minimal AppArmor profile. =head1 BUGS This program does not perform full static analysis of executables, so the profiles generated are necessarily incomplete. If you find any bugs, please report them at L. =head1 SEE ALSO apparmor(7), apparmor.d(5), aa-complain(1), aa-enforce(1), aa-disable(1), aa_change_hat(2), and L. =cut apparmor-2.8.95~2430/utils/logprof.conf0000644000175000017500000001115512273451120017514 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2004-2006 Novell/SUSE # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ [settings] profiledir = /etc/apparmor.d /etc/subdomain.d inactive_profiledir = /usr/share/apparmor/extra-profiles logfiles = /var/log/audit/audit.log /var/log/syslog /var/log/messages parser = /sbin/apparmor_parser /sbin/subdomain_parser ldd = /usr/bin/ldd logger = /bin/logger /usr/bin/logger # customize how file ownership permissions are presented # 0 - off # 1 - default of what ever mode the log reported # 2 - force the new permissions to be user # 3 - force all perms on the rule to be user default_owner_prompt = 1 # custom directory locations to look for #includes # # each name should be a valid directory containing possible #include # candidate files under the profile dir which by default is /etc/apparmor.d. # # So an entry of my-includes will allow /etc/apparmor.d/my-includes to # be used by the yast UI and profiling tools as a source of #include # files. custom_includes = [repository] distro = ubuntu-intrepid url = http://apparmor.test.opensuse.org/backend/api preferred_user = ubuntu [qualifiers] # things will be painfully broken if bash has a profile /bin/bash = icnu /usr/bin/bash = icnu /bin/ksh = icnu /usr/bin/ksh = icnu /bin/dash = icnu /usr/bin/dash = icnu # these programs can't function if they're confined /bin/mount = u /usr/bin/mount = u /etc/init.d/subdomain = u /sbin/cardmgr = u /usr/sbin/cardmgr = u /sbin/subdomain_parser = u /usr/sbin/subdomain_parser = u /usr/sbin/genprof = u /usr/sbin/logprof = u /usr/lib/YaST2/servers_non_y2/ag_genprof = u /usr/lib/YaST2/servers_non_y2/ag_logprof = u # these ones shouln't have their own profiles /bin/awk = icn /usr/bin/awk = icn /bin/cat = icn /usr/bin/cat = icn /bin/chmod = icn /usr/bin/chmod = icn /bin/chown = icn /usr/bin/chown = icn /bin/cp = icn /usr/bin/cp = icn /bin/gawk = icn /usr/bin/gawk = icn /bin/grep = icn /usr/bin/grep = icn /bin/gunzip = icn /usr/bin/gunzip = icn /bin/gzip = icn /usr/bin/gzip = icn /bin/kill = icn /usr/bin/kill = icn /bin/ln = icn /usr/bin/ln = icn /bin/ls = icn /usr/bin/ls = icn /bin/mkdir = icn /usr/bin/mkdir = icn /bin/mv = icn /usr/bin/mv = icn /bin/readlink = icn /usr/bin/readlink = icn /bin/rm = icn /usr/bin/rm = icn /bin/sed = icn /usr/bin/sed = icn /bin/touch = icn /usr/bin/touch = icn /sbin/killall5 = icn /usr/sbin/killall5 = icn /usr/bin/find = icn /usr/bin/killall = icn /usr/bin/nice = icn /usr/bin/perl = icn /usr/bin/tr = icn [required_hats] ^.+/apache(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT ^.+/httpd(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT [defaulthat] ^.+/apache(|2|2-prefork)$ = DEFAULT_URI ^.+/httpd(|2|2-prefork)$ = DEFAULT_URI [globs] # /foo/bar/lib/libbaz.so -> /foo/bar/lib/lib* /lib/lib[^\/]+so[^\/]*$ = /lib/lib*so* # strip kernel version numbers from kernel module accesses ^/lib/modules/[^\/]+\/ = /lib/modules/*/ # strip pid numbers from /proc accesses ^/proc/\d+/ = /proc/*/ # if it looks like a home directory, glob out the username ^/home/[^\/]+ = /home/* # if they use any perl modules, grant access to all ^/usr/lib/perl5/.+$ = /usr/lib/perl5/** # locale foo ^/usr/lib/locale/.+$ = /usr/lib/locale/** ^/usr/share/locale/.+$ = /usr/share/locale/** # timezone fun ^/usr/share/zoneinfo/.+$ = /usr/share/zoneinfo/** # /foobar/fonts/baz -> /foobar/fonts/** /fonts/.+$ = /fonts/** # turn /foo/bar/baz.8907234 into /foo/bar/baz.* # BUGBUG - this one looked weird because it would suggest a glob for # BUGBUG - libfoo.so.5.6.0 that looks like libfoo.so.5.6.* # \.\d+$ = .* # some various /etc/security poo -- dunno about these ones... ^/etc/security/_[^\/]+$ = /etc/security/* ^/lib/security/pam_filter/[^\/]+$ = /lib/security/pam_filter/* ^/lib/security/pam_[^\/]+\.so$ = /lib/security/pam_*.so ^/etc/pam.d/[^\/]+$ = /etc/pam.d/* ^/etc/profile.d/[^\/]+\.sh$ = /etc/profile.d/*.sh apparmor-2.8.95~2430/utils/aa-complain0000755000175000017500000000224012306150644017303 0ustar sarnoldsarnold#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import apparmor.tools # setup module translations from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_('Switch the given program to complain mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() tool = apparmor.tools.aa_tools('complain', args) #print(args) tool.cmd_complain() apparmor-2.8.95~2430/utils/easyprof/0000755000175000017500000000000012311706710017023 5ustar sarnoldsarnoldapparmor-2.8.95~2430/utils/easyprof/policygroups/0000755000175000017500000000000012311706710021562 5ustar sarnoldsarnoldapparmor-2.8.95~2430/utils/easyprof/policygroups/user-application0000644000175000017500000000060211752130454024766 0ustar sarnoldsarnold# Policy group allowing various writes to standard directories in @{HOMEDIRS} #include owner @{HOMEDIRS}/.cache/@{APPNAME}/ rw, owner @{HOMEDIRS}/.cache/@{APPNAME}/** rwkl, owner @{HOMEDIRS}/.config/@{APPNAME}/ rw, owner @{HOMEDIRS}/.config/@{APPNAME}/** rwkl, owner @{HOMEDIRS}/.local/share/@{APPNAME}/ rw, owner @{HOMEDIRS}/.local/share/@{APPNAME}/** rwkl, apparmor-2.8.95~2430/utils/easyprof/policygroups/opt-application0000644000175000017500000000013611752130454024614 0ustar sarnoldsarnold# Policy group for applications installed in /opt /opt/@{APPNAME}/ r, /opt/@{APPNAME}/** mrk, apparmor-2.8.95~2430/utils/easyprof/templates/0000755000175000017500000000000012311706721021023 5ustar sarnoldsarnoldapparmor-2.8.95~2430/utils/easyprof/templates/sandbox-x0000644000175000017500000000166612277273444022676 0ustar sarnoldsarnold# # Example usage for a program named 'foo' which is installed in /opt/foo # $ aa-easyprof --template=sandbox \ # --template-var="@{APPNAME}=foo" \ # --policy-groups=opt-application,user-application \ # /opt/foo/bin/foo # ###ENDUSAGE### # vim:syntax=apparmor # AppArmor policy for ###NAME### #include ###VAR### ###PROFILEATTACH### { #include #include #include #include audit deny @{HOME}/.Xauthority mrwlk, /etc/passwd r, / r, /**/ r, /usr/** r, /var/lib/dbus/machine-id r, owner @{PROC}/[0-9]*/auxv r, owner @{PROC}/[0-9]*/fd/ r, owner @{PROC}/[0-9]*/environ r, owner @{PROC}/[0-9]*/mounts r, owner @{PROC}/[0-9]*/smaps r, owner @{PROC}/[0-9]*/statm r, owner @{PROC}/[0-9]*/task/[0-9]*/stat r, ###ABSTRACTIONS### ###POLICYGROUPS### ###READS### ###WRITES### } apparmor-2.8.95~2430/utils/easyprof/templates/user-application0000644000175000017500000000106212277273444024240 0ustar sarnoldsarnold# # Example usage for a program named 'foo' which is installed in /opt/foo # $ aa-easyprof --template=user-application \ # --template-var="@{APPNAME}=foo" \ # --policy-groups=opt-application,user-application \ # /opt/foo/bin/foo # ###ENDUSAGE### # vim:syntax=apparmor # AppArmor policy for ###NAME### # ###AUTHOR### # ###COPYRIGHT### # ###COMMENT### #include ###VAR### ###PROFILEATTACH### { #include ###ABSTRACTIONS### ###POLICYGROUPS### ###READS### ###WRITES### } apparmor-2.8.95~2430/utils/easyprof/templates/sandbox0000644000175000017500000000102612277273444022417 0ustar sarnoldsarnold# # Example usage for a program named 'foo' which is installed in /opt/foo # $ aa-easyprof --template=sandbox \ # --template-var="@{APPNAME}=foo" \ # --policy-groups=opt-application,user-application \ # /opt/foo/bin/foo # ###ENDUSAGE### # vim:syntax=apparmor # AppArmor policy for ###NAME### #include ###VAR### ###PROFILEATTACH### { #include / r, /**/ r, /usr/** r, ###ABSTRACTIONS### ###POLICYGROUPS### ###READS### ###WRITES### } apparmor-2.8.95~2430/utils/easyprof/templates/default0000644000175000017500000000056412277273444022413 0ustar sarnoldsarnold# # Example usage: # $ aa-easyprof --policy-groups=user-application /usr/bin/foo # ###ENDUSAGE### # vim:syntax=apparmor # AppArmor policy for ###NAME### # ###AUTHOR### # ###COPYRIGHT### # ###COMMENT### #include ###VAR### ###PROFILEATTACH### { #include ###ABSTRACTIONS### ###POLICYGROUPS### ###READS### ###WRITES### } apparmor-2.8.95~2430/utils/easyprof/README0000644000175000017500000000303411752130454017707 0ustar sarnoldsarnoldAppArmor Easy Profiler ---------------------- aa-easyprof is a standalone CLI application which can also be imported into developer SDKs. See test/test-aa-easyprof.py for an example of how to import this into your SDK. Templates --------- Any number of templates can be used. The user may specify one on the command line or use a system-wide template from /usr/share/apparmor/easyprof/templates. Currently the combination of the user-application and the opt-application and user-application policygroups should achieve a working policy for Ubuntu's Application Review Board: - http://developer.ubuntu.com/publish/my-apps-packages/ Eg: $ aa-easyprof --template=user-application \ --template-var="@{APPNAME}=foo" \ --policy-groups=opt-application,user-application \ /opt/foo/bin/foo Testing ------- Unit tests: $ ./test/test-aa-easyprof.py In source manual testing: $ ./aa-easyprof --templates-dir=./easyprof/templates \ --policy-groups-dir=./easyprof/policygroups \ ... \ /opt/foo/bin/foo Post-install manual testing: $ make DESTDIR=/tmp/test PERLDIR=/tmp/test/usr/share/perl5/Immunix install $ cd /tmp/test $ PYTHONPATH=/tmp/test/usr/local/.../dist-packages ./usr/bin/aa-easyprof \ --templates-dir=/tmp/test/usr/share/apparmor/easyprof/templates \ --policy-groups-dir=/tmp/test/usr/share/apparmor/easyprof/policygroups \ /opt/bin/foo (you may also adjust /tmp/test/etc/apparmor/easyprof.conf to avoid specifying --templates-dir and --policy-groups-dir). apparmor-2.8.95~2430/utils/easyprof/easyprof.conf0000644000175000017500000000026611752130454021532 0ustar sarnoldsarnold# Location of system policygroups POLICYGROUPS_DIR="/usr/share/apparmor/easyprof/policygroups" # Location of system templates TEMPLATES_DIR="/usr/share/apparmor/easyprof/templates" apparmor-2.8.95~2430/utils/aa-autodep0000755000175000017500000000241412306151176017146 0ustar sarnoldsarnold#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import apparmor.tools # setup module translations from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_('Generate a basic AppArmor profile by guessing requirements')) parser.add_argument('--force', action='store_true', default=False, help=_('overwrite existing profile')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() tool = apparmor.tools.aa_tools('autodep', args) tool.cmd_autodep() apparmor-2.8.95~2430/utils/aa-easyprof0000755000175000017500000000731312277273444017352 0ustar sarnoldsarnold#! /usr/bin/env python # ------------------------------------------------------------------ # # Copyright (C) 2011-2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ import apparmor.easyprof from apparmor.easyprof import AppArmorException, error import os import sys if __name__ == "__main__": def usage(): '''Return usage information''' return 'USAGE: %s [options] ' % \ os.path.basename(sys.argv[0]) (opt, args) = apparmor.easyprof.parse_args() binary = None manifest = None m = usage() if opt.show_policy_group and not opt.policy_groups: error("Must specify -p with --show-policy-group") elif not opt.template and not opt.policy_groups and len(args) < 1: error("Must specify full path to binary\n%s" % m) binary = None if len(args) >= 1: binary = args[0] # parse_manifest() returns a list of tuples (binary, options). Create a # list of these profile tuples to support multiple profiles in one manifest profiles = [] if opt.manifest: try: # should hide this in a common function if sys.version_info[0] >= 3: f = open(opt.manifest, "r", encoding="utf-8") else: f = open(opt.manifest, "r") manifest = f.read() except EnvironmentError as e: error("Could not read '%s': %s (%d)\n" % (opt.manifest, os.strerror(e.errno), e.errno)) profiles = apparmor.easyprof.parse_manifest(manifest, opt) else: # fake up a tuple list when processing command line args profiles.append( (binary, opt) ) count = 0 for (binary, options) in profiles: if len(profiles) > 1: count += 1 try: easyp = apparmor.easyprof.AppArmorEasyProfile(binary, options) except AppArmorException as e: error(e.value) except Exception: raise if options.list_templates: apparmor.easyprof.print_basefilenames(easyp.get_templates()) sys.exit(0) elif options.template and options.show_template: files = [os.path.join(easyp.dirs['templates'], options.template)] apparmor.easyprof.print_files(files) sys.exit(0) elif options.list_policy_groups: apparmor.easyprof.print_basefilenames(easyp.get_policy_groups()) sys.exit(0) elif options.policy_groups and options.show_policy_group: for g in options.policy_groups.split(','): files = [os.path.join(easyp.dirs['policygroups'], g)] apparmor.easyprof.print_files(files) sys.exit(0) elif binary == None and not options.profile_name and \ not options.manifest: error("Must specify binary and/or profile name\n%s" % m) params = apparmor.easyprof.gen_policy_params(binary, options) if options.manifest and options.verify_manifest and \ not apparmor.easyprof.verify_manifest(params): error("Manifest file requires review") if options.output_format == "json": sys.stdout.write('%s\n' % easyp.gen_manifest(params)) else: params['no_verify'] = options.no_verify try: easyp.output_policy(params, count, opt.output_directory) except AppArmorException as e: error(e) apparmor-2.8.95~2430/utils/aa-disable.pod0000644000175000017500000000347112305204543017666 0ustar sarnoldsarnold# This publication is intellectual property of Novell Inc. and Canonical # Ltd. Its contents can be duplicated, either in part or in whole, provided # that a copyright label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators # shall be held liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. SUSE LINUX GmbH # and Canonical Ltd. essentially adhere to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-disable - disable an AppArmor security profile =head1 SYNOPSIS BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] [I<-r>]> =head1 OPTIONS B<-d --dir /path/to/profiles> Specifies where to look for the AppArmor security profile set. Defaults to /etc/apparmor.d. =head1 DESCRIPTION B is used to I one or more profiles. This command will unload the profile from the kernel and prevent the profile from being loaded on AppArmor startup. The I and I utilities may be used to to change this behavior. =head1 BUGS If you find any bugs, please report them at L. =head1 SEE ALSO apparmor(7), apparmor.d(5), aa-enforce(1), aa-complain(1), aa_change_hat(2), and L. =cut apparmor-2.8.95~2430/utils/aa-logprof0000755000175000017500000000362612277515713017173 0ustar sarnoldsarnold#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import os import apparmor.aa as apparmor # setup module translations from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_('Process log entries to generate profiles')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-f', '--file', type=str, help=_('path to logfile')) parser.add_argument('-m', '--mark', type=str, help=_('mark in the log to start processing after')) args = parser.parse_args() profiledir = args.dir filename = args.file logmark = args.mark or '' if filename: if not os.path.isfile(filename): raise apparmor.AppArmorException(_('The logfile %s does not exist. Please check the path') % filename) else: apparmor.filename = filename aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) if profiledir: apparmor.profile_dir = apparmor.get_full_path(profiledir) if not os.path.isdir(apparmor.profile_dir): raise apparmor.AppArmorException("%s is not a directory."%profiledir) apparmor.loadincludes() apparmor.do_logprof_pass(logmark) apparmor-2.8.95~2430/utils/aa-audit.pod0000644000175000017500000000165112277004630017372 0ustar sarnoldsarnold=pod =head1 NAME aa-audit - set an AppArmor security profile to I mode. =head1 SYNOPSIS BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] [I<-r>]> =head1 OPTIONS B<-d --dir /path/to/profiles> Specifies where to look for the AppArmor security profile set. Defaults to /etc/apparmor.d. B<-r --remove> Removes the audit mode for the profile. =head1 DESCRIPTION B is used to set one or more profiles to audit mode. In this mode security policy is enforced and all access (successes and failures) are logged to the system log. The I<--remove> option can be used to remove the audit mode for the profile. =head1 BUGS If you find any bugs, please report them at L. =head1 SEE ALSO apparmor(7), apparmor.d(5), aa-enforce(1), aa-complain(1), aa-disable(1), aa_change_hat(2), and L. =cut apparmor-2.8.95~2430/utils/aa-enforce0000755000175000017500000000222112306150644017121 0ustar sarnoldsarnold#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import apparmor.tools # setup module translations from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_('Switch the given program to enforce mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() tool = apparmor.tools.aa_tools('enforce', args) tool.cmd_enforce() apparmor-2.8.95~2430/utils/notify.conf0000644000175000017500000000101311464374150017354 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2010 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ # Set to 'no' to disable AppArmor notifications globally show_notifications="yes" # Only people in use_group can use aa-notify use_group="admin" apparmor-2.8.95~2430/utils/aa-status.pod0000644000175000017500000000614412216646723017621 0ustar sarnoldsarnold# This publication is intellectual property of Novell Inc. and Canonical # Ltd. Its contents can be duplicated, either in part or in whole, provided # that a copyright label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators # shall be held liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. SUSE LINUX GmbH # and Canonical Ltd. essentially adhere to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-status - display various information about the current AppArmor policy. =head1 SYNOPSIS B [option] =head1 DESCRIPTION B will report various aspects of the current state of AppArmor confinement. By default, it displays the same information as if the I<--verbose> argument were given. A sample of what this looks like is: apparmor module is loaded. 110 profiles are loaded. 102 profiles are in enforce mode. 8 profiles are in complain mode. Out of 129 processes running: 13 processes have profiles defined. 8 processes have profiles in enforce mode. 5 processes have profiles in complain mode. Other argument options are provided to report individual aspects, to support being used in scripts. =head1 OPTIONS B accepts only one argument at a time out of: =over 4 =item --enabled returns error code if AppArmor is not enabled. =item --profiled displays the number of loaded AppArmor policies. =item --enforced displays the number of loaded enforcing AppArmor policies. =item --complaining displays the number of loaded non-enforcing AppArmor policies. =item --verbose displays multiple data points about loaded AppArmor policy set (the default action if no arguments are given). =item --help displays a short usage statement. =back =head1 BUGS B must be run as root to read the state of the loaded policy from the apparmor module. It uses the /proc filesystem to determine which processes are confined and so is susceptible to race conditions. Upon exiting, B will set its return value to the following values: =over 4 =item 0 if apparmor is enabled and policy is loaded. =item 1 if apparmor is not enabled/loaded. =item 2 if apparmor is enabled but no policy is loaded. =item 3 if the apparmor control files aren't available under /sys/kernel/security/. =item 4 if the user running the script doesn't have enough privileges to read the apparmor control files. =back If you find any additional bugs, please report them at L. =head1 SEE ALSO apparmor(7), apparmor.d(5), and L. =cut apparmor-2.8.95~2430/utils/aa-cleanprof0000755000175000017500000000237612277515713017475 0ustar sarnoldsarnold#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import apparmor.tools # setup module translations from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_('Cleanup the profiles for the given programs')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) parser.add_argument('-s', '--silent', action='store_true', help=_('Silently overwrite with a clean profile')) args = parser.parse_args() clean = apparmor.tools.aa_tools('cleanprof', args) clean.act() apparmor-2.8.95~2430/utils/aa-sandbox.pod0000644000175000017500000001420412017416733017723 0ustar sarnoldsarnold# This publication is intellectual property of Canonical Ltd. Its contents # can be duplicated, either in part or in whole, provided that a copyright # label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither Canonical Ltd, the authors, nor the translators shall be held # liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. Canonical Ltd # essentially adheres to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-sandbox - AppArmor sandboxing =head1 SYNOPSIS B [option] =head1 DESCRIPTION B provides a mechanism for sandboxing an application using an existing profile or via dynamic profile generation. Please note that while this tool can help with quickly confining an application, its utility is dependent on the quality of the templates, policy groups and abstractions used. Also, this tool may create policy which is less restrictive than creating policy by hand or with B and B. =head1 OPTIONS B accepts the following arguments: =over 4 =item -t TEMPLATE, --template=TEMPLATE Specify the template used to generate a profile. May specify either a system template or a filename for the template to use. If not specified, uses B or B when B<-X> is specified. See aa-easyprof(8) for details. Privileged access is required to load the dynamically generated profile (B will prompt for a password). =item -p POLICYGROUPS, --policy-groups=POLICYGROUPS Specify POLICYGROUPS as a comma-separated list of policy groups. See aa-easyprof(8) for more information on POLICYGROUPS. =item -a ABSTRACTIONS, --abstractions=ABSTRACTIONS Specify ABSTRACTIONS as a comma-separated list of AppArmor abstractions. AppArmor abstractions are located in /etc/apparmor.d/abstractions. See apparmor.d(5) for details. =item -r PATH, --read-path=PATH Specify a PATH to allow reads. May be specified multiple times. If the PATH ends in a '/', then PATH is treated as a directory and reads are allowed to all files under this directory. Can optionally use '/*' at the end of the PATH to only allow reads to files directly in PATH. =item -w PATH, --write-dir=PATH Like --read-path but also allow writes in addition to reads. =item --profile=PROFILE Instead of generating a dynamic profile, specify an existing, loaded profile. This does not require privileged access. =item -X, --with-x Run the sandboxed application in an isolated X server. =item --with-xauthority=XAUTHORITY Specify an Xauthority file to use rather than a dynamically generated one. This is particularly useful in combination with --profile. This option must be used with care to not allow too much access to the sandboxed application. In particular, the profile specified with --profile must add a rule to deny access to ~/.Xauthority for X sandboxing to be effective. Eg: =over audit deny @{HOME}/.Xauthority mrwlk, =back =item --with-xserver=XSERVER Choose the nested XSERVER to use. Supported servers are: B (the default), B and B. xpra uses the Xvfb(1) virtual framebuffer X server while xpra3d uses the Xorg(1) server with the Xdummy (dummy_drv.so) driver. =item --with-clipboard Allow access to the clipboard when using B or B. =item --with-xephyr-geometry=GEOMETRY The starting geometry for the Xephyr(1) server to use. =back =head1 EXAMPLES Use the existing system profile 'firefox' to sandbox /usr/bin/firefox: =over $ aa-sandbox -X --profile=firefox /usr/bin/firefox =back Sandbox xeyes: =over $ aa-sandbox -X /usr/bin/xeyes =back Sandbox glxgears: =over $ aa-sandbox -X --with-xserver=xpra3d /usr/bin/glxgears =back Sandbox uptime: =over $ aa-sandbox --read-path="/proc/*" /usr/bin/uptime =back =head1 NOTES B currently relies on Xsecurity rules based on Xauthority. As such, xhost access controls need to be enabled and server interpreted values for localuser must be removed. One way of achieving this is adding a late running Xsession(5) script of the form: =over # Create an Xauthority file if it doesn't exist [ ! -f "$HOME/.Xauthority" ] && [ -x /usr/bin/xauth ] && xauth generate :0 . trusted > /dev/null # Default to the Xauthority file [ -f "$HOME/.Xauthority" ] && [ -x /usr/bin/xhost ] && [ -x /usr/bin/id ] && xhost -si:localuser:`id -un` > /dev/null =back After adding the above, it is recommended you remove the existing ~/.Xauthority file, then restart your session. =head1 KNOWN LIMITATIONS While B may be useful in certain situations, there are a number of limitations regarding both confinement and usability: =over As mentioned, the quality of the template or the specified profile directly affects the application's confinement. DBus system access is all or nothing and DBus session access is unconditionally allowed. No environment filtering is performed. X server usage has not been fully audited (though simple attacks are believed to be protected against when the system is properly setup. See B, above). Using a nested X server for each application is expensive. Only the old X cursor is available with B and B. The Ubuntu global menu is not currently supported. Gtk and Qt applications should display the non-global menu by default, but applications like Firefox and Thunderbird should be adjusted to disable the global menu. Xpra does not handle screen resizing when hotplugging monitors gracefully. Restarting the sandbox will resolve the issue. =back =head1 BUGS If you find any bugs, please report them to Launchpad at L. =head1 SEE ALSO apparmor(7) apparmor.d(5) aa-easyprof(8) Xorg(1) Xecurity(7) xpra(1) Xvfb(1) Xephyr(1) =cut apparmor-2.8.95~2430/utils/aa-easyprof.pod0000644000175000017500000002177312277273444020136 0ustar sarnoldsarnold# This publication is intellectual property of Canonical Ltd. Its contents # can be duplicated, either in part or in whole, provided that a copyright # label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither Canonical Ltd, the authors, nor the translators shall be held # liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. Canonical Ltd # essentially adheres to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-easyprof - AppArmor profile generation made easy. =head1 SYNOPSIS B [option] =head1 DESCRIPTION B provides an easy to use interface for AppArmor policy generation. B supports the use of templates and policy groups to quickly profile an application. Please note that while this tool can help with policy generation, its utility is dependent on the quality of the templates, policy groups and abstractions used. Also, this tool may create policy which is less restricted than creating policy by hand or with B and B. =head1 OPTIONS B accepts the following arguments: =over 4 =item -t TEMPLATE, --template=TEMPLATE Specify which template to use. May specify either a system template from /usr/share/apparmor/easyprof/templates or a filename for the template to use. If not specified, use /usr/share/apparmor/easyprof/templates/default. =item -p POLICYGROUPS, --policy-groups=POLICYGROUPS Specify POLICY as a comma-separated list of policy groups. See --list-templates for supported policy groups. The available policy groups are in /usr/share/apparmor/easyprof/policy. Policy groups are simply groupings of AppArmor rules or policies. They are similar to AppArmor abstractions, but usually encompass more policy rules. =item -a ABSTRACTIONS, --abstractions=ABSTRACTIONS Specify ABSTRACTIONS as a comma-separated list of AppArmor abstractions. It is usually recommended you use policy groups instead, but this is provided as a convenience. AppArmor abstractions are located in /etc/apparmor.d/abstractions. See apparmor.d(5) for details. =item -r PATH, --read-path=PATH Specify a PATH to allow owner reads. May be specified multiple times. If the PATH ends in a '/', then PATH is treated as a directory and reads are allowed to all files under this directory. Can optionally use '/*' at the end of the PATH to only allow reads to files directly in PATH. =item -w PATH, --write-dir=PATH Like --read-path but also allow owner writes in additions to reads. =item -n NAME, --name=NAME Specify NAME of policy. If not specified, NAME is set to the name of the binary. The NAME of the policy is typically only used for profile meta data and does not specify the AppArmor profile name. =item --profile-name=PROFILENAME Specify the AppArmor profile name. When set, uses 'profile PROFILENAME' in the profile. When set and specifying a binary, uses 'profile PROFILENAME BINARY' in the profile. If not set, the binary will be used as the profile name and profile attachment. =item --template-var="@{VAR}=VALUE" Set VAR to VALUE in the resulting policy. This typically only makes sense if the specified template uses this value. May be specified multiple times. =item --list-templates List available templates. =item --show-template=TEMPLATE Display template specified with --template. =item --templates-dir=PATH Use PATH instead of system templates directory. =item --list-policy-groups List available policy groups. =item --show-policy-group Display policy groups specified with --policy. =item --policy-groups-dir=PATH Use PATH instead of system policy-groups directory. =item --policy-version=VERSION Must be used with --policy-vendor and is used to specify the version of policy groups and templates. When specified, B looks for the subdirectory VENDOR/VERSION within the policy-groups and templates directory. The specified version must be a positive decimal number compatible with the JSON Number type. Eg, when using: =over $ aa-easyprof --templates-dir=/usr/share/apparmor/easyprof/templates \ --policy-groups-dir=/usr/share/apparmor/easyprof/policygroups \ --policy-vendor="foo" \ --policy-version=1.0 =back Then /usr/share/apparmor/easyprof/templates/foo/1.0 will be searched for templates and /usr/share/apparmor/easyprof/policygroups/foo/1.0 for policy groups. =item --policy-vendor=VENDOR Must be used with --policy-version and is used to specify the vendor for policy groups and templates. See --policy-version for more information. =item --author Specify author of the policy. =item --copyright Specify copyright of the policy. =item --comment Specify comment for the policy. =item -m MANIFEST, --manifest=MANIFEST B also supports using a JSON manifest file for specifying options related to policy. Unlike command line arguments, the JSON file may specify multiple profiles. The structure of the JSON is: { "security": { "profiles": { "": { ... attributes specific to this profile ... }, "": { ... } } } } Each profile JSON object (ie, everything under a profile name) may specify any fields related to policy. The "security" JSON container object is optional and may be omitted. An example manifest file demonstrating all fields is: { "security": { "profiles": { "com.example.foo": { "abstractions": [ "audio", "gnome" ], "author": "Your Name", "binary": "/opt/foo/**", "comment": "Unstructured single-line comment", "copyright": "Unstructured single-line copyright statement", "name": "My Foo App", "policy_groups": [ "networking", "user-application" ], "policy_vendor": "somevendor", "policy_version": 1.0, "read_path": [ "/tmp/foo_r", "/tmp/bar_r/" ], "template": "user-application", "template_variables": { "APPNAME": "foo", "VAR1": "bar", "VAR2": "baz" }, "write_path": [ "/tmp/foo_w", "/tmp/bar_w/" ] } } } } A manifest file does not have to include all the fields. Eg, a manifest file for an Ubuntu SDK application might be: { "security": { "profiles": { "com.ubuntu.developer.myusername.MyCoolApp": { "policy_groups": [ "networking", "online-accounts" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "ubuntu-sdk", "template_variables": { "APPNAME": "MyCoolApp", "APPVERSION": "0.1.2" } } } } } =item --verify-manifest When used with --manifest, warn about potentially unsafe definitions in the manifest file. =item --output-format=FORMAT Specify either B (default if unspecified) for AppArmor policy output or B for JSON manifest format. =item --output-directory=DIR Specify output directory for profile. If unspecified, policy is sent to stdout. =back =head1 EXAMPLE Example usage for a program named 'foo' which is installed in /opt/foo: =over $ aa-easyprof --template=user-application --template-var="@{APPNAME}=foo" \ --policy-groups=opt-application,user-application \ /opt/foo/bin/FooApp =back When using a manifest file: =over $ aa-easyprof --manifest=manifest.json =back To output a manifest file based on aa-easyprof arguments: =over $ aa-easyprof --output-format=json \ --author="Your Name" \ --comment="Unstructured single-line comment" \ --copyright="Unstructured single-line copyright statement" \ --name="My Foo App" \ --profile-name="com.example.foo" \ --template="user-application" \ --policy-groups="user-application,networking" \ --abstractions="audio,gnome" \ --read-path="/tmp/foo_r" \ --read-path="/tmp/bar_r/" \ --write-path="/tmp/foo_w" \ --write-path=/tmp/bar_w/ \ --template-var="@{APPNAME}=foo" \ --template-var="@{VAR1}=bar" \ --template-var="@{VAR2}=baz" \ "/opt/foo/**" =back =head1 BUGS If you find any additional bugs, please report them to Launchpad at L. =head1 SEE ALSO apparmor(7) apparmor.d(5) =cut apparmor-2.8.95~2430/utils/test/0000755000175000017500000000000012311706721016154 5ustar sarnoldsarnoldapparmor-2.8.95~2430/utils/test/severity_broken.db0000644000175000017500000002435312277004630021705 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2002-2005 Novell/SUSE # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ # Allow this process to 0wn the machine: CAP_SYS_ADMIN 10 CAP_SYS_CHROOT 10 CAP_SYS_MODULE CAP_SYS_PTRACE 10 CAP_SYS_RAWIO 10 CAP_MAC_ADMIN 10 CAP_MAC_OVERRIDE 10 # Allow other processes to 0wn the machine: CAP_SETPCAP 9 CAP_SETFCAP 9 CAP_CHOWN 9 CAP_FSETID 9 CAP_MKNOD 9 CAP_LINUX_IMMUTABLE 9 CAP_DAC_OVERRIDE 9 CAP_SETGID 9 CAP_SETUID 9 CAP_FOWNER 9 # Denial of service, bypass audit controls, information leak CAP_SYS_TIME 8 CAP_NET_ADMIN 8 CAP_SYS_RESOURCE 8 CAP_KILL 8 CAP_IPC_OWNER 8 CAP_SYS_PACCT 8 CAP_SYS_BOOT 8 CAP_NET_BIND_SERVICE 8 CAP_NET_RAW 8 CAP_SYS_NICE 8 CAP_LEASE 8 CAP_IPC_LOCK 8 CAP_SYS_TTY_CONFIG 8 CAP_AUDIT_CONTROL 8 CAP_AUDIT_WRITE 8 CAP_SYSLOG 8 CAP_WAKE_ALARM 8 CAP_BLOCK_SUSPEND 8 CAP_DAC_READ_SEARCH 7 # unused CAP_NET_BROADCAST 0 # filename r w x # 'hard drives' are generally 4 10 0 /**/lost+found/** 5 5 0 /boot/** 7 10 0 /etc/passwd* 4 8 0 /etc/group* 4 8 0 /etc/shadow* 7 9 0 /etc/shadow* 7 9 0 /home/*/.ssh/** 7 9 0 /home/*/.gnupg/** 5 7 0 /home/** 4 6 0 /srv/** 4 6 0 /proc/** 6 9 0 /proc/sys/kernel/hotplug 2 10 0 /proc/sys/kernel/modprobe 2 10 0 /proc/kallsyms 7 0 0 /sys/** 4 8 0 /sys/power/state 2 8 0 /sys/firmware/** 2 10 0 /dev/pts/* 8 9 0 /dev/ptmx 8 9 0 /dev/pty* 8 9 0 /dev/null 0 0 0 /dev/adbmouse 3 8 0 /dev/ataraid 9 10 0 /dev/zero 0 0 0 /dev/agpgart* 8 10 0 /dev/aio 3 3 0 /dev/cbd/* 5 5 0 /dev/cciss/* 4 10 0 /dev/capi* 4 6 0 /dev/cfs0 4 10 0 /dev/compaq/* 4 10 0 /dev/cdouble* 4 8 0 /dev/cpu** 5 5 0 /dev/cpu**microcode 1 10 0 /dev/double* 4 8 0 /dev/hd* 4 10 0 /dev/sd* 4 10 0 /dev/ida/* 4 10 0 /dev/input/* 4 8 0 /dev/mapper/control 4 10 0 /dev/*mem 8 10 0 /dev/loop* 4 10 0 /dev/lp* 0 4 0 /dev/md* 4 10 0 /dev/msr 4 10 0 /dev/nb* 4 10 0 /dev/ram* 8 10 0 /dev/rd/* 4 10 0 /dev/*random 3 1 0 /dev/sbpcd* 4 0 0 /dev/rtc 6 0 0 /dev/sd* 4 10 0 /dev/sc* 4 10 0 /dev/sg* 4 10 0 /dev/st* 4 10 0 /dev/snd/* 3 8 0 /dev/usb/mouse* 4 6 0 /dev/usb/hid* 4 6 0 /dev/usb/tty* 4 6 0 /dev/tty* 8 9 0 /dev/stderr 0 0 0 /dev/stdin 0 0 0 /dev/stdout 0 0 0 /dev/ubd* 4 10 0 /dev/usbmouse* 4 6 0 /dev/userdma 8 10 0 /dev/vcs* 8 9 0 /dev/xta* 4 10 0 /dev/zero 0 0 0 /dev/inittcl 8 10 0 /dev/log 5 7 0 /etc/fstab 3 8 0 /etc/mtab 3 5 0 /etc/SuSEconfig/* 1 8 0 /etc/X11/* 2 7 0 /etc/X11/xinit/* 2 8 0 /etc/SuSE-release 1 5 0 /etc/issue* 1 3 0 /etc/motd 1 3 0 /etc/aliases.d/* 1 7 0 /etc/cron* 1 9 0 /etc/cups/* 2 7 0 /etc/default/* 3 8 0 /etc/init.d/* 1 10 0 /etc/permissions.d/* 1 8 0 /etc/ppp/* 2 6 0 /etc/ppp/*secrets 8 6 0 /etc/profile.d/* 1 8 0 /etc/skel/* 0 7 0 /etc/sysconfig/* 4 10 0 /etc/xinetd.d/* 1 9 0 /etc/termcap/* 1 4 0 /etc/ld.so.* 1 9 0 /etc/pam.d/* 3 9 0 /etc/udev/* 3 9 0 /etc/insserv.conf 3 6 0 /etc/security/* 1 9 0 /etc/securetty 0 7 0 /etc/sudoers 4 9 0 /etc/hotplug/* 2 10 0 /etc/xinitd.conf 1 9 0 /etc/gpm/* 2 10 0 /etc/ssl/** 2 7 0 /etc/shadow* 5 9 0 /etc/bash.bashrc 1 9 0 /etc/csh.cshrc 1 9 0 /etc/csh.login 1 9 0 /etc/inittab 1 10 0 /etc/profile* 1 9 0 /etc/shells 1 5 0 /etc/alternatives 1 6 0 /etc/sysctl.conf 3 7 0 /etc/dev.d/* 1 8 0 /etc/manpath.config 1 6 0 /etc/permissions* 1 8 0 /etc/evms.conf 3 8 0 /etc/exports 3 8 0 /etc/samba/* 5 8 0 /etc/ssh/* 3 8 0 /etc/ssh/ssh_host_*key 8 8 0 /etc/krb5.conf 4 8 0 /etc/ntp.conf 3 8 0 /etc/auto.* 3 8 0 /etc/postfix/* 3 7 0 /etc/postfix/*passwd* 6 7 0 /etc/postfix/*cert* 6 7 0 /etc/foomatic/* 3 5 0 /etc/printcap 3 5 0 /etc/youservers 4 9 0 /etc/grub.conf 7 10 0 /etc/modules.conf 4 10 0 /etc/resolv.conf 2 7 0 /etc/apache2/** 3 7 0 /etc/apache2/**ssl** 7 7 0 /etc/subdomain.d/** 6 10 0 /etc/apparmor.d/** 6 10 0 /etc/apparmor/** 6 10 0 /var/log/** 3 8 0 /var/adm/SuSEconfig/** 3 8 0 /var/adm/** 3 7 0 /var/lib/rpm/** 4 8 0 /var/run/nscd/* 3 3 0 /var/run/.nscd_socket 3 3 0 /usr/share/doc/** 1 1 0 /usr/share/man/** 3 5 0 /usr/X11/man/** 3 5 0 /usr/share/info/** 2 4 0 /usr/share/java/** 2 5 0 /usr/share/locale/** 2 4 0 /usr/share/sgml/** 2 4 0 /usr/share/YaST2/** 3 9 0 /usr/share/ghostscript/** 3 5 0 /usr/share/terminfo/** 1 8 0 /usr/share/latex2html/** 2 4 0 /usr/share/cups/** 5 6 0 /usr/share/susehelp/** 2 6 0 /usr/share/susehelp/cgi-bin/** 3 7 7 /usr/share/zoneinfo/** 2 7 0 /usr/share/zsh/** 3 6 0 /usr/share/vim/** 3 8 0 /usr/share/groff/** 3 7 0 /usr/share/vnc/** 3 8 0 /usr/share/wallpapers/** 2 4 0 /usr/X11** 3 8 5 /usr/X11*/bin/XFree86 3 8 8 /usr/X11*/bin/Xorg 3 8 8 /usr/X11*/bin/sux 3 8 8 /usr/X11*/bin/xconsole 3 7 7 /usr/X11*/bin/xhost 3 7 7 /usr/X11*/bin/xauth 3 7 7 /usr/X11*/bin/ethereal 3 6 8 /usr/lib/ooo-** 3 6 5 /usr/lib/lsb/** 2 8 8 /usr/lib/pt_chwon 2 8 5 /usr/lib/tcl** 2 5 3 /usr/lib/lib*so* 3 8 4 /usr/lib/iptables/* 2 8 2 /usr/lib/perl5/** 4 10 6 /usr/lib/gconv/* 4 7 4 /usr/lib/locale/** 4 8 0 /usr/lib/jvm/** 5 7 5 /usr/lib/sasl*/** 5 8 4 /usr/lib/jvm-exports/** 5 7 5 /usr/lib/jvm-private/** 5 7 5 /usr/lib/python*/** 5 7 5 /usr/lib/libkrb5* 4 8 4 /usr/lib/postfix/* 4 7 4 /usr/lib/rpm/** 4 8 6 /usr/lib/rpm/gnupg/** 4 9 0 /usr/lib/apache2** 4 7 4 /usr/lib/mailman/** 4 6 4 /usr/bin/ldd 1 7 4 /usr/bin/netcat 5 7 8 /usr/bin/clear 2 6 3 /usr/bin/reset 2 6 3 /usr/bin/tput 2 6 3 /usr/bin/tset 2 6 3 /usr/bin/file 2 6 3 /usr/bin/ftp 3 7 5 /usr/bin/busybox 4 8 6 /usr/bin/rbash 4 8 5 /usr/bin/screen 3 6 5 /usr/bin/getfacl 3 7 4 /usr/bin/setfacl 3 7 9 /usr/bin/*awk* 3 7 7 /usr/bin/sudo 2 9 10 /usr/bin/lsattr 2 6 5 /usr/bin/chattr 2 7 8 /usr/bin/sed 3 7 6 /usr/bin/grep 2 7 2 /usr/bin/chroot 2 6 10 /usr/bin/dircolors 2 9 3 /usr/bin/cut 2 7 2 /usr/bin/du 2 7 3 /usr/bin/env 2 7 2 /usr/bin/head 2 7 2 /usr/bin/tail 2 7 2 /usr/bin/install 2 8 4 /usr/bin/link 2 6 4 /usr/bin/logname 2 6 2 /usr/bin/md5sum 2 8 3 /usr/bin/mkfifo 2 6 10 /usr/bin/nice 2 7 7 /usr/bin/nohup 2 7 7 /usr/bin/printf 2 7 1 /usr/bin/readlink 2 7 3 /usr/bin/seq 2 7 1 /usr/bin/sha1sum 2 8 3 /usr/bin/shred 2 7 3 /usr/bin/sort 2 7 3 /usr/bin/split 2 7 3 /usr/bin/stat 2 7 4 /usr/bin/sum 2 8 3 /usr/bin/tac 2 7 3 /usr/bin/tail 3 8 4 /usr/bin/tee 2 7 3 /usr/bin/test 2 8 4 /usr/bin/touch 2 7 3 /usr/bin/tr 2 8 3 /usr/bin/tsort 2 7 3 /usr/bin/tty 2 7 3 /usr/bin/unexpand 2 7 3 /usr/bin/uniq 2 7 3 /usr/bin/unlink 2 8 4 /usr/bin/uptime 2 7 3 /usr/bin/users 2 8 4 /usr/bin/vdir 2 8 4 /usr/bin/wc 2 7 3 /usr/bin/who 2 8 4 /usr/bin/whoami 2 8 4 /usr/bin/yes 1 6 1 /usr/bin/ed 2 7 5 /usr/bin/red 2 7 4 /usr/bin/find 2 8 5 /usr/bin/xargs 2 7 5 /usr/bin/ispell 2 7 4 /usr/bin/a2p 2 7 5 /usr/bin/perlcc 2 7 5 /usr/bin/perldoc 2 7 5 /usr/bin/pod2* 2 7 5 /usr/bin/prove 2 7 5 /usr/bin/perl 2 10 7 /usr/bin/perl* 2 10 7 /usr/bin/suidperl 2 8 8 /usr/bin/csh 2 8 8 /usr/bin/tcsh 2 8 8 /usr/bin/tree 2 6 5 /usr/bin/last 2 7 5 /usr/bin/lastb 2 7 5 /usr/bin/utmpdump 2 6 5 /usr/bin/alsamixer 2 6 8 /usr/bin/amixer 2 6 8 /usr/bin/amidi 2 6 8 /usr/bin/aoss 2 6 8 /usr/bin/aplay 2 6 8 /usr/bin/aplaymidi 2 6 8 /usr/bin/arecord 2 6 8 /usr/bin/arecordmidi 2 6 8 /usr/bin/aseqnet 2 6 8 /usr/bin/aserver 2 6 8 /usr/bin/iecset 2 6 8 /usr/bin/rview 2 6 5 /usr/bin/ex 2 7 5 /usr/bin/enscript 2 6 5 /usr/bin/genscript 2 6 5 /usr/bin/xdelta 2 6 5 /usr/bin/edit 2 6 5 /usr/bin/vimtutor 2 6 5 /usr/bin/rvim 2 6 5 /usr/bin/vim 2 8 7 /usr/bin/vimdiff 2 8 7 /usr/bin/aspell 2 6 5 /usr/bin/xxd 2 6 5 /usr/bin/spell 2 6 5 /usr/bin/eqn 2 6 5 /usr/bin/eqn2graph 2 6 5 /usr/bin/word-list-compress 2 6 4 /usr/bin/afmtodit 2 6 4 /usr/bin/hpf2dit 2 6 4 /usr/bin/geqn 2 6 4 /usr/bin/grn 2 6 4 /usr/bin/grodvi 2 6 4 /usr/bin/groff 2 6 5 /usr/bin/groffer 2 6 4 /usr/bin/grolj4 2 6 4 /usr/bin/grotty 2 6 4 /usr/bin/gtbl 2 6 4 /usr/bin/pic2graph 2 6 4 /usr/bin/indxbib 2 6 4 /usr/bin/lkbib 2 6 4 /usr/bin/lookbib 2 6 4 /usr/bin/mmroff 2 6 4 /usr/bin/neqn 2 6 4 /usr/bin/pfbtops 2 6 4 /usr/bin/pic 2 6 4 /usr/bin/tfmtodit 2 6 4 /usr/bin/tbl 2 6 4 /usr/bin/post-grohtml 2 6 4 /usr/bin/pre-grohtml 2 6 4 /usr/bin/refer 2 6 4 /usr/bin/soelim 2 6 4 /usr/bin/disable-paste 2 6 6 /usr/bin/troff 2 6 4 /usr/bin/strace-graph 2 6 4 /usr/bin/gpm-root 2 6 7 /usr/bin/hltest 2 6 7 /usr/bin/mev 2 6 6 /usr/bin/mouse-test 2 6 6 /usr/bin/strace 2 8 9 /usr/bin/scsiformat 2 7 10 /usr/bin/lsscsi 2 7 7 /usr/bin/scsiinfo 2 7 7 /usr/bin/sg_* 2 7 7 /usr/bin/build-classpath 2 6 6 /usr/bin/build-classpath-directory 2 6 6 /usr/bin/build-jar-repository 2 6 6 /usr/bin/diff-jars 2 6 6 /usr/bin/jvmjar 2 6 6 /usr/bin/rebuild-jar-repository 2 6 6 /usr/bin/scriptreplay 2 6 5 /usr/bin/cal 2 6 3 /usr/bin/chkdupexe 2 6 5 /usr/bin/col 2 6 4 /usr/bin/colcrt 2 6 4 /usr/bin/colrm 2 6 3 /usr/bin/column 2 6 4 /usr/bin/cytune 2 6 6 /usr/bin/ddate 2 6 3 /usr/bin/fdformat 2 6 6 /usr/bin/getopt 2 8 6 /usr/bin/hexdump 2 6 4 /usr/bin/hostid 2 6 4 /usr/bin/ipcrm 2 7 7 /usr/bin/ipcs 2 7 6 /usr/bin/isosize 2 6 4 /usr/bin/line 2 6 4 /usr/bin/look 2 6 5 /usr/bin/mcookie 2 7 5 /usr/bin/mesg 2 6 4 /usr/bin/namei 2 6 5 /usr/bin/rename 2 6 5 /usr/bin/renice 2 6 7 /usr/bin/rev 2 6 5 /usr/bin/script 2 6 6 /usr/bin/ChangeSymlinks 2 8 8 /usr/bin/setfdprm 2 6 7 /usr/bin/setsid 2 6 3 /usr/bin/setterm 2 6 5 /usr/bin/tailf 2 6 4 /usr/bin/time 2 6 4 /usr/bin/ul 2 6 4 /usr/bin/wall 2 6 5 /usr/bin/whereis 2 6 4 /usr/bin/which 2 6 3 /usr/bin/c_rehash 2 7 6 /usr/bin/openssl 2 8 6 /usr/bin/lsdev 2 6 5 /usr/bin/procinfo 2 6 5 /usr/bin/socklist 2 6 5 /usr/bin/filesize 2 6 3 /usr/bin/linkto 2 6 3 /usr/bin/mkinfodir 2 6 5 /usr/bin/old 2 6 4 /usr/bin/rpmlocate 2 6 5 /usr/bin/safe-rm 2 8 6 /usr/bin/safe-rmdir 2 8 6 /usr/bin/setJava 2 6 1 /usr/bin/vmstat 2 6 4 /usr/bin/top 2 6 6 /usr/bin/pinentry* 2 7 6 /usr/bin/free 2 8 4 /usr/bin/pmap 2 6 5 /usr/bin/slabtop 2 6 4 /usr/bin/tload 2 6 4 /usr/bin/watch 2 6 3 /usr/bin/w 2 6 4 /usr/bin/pstree.x11 2 6 4 /usr/bin/pstree 2 6 4 /usr/bin/snice 2 6 6 /usr/bin/skill 2 6 7 /usr/bin/pgrep 2 6 4 /usr/bin/killall 2 6 7 /usr/bin/curl 2 7 7 /usr/bin/slptool 2 7 8 /usr/bin/ldap* 2 7 7 /usr/bin/whatis 2 7 5 apparmor-2.8.95~2430/utils/test/aa_test.py0000755000175000017500000001455112277216724020171 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import unittest import sys sys.path.append('../') import apparmor.aa import apparmor.logparser class Test(unittest.TestCase): def setUp(self): self.MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC, 'w': apparmor.aamode.AA_MAY_WRITE, 'r': apparmor.aamode.AA_MAY_READ, 'a': apparmor.aamode.AA_MAY_APPEND, 'l': apparmor.aamode.AA_MAY_LINK, 'k': apparmor.aamode.AA_MAY_LOCK, 'm': apparmor.aamode.AA_EXEC_MMAP, 'i': apparmor.aamode.AA_EXEC_INHERIT, 'u': apparmor.aamode.AA_EXEC_UNCONFINED | apparmor.aamode.AA_EXEC_UNSAFE, 'U': apparmor.aamode.AA_EXEC_UNCONFINED, 'p': apparmor.aamode.AA_EXEC_PROFILE | apparmor.aamode.AA_EXEC_UNSAFE, 'P': apparmor.aamode.AA_EXEC_PROFILE, 'c': apparmor.aamode.AA_EXEC_CHILD | apparmor.aamode.AA_EXEC_UNSAFE, 'C': apparmor.aamode.AA_EXEC_CHILD, } def test_loadinclude(self): apparmor.aa.loadincludes() def test_path_globs(self): globs = { '/foo/': '/*/', '/foo': '/*', '/b*': '/*', '/*b': '/*', '/*': '/*', '/*/': '/*/', '/*.foo/': '/*/', '/**.foo/': '/**/', '/foo/*/': '/**/', '/usr/foo/*': '/usr/**', '/usr/foo/**': '/usr/**', '/usr/foo/bar**': '/usr/foo/**', '/usr/foo/**bar': '/usr/foo/**', '/usr/bin/foo**bar': '/usr/bin/**', '/usr/foo/**/bar': '/usr/foo/**/*', '/usr/foo/**/*': '/usr/foo/**', '/usr/foo/*/bar': '/usr/foo/*/*', '/usr/bin/foo*bar': '/usr/bin/*', '/usr/bin/*foo*': '/usr/bin/*', '/usr/foo/*/*': '/usr/foo/**', '/usr/foo/*/**': '/usr/foo/**', '/**': '/**', '/**/': '/**/' } for path in globs.keys(): self.assertEqual(apparmor.aa.glob_path(path), globs[path], 'Unexpected glob generated for path: %s'%path) def test_path_withext_globs(self): globs = { '/foo/bar': '/foo/bar', '/foo/**/bar': '/foo/**/bar', '/foo.bar': '/*.bar', '/*.foo': '/*.foo' , '/usr/*.bar': '/**.bar', '/usr/**.bar': '/**.bar', '/usr/foo**.bar': '/usr/**.bar', '/usr/foo*.bar': '/usr/*.bar', '/usr/fo*oo.bar': '/usr/*.bar', '/usr/*foo*.bar': '/usr/*.bar', '/usr/**foo.bar': '/usr/**.bar', '/usr/*foo.bar': '/usr/*.bar', '/usr/foo.b*': '/usr/*.b*' } for path in globs.keys(): self.assertEqual(apparmor.aa.glob_path_withext(path), globs[path], 'Unexpected glob generated for path: %s'%path) def test_parse_event(self): parser = apparmor.logparser.ReadLog('', '', '', '', '') event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_foo" name=2F686F6D652F7777772F666F6F2E6261722E696E2F68747470646F63732F61707061726D6F722F696D616765732F746573742F696D61676520312E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30' parsed_event = parser.parse_event(event) self.assertEqual(parsed_event['name'], '/home/www/foo.bar.in/httpdocs/apparmor/images/test/image 1.jpg', 'Incorrectly parsed/decoded name') self.assertEqual(parsed_event['profile'], '/usr/sbin/httpd2-prefork//vhost_foo', 'Incorrectly parsed/decode profile name') self.assertEqual(parsed_event['aamode'], 'PERMITTING') self.assertEqual(parsed_event['request_mask'], set(['w', 'a', '::w', '::a'])) #print(parsed_event) #event = 'type=AVC msg=audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name=74657374207370616365 pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0' #parsed_event = apparmor.aa.parse_event(event) #print(parsed_event) event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=666F6F20626172 name="/home/foo/.bash_history" pid=17011 comm="bash" requested_mask="rw" denied_mask="rw" fsuid=0 ouid=1000' parsed_event = parser.parse_event(event) self.assertEqual(parsed_event['name'], '/home/foo/.bash_history', 'Incorrectly parsed/decoded name') self.assertEqual(parsed_event['profile'], 'foo bar', 'Incorrectly parsed/decode profile name') self.assertEqual(parsed_event['aamode'], 'PERMITTING') self.assertEqual(parsed_event['request_mask'], set(['r', 'w', 'a','::r' , '::w', '::a'])) #print(parsed_event) def test_modes_to_string(self): for string in self.MODE_TEST.keys(): mode = self.MODE_TEST[string] self.assertEqual(apparmor.aamode.mode_to_str(mode), string, 'mode is %s and string is %s'%(mode, string)) def test_string_to_modes(self): for string in self.MODE_TEST.keys(): mode = self.MODE_TEST[string] | apparmor.aamode.AA_OTHER(self.MODE_TEST[string]) #print("mode: %s string: %s str_to_mode(string): %s" % (mode, string, apparmor.aamode.str_to_mode(string))) self.assertEqual(mode, apparmor.aamode.str_to_mode(string), 'mode is %s and string is %s'%(mode, string)) if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() apparmor-2.8.95~2430/utils/test/minitools_test.py0000755000175000017500000001654512305204543021616 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import atexit import os import shutil import subprocess import sys import unittest import filecmp import apparmor.aa as apparmor # Path for the program test_path = '/usr/sbin/ntpd' # Path for the target file containing profile local_profilename = './profiles/usr.sbin.ntpd' python_interpreter = 'python' if sys.version_info >= (3, 0): python_interpreter = 'python3' class Test(unittest.TestCase): def test_audit(self): #Set ntpd profile to audit mode and check if it was correctly set str(subprocess.check_output('%s ./../aa-audit -d ./profiles %s'%(python_interpreter, test_path), shell=True)) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit', 'Audit flag could not be set in profile %s'%local_profilename) #Remove audit mode from ntpd profile and check if it was correctly removed subprocess.check_output('%s ./../aa-audit -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename) def test_complain(self): #Set ntpd profile to complain mode and check if it was correctly set subprocess.check_output('%s ./../aa-complain -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) #Set ntpd profile to enforce mode and check if it was correctly set subprocess.check_output('%s ./../aa-complain -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename) # Set audit flag and then complain flag in a profile subprocess.check_output('%s ./../aa-audit -d ./profiles %s'%(python_interpreter, test_path), shell=True) subprocess.check_output('%s ./../aa-complain -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit,complain', 'Complain flag could not be set in profile %s'%local_profilename) #Remove complain flag first i.e. set to enforce mode subprocess.check_output('%s ./../aa-complain -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit', 'Complain flag could not be removed in profile %s'%local_profilename) #Remove audit flag subprocess.check_output('%s ./../aa-audit -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) def test_enforce(self): #Set ntpd profile to complain mode and check if it was correctly set #Set ntpd profile to enforce mode and check if it was correctly set subprocess.check_output('%s ./../aa-enforce -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename) def test_disable(self): #Disable the ntpd profile and check if it was correctly disabled subprocess.check_output('%s ./../aa-disable -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in disable'%local_profilename) def test_autodep(self): pass def test_unconfined(self): output = subprocess.check_output('%s ./../aa-unconfined'%python_interpreter, shell=True) output_force = subprocess.check_output('%s ./../aa-unconfined --paranoid'%python_interpreter, shell=True) self.assertIsNot(output, '', 'Failed to run aa-unconfined') self.assertIsNot(output_force, '', 'Failed to run aa-unconfined in paranoid mode') def test_cleanprof(self): input_file = 'cleanprof_test.in' output_file = 'cleanprof_test.out' #We position the local testfile shutil.copy('./%s'%input_file, './profiles') #Our silly test program whose profile we wish to clean cleanprof_test = '/usr/bin/a/simple/cleanprof/test/profile' subprocess.check_output('%s ./../aa-cleanprof -d ./profiles -s %s' % (python_interpreter, cleanprof_test), shell=True) #Strip off the first line (#modified line) subprocess.check_output('sed -i 1d ./profiles/%s'%(input_file), shell=True) self.assertEqual(filecmp.cmp('./profiles/%s'%input_file, './%s'%output_file, False), True, 'Failed to cleanup profile properly') def clean_profile_dir(): #Wipe the local profiles from the test directory shutil.rmtree('./profiles') if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] if os.path.exists('./profiles'): shutil.rmtree('./profiles') #copy the local profiles to the test directory #Should be the set of cleanprofile shutil.copytree('/etc/apparmor.d', './profiles', symlinks=True) apparmor.profile_dir = './profiles' atexit.register(clean_profile_dir) unittest.main() apparmor-2.8.95~2430/utils/test/severity.db0000644000175000017500000002435512277004630020347 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2002-2005 Novell/SUSE # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ # Allow this process to 0wn the machine: CAP_SYS_ADMIN 10 CAP_SYS_CHROOT 10 CAP_SYS_MODULE 10 CAP_SYS_PTRACE 10 CAP_SYS_RAWIO 10 CAP_MAC_ADMIN 10 CAP_MAC_OVERRIDE 10 # Allow other processes to 0wn the machine: CAP_SETPCAP 9 CAP_SETFCAP 9 CAP_CHOWN 9 CAP_FSETID 9 CAP_MKNOD 9 CAP_LINUX_IMMUTABLE 9 CAP_DAC_OVERRIDE 9 CAP_SETGID 9 CAP_SETUID 9 CAP_FOWNER 9 # Denial of service, bypass audit controls, information leak CAP_SYS_TIME 8 CAP_NET_ADMIN 8 CAP_SYS_RESOURCE 8 CAP_KILL 8 CAP_IPC_OWNER 8 CAP_SYS_PACCT 8 CAP_SYS_BOOT 8 CAP_NET_BIND_SERVICE 8 CAP_NET_RAW 8 CAP_SYS_NICE 8 CAP_LEASE 8 CAP_IPC_LOCK 8 CAP_SYS_TTY_CONFIG 8 CAP_AUDIT_CONTROL 8 CAP_AUDIT_WRITE 8 CAP_SYSLOG 8 CAP_WAKE_ALARM 8 CAP_BLOCK_SUSPEND 8 CAP_DAC_READ_SEARCH 7 # unused CAP_NET_BROADCAST 0 # filename r w x # 'hard drives' are generally 4 10 0 /**/lost+found/** 5 5 0 /boot/** 7 10 0 /etc/passwd* 4 8 0 /etc/group* 4 8 0 /etc/shadow* 7 9 0 /etc/shadow* 7 9 0 /home/*/.ssh/** 7 9 0 /home/*/.gnupg/** 5 7 0 /home/** 4 6 0 /srv/** 4 6 0 /proc/** 6 9 0 /proc/sys/kernel/hotplug 2 10 0 /proc/sys/kernel/modprobe 2 10 0 /proc/kallsyms 7 0 0 /sys/** 4 8 0 /sys/power/state 2 8 0 /sys/firmware/** 2 10 0 /dev/pts/* 8 9 0 /dev/ptmx 8 9 0 /dev/pty* 8 9 0 /dev/null 0 0 0 /dev/adbmouse 3 8 0 /dev/ataraid 9 10 0 /dev/zero 0 0 0 /dev/agpgart* 8 10 0 /dev/aio 3 3 0 /dev/cbd/* 5 5 0 /dev/cciss/* 4 10 0 /dev/capi* 4 6 0 /dev/cfs0 4 10 0 /dev/compaq/* 4 10 0 /dev/cdouble* 4 8 0 /dev/cpu** 5 5 0 /dev/cpu**microcode 1 10 0 /dev/double* 4 8 0 /dev/hd* 4 10 0 /dev/sd* 4 10 0 /dev/ida/* 4 10 0 /dev/input/* 4 8 0 /dev/mapper/control 4 10 0 /dev/*mem 8 10 0 /dev/loop* 4 10 0 /dev/lp* 0 4 0 /dev/md* 4 10 0 /dev/msr 4 10 0 /dev/nb* 4 10 0 /dev/ram* 8 10 0 /dev/rd/* 4 10 0 /dev/*random 3 1 0 /dev/sbpcd* 4 0 0 /dev/rtc 6 0 0 /dev/sd* 4 10 0 /dev/sc* 4 10 0 /dev/sg* 4 10 0 /dev/st* 4 10 0 /dev/snd/* 3 8 0 /dev/usb/mouse* 4 6 0 /dev/usb/hid* 4 6 0 /dev/usb/tty* 4 6 0 /dev/tty* 8 9 0 /dev/stderr 0 0 0 /dev/stdin 0 0 0 /dev/stdout 0 0 0 /dev/ubd* 4 10 0 /dev/usbmouse* 4 6 0 /dev/userdma 8 10 0 /dev/vcs* 8 9 0 /dev/xta* 4 10 0 /dev/zero 0 0 0 /dev/inittcl 8 10 0 /dev/log 5 7 0 /etc/fstab 3 8 0 /etc/mtab 3 5 0 /etc/SuSEconfig/* 1 8 0 /etc/X11/* 2 7 0 /etc/X11/xinit/* 2 8 0 /etc/SuSE-release 1 5 0 /etc/issue* 1 3 0 /etc/motd 1 3 0 /etc/aliases.d/* 1 7 0 /etc/cron* 1 9 0 /etc/cups/* 2 7 0 /etc/default/* 3 8 0 /etc/init.d/* 1 10 0 /etc/permissions.d/* 1 8 0 /etc/ppp/* 2 6 0 /etc/ppp/*secrets 8 6 0 /etc/profile.d/* 1 8 0 /etc/skel/* 0 7 0 /etc/sysconfig/* 4 10 0 /etc/xinetd.d/* 1 9 0 /etc/termcap/* 1 4 0 /etc/ld.so.* 1 9 0 /etc/pam.d/* 3 9 0 /etc/udev/* 3 9 0 /etc/insserv.conf 3 6 0 /etc/security/* 1 9 0 /etc/securetty 0 7 0 /etc/sudoers 4 9 0 /etc/hotplug/* 2 10 0 /etc/xinitd.conf 1 9 0 /etc/gpm/* 2 10 0 /etc/ssl/** 2 7 0 /etc/shadow* 5 9 0 /etc/bash.bashrc 1 9 0 /etc/csh.cshrc 1 9 0 /etc/csh.login 1 9 0 /etc/inittab 1 10 0 /etc/profile* 1 9 0 /etc/shells 1 5 0 /etc/alternatives 1 6 0 /etc/sysctl.conf 3 7 0 /etc/dev.d/* 1 8 0 /etc/manpath.config 1 6 0 /etc/permissions* 1 8 0 /etc/evms.conf 3 8 0 /etc/exports 3 8 0 /etc/samba/* 5 8 0 /etc/ssh/* 3 8 0 /etc/ssh/ssh_host_*key 8 8 0 /etc/krb5.conf 4 8 0 /etc/ntp.conf 3 8 0 /etc/auto.* 3 8 0 /etc/postfix/* 3 7 0 /etc/postfix/*passwd* 6 7 0 /etc/postfix/*cert* 6 7 0 /etc/foomatic/* 3 5 0 /etc/printcap 3 5 0 /etc/youservers 4 9 0 /etc/grub.conf 7 10 0 /etc/modules.conf 4 10 0 /etc/resolv.conf 2 7 0 /etc/apache2/** 3 7 0 /etc/apache2/**ssl** 7 7 0 /etc/subdomain.d/** 6 10 0 /etc/apparmor.d/** 6 10 0 /etc/apparmor/** 6 10 0 /var/log/** 3 8 0 /var/adm/SuSEconfig/** 3 8 0 /var/adm/** 3 7 0 /var/lib/rpm/** 4 8 0 /var/run/nscd/* 3 3 0 /var/run/.nscd_socket 3 3 0 /usr/share/doc/** 1 1 0 /usr/share/man/** 3 5 0 /usr/X11/man/** 3 5 0 /usr/share/info/** 2 4 0 /usr/share/java/** 2 5 0 /usr/share/locale/** 2 4 0 /usr/share/sgml/** 2 4 0 /usr/share/YaST2/** 3 9 0 /usr/share/ghostscript/** 3 5 0 /usr/share/terminfo/** 1 8 0 /usr/share/latex2html/** 2 4 0 /usr/share/cups/** 5 6 0 /usr/share/susehelp/** 2 6 0 /usr/share/susehelp/cgi-bin/** 3 7 7 /usr/share/zoneinfo/** 2 7 0 /usr/share/zsh/** 3 6 0 /usr/share/vim/** 3 8 0 /usr/share/groff/** 3 7 0 /usr/share/vnc/** 3 8 0 /usr/share/wallpapers/** 2 4 0 /usr/X11** 3 8 5 /usr/X11*/bin/XFree86 3 8 8 /usr/X11*/bin/Xorg 3 8 8 /usr/X11*/bin/sux 3 8 8 /usr/X11*/bin/xconsole 3 7 7 /usr/X11*/bin/xhost 3 7 7 /usr/X11*/bin/xauth 3 7 7 /usr/X11*/bin/ethereal 3 6 8 /usr/lib/ooo-** 3 6 5 /usr/lib/lsb/** 2 8 8 /usr/lib/pt_chwon 2 8 5 /usr/lib/tcl** 2 5 3 /usr/lib/lib*so* 3 8 4 /usr/lib/iptables/* 2 8 2 /usr/lib/perl5/** 4 10 6 /usr/lib/gconv/* 4 7 4 /usr/lib/locale/** 4 8 0 /usr/lib/jvm/** 5 7 5 /usr/lib/sasl*/** 5 8 4 /usr/lib/jvm-exports/** 5 7 5 /usr/lib/jvm-private/** 5 7 5 /usr/lib/python*/** 5 7 5 /usr/lib/libkrb5* 4 8 4 /usr/lib/postfix/* 4 7 4 /usr/lib/rpm/** 4 8 6 /usr/lib/rpm/gnupg/** 4 9 0 /usr/lib/apache2** 4 7 4 /usr/lib/mailman/** 4 6 4 /usr/bin/ldd 1 7 4 /usr/bin/netcat 5 7 8 /usr/bin/clear 2 6 3 /usr/bin/reset 2 6 3 /usr/bin/tput 2 6 3 /usr/bin/tset 2 6 3 /usr/bin/file 2 6 3 /usr/bin/ftp 3 7 5 /usr/bin/busybox 4 8 6 /usr/bin/rbash 4 8 5 /usr/bin/screen 3 6 5 /usr/bin/getfacl 3 7 4 /usr/bin/setfacl 3 7 9 /usr/bin/*awk* 3 7 7 /usr/bin/sudo 2 9 10 /usr/bin/lsattr 2 6 5 /usr/bin/chattr 2 7 8 /usr/bin/sed 3 7 6 /usr/bin/grep 2 7 2 /usr/bin/chroot 2 6 10 /usr/bin/dircolors 2 9 3 /usr/bin/cut 2 7 2 /usr/bin/du 2 7 3 /usr/bin/env 2 7 2 /usr/bin/head 2 7 2 /usr/bin/tail 2 7 2 /usr/bin/install 2 8 4 /usr/bin/link 2 6 4 /usr/bin/logname 2 6 2 /usr/bin/md5sum 2 8 3 /usr/bin/mkfifo 2 6 10 /usr/bin/nice 2 7 7 /usr/bin/nohup 2 7 7 /usr/bin/printf 2 7 1 /usr/bin/readlink 2 7 3 /usr/bin/seq 2 7 1 /usr/bin/sha1sum 2 8 3 /usr/bin/shred 2 7 3 /usr/bin/sort 2 7 3 /usr/bin/split 2 7 3 /usr/bin/stat 2 7 4 /usr/bin/sum 2 8 3 /usr/bin/tac 2 7 3 /usr/bin/tail 3 8 4 /usr/bin/tee 2 7 3 /usr/bin/test 2 8 4 /usr/bin/touch 2 7 3 /usr/bin/tr 2 8 3 /usr/bin/tsort 2 7 3 /usr/bin/tty 2 7 3 /usr/bin/unexpand 2 7 3 /usr/bin/uniq 2 7 3 /usr/bin/unlink 2 8 4 /usr/bin/uptime 2 7 3 /usr/bin/users 2 8 4 /usr/bin/vdir 2 8 4 /usr/bin/wc 2 7 3 /usr/bin/who 2 8 4 /usr/bin/whoami 2 8 4 /usr/bin/yes 1 6 1 /usr/bin/ed 2 7 5 /usr/bin/red 2 7 4 /usr/bin/find 2 8 5 /usr/bin/xargs 2 7 5 /usr/bin/ispell 2 7 4 /usr/bin/a2p 2 7 5 /usr/bin/perlcc 2 7 5 /usr/bin/perldoc 2 7 5 /usr/bin/pod2* 2 7 5 /usr/bin/prove 2 7 5 /usr/bin/perl 2 10 7 /usr/bin/perl* 2 10 7 /usr/bin/suidperl 2 8 8 /usr/bin/csh 2 8 8 /usr/bin/tcsh 2 8 8 /usr/bin/tree 2 6 5 /usr/bin/last 2 7 5 /usr/bin/lastb 2 7 5 /usr/bin/utmpdump 2 6 5 /usr/bin/alsamixer 2 6 8 /usr/bin/amixer 2 6 8 /usr/bin/amidi 2 6 8 /usr/bin/aoss 2 6 8 /usr/bin/aplay 2 6 8 /usr/bin/aplaymidi 2 6 8 /usr/bin/arecord 2 6 8 /usr/bin/arecordmidi 2 6 8 /usr/bin/aseqnet 2 6 8 /usr/bin/aserver 2 6 8 /usr/bin/iecset 2 6 8 /usr/bin/rview 2 6 5 /usr/bin/ex 2 7 5 /usr/bin/enscript 2 6 5 /usr/bin/genscript 2 6 5 /usr/bin/xdelta 2 6 5 /usr/bin/edit 2 6 5 /usr/bin/vimtutor 2 6 5 /usr/bin/rvim 2 6 5 /usr/bin/vim 2 8 7 /usr/bin/vimdiff 2 8 7 /usr/bin/aspell 2 6 5 /usr/bin/xxd 2 6 5 /usr/bin/spell 2 6 5 /usr/bin/eqn 2 6 5 /usr/bin/eqn2graph 2 6 5 /usr/bin/word-list-compress 2 6 4 /usr/bin/afmtodit 2 6 4 /usr/bin/hpf2dit 2 6 4 /usr/bin/geqn 2 6 4 /usr/bin/grn 2 6 4 /usr/bin/grodvi 2 6 4 /usr/bin/groff 2 6 5 /usr/bin/groffer 2 6 4 /usr/bin/grolj4 2 6 4 /usr/bin/grotty 2 6 4 /usr/bin/gtbl 2 6 4 /usr/bin/pic2graph 2 6 4 /usr/bin/indxbib 2 6 4 /usr/bin/lkbib 2 6 4 /usr/bin/lookbib 2 6 4 /usr/bin/mmroff 2 6 4 /usr/bin/neqn 2 6 4 /usr/bin/pfbtops 2 6 4 /usr/bin/pic 2 6 4 /usr/bin/tfmtodit 2 6 4 /usr/bin/tbl 2 6 4 /usr/bin/post-grohtml 2 6 4 /usr/bin/pre-grohtml 2 6 4 /usr/bin/refer 2 6 4 /usr/bin/soelim 2 6 4 /usr/bin/disable-paste 2 6 6 /usr/bin/troff 2 6 4 /usr/bin/strace-graph 2 6 4 /usr/bin/gpm-root 2 6 7 /usr/bin/hltest 2 6 7 /usr/bin/mev 2 6 6 /usr/bin/mouse-test 2 6 6 /usr/bin/strace 2 8 9 /usr/bin/scsiformat 2 7 10 /usr/bin/lsscsi 2 7 7 /usr/bin/scsiinfo 2 7 7 /usr/bin/sg_* 2 7 7 /usr/bin/build-classpath 2 6 6 /usr/bin/build-classpath-directory 2 6 6 /usr/bin/build-jar-repository 2 6 6 /usr/bin/diff-jars 2 6 6 /usr/bin/jvmjar 2 6 6 /usr/bin/rebuild-jar-repository 2 6 6 /usr/bin/scriptreplay 2 6 5 /usr/bin/cal 2 6 3 /usr/bin/chkdupexe 2 6 5 /usr/bin/col 2 6 4 /usr/bin/colcrt 2 6 4 /usr/bin/colrm 2 6 3 /usr/bin/column 2 6 4 /usr/bin/cytune 2 6 6 /usr/bin/ddate 2 6 3 /usr/bin/fdformat 2 6 6 /usr/bin/getopt 2 8 6 /usr/bin/hexdump 2 6 4 /usr/bin/hostid 2 6 4 /usr/bin/ipcrm 2 7 7 /usr/bin/ipcs 2 7 6 /usr/bin/isosize 2 6 4 /usr/bin/line 2 6 4 /usr/bin/look 2 6 5 /usr/bin/mcookie 2 7 5 /usr/bin/mesg 2 6 4 /usr/bin/namei 2 6 5 /usr/bin/rename 2 6 5 /usr/bin/renice 2 6 7 /usr/bin/rev 2 6 5 /usr/bin/script 2 6 6 /usr/bin/ChangeSymlinks 2 8 8 /usr/bin/setfdprm 2 6 7 /usr/bin/setsid 2 6 3 /usr/bin/setterm 2 6 5 /usr/bin/tailf 2 6 4 /usr/bin/time 2 6 4 /usr/bin/ul 2 6 4 /usr/bin/wall 2 6 5 /usr/bin/whereis 2 6 4 /usr/bin/which 2 6 3 /usr/bin/c_rehash 2 7 6 /usr/bin/openssl 2 8 6 /usr/bin/lsdev 2 6 5 /usr/bin/procinfo 2 6 5 /usr/bin/socklist 2 6 5 /usr/bin/filesize 2 6 3 /usr/bin/linkto 2 6 3 /usr/bin/mkinfodir 2 6 5 /usr/bin/old 2 6 4 /usr/bin/rpmlocate 2 6 5 /usr/bin/safe-rm 2 8 6 /usr/bin/safe-rmdir 2 8 6 /usr/bin/setJava 2 6 1 /usr/bin/vmstat 2 6 4 /usr/bin/top 2 6 6 /usr/bin/pinentry* 2 7 6 /usr/bin/free 2 8 4 /usr/bin/pmap 2 6 5 /usr/bin/slabtop 2 6 4 /usr/bin/tload 2 6 4 /usr/bin/watch 2 6 3 /usr/bin/w 2 6 4 /usr/bin/pstree.x11 2 6 4 /usr/bin/pstree 2 6 4 /usr/bin/snice 2 6 6 /usr/bin/skill 2 6 7 /usr/bin/pgrep 2 6 4 /usr/bin/killall 2 6 7 /usr/bin/curl 2 7 7 /usr/bin/slptool 2 7 8 /usr/bin/ldap* 2 7 7 /usr/bin/whatis 2 7 5 apparmor-2.8.95~2430/utils/test/logprof.conf0000644000175000017500000001004712277004630020476 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2004-2006 Novell/SUSE # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ [settings] profiledir = /etc/apparmor.d /etc/subdomain.d inactive_profiledir = /usr/share/doc/apparmor-profiles/extras logfiles = /var/log/audit/audit.log /var/log/syslog /var/log/messages parser = /sbin/apparmor_parser /sbin/subdomain_parser ldd = /usr/bin/ldd logger = /bin/logger /usr/bin/logger # customize how file ownership permissions are presented # 0 - off # 1 - default of what ever mode the log reported # 2 - force the new permissions to be user # 3 - force all perms on the rule to be user default_owner_prompt = 1 # custom directory locations to look for #includes # # each name should be a valid directory containing possible #include # candidate files under the profile dir which by default is /etc/apparmor.d. # # So an entry of my-includes will allow /etc/apparmor.d/my-includes to # be used by the yast UI and profiling tools as a source of #include # files. custom_includes = [repository] distro = ubuntu-intrepid url = http://apparmor.test.opensuse.org/backend/api preferred_user = ubuntu [qualifiers] # things will be painfully broken if bash has a profile /bin/bash = icnu /bin/ksh = icnu /bin/dash = icnu # these programs can't function if they're confined /bin/mount = u /etc/init.d/subdomain = u /sbin/cardmgr = u /sbin/subdomain_parser = u /usr/sbin/genprof = u /usr/sbin/logprof = u /usr/lib/YaST2/servers_non_y2/ag_genprof = u /usr/lib/YaST2/servers_non_y2/ag_logprof = u # these ones shouln't have their own profiles /bin/awk = icn /bin/cat = icn /bin/chmod = icn /bin/chown = icn /bin/cp = icn /bin/gawk = icn /bin/grep = icn /bin/gunzip = icn /bin/gzip = icn /bin/kill = icn /bin/ln = icn /bin/ls = icn /bin/mkdir = icn /bin/mv = icn /bin/readlink = icn /bin/rm = icn /bin/sed = icn /bin/touch = icn /sbin/killall5 = icn /usr/bin/find = icn /usr/bin/killall = icn /usr/bin/nice = icn /usr/bin/perl = icn /usr/bin/tr = icn [required_hats] ^.+/apache(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT ^.+/httpd(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT [defaulthat] ^.+/apache(|2|2-prefork)$ = DEFAULT_URI ^.+/httpd(|2|2-prefork)$ = DEFAULT_URI [globs] # /foo/bar/lib/libbaz.so -> /foo/bar/lib/lib* /lib/lib[^\/]+so[^\/]*$ = /lib/lib*so* # strip kernel version numbers from kernel module accesses ^/lib/modules/[^\/]+\/ = /lib/modules/*/ # strip pid numbers from /proc accesses ^/proc/\d+/ = /proc/*/ # if it looks like a home directory, glob out the username ^/home/[^\/]+ = /home/* # if they use any perl modules, grant access to all ^/usr/lib/perl5/.+$ = /usr/lib/perl5/** # locale foo ^/usr/lib/locale/.+$ = /usr/lib/locale/** ^/usr/share/locale/.+$ = /usr/share/locale/** # timezone fun ^/usr/share/zoneinfo/.+$ = /usr/share/zoneinfo/** # /foobar/fonts/baz -> /foobar/fonts/** /fonts/.+$ = /fonts/** # turn /foo/bar/baz.8907234 into /foo/bar/baz.* # BUGBUG - this one looked weird because it would suggest a glob for # BUGBUG - libfoo.so.5.6.0 that looks like libfoo.so.5.6.* # \.\d+$ = .* # some various /etc/security poo -- dunno about these ones... ^/etc/security/_[^\/]+$ = /etc/security/* ^/lib/security/pam_filter/[^\/]+$ = /lib/security/pam_filter/* ^/lib/security/pam_[^\/]+\.so$ = /lib/security/pam_*.so ^/etc/pam.d/[^\/]+$ = /etc/pam.d/* ^/etc/profile.d/[^\/]+\.sh$ = /etc/profile.d/*.sh apparmor-2.8.95~2430/utils/test/test-aa-decode.py0000755000175000017500000002120112303753407021310 0ustar sarnoldsarnold#! /usr/bin/env python # ------------------------------------------------------------------ # # Copyright (C) 2011-2012 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ import os import signal import subprocess import tempfile import unittest # The locationg of the aa-decode utility can be overridden by setting # the APPARMOR_DECODE environment variable; this is useful for running # these tests in an installed environment aadecode_bin = "../aa-decode" # http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html # This is needed so that the subprocesses that produce endless output # actually quit when the reader goes away. def subprocess_setup(): # Python installs a SIGPIPE handler by default. This is usually not what # non-Python subprocesses expect. signal.signal(signal.SIGPIPE, signal.SIG_DFL) def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, stdin = None, timeout = None): '''Try to execute given command (array) and return its stdout, or return a textual error if it failed.''' try: sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup) except OSError as e: return [127, str(e)] out, outerr = sp.communicate(input) # Handle redirection of stdout if out == None: out = b'' # Handle redirection of stderr if outerr == None: outerr = b'' return [sp.returncode, out.decode('utf-8') + outerr.decode('utf-8')] def mkstemp_fill(contents, suffix='', prefix='tst-aadecode-', dir=None): '''As tempfile.mkstemp does, return a (file, name) pair, but with prefilled contents.''' handle, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir) os.close(handle) handle = open(name, "w+") handle.write(contents) handle.flush() handle.seek(0) return handle, name class AADecodeTest(unittest.TestCase): def setUp(self): self.tmpfile = None def tearDown(self): if self.tmpfile and os.path.exists(self.tmpfile): os.remove(self.tmpfile) def test_help(self): '''Test --help argument''' expected = 0 rc, report = cmd([aadecode_bin, "--help"]) result = 'Got exit code %d, expected %d\n' % (rc, expected) self.assertEqual(expected, rc, result + report) def _run_file_test(self, content, expected_list): '''test case helper function; takes log content and a list of expected strings as arguments''' expected = 0 (f, self.tmpfile) = mkstemp_fill(content) rc, report = cmd([aadecode_bin], stdin=f) result = 'Got exit code %d, expected %d\n' % (rc, expected) self.assertEqual(expected, rc, result + report) for expected_string in expected_list: result = 'could not find expected %s in output:\n' % (expected_string) self.assertIn(expected_string, report, result + report) f.close() def test_simple_decode(self): '''Test simple decode on command line''' expected = 0 expected_output = 'Decoded: /tmp/foo bar' test_code = '2F746D702F666F6F20626172' rc, report = cmd([aadecode_bin, test_code]) result = 'Got exit code %d, expected %d\n' % (rc, expected) self.assertEqual(expected, rc, result + report) result = 'Got output "%s", expected "%s"\n' % (report, expected_output) self.assertIn(expected_output, report, result + report) def test_simple_filter(self): '''test simple decoding of the name argument''' expected_string = 'name="/tmp/foo bar"' content = \ '''type=AVC msg=audit(1348982151.183:2934): apparmor="DENIED" operation="open" parent=30751 profile="/usr/lib/firefox/firefox{,*[^s] [^h]}" name=2F746D702F666F6F20626172 pid=30833 comm="plugin-containe" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 ''' self._run_file_test(content, [expected_string]) def test_simple_multiline(self): '''test simple multiline decoding of the name argument''' expected_strings = ['ses=4294967295 new ses=2762', 'name="/tmp/foo bar"', 'name="/home/steve/tmp/my test file"'] content = \ ''' type=LOGIN msg=audit(1348980001.155:2925): login pid=17875 uid=0 old auid=4294967295 new auid=0 old ses=4294967295 new ses=2762 type=AVC msg=audit(1348982151.183:2934): apparmor="DENIED" operation="open" parent=30751 profile="/usr/lib/firefox/firefox{,*[^s] [^h]}" name=2F746D702F666F6F20626172 pid=30833 comm="plugin-containe" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 type=AVC msg=audit(1348982148.195:2933): apparmor="DENIED" operation="file_lock" parent=5490 profile="/usr/lib/firefox/firefox{,*[^s][^h]}" name=2F686F6D652F73746576652F746D702F6D7920746573742066696C65 pid=30737 comm="firefox" requested_mask="k" denied_mask="k" fsuid=1000 ouid=1000 ''' self._run_file_test(content, expected_strings) def test_simple_profile(self): '''test simple decoding of the profile argument''' '''Example take from LP: #897957''' expected_strings = ['name="/lib/x86_64-linux-gnu/libdl-2.13.so"', 'profile="/test space"'] content = \ '''[289763.843292] type=1400 audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=2F74657374207370616365 name="/lib/x86_64-linux-gnu/libdl-2.13.so" pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 ''' self._run_file_test(content, expected_strings) def test_simple_profile2(self): '''test simple decoding of name and profile argument''' '''Example take from LP: #897957''' expected_strings = ['name="/home/steve/tmp/my test file"', 'profile="/home/steve/tmp/my prog.sh"'] content = \ '''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile=2F686F6D652F73746576652F746D702F6D792070726F672E7368 name=2F686F6D652F73746576652F746D702F6D7920746573742066696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 ''' self._run_file_test(content, expected_strings) def test_simple_embedded_carat(self): '''test simple decoding of embedded ^ in files''' expected_strings = ['name="/home/steve/tmp/my test ^file"'] content = \ '''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile="/usr/bin/test_profile" name=2F686F6D652F73746576652F746D702F6D792074657374205E66696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 ''' self._run_file_test(content, expected_strings) def test_simple_embedded_backslash_carat(self): '''test simple decoding of embedded \^ in files''' expected_strings = ['name="/home/steve/tmp/my test \^file"'] content = \ '''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile="/usr/bin/test_profile" name=2F686F6D652F73746576652F746D702F6D792074657374205C5E66696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 ''' self._run_file_test(content, expected_strings) def test_simple_embedded_singlequote(self): '''test simple decoding of embedded \' in files''' expected_strings = ['name="/home/steve/tmp/my test \'file"'] content = \ '''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile="/usr/bin/test_profile" name=2F686F6D652F73746576652F746D702F6D792074657374202766696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 ''' self._run_file_test(content, expected_strings) def test_simple_encoded_nonpath_profiles(self): '''test simple decoding of nonpath profiles''' expected_strings = ['name="/lib/x86_64-linux-gnu/libdl-2.13.so"', 'profile="test space"'] content = \ '''[289763.843292] type=1400 audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name="/lib/x86_64-linux-gnu/libdl-2.13.so" pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 ''' self._run_file_test(content, expected_strings) # # Main # if __name__ == '__main__': if 'APPARMOR_DECODE' in os.environ: aadecode_bin = os.environ['APPARMOR_DECODE'] unittest.main() apparmor-2.8.95~2430/utils/test/severity_test.py0000755000175000017500000000727012277014357021460 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import os import shutil import sys import unittest sys.path.append('../') import apparmor.severity as severity from apparmor.common import AppArmorException class Test(unittest.TestCase): def setUp(self): #copy the local profiles to the test directory if os.path.exists('./profiles'): shutil.rmtree('./profiles') shutil.copytree('/etc/apparmor.d/', './profiles/', symlinks=True) def tearDown(self): #Wipe the local profiles from the test directory shutil.rmtree('./profiles') def testRank_Test(self): sev_db = severity.Severity('severity.db') rank = sev_db.rank('/usr/bin/whatis', 'x') self.assertEqual(rank, 5, 'Wrong rank') rank = sev_db.rank('/etc', 'x') self.assertEqual(rank, 10, 'Wrong rank') rank = sev_db.rank('/dev/doublehit', 'x') self.assertEqual(rank, 0, 'Wrong rank') rank = sev_db.rank('/dev/doublehit', 'rx') self.assertEqual(rank, 4, 'Wrong rank') rank = sev_db.rank('/dev/doublehit', 'rwx') self.assertEqual(rank, 8, 'Wrong rank') rank = sev_db.rank('/dev/tty10', 'rwx') self.assertEqual(rank, 9, 'Wrong rank') rank = sev_db.rank('/var/adm/foo/**', 'rx') self.assertEqual(rank, 3, 'Wrong rank') rank = sev_db.rank('CAP_KILL') self.assertEqual(rank, 8, 'Wrong rank') rank = sev_db.rank('CAP_SETPCAP') self.assertEqual(rank, 9, 'Wrong rank') self.assertEqual(sev_db.rank('/etc/apparmor/**', 'r') , 6, 'Invalid Rank') self.assertEqual(sev_db.rank('/etc/**', 'r') , 10, 'Invalid Rank') # Load all variables for /sbin/klogd and test them sev_db.load_variables('profiles/sbin.klogd') self.assertEqual(sev_db.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(sev_db.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') self.assertEqual(sev_db.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') sev_db.unload_variables() sev_db.load_variables('profiles/usr.sbin.dnsmasq') self.assertEqual(sev_db.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(sev_db.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(sev_db.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') self.assertEqual(sev_db.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') #self.assertEqual(sev_db.rank('/proc/@{PID}/maps', 'rw'), 9, 'Invalid Rank') def testInvalid(self): sev_db = severity.Severity('severity.db') rank = sev_db.rank('/dev/doublehit', 'i') self.assertEqual(rank, 10, 'Wrong') try: severity.Severity('severity_broken.db') except AppArmorException: pass rank = sev_db.rank('CAP_UNKOWN') rank = sev_db.rank('CAP_K*') if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() apparmor-2.8.95~2430/utils/test/test-dbus_parse.py0000644000175000017500000000203312306404336021631 0ustar sarnoldsarnold#! /usr/bin/env python # ------------------------------------------------------------------ # # Copyright (C) 2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ import apparmor.aa as aa import unittest class AAParseDBUSTest(unittest.TestCase): def test_parse_plain_dbus_rule(self): dstring = 'dbus,' dbus = aa.parse_dbus_rule(dstring) self.assertEqual(dstring, dbus.serialize(), 'dbus object returned "%s", expected "%s"' % (dbus.serialize(), dstring)) def test_parse_dbus_simple_send_rule(self): dstring = 'dbus send,' dbus = aa.parse_dbus_rule(dstring) self.assertEqual(dstring, dbus.serialize(), 'dbus object returned "%s", expected "%s"' % (dbus.serialize(), dstring)) if __name__ == '__main__': unittest.main() apparmor-2.8.95~2430/utils/test/test-aa-easyprof.py0000755000175000017500000025243212306137266021733 0ustar sarnoldsarnold#! /usr/bin/env python # ------------------------------------------------------------------ # # Copyright (C) 2011-2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ import glob import json import optparse import os import shutil import sys import tempfile import unittest topdir = None debugging = False def recursive_rm(dirPath, contents_only=False): '''recursively remove directory''' names = os.listdir(dirPath) for name in names: path = os.path.join(dirPath, name) if os.path.islink(path) or not os.path.isdir(path): os.unlink(path) else: recursive_rm(path) if contents_only == False: os.rmdir(dirPath) # From Lib/test/test_optparse.py from python 2.7.4 class InterceptedError(Exception): def __init__(self, error_message=None, exit_status=None, exit_message=None): self.error_message = error_message self.exit_status = exit_status self.exit_message = exit_message def __str__(self): return self.error_message or self.exit_message or "intercepted error" class InterceptingOptionParser(optparse.OptionParser): def exit(self, status=0, msg=None): raise InterceptedError(exit_status=status, exit_message=msg) def error(self, msg): raise InterceptedError(error_message=msg) class Manifest(object): def __init__(self, profile_name): self.security = dict() self.security['profiles'] = dict() self.profile_name = profile_name self.security['profiles'][self.profile_name] = dict() def add_policygroups(self, policy_list): self.security['profiles'][self.profile_name]['policy_groups'] = policy_list.split(",") def add_author(self, author): self.security['profiles'][self.profile_name]['author'] = author def add_copyright(self, copyright): self.security['profiles'][self.profile_name]['copyright'] = copyright def add_comment(self, comment): self.security['profiles'][self.profile_name]['comment'] = comment def add_binary(self, binary): self.security['profiles'][self.profile_name]['binary'] = binary def add_template(self, template): self.security['profiles'][self.profile_name]['template'] = template def add_template_variable(self, name, value): if not 'template_variables' in self.security['profiles'][self.profile_name]: self.security['profiles'][self.profile_name]['template_variables'] = dict() self.security['profiles'][self.profile_name]['template_variables'][name] = value def emit_json(self, use_security_prefix=True): manifest = dict() manifest['security'] = self.security if use_security_prefix: dumpee = manifest else: dumpee = self.security return json.dumps(dumpee, indent=2) # # Our test class # class T(unittest.TestCase): # work around UsrMove ls = os.path.realpath('/bin/ls') def setUp(self): '''Setup for tests''' global topdir self.tmpdir = os.path.realpath(tempfile.mkdtemp(prefix='test-aa-easyprof')) # Copy everything into place for d in ['easyprof/policygroups', 'easyprof/templates']: shutil.copytree(os.path.join(topdir, d), os.path.join(self.tmpdir, os.path.basename(d))) # Create a test template self.test_template = "test-template" contents = '''# vim:syntax=apparmor # %s # AppArmor policy for ###NAME### # ###AUTHOR### # ###COPYRIGHT### # ###COMMENT### #include ###VAR### ###PROFILEATTACH### { #include ###ABSTRACTIONS### ###POLICYGROUPS### ###READS### ###WRITES### } ''' % (self.test_template) open(os.path.join(self.tmpdir, 'templates', self.test_template), 'w').write(contents) # Create a test policygroup self.test_policygroup = "test-policygroup" contents = ''' # %s #include #include ''' % (self.test_policygroup) open(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), 'w').write(contents) # setup our conffile self.conffile = os.path.join(self.tmpdir, 'easyprof.conf') contents = ''' POLICYGROUPS_DIR="%s/policygroups" TEMPLATES_DIR="%s/templates" ''' % (self.tmpdir, self.tmpdir) open(self.conffile, 'w').write(contents) self.binary = "/opt/bin/foo" self.full_args = ['-c', self.conffile, self.binary] if debugging: self.full_args.append('-d') (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) def tearDown(self): '''Teardown for tests''' if os.path.exists(self.tmpdir): if debugging: sys.stdout.write("%s\n" % self.tmpdir) else: recursive_rm(self.tmpdir) # # config file tests # def test_configuration_file_p_invalid(self): '''Test config parsing (invalid POLICYGROUPS_DIR)''' contents = ''' POLICYGROUPS_DIR= TEMPLATES_DIR="%s/templates" ''' % (self.tmpdir) open(self.conffile, 'w').write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) except easyprof.AppArmorException: return except Exception: raise raise Exception ("File should have been invalid") def test_configuration_file_p_empty(self): '''Test config parsing (empty POLICYGROUPS_DIR)''' contents = ''' POLICYGROUPS_DIR="%s" TEMPLATES_DIR="%s/templates" ''' % ('', self.tmpdir) open(self.conffile, 'w').write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) except easyprof.AppArmorException: return except Exception: raise raise Exception ("File should have been invalid") def test_configuration_file_p_nonexistent(self): '''Test config parsing (nonexistent POLICYGROUPS_DIR)''' contents = ''' POLICYGROUPS_DIR="%s/policygroups" TEMPLATES_DIR="%s/templates" ''' % ('/nonexistent', self.tmpdir) open(self.conffile, 'w').write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) except easyprof.AppArmorException: return except Exception: raise raise Exception ("File should have been invalid") def test_policygroups_dir_relative(self): '''Test --policy-groups-dir (relative DIR)''' os.chdir(self.tmpdir) rel = os.path.join(self.tmpdir, 'relative') os.mkdir(rel) shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), os.path.join(rel, self.test_policygroup)) args = self.full_args args += ['--policy-groups-dir', './relative', '--show-policy-group', '--policy-groups=%s' % self.test_policygroup] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) # no fallback self.assertTrue(easyp.dirs['policygroups'] == rel, "Not using specified --policy-groups-dir\n" + "Specified dir: %s\nActual dir: %s" % (rel, str(easyp.dirs['policygroups']))) self.assertFalse(easyp.get_policy_groups() == None, "Could not find policy-groups") def test_policygroups_dir_nonexistent(self): '''Test --policy-groups-dir (nonexistent DIR)''' os.chdir(self.tmpdir) rel = os.path.join(self.tmpdir, 'nonexistent') args = self.full_args args += ['--policy-groups-dir', rel, '--show-policy-group', '--policy-groups=%s' % self.test_policygroup] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) # test if using fallback self.assertFalse(easyp.dirs['policygroups'] == rel, "Using nonexistent --policy-groups-dir") # test fallback self.assertTrue(easyp.get_policy_groups() != None, "Found policy-groups when shouldn't have") def test_policygroups_dir_valid(self): '''Test --policy-groups-dir (valid DIR)''' os.chdir(self.tmpdir) valid = os.path.join(self.tmpdir, 'valid') os.mkdir(valid) shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), os.path.join(valid, self.test_policygroup)) args = self.full_args args += ['--policy-groups-dir', valid, '--show-policy-group', '--policy-groups=%s' % self.test_policygroup] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) # no fallback self.assertTrue(easyp.dirs['policygroups'] == valid, "Not using specified --policy-groups-dir") self.assertFalse(easyp.get_policy_groups() == None, "Could not find policy-groups") def test_policygroups_dir_valid_with_vendor(self): '''Test --policy-groups-dir (valid DIR with vendor)''' os.chdir(self.tmpdir) valid = os.path.join(self.tmpdir, 'valid') os.mkdir(valid) shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), os.path.join(valid, self.test_policygroup)) vendor = "ubuntu" version = "1.0" valid_distro = os.path.join(valid, vendor, version) os.mkdir(os.path.join(valid, vendor)) os.mkdir(valid_distro) shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), valid_distro) args = self.full_args args += ['--policy-groups-dir', valid, '--show-policy-group', '--policy-groups=%s' % self.test_policygroup] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) self.assertTrue(easyp.dirs['policygroups'] == valid, "Not using specified --policy-groups-dir") self.assertFalse(easyp.get_policy_groups() == None, "Could not find policy-groups") for f in easyp.get_policy_groups(): self.assertFalse(os.path.basename(f) == vendor, "Found '%s' in %s" % (vendor, f)) def test_configuration_file_t_invalid(self): '''Test config parsing (invalid TEMPLATES_DIR)''' contents = ''' TEMPLATES_DIR= POLICYGROUPS_DIR="%s/templates" ''' % (self.tmpdir) open(self.conffile, 'w').write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) except easyprof.AppArmorException: return except Exception: raise raise Exception ("File should have been invalid") def test_configuration_file_t_empty(self): '''Test config parsing (empty TEMPLATES_DIR)''' contents = ''' TEMPLATES_DIR="%s" POLICYGROUPS_DIR="%s/templates" ''' % ('', self.tmpdir) open(self.conffile, 'w').write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) except easyprof.AppArmorException: return except Exception: raise raise Exception ("File should have been invalid") def test_configuration_file_t_nonexistent(self): '''Test config parsing (nonexistent TEMPLATES_DIR)''' contents = ''' TEMPLATES_DIR="%s/policygroups" POLICYGROUPS_DIR="%s/templates" ''' % ('/nonexistent', self.tmpdir) open(self.conffile, 'w').write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) except easyprof.AppArmorException: return except Exception: raise raise Exception ("File should have been invalid") def test_templates_dir_relative(self): '''Test --templates-dir (relative DIR)''' os.chdir(self.tmpdir) rel = os.path.join(self.tmpdir, 'relative') os.mkdir(rel) shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), os.path.join(rel, self.test_template)) args = self.full_args args += ['--templates-dir', './relative', '--show-template', '--template=%s' % self.test_template] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) # no fallback self.assertTrue(easyp.dirs['templates'] == rel, "Not using specified --template-dir\n" + "Specified dir: %s\nActual dir: %s" % (rel, str(easyp.dirs['templates']))) self.assertFalse(easyp.get_templates() == None, "Could not find templates") def test_templates_dir_nonexistent(self): '''Test --templates-dir (nonexistent DIR)''' os.chdir(self.tmpdir) rel = os.path.join(self.tmpdir, 'nonexistent') args = self.full_args args += ['--templates-dir', rel, '--show-template', '--template=%s' % self.test_template] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) # test if using fallback self.assertFalse(easyp.dirs['templates'] == rel, "Using nonexistent --template-dir") # test fallback self.assertTrue(easyp.get_templates() != None, "Found templates when shouldn't have") def test_templates_dir_valid(self): '''Test --templates-dir (valid DIR)''' os.chdir(self.tmpdir) valid = os.path.join(self.tmpdir, 'valid') os.mkdir(valid) shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), os.path.join(valid, self.test_template)) args = self.full_args args += ['--templates-dir', valid, '--show-template', '--template=%s' % self.test_template] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) # no fallback self.assertTrue(easyp.dirs['templates'] == valid, "Not using specified --template-dir") self.assertFalse(easyp.get_templates() == None, "Could not find templates") def test_templates_dir_valid_with_vendor(self): '''Test --templates-dir (valid DIR with vendor)''' os.chdir(self.tmpdir) valid = os.path.join(self.tmpdir, 'valid') os.mkdir(valid) shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), os.path.join(valid, self.test_template)) vendor = "ubuntu" version = "1.0" valid_distro = os.path.join(valid, vendor, version) os.mkdir(os.path.join(valid, vendor)) os.mkdir(valid_distro) shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), valid_distro) args = self.full_args args += ['--templates-dir', valid, '--show-template', '--template=%s' % self.test_template] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) self.assertTrue(easyp.dirs['templates'] == valid, "Not using specified --template-dir") self.assertFalse(easyp.get_templates() == None, "Could not find templates") for f in easyp.get_templates(): self.assertFalse(os.path.basename(f) == vendor, "Found '%s' in %s" % (vendor, f)) # # Binary file tests # def test_binary_without_profile_name(self): '''Test binary ( { })''' easyprof.AppArmorEasyProfile(self.ls, self.options) def test_binary_with_profile_name(self): '''Test binary (profile { })''' args = self.full_args args += ['--profile-name=some-profile-name'] (self.options, self.args) = easyprof.parse_args(args) easyprof.AppArmorEasyProfile(self.ls, self.options) def test_binary_omitted_with_profile_name(self): '''Test binary (profile { })''' args = self.full_args args += ['--profile-name=some-profile-name'] (self.options, self.args) = easyprof.parse_args(args) easyprof.AppArmorEasyProfile(None, self.options) def test_binary_nonexistent(self): '''Test binary (nonexistent)''' easyprof.AppArmorEasyProfile(os.path.join(self.tmpdir, 'nonexistent'), self.options) def test_binary_relative(self): '''Test binary (relative)''' try: easyprof.AppArmorEasyProfile('./foo', self.options) except easyprof.AppArmorException: return except Exception: raise raise Exception ("Binary should have been invalid") def test_binary_symlink(self): '''Test binary (symlink)''' exe = os.path.join(self.tmpdir, 'exe') open(exe, 'a').close() symlink = exe + ".lnk" os.symlink(exe, symlink) try: easyprof.AppArmorEasyProfile(symlink, self.options) except easyprof.AppArmorException: return except Exception: raise raise Exception ("Binary should have been invalid") # # Templates tests # def test_templates_list(self): '''Test templates (list)''' args = self.full_args args.append('--list-templates') (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(None, self.options) for i in easyp.get_templates(): self.assertTrue(os.path.exists(i), "Could not find '%s'" % i) def test_templates_show(self): '''Test templates (show)''' files = [] for f in glob.glob("%s/templates/*" % self.tmpdir): files.append(f) for f in files: args = self.full_args args += ['--show-template', '--template', f] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(None, self.options) path = os.path.join(easyp.dirs['templates'], f) self.assertTrue(os.path.exists(path), "Could not find '%s'" % path) open(path).read() # # Policygroups tests # def test_policygroups_list(self): '''Test policygroups (list)''' args = self.full_args args.append('--list-policy-groups') (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(None, self.options) for i in easyp.get_templates(): self.assertTrue(os.path.exists(i), "Could not find '%s'" % i) def test_policygroups_show(self): '''Test policygroups (show)''' files = [] for f in glob.glob("%s/policygroups/*" % self.tmpdir): files.append(f) for f in files: args = self.full_args args += ['--show-template', '--template', f] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(None, self.options) path = os.path.join(easyp.dirs['policygroups'], f) self.assertTrue(os.path.exists(path), "Could not find '%s'" % path) open(path).read() # # Manifest file argument tests # def test_manifest_argument(self): '''Test manifest argument''' # setup our manifest self.manifest = os.path.join(self.tmpdir, 'manifest.json') contents = ''' {"security": {"domain.reverse.appname": {"name": "simple-app"}}} ''' open(self.manifest, 'w').write(contents) args = self.full_args args.extend(['--manifest', self.manifest]) easyprof.parse_args(args) def _manifest_conflicts(self, opt, value): '''Helper for conflicts tests''' # setup our manifest self.manifest = os.path.join(self.tmpdir, 'manifest.json') contents = ''' {"security": {"domain.reverse.appname": {"binary": /nonexistent"}}} ''' open(self.manifest, 'w').write(contents) # opt first args = self.full_args args.extend([opt, value, '--manifest', self.manifest]) raised = False try: easyprof.parse_args(args, InterceptingOptionParser()) except InterceptedError: raised = True self.assertTrue(raised, msg="%s and manifest arguments did not " \ "raise a parse error" % opt) # manifest first args = self.full_args args.extend(['--manifest', self.manifest, opt, value]) raised = False try: easyprof.parse_args(args, InterceptingOptionParser()) except InterceptedError: raised = True self.assertTrue(raised, msg="%s and manifest arguments did not " \ "raise a parse error" % opt) def test_manifest_conflicts_profilename(self): '''Test manifest arg conflicts with profile_name arg''' self._manifest_conflicts("--profile-name", "simple-app") def test_manifest_conflicts_copyright(self): '''Test manifest arg conflicts with copyright arg''' self._manifest_conflicts("--copyright", "2013-01-01") def test_manifest_conflicts_author(self): '''Test manifest arg conflicts with author arg''' self._manifest_conflicts("--author", "Foo Bar") def test_manifest_conflicts_comment(self): '''Test manifest arg conflicts with comment arg''' self._manifest_conflicts("--comment", "some comment") def test_manifest_conflicts_abstractions(self): '''Test manifest arg conflicts with abstractions arg''' self._manifest_conflicts("--abstractions", "base") def test_manifest_conflicts_read_path(self): '''Test manifest arg conflicts with read-path arg''' self._manifest_conflicts("--read-path", "/etc/passwd") def test_manifest_conflicts_write_path(self): '''Test manifest arg conflicts with write-path arg''' self._manifest_conflicts("--write-path", "/tmp/foo") def test_manifest_conflicts_policy_groups(self): '''Test manifest arg conflicts with policy-groups arg''' self._manifest_conflicts("--policy-groups", "opt-application") def test_manifest_conflicts_name(self): '''Test manifest arg conflicts with name arg''' self._manifest_conflicts("--name", "foo") def test_manifest_conflicts_template_var(self): '''Test manifest arg conflicts with template-var arg''' self._manifest_conflicts("--template-var", "foo") def test_manifest_conflicts_policy_version(self): '''Test manifest arg conflicts with policy-version arg''' self._manifest_conflicts("--policy-version", "1.0") def test_manifest_conflicts_policy_vendor(self): '''Test manifest arg conflicts with policy-vendor arg''' self._manifest_conflicts("--policy-vendor", "somevendor") # # Test genpolicy # def _gen_policy(self, name=None, template=None, extra_args=[]): '''Generate a policy''' # Build up our args args = self.full_args if template == None: args.append('--template=%s' % self.test_template) else: args.append('--template=%s' % template) if name != None: args.append('--name=%s' % name) if len(extra_args) > 0: args += extra_args args.append(self.binary) # Now parse our args (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) params = easyprof.gen_policy_params(self.binary, self.options) p = easyp.gen_policy(**params) # We always need to check for these search_terms = [self.binary] if name != None: search_terms.append(name) if template == None: search_terms.append(self.test_template) for s in search_terms: self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) # ###NAME### should be replaced with self.binary or 'name'. Check for that inv_s = '###NAME###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) if debugging: sys.stdout.write("%s\n" % p) return p def _gen_manifest_policy(self, manifest, use_security_prefix=True): # Build up our args args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, self.options) = easyprof.parse_manifest(manifest.emit_json(use_security_prefix), self.options)[0] easyp = easyprof.AppArmorEasyProfile(binary, self.options) params = easyprof.gen_policy_params(binary, self.options) p = easyp.gen_policy(**params) # ###NAME### should be replaced with self.binary or 'name'. Check for that inv_s = '###NAME###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) if debugging: sys.stdout.write("%s\n" % p) return p def test__is_safe(self): '''Test _is_safe()''' bad = [ "/../../../../etc/passwd", "abstraction with spaces", "semicolon;bad", "bad\x00baz", "foo/bar", "foo'bar", 'foo"bar', ] for s in bad: self.assertFalse(easyprof._is_safe(s), "'%s' should be bad" %s) def test_genpolicy_templates_abspath(self): '''Test genpolicy (abspath to template)''' # create a new template template = os.path.join(self.tmpdir, "test-abspath-template") shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), template) contents = open(template).read() test_string = "#teststring" open(template, 'w').write(contents + "\n%s\n" % test_string) p = self._gen_policy(template=template) for s in [self.test_template, test_string]: self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) def test_genpolicy_templates_system(self): '''Test genpolicy (system template)''' self._gen_policy() def test_genpolicy_templates_nonexistent(self): '''Test genpolicy (nonexistent template)''' try: self._gen_policy(template=os.path.join(self.tmpdir, "/nonexistent")) except easyprof.AppArmorException: return except Exception: raise raise Exception ("template should be invalid") def test_genpolicy_name(self): '''Test genpolicy (name)''' self._gen_policy(name='test-foo') def test_genpolicy_comment(self): '''Test genpolicy (comment)''' s = "test comment" p = self._gen_policy(extra_args=['--comment=%s' % s]) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###COMMENT###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_author(self): '''Test genpolicy (author)''' s = "Archibald Poindexter" p = self._gen_policy(extra_args=['--author=%s' % s]) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###AUTHOR###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_copyright(self): '''Test genpolicy (copyright)''' s = "2112/01/01" p = self._gen_policy(extra_args=['--copyright=%s' % s]) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###COPYRIGHT###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_abstractions(self): '''Test genpolicy (single abstraction)''' s = "nameservice" p = self._gen_policy(extra_args=['--abstractions=%s' % s]) search = "#include " % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###ABSTRACTIONS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_abstractions_multiple(self): '''Test genpolicy (multiple abstractions)''' abstractions = "authentication,X,user-tmp" p = self._gen_policy(extra_args=['--abstractions=%s' % abstractions]) for s in abstractions.split(','): search = "#include " % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###ABSTRACTIONS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_abstractions_bad(self): '''Test genpolicy (abstractions - bad values)''' bad = [ "nonexistent", "/../../../../etc/passwd", "abstraction with spaces", ] for s in bad: try: self._gen_policy(extra_args=['--abstractions=%s' % s]) except easyprof.AppArmorException: continue except Exception: raise raise Exception ("abstraction '%s' should be invalid" % s) def test_genpolicy_profile_name_bad(self): '''Test genpolicy (profile name - bad values)''' bad = [ "/../../../../etc/passwd", "../../../../etc/passwd", "profile name with spaces", ] for s in bad: try: self._gen_policy(extra_args=['--profile-name=%s' % s]) except easyprof.AppArmorException: continue except Exception: raise raise Exception ("profile_name '%s' should be invalid" % s) def test_genpolicy_policy_group_bad(self): '''Test genpolicy (policy group - bad values)''' bad = [ "/../../../../etc/passwd", "../../../../etc/passwd", "profile name with spaces", ] for s in bad: try: self._gen_policy(extra_args=['--policy-groups=%s' % s]) except easyprof.AppArmorException: continue except Exception: raise raise Exception ("policy group '%s' should be invalid" % s) def test_genpolicy_policygroups(self): '''Test genpolicy (single policygroup)''' groups = self.test_policygroup p = self._gen_policy(extra_args=['--policy-groups=%s' % groups]) for s in ['#include ', '#include ']: self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###POLICYGROUPS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_policygroups_multiple(self): '''Test genpolicy (multiple policygroups)''' test_policygroup2 = "test-policygroup2" contents = ''' # %s #include #include ''' % (self.test_policygroup) open(os.path.join(self.tmpdir, 'policygroups', test_policygroup2), 'w').write(contents) groups = "%s,%s" % (self.test_policygroup, test_policygroup2) p = self._gen_policy(extra_args=['--policy-groups=%s' % groups]) for s in ['#include ', '#include ', '#include ', '#include ']: self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###POLICYGROUPS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_policygroups_nonexistent(self): '''Test genpolicy (nonexistent policygroup)''' try: self._gen_policy(extra_args=['--policy-groups=nonexistent']) except easyprof.AppArmorException: return except Exception: raise raise Exception ("policygroup should be invalid") def test_genpolicy_readpath_file(self): '''Test genpolicy (read-path file)''' s = "/opt/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search = "%s rk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_home_file(self): '''Test genpolicy (read-path file in /home)''' s = "/home/*/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search = "owner %s rk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_homevar_file(self): '''Test genpolicy (read-path file in @{HOME})''' s = "@{HOME}/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search = "owner %s rk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_homedirs_file(self): '''Test genpolicy (read-path file in @{HOMEDIRS})''' s = "@{HOMEDIRS}/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search = "owner %s rk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_dir(self): '''Test genpolicy (read-path directory/)''' s = "/opt/test-foo-dir/" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search_terms = ["%s rk," % s, "%s** rk," % s] for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_dir_glob(self): '''Test genpolicy (read-path directory/*)''' s = "/opt/test-foo-dir/*" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search_terms = ["%s rk," % os.path.dirname(s), "%s rk," % s] for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_dir_glob_all(self): '''Test genpolicy (read-path directory/**)''' s = "/opt/test-foo-dir/**" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search_terms = ["%s rk," % os.path.dirname(s), "%s rk," % s] for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_multiple(self): '''Test genpolicy (read-path multiple)''' paths = ["/opt/test-foo", "/home/*/test-foo", "@{HOME}/test-foo", "@{HOMEDIRS}/test-foo", "/opt/test-foo-dir/", "/opt/test-foo-dir/*", "/opt/test-foo-dir/**"] args = [] search_terms = [] for s in paths: args.append('--read-path=%s' % s) # This mimics easyprof.gen_path_rule() owner = "" if s.startswith('/home/') or s.startswith("@{HOME"): owner = "owner " if s.endswith('/'): search_terms.append("%s rk," % (s)) search_terms.append("%s%s** rk," % (owner, s)) elif s.endswith('/**') or s.endswith('/*'): search_terms.append("%s rk," % (os.path.dirname(s))) search_terms.append("%s%s rk," % (owner, s)) else: search_terms.append("%s%s rk," % (owner, s)) p = self._gen_policy(extra_args=args) for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_bad(self): '''Test genpolicy (read-path bad)''' s = "bar" try: self._gen_policy(extra_args=['--read-path=%s' % s]) except easyprof.AppArmorException: return except Exception: raise raise Exception ("read-path should be invalid") def test_genpolicy_writepath_file(self): '''Test genpolicy (write-path file)''' s = "/opt/test-foo" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search = "%s rwk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_home_file(self): '''Test genpolicy (write-path file in /home)''' s = "/home/*/test-foo" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search = "owner %s rwk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_homevar_file(self): '''Test genpolicy (write-path file in @{HOME})''' s = "@{HOME}/test-foo" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search = "owner %s rwk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_homedirs_file(self): '''Test genpolicy (write-path file in @{HOMEDIRS})''' s = "@{HOMEDIRS}/test-foo" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search = "owner %s rwk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_dir(self): '''Test genpolicy (write-path directory/)''' s = "/opt/test-foo-dir/" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search_terms = ["%s rwk," % s, "%s** rwk," % s] for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_dir_glob(self): '''Test genpolicy (write-path directory/*)''' s = "/opt/test-foo-dir/*" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search_terms = ["%s rwk," % os.path.dirname(s), "%s rwk," % s] for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_dir_glob_all(self): '''Test genpolicy (write-path directory/**)''' s = "/opt/test-foo-dir/**" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search_terms = ["%s rwk," % os.path.dirname(s), "%s rwk," % s] for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_multiple(self): '''Test genpolicy (write-path multiple)''' paths = ["/opt/test-foo", "/home/*/test-foo", "@{HOME}/test-foo", "@{HOMEDIRS}/test-foo", "/opt/test-foo-dir/", "/opt/test-foo-dir/*", "/opt/test-foo-dir/**"] args = [] search_terms = [] for s in paths: args.append('--write-path=%s' % s) # This mimics easyprof.gen_path_rule() owner = "" if s.startswith('/home/') or s.startswith("@{HOME"): owner = "owner " if s.endswith('/'): search_terms.append("%s rwk," % (s)) search_terms.append("%s%s** rwk," % (owner, s)) elif s.endswith('/**') or s.endswith('/*'): search_terms.append("%s rwk," % (os.path.dirname(s))) search_terms.append("%s%s rwk," % (owner, s)) else: search_terms.append("%s%s rwk," % (owner, s)) p = self._gen_policy(extra_args=args) for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_bad(self): '''Test genpolicy (write-path bad)''' s = "bar" try: self._gen_policy(extra_args=['--write-path=%s' % s]) except easyprof.AppArmorException: return except Exception: raise raise Exception ("write-path should be invalid") def test_genpolicy_templatevar(self): '''Test genpolicy (template-var single)''' s = "@{FOO}=bar" p = self._gen_policy(extra_args=['--template-var=%s' % s]) k, v = s.split('=') s = '%s="%s"' % (k, v) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###TEMPLATEVAR###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_templatevar_multiple(self): '''Test genpolicy (template-var multiple)''' variables = ['@{FOO}=bar', '@{BAR}=baz'] args = [] for s in variables: args.append('--template-var=%s' % s) p = self._gen_policy(extra_args=args) for s in variables: k, v = s.split('=') s = '%s="%s"' % (k, v) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###TEMPLATEVAR###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_templatevar_bad(self): '''Test genpolicy (template-var - bad values)''' bad = [ "{FOO}=bar", "@FOO}=bar", "@{FOO=bar", "FOO=bar", "@FOO=bar", "@{FOO}=/../../../etc/passwd", "@{FOO}=bar=foo", "@{FOO;BAZ}=bar", '@{FOO}=bar"baz', ] for s in bad: try: self._gen_policy(extra_args=['--template-var=%s' % s]) except easyprof.AppArmorException: continue except Exception: raise raise Exception ("template-var should be invalid") def test_genpolicy_invalid_template_policy(self): '''Test genpolicy (invalid template policy)''' # create a new template template = os.path.join(self.tmpdir, "test-invalid-template") shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), template) contents = open(template).read() bad_pol = "" bad_string = "bzzzt" for line in contents.splitlines(): if '}' in line: bad_pol += bad_string else: bad_pol += line bad_pol += "\n" open(template, 'w').write(bad_pol) try: self._gen_policy(template=template) except easyprof.AppArmorException: return except Exception: raise raise Exception ("policy should be invalid") def test_genpolicy_no_binary_without_profile_name(self): '''Test genpolicy (no binary with no profile name)''' try: easyprof.gen_policy_params(None, self.options) except easyprof.AppArmorException: return except Exception: raise raise Exception ("No binary or profile name should have been invalid") def test_genpolicy_with_binary_with_profile_name(self): '''Test genpolicy (binary with profile name)''' profile_name = "some-profile-name" p = self._gen_policy(extra_args=['--profile-name=%s' % profile_name]) s = 'profile "%s" "%s" {' % (profile_name, self.binary) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###PROFILEATTACH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_with_binary_without_profile_name(self): '''Test genpolicy (binary without profile name)''' p = self._gen_policy() s = '"%s" {' % (self.binary) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###PROFILEATTACH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_without_binary_with_profile_name(self): '''Test genpolicy (no binary with profile name)''' profile_name = "some-profile-name" args = self.full_args args.append('--profile-name=%s' % profile_name) (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(None, self.options) params = easyprof.gen_policy_params(None, self.options) p = easyp.gen_policy(**params) s = 'profile "%s" {' % (profile_name) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###PROFILEATTACH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) # manifest tests def test_gen_manifest_policy_with_binary_with_profile_name(self): '''Test gen_manifest_policy (binary with profile name)''' m = Manifest("test_gen_manifest_policy") m.add_binary(self.ls) self._gen_manifest_policy(m) def test_gen_manifest_policy_without_binary_with_profile_name(self): '''Test gen_manifest_policy (no binary with profile name)''' m = Manifest("test_gen_manifest_policy") self._gen_manifest_policy(m) def test_gen_manifest_policy_templates_system(self): '''Test gen_manifest_policy (system template)''' m = Manifest("test_gen_manifest_policy") m.add_template(self.test_template) self._gen_manifest_policy(m) def test_gen_manifest_policy_templates_system_noprefix(self): '''Test gen_manifest_policy (system template, no security prefix)''' m = Manifest("test_gen_manifest_policy") m.add_template(self.test_template) self._gen_manifest_policy(m, use_security_prefix=False) def test_gen_manifest_abs_path_template(self): '''Test gen_manifest_policy (abs path template)''' m = Manifest("test_gen_manifest_policy") m.add_template("/etc/shadow") try: self._gen_manifest_policy(m) except easyprof.AppArmorException: return except Exception: raise raise Exception ("abs path template name should be invalid") def test_gen_manifest_escape_path_templates(self): '''Test gen_manifest_policy (esc path template)''' m = Manifest("test_gen_manifest_policy") m.add_template("../../../../../../../../etc/shadow") try: self._gen_manifest_policy(m) except easyprof.AppArmorException: return except Exception: raise raise Exception ("../ template name should be invalid") def test_gen_manifest_policy_templates_nonexistent(self): '''Test gen manifest policy (nonexistent template)''' m = Manifest("test_gen_manifest_policy") m.add_template("nonexistent") try: self._gen_manifest_policy(m) except easyprof.AppArmorException: return except Exception: raise raise Exception ("template should be invalid") def test_gen_manifest_policy_comment(self): '''Test gen manifest policy (comment)''' s = "test comment" m = Manifest("test_gen_manifest_policy") m.add_comment(s) p = self._gen_manifest_policy(m) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###COMMENT###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_author(self): '''Test gen manifest policy (author)''' s = "Archibald Poindexter" m = Manifest("test_gen_manifest_policy") m.add_author(s) p = self._gen_manifest_policy(m) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###AUTHOR###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_copyright(self): '''Test genpolicy (copyright)''' s = "2112/01/01" m = Manifest("test_gen_manifest_policy") m.add_copyright(s) p = self._gen_manifest_policy(m) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###COPYRIGHT###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_policygroups(self): '''Test gen manifest policy (single policygroup)''' groups = self.test_policygroup m = Manifest("test_gen_manifest_policy") m.add_policygroups(groups) p = self._gen_manifest_policy(m) for s in ['#include ', '#include ']: self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###POLICYGROUPS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_policygroups_multiple(self): '''Test genpolicy (multiple policygroups)''' test_policygroup2 = "test-policygroup2" contents = ''' # %s #include #include ''' % (self.test_policygroup) open(os.path.join(self.tmpdir, 'policygroups', test_policygroup2), 'w').write(contents) groups = "%s,%s" % (self.test_policygroup, test_policygroup2) m = Manifest("test_gen_manifest_policy") m.add_policygroups(groups) p = self._gen_manifest_policy(m) for s in ['#include ', '#include ', '#include ', '#include ']: self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###POLICYGROUPS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_policygroups_nonexistent(self): '''Test gen manifest policy (nonexistent policygroup)''' groups = "nonexistent" m = Manifest("test_gen_manifest_policy") m.add_policygroups(groups) try: self._gen_manifest_policy(m) except easyprof.AppArmorException: return except Exception: raise raise Exception ("policygroup should be invalid") def test_gen_manifest_policy_templatevar(self): '''Test gen manifest policy (template-var single)''' m = Manifest("test_gen_manifest_policy") m.add_template_variable("FOO", "bar") p = self._gen_manifest_policy(m) s = '@{FOO}="bar"' self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###TEMPLATEVAR###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_templatevar_multiple(self): '''Test gen manifest policy (template-var multiple)''' variables = [["FOO", "bar"], ["BAR", "baz"]] m = Manifest("test_gen_manifest_policy") for s in variables: m.add_template_variable(s[0], s[1]) p = self._gen_manifest_policy(m) for s in variables: str_s = '@{%s}="%s"' % (s[0], s[1]) self.assertTrue(str_s in p, "Could not find '%s' in:\n%s" % (str_s, p)) inv_s = '###TEMPLATEVAR###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_invalid_keys(self): '''Test gen manifest policy (invalid keys)''' keys = ['config_file', 'debug', 'help', 'list-templates', 'list_templates', 'show-template', 'show_template', 'list-policy-groups', 'list_policy_groups', 'show-policy-group', 'show_policy_group', 'templates-dir', 'templates_dir', 'policy-groups-dir', 'policy_groups_dir', 'nonexistent', 'no_verify', ] args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) for k in keys: security = dict() security["profile_name"] = "test-app" security[k] = "bad" j = json.dumps(security, indent=2) try: easyprof.parse_manifest(j, self.options) except easyprof.AppArmorException: continue raise Exception ("'%s' should be invalid" % k) def test_gen_manifest(self): '''Test gen_manifest''' # this should come from manpage m = '''{ "security": { "profiles": { "com.example.foo": { "abstractions": [ "audio", "gnome" ], "author": "Your Name", "binary": "/opt/foo/**", "comment": "Unstructured single-line comment", "copyright": "Unstructured single-line copyright statement", "name": "My Foo App", "policy_groups": [ "opt-application", "user-application" ], "policy_vendor": "somevendor", "policy_version": 1.0, "read_path": [ "/tmp/foo_r", "/tmp/bar_r/" ], "template": "user-application", "template_variables": { "APPNAME": "foo", "VAR1": "bar", "VAR2": "baz" }, "write_path": [ "/tmp/foo_w", "/tmp/bar_w/" ] } } } }''' for d in ['policygroups', 'templates']: shutil.copytree(os.path.join(self.tmpdir, d), os.path.join(self.tmpdir, d, "somevendor/1.0")) args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] easyp = easyprof.AppArmorEasyProfile(binary, self.options) params = easyprof.gen_policy_params(binary, self.options) # verify we get the same manifest back man_new = easyp.gen_manifest(params) self.assertEquals(m, man_new) def test_gen_manifest_ubuntu(self): '''Test gen_manifest (ubuntu)''' # this should be based on the manpage (but use existing policy_groups # and template m = '''{ "security": { "profiles": { "com.ubuntu.developer.myusername.MyCoolApp": { "name": "MyCoolApp", "policy_groups": [ "opt-application", "user-application" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "user-application", "template_variables": { "APPNAME": "MyCoolApp", "APPVERSION": "0.1.2" } } } } }''' for d in ['policygroups', 'templates']: shutil.copytree(os.path.join(self.tmpdir, d), os.path.join(self.tmpdir, d, "ubuntu/1.0")) args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] easyp = easyprof.AppArmorEasyProfile(binary, self.options) params = easyprof.gen_policy_params(binary, self.options) # verify we get the same manifest back man_new = easyp.gen_manifest(params) self.assertEquals(m, man_new) def test_parse_manifest_no_version(self): '''Test parse_manifest (vendor with no version)''' # this should come from manpage m = '''{ "security": { "profiles": { "com.ubuntu.developer.myusername.MyCoolApp": { "policy_groups": [ "opt-application", "user-application" ], "policy_vendor": "ubuntu", "template": "user-application", "template_variables": { "APPNAME": "MyCoolApp", "APPVERSION": "0.1.2" } } } } }''' args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] try: easyprof.AppArmorEasyProfile(binary, self.options) except easyprof.AppArmorException: return raise Exception ("Should have failed on missing version") def test_parse_manifest_no_vendor(self): '''Test parse_manifest (version with no vendor)''' # this should come from manpage m = '''{ "security": { "profiles": { "com.ubuntu.developer.myusername.MyCoolApp": { "policy_groups": [ "opt-application", "user-application" ], "policy_version": 1.0, "template": "user-application", "template_variables": { "APPNAME": "MyCoolApp", "APPVERSION": "0.1.2" } } } } }''' args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] try: easyprof.AppArmorEasyProfile(binary, self.options) except easyprof.AppArmorException: return raise Exception ("Should have failed on missing vendor") def test_parse_manifest_multiple(self): '''Test parse_manifest_multiple''' m = '''{ "security": { "profiles": { "com.example.foo": { "abstractions": [ "audio", "gnome" ], "author": "Your Name", "binary": "/opt/foo/**", "comment": "Unstructured single-line comment", "copyright": "Unstructured single-line copyright statement", "name": "My Foo App", "policy_groups": [ "opt-application", "user-application" ], "read_path": [ "/tmp/foo_r", "/tmp/bar_r/" ], "template": "user-application", "template_variables": { "APPNAME": "foo", "VAR1": "bar", "VAR2": "baz" }, "write_path": [ "/tmp/foo_w", "/tmp/bar_w/" ] }, "com.ubuntu.developer.myusername.MyCoolApp": { "policy_groups": [ "opt-application" ], "policy_vendor": "ubuntu", "policy_version": 1.0, "template": "user-application", "template_variables": { "APPNAME": "MyCoolApp", "APPVERSION": "0.1.2" } } } } }''' for d in ['policygroups', 'templates']: shutil.copytree(os.path.join(self.tmpdir, d), os.path.join(self.tmpdir, d, "ubuntu/1.0")) args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) profiles = easyprof.parse_manifest(m, self.options) for (binary, options) in profiles: easyp = easyprof.AppArmorEasyProfile(binary, options) params = easyprof.gen_policy_params(binary, options) easyp.gen_manifest(params) easyp.gen_policy(**params) # verify manifest tests def _verify_manifest(self, m, expected, invalid=False): args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) try: (binary, options) = easyprof.parse_manifest(m, self.options)[0] except easyprof.AppArmorException: if invalid: return raise params = easyprof.gen_policy_params(binary, options) if expected: self.assertTrue(easyprof.verify_manifest(params), "params=%s\nmanifest=%s" % (params,m)) else: self.assertFalse(easyprof.verify_manifest(params), "params=%s\nmanifest=%s" % (params,m)) def test_verify_manifest_full(self): '''Test verify_manifest (full)''' m = '''{ "security": { "profiles": { "com.example.foo": { "abstractions": [ "base" ], "author": "Your Name", "binary": "/opt/com.example/foo/**", "comment": "some free-form single-line comment", "copyright": "Unstructured single-line copyright statement", "name": "foo", "policy_groups": [ "user-application", "opt-application" ], "template": "user-application", "template_variables": { "OK1": "foo", "OK2": "com.example.foo" } } } } }''' self._verify_manifest(m, expected=True) def test_verify_manifest_full_bad(self): '''Test verify_manifest (full bad)''' m = '''{ "security": { "profiles": { "/com.example.foo": { "abstractions": [ "audio", "gnome" ], "author": "Your Name", "binary": "/usr/foo/**", "comment": "some free-form single-line comment", "copyright": "Unstructured single-line copyright statement", "name": "foo", "policy_groups": [ "user-application", "opt-application" ], "read_path": [ "/tmp/foo_r", "/tmp/bar_r/" ], "template": "user-application", "template_variables": { "VAR1": "f*o", "VAR2": "*foo", "VAR3": "fo*", "VAR4": "b{ar", "VAR5": "b{a,r}", "VAR6": "b}ar", "VAR7": "bar[0-9]", "VAR8": "b{ar", "VAR9": "/tmp/../etc/passwd" }, "write_path": [ "/tmp/foo_w", "/tmp/bar_w/" ] } } } }''' self._verify_manifest(m, expected=False, invalid=True) def test_verify_manifest_binary(self): '''Test verify_manifest (binary in /usr)''' m = '''{ "security": { "profiles": { "com.example.foo": { "binary": "/usr/foo/**", "template": "user-application" } } } }''' self._verify_manifest(m, expected=True) def test_verify_manifest_profile_profile_name_bad(self): '''Test verify_manifest (bad profile_name)''' m = '''{ "security": { "profiles": { "/foo": { "binary": "/opt/com.example/foo/**", "template": "user-application" } } } }''' self._verify_manifest(m, expected=False, invalid=True) m = '''{ "security": { "profiles": { "bin/*": { "binary": "/opt/com.example/foo/**", "template": "user-application" } } } }''' self._verify_manifest(m, expected=False) def test_verify_manifest_profile_profile_name(self): '''Test verify_manifest (profile_name)''' m = '''{ "security": { "profiles": { "com.example.foo": { "binary": "/opt/com.example/foo/**", "template": "user-application" } } } }''' self._verify_manifest(m, expected=True) def test_verify_manifest_profile_abstractions(self): '''Test verify_manifest (abstractions)''' m = '''{ "security": { "profiles": { "com.example.foo": { "binary": "/opt/com.example/foo/**", "template": "user-application", "abstractions": [ "base" ] } } } }''' self._verify_manifest(m, expected=True) def test_verify_manifest_profile_abstractions_bad(self): '''Test verify_manifest (bad abstractions)''' m = '''{ "security": { "profiles": { "com.example.foo": { "binary": "/opt/com.example/foo/**", "template": "user-application", "abstractions": [ "user-tmp" ] } } } }''' self._verify_manifest(m, expected=False) def test_verify_manifest_profile_template_var(self): '''Test verify_manifest (good template_var)''' m = '''{ "security": { "profiles": { "com.example.foo": { "binary": "/opt/com.example/something with spaces/**", "template": "user-application", "template_variables": { "OK1": "foo", "OK2": "com.example.foo", "OK3": "something with spaces" } } } } }''' self._verify_manifest(m, expected=True) def test_verify_manifest_profile_template_var_bad(self): '''Test verify_manifest (bad template_var)''' for v in ['"VAR1": "f*o"', '"VAR2": "*foo"', '"VAR3": "fo*"', '"VAR4": "b{ar"', '"VAR5": "b{a,r}"', '"VAR6": "b}ar"', '"VAR7": "bar[0-9]"', '"VAR8": "b{ar"', '"VAR9": "foo/bar"' # this is valid, but potentially unsafe ]: m = '''{ "security": { "profiles": { "com.example.foo": { "binary": "/opt/com.example/foo/**", "template": "user-application", "template_variables": { %s } } } } }''' % v self._verify_manifest(m, expected=False) def test_manifest_invalid(self): '''Test invalid manifest (parse error)''' m = '''{ "security": { "com.example.foo": { "binary": "/opt/com.example/foo/**", "template": "user-application", "abstractions": [ "base" ] }''' self._verify_manifest(m, expected=False, invalid=True) def test_manifest_invalid2(self): '''Test invalid manifest (profile_name is not key)''' m = '''{ "security": { "binary": "/opt/com.example/foo/**", "template": "user-application", "abstractions": [ "base" ] } }''' self._verify_manifest(m, expected=False, invalid=True) def test_manifest_invalid3(self): '''Test invalid manifest (profile_name in dict)''' m = '''{ "security": { "binary": "/opt/com.example/foo/**", "template": "user-application", "abstractions": [ "base" ], "profile_name": "com.example.foo" } }''' self._verify_manifest(m, expected=False, invalid=True) def test_manifest_invalid4(self): '''Test invalid manifest (bad path in template var)''' for v in ['"VAR1": "/tmp/../etc/passwd"', '"VAR2": "./"', '"VAR3": "foo\"bar"', '"VAR4": "foo//bar"', ]: m = '''{ "security": { "profiles": { "com.example.foo": { "binary": "/opt/com.example/foo/**", "template": "user-application", "template_variables": { %s } } } } }''' % v args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, options) = easyprof.parse_manifest(m, self.options)[0] params = easyprof.gen_policy_params(binary, options) try: easyprof.verify_manifest(params) except easyprof.AppArmorException: return raise Exception ("Should have failed with invalid variable declaration") # policy version tests def test_policy_vendor_manifest_nonexistent(self): '''Test policy vendor via manifest (nonexistent)''' m = '''{ "security": { "profiles": { "com.example.foo": { "policy_vendor": "nonexistent", "policy_version": 1.0, "binary": "/opt/com.example/foo/**", "template": "user-application" } } } }''' # Build up our args args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] try: easyprof.AppArmorEasyProfile(binary, self.options) except easyprof.AppArmorException: return raise Exception ("Should have failed with non-existent directory") def test_policy_version_manifest(self): '''Test policy version via manifest (good)''' policy_vendor = "somevendor" policy_version = "1.0" policy_subdir = "%s/%s" % (policy_vendor, policy_version) m = '''{ "security": { "profiles": { "com.example.foo": { "policy_vendor": "%s", "policy_version": %s, "binary": "/opt/com.example/foo/**", "template": "user-application" } } } }''' % (policy_vendor, policy_version) for d in ['policygroups', 'templates']: shutil.copytree(os.path.join(self.tmpdir, d), os.path.join(self.tmpdir, d, policy_subdir)) # Build up our args args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] easyp = easyprof.AppArmorEasyProfile(binary, self.options) tdir = os.path.join(self.tmpdir, 'templates', policy_subdir) for t in easyp.get_templates(): self.assertTrue(t.startswith(tdir)) pdir = os.path.join(self.tmpdir, 'policygroups', policy_subdir) for p in easyp.get_policy_groups(): self.assertTrue(p.startswith(pdir)) params = easyprof.gen_policy_params(binary, self.options) easyp.gen_policy(**params) def test_policy_vendor_version_args(self): '''Test policy vendor and version via command line args (good)''' policy_version = "1.0" policy_vendor = "somevendor" policy_subdir = "%s/%s" % (policy_vendor, policy_version) # Create the directories for d in ['policygroups', 'templates']: shutil.copytree(os.path.join(self.tmpdir, d), os.path.join(self.tmpdir, d, policy_subdir)) # Build up our args args = self.full_args args.append("--policy-version=%s" % policy_version) args.append("--policy-vendor=%s" % policy_vendor) (self.options, self.args) = easyprof.parse_args(args) (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) tdir = os.path.join(self.tmpdir, 'templates', policy_subdir) for t in easyp.get_templates(): self.assertTrue(t.startswith(tdir), \ "'%s' does not start with '%s'" % (t, tdir)) pdir = os.path.join(self.tmpdir, 'policygroups', policy_subdir) for p in easyp.get_policy_groups(): self.assertTrue(p.startswith(pdir), \ "'%s' does not start with '%s'" % (p, pdir)) params = easyprof.gen_policy_params(self.binary, self.options) easyp.gen_policy(**params) def test_policy_vendor_args_nonexistent(self): '''Test policy vendor via command line args (nonexistent)''' policy_vendor = "nonexistent" policy_version = "1.0" args = self.full_args args.append("--policy-version=%s" % policy_version) args.append("--policy-vendor=%s" % policy_vendor) (self.options, self.args) = easyprof.parse_args(args) (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) try: easyprof.AppArmorEasyProfile(self.binary, self.options) except easyprof.AppArmorException: return raise Exception ("Should have failed with non-existent directory") def test_policy_version_args_bad(self): '''Test policy version via command line args (bad)''' bad = [ "../../../../../../etc", "notanumber", "v1.0a", "-1", ] for policy_version in bad: args = self.full_args args.append("--policy-version=%s" % policy_version) args.append("--policy-vendor=somevendor") (self.options, self.args) = easyprof.parse_args(args) (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) try: easyprof.AppArmorEasyProfile(self.binary, self.options) except easyprof.AppArmorException: continue raise Exception ("Should have failed with bad version") def test_policy_vendor_args_bad(self): '''Test policy vendor via command line args (bad)''' bad = [ "../../../../../../etc", "vendor with space", "semicolon;isbad", ] for policy_vendor in bad: args = self.full_args args.append("--policy-vendor=%s" % policy_vendor) args.append("--policy-version=1.0") (self.options, self.args) = easyprof.parse_args(args) (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) try: easyprof.AppArmorEasyProfile(self.binary, self.options) except easyprof.AppArmorException: continue raise Exception ("Should have failed with bad vendor") # output_directory tests def test_output_directory_multiple(self): '''Test output_directory (multiple)''' files = dict() files["com.example.foo"] = "com.example.foo" files["com.ubuntu.developer.myusername.MyCoolApp"] = "com.ubuntu.developer.myusername.MyCoolApp" files["usr.bin.baz"] = "/usr/bin/baz" m = '''{ "security": { "profiles": { "%s": { "abstractions": [ "audio", "gnome" ], "author": "Your Name", "binary": "/opt/foo/**", "comment": "Unstructured single-line comment", "copyright": "Unstructured single-line copyright statement", "name": "My Foo App", "policy_groups": [ "opt-application", "user-application" ], "read_path": [ "/tmp/foo_r", "/tmp/bar_r/" ], "template": "user-application", "template_variables": { "APPNAME": "foo", "VAR1": "bar", "VAR2": "baz" }, "write_path": [ "/tmp/foo_w", "/tmp/bar_w/" ] }, "%s": { "policy_groups": [ "opt-application", "user-application" ], "template": "user-application", "template_variables": { "APPNAME": "MyCoolApp", "APPVERSION": "0.1.2" } }, "%s": { "abstractions": [ "gnome" ], "policy_groups": [ "user-application" ], "template_variables": { "APPNAME": "baz" } } } } }''' % (files["com.example.foo"], files["com.ubuntu.developer.myusername.MyCoolApp"], files["usr.bin.baz"]) out_dir = os.path.join(self.tmpdir, "output") args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) profiles = easyprof.parse_manifest(m, self.options) for (binary, options) in profiles: easyp = easyprof.AppArmorEasyProfile(binary, options) params = easyprof.gen_policy_params(binary, options) easyp.output_policy(params, dir=out_dir) for fn in files: f = os.path.join(out_dir, fn) self.assertTrue(os.path.exists(f), "Could not find '%s'" % f) def test_output_directory_single(self): '''Test output_directory (single)''' files = dict() files["com.example.foo"] = "com.example.foo" m = '''{ "security": { "profiles": { "%s": { "abstractions": [ "audio", "gnome" ], "author": "Your Name", "binary": "/opt/foo/**", "comment": "Unstructured single-line comment", "copyright": "Unstructured single-line copyright statement", "name": "My Foo App", "policy_groups": [ "opt-application", "user-application" ], "read_path": [ "/tmp/foo_r", "/tmp/bar_r/" ], "template": "user-application", "template_variables": { "APPNAME": "foo", "VAR1": "bar", "VAR2": "baz" }, "write_path": [ "/tmp/foo_w", "/tmp/bar_w/" ] } } } }''' % (files["com.example.foo"]) out_dir = os.path.join(self.tmpdir, "output") args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) profiles = easyprof.parse_manifest(m, self.options) for (binary, options) in profiles: easyp = easyprof.AppArmorEasyProfile(binary, options) params = easyprof.gen_policy_params(binary, options) easyp.output_policy(params, dir=out_dir) for fn in files: f = os.path.join(out_dir, fn) self.assertTrue(os.path.exists(f), "Could not find '%s'" % f) def test_output_directory_invalid(self): '''Test output_directory (output directory exists as file)''' files = dict() files["usr.bin.baz"] = "/usr/bin/baz" m = '''{ "security": { "profiles": { "%s": { "abstractions": [ "gnome" ], "policy_groups": [ "user-application" ], "template_variables": { "APPNAME": "baz" } } } } }''' % files["usr.bin.baz"] out_dir = os.path.join(self.tmpdir, "output") open(out_dir, 'w').close() args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, options) = easyprof.parse_manifest(m, self.options)[0] easyp = easyprof.AppArmorEasyProfile(binary, options) params = easyprof.gen_policy_params(binary, options) try: easyp.output_policy(params, dir=out_dir) except easyprof.AppArmorException: return raise Exception ("Should have failed with 'is not a directory'") def test_output_directory_invalid_params(self): '''Test output_directory (no binary or profile_name)''' files = dict() files["usr.bin.baz"] = "/usr/bin/baz" m = '''{ "security": { "profiles": { "%s": { "abstractions": [ "gnome" ], "policy_groups": [ "user-application" ], "template_variables": { "APPNAME": "baz" } } } } }''' % files["usr.bin.baz"] out_dir = os.path.join(self.tmpdir, "output") args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, options) = easyprof.parse_manifest(m, self.options)[0] easyp = easyprof.AppArmorEasyProfile(binary, options) params = easyprof.gen_policy_params(binary, options) del params['binary'] try: easyp.output_policy(params, dir=out_dir) except easyprof.AppArmorException: return raise Exception ("Should have failed with 'Must specify binary and/or profile name'") def test_output_directory_invalid2(self): '''Test output_directory (profile exists)''' files = dict() files["usr.bin.baz"] = "/usr/bin/baz" m = '''{ "security": { "profiles": { "%s": { "abstractions": [ "gnome" ], "policy_groups": [ "user-application" ], "template_variables": { "APPNAME": "baz" } } } } }''' % files["usr.bin.baz"] out_dir = os.path.join(self.tmpdir, "output") os.mkdir(out_dir) open(os.path.join(out_dir, "usr.bin.baz"), 'w').close() args = self.full_args args.append("--manifest=/dev/null") (self.options, self.args) = easyprof.parse_args(args) (binary, options) = easyprof.parse_manifest(m, self.options)[0] easyp = easyprof.AppArmorEasyProfile(binary, options) params = easyprof.gen_policy_params(binary, options) try: easyp.output_policy(params, dir=out_dir) except easyprof.AppArmorException: return raise Exception ("Should have failed with 'already exists'") def test_output_directory_args(self): '''Test output_directory (args)''' files = dict() files["usr.bin.baz"] = "/usr/bin/baz" # Build up our args args = self.full_args args.append('--template=%s' % self.test_template) args.append('--name=%s' % 'foo') args.append(files["usr.bin.baz"]) out_dir = os.path.join(self.tmpdir, "output") # Now parse our args (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(files["usr.bin.baz"], self.options) params = easyprof.gen_policy_params(files["usr.bin.baz"], self.options) easyp.output_policy(params, dir=out_dir) for fn in files: f = os.path.join(out_dir, fn) self.assertTrue(os.path.exists(f), "Could not find '%s'" % f) # # utility classes # def test_valid_profile_name(self): '''Test valid_profile_name''' names = ['foo', 'com.example.foo', '/usr/bin/foo', 'com.example.app_myapp_1:2.3+ab12~foo', ] for n in names: self.assertTrue(easyprof.valid_profile_name(n), "'%s' should be valid" % n) def test_valid_profile_name_invalid(self): '''Test valid_profile_name (invalid)''' names = ['fo/o', '/../../etc/passwd', '../../etc/passwd', './../etc/passwd', './etc/passwd', '/usr/bin//foo', '/usr/bin/./foo', 'foo`', 'foo!', 'foo@', 'foo$', 'foo#', 'foo%', 'foo^', 'foo&', 'foo*', 'foo(', 'foo)', 'foo=', 'foo{', 'foo}', 'foo[', 'foo]', 'foo|', 'foo/', 'foo\\', 'foo;', 'foo\'', 'foo"', 'foo<', 'foo>', 'foo?', 'foo\/', 'foo,', '_foo', ] for n in names: self.assertFalse(easyprof.valid_profile_name(n), "'%s' should be invalid" % n) def test_valid_path(self): '''Test valid_path''' names = ['/bin/bar', '/etc/apparmor.d/com.example.app_myapp_1:2.3+ab12~foo', ] names_rel = ['bin/bar', 'apparmor.d/com.example.app_myapp_1:2.3+ab12~foo', 'com.example.app_myapp_1:2.3+ab12~foo', ] for n in names: self.assertTrue(easyprof.valid_path(n), "'%s' should be valid" % n) for n in names_rel: self.assertTrue(easyprof.valid_path(n, relative_ok=True), "'%s' should be valid" % n) def test_zz_valid_path_invalid(self): '''Test valid_path (invalid)''' names = ['/bin//bar', 'bin/bar', '/../etc/passwd', './bin/bar', './', ] names_rel = ['bin/../bar', 'apparmor.d/../passwd', 'com.example.app_"myapp_1:2.3+ab12~foo', ] for n in names: self.assertFalse(easyprof.valid_path(n, relative_ok=False), "'%s' should be invalid" % n) for n in names_rel: self.assertFalse(easyprof.valid_path(n, relative_ok=True), "'%s' should be invalid" % n) # # End test class # # # Main # if __name__ == '__main__': def cleanup(files): for f in files: if os.path.exists(f): os.unlink(f) absfn = os.path.abspath(sys.argv[0]) topdir = os.path.dirname(os.path.dirname(absfn)) if len(sys.argv) > 1 and (sys.argv[1] == '-d' or sys.argv[1] == '--debug'): debugging = True created = [] # Create the necessary files to import aa-easyprof init = os.path.join(os.path.dirname(absfn), '__init__.py') if not os.path.exists(init): open(init, 'a').close() created.append(init) symlink = os.path.join(os.path.dirname(absfn), 'easyprof.py') if not os.path.exists(symlink): os.symlink(os.path.join(topdir, 'apparmor', 'easyprof.py'), symlink) created.append(symlink) created.append(symlink + 'c') # Now that we have everything we need, import aa-easyprof import easyprof # run the tests suite = unittest.TestSuite() suite.addTest(unittest.TestLoader().loadTestsFromTestCase(T)) rc = unittest.TextTestRunner(verbosity=2).run(suite) cleanup(created) if not rc.wasSuccessful(): sys.exit(1) apparmor-2.8.95~2430/utils/test/runtests-py2.sh0000755000175000017500000000011112277014357021113 0ustar sarnoldsarnoldfor file in *.py ; do echo "running $file..." ; python $file; echo; done apparmor-2.8.95~2430/utils/test/runtests-py3.sh0000755000175000017500000000011212277014357021115 0ustar sarnoldsarnoldfor file in *.py ; do echo "running $file..." ; python3 $file; echo; done apparmor-2.8.95~2430/utils/test/config_test.py0000755000175000017500000000416512304063504021040 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import unittest import sys sys.path.append('../') import apparmor.config as config class Test(unittest.TestCase): def test_IniConfig(self): ini_config = config.Config('ini') ini_config.CONF_DIR = '.' conf = ini_config.read_config('logprof.conf') logprof_sections = ['settings', 'repository', 'qualifiers', 'required_hats', 'defaulthat', 'globs'] logprof_sections_options = ['profiledir', 'inactive_profiledir', 'logfiles', 'parser', 'ldd', 'logger', 'default_owner_prompt', 'custom_includes'] logprof_settings_parser = '/sbin/apparmor_parser /sbin/subdomain_parser' self.assertEqual(conf.sections(), logprof_sections) self.assertEqual(conf.options('settings'), logprof_sections_options) self.assertEqual(conf['settings']['parser'], logprof_settings_parser) def test_ShellConfig(self): shell_config = config.Config('shell') shell_config.CONF_DIR = '.' conf = shell_config.read_config('easyprof.conf') easyprof_sections = ['POLICYGROUPS_DIR', 'TEMPLATES_DIR'] easyprof_Policygroup = './policygroups' easyprof_Templates = './templates' self.assertEqual(sorted(list(conf[''].keys())), sorted(easyprof_sections)) self.assertEqual(conf['']['POLICYGROUPS_DIR'], easyprof_Policygroup) self.assertEqual(conf['']['TEMPLATES_DIR'], easyprof_Templates) if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testConfig'] unittest.main() apparmor-2.8.95~2430/utils/test/common_test.py0000755000175000017500000000310712277014357021071 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import unittest import re import sys sys.path.append('../') import apparmor.common import apparmor.config class Test(unittest.TestCase): def test_RegexParser(self): tests = apparmor.config.Config('ini') tests.CONF_DIR = '.' regex_tests = tests.read_config('regex_tests.ini') for regex in regex_tests.sections(): parsed_regex = re.compile(apparmor.common.convert_regexp(regex)) for regex_testcase in regex_tests.options(regex): self.assertEqual(bool(parsed_regex.search(regex_testcase)), eval(regex_tests[regex][regex_testcase]), 'Incorrectly Parsed regex: %s' %regex) #def test_readkey(self): # print("Please press the Y button on the keyboard.") # self.assertEqual(apparmor.common.readkey().lower(), 'y', 'Error reading key from shell!') if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.test_RegexParser'] unittest.main() apparmor-2.8.95~2430/utils/test/cleanprof_test.in0000644000175000017500000000105612277004630021517 0ustar sarnoldsarnold# A simple test comment which will persist #include /usr/bin/a/simple/cleanprof/test/profile { # Just for the heck of it, this comment wont see the day of light #include #Below rule comes from abstractions/base allow /usr/share/X11/locale/** r, allow /home/*/** r, allow /home/foo/bar r, allow /home/foo/** w, } /usr/bin/other/cleanprof/test/profile { # This one shouldn't be affected by the processing # However this comment will be wiped, need to change that allow /home/*/** rw, allow /home/foo/bar r, }apparmor-2.8.95~2430/utils/test/Makefile0000644000175000017500000000246312306404336017622 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (c) 1999, 2004-2009 NOVELL (All rights reserved) # Copyright (c) 2010-2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # ---------------------------------------------------------------------- NAME = apparmor-utils all: COMMONDIR=../../common/ include common/Make.rules COMMONDIR_EXISTS=$(strip $(shell [ -d ${COMMONDIR} ] && echo true)) ifeq ($(COMMONDIR_EXISTS), true) common/Make.rules: $(COMMONDIR)/Make.rules ln -sf $(COMMONDIR) . endif .PHONY: clean ifndef VERBOSE .SILENT: clean endif clean: _clean rm -rf __pycache__/ common .PHONY: check ifndef VERBOSE .SILENT: check endif check: export PYTHONPATH=.. ; $(foreach test, $(wildcard test-*.py), $(call pyalldo, $(test))) apparmor-2.8.95~2430/utils/test/test-regex_matches.py0000644000175000017500000001341012306405111022311 0ustar sarnoldsarnold#! /usr/bin/env python # ------------------------------------------------------------------ # # Copyright (C) 2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ import apparmor.aa as aa import unittest class AARegexHasComma(unittest.TestCase): '''Tests for apparmor.aa.RE_RULE_HAS_COMMA''' def _check(self, line, expected=True): result = aa.RE_RULE_HAS_COMMA.search(line) if expected: self.assertTrue(result, 'Couldn\'t find a comma in "%s"' % line) else: self.assertEqual(None, result, 'Found an unexpected comma in "%s"' % line) regex_has_comma_testcases = [ ('dbus send%s', 'simple'), ('dbus (r, w, bind, eavesdrop)%s', 'embedded parens 01'), ('dbus (r, w,, bind, eavesdrop) %s', 'embedded parens 02'), ('dbus (r, w,, ) %s', 'embedded parens 03'), ('dbus () %s', 'empty parens'), ('member={Hello,AddMatch,RemoveMatch,GetNameOwner,NameHasOwner,StartServiceByName} %s ', 'embedded curly braces 01'), ('member={Hello,,,,,,AddMatch,,,NameHasOwner,StartServiceByName} %s ', 'embedded curly braces 02'), ('member={,Hello,,,,,,AddMatch,,,NameHasOwner,} %s ', 'embedded curly braces 03'), ('member={} %s ', 'empty curly braces'), ('dbus send%s# this is a comment', 'comment 01'), ('dbus send%s# this is a comment,', 'comment 02'), ('audit "/tmp/foo, bar" rw%s', 'quotes 01'), ('audit "/tmp/foo, bar" rw%s # comment', 'quotes 02'), ('audit "/tmp/foo, # bar" rw%s', 'comment embedded in quote 01'), ('audit "/tmp/foo, # bar" rw%s # comment', 'comment embedded in quote 02'), # lifted from parser/tst/simple_tests/vars/vars_alternation_3.sd ('/does/not/@{BAR},exist,notexist} r%s', 'partial alternation') # the following fail due to inadequacies in the regex # ('dbus (r, w, %s', 'incomplete dbus action'), # ('member="{Hello,AddMatch,RemoveMatch, %s', 'incomplete {} regex'), # also invalid policy # ('member={Hello,AddMatch,RemoveMatch, %s', 'incomplete {} regex'), # also invalid policy when trailing comma exists # the following passes the tests, but variable declarations are # odd in that they *don't* allow trailing commas; commas at the end # of the line need to be quoted. # ('@{BAR}={bar,baz,blort %s', 'tricksy variable declaration') # ('@{BAR}="{bar,baz,blort," %s', 'tricksy variable declaration') # The following fails the no comma test, but is invalid # ('@{BAR}={bar,baz,blort, %s', 'tricksy variable declaration') # The following fails the comma test, because it's really a no comma situation # ('@{BAR}="{bar,baz,blort%s" ', 'tricksy variable declaration') ] def setup_has_comma_testcases(): i = 0 for (test_string, description) in regex_has_comma_testcases: i += 1 def stub_test_comma(self, test_string=test_string): self._check(test_string % ',') def stub_test_no_comma(self, test_string=test_string): self._check(test_string % ' ', False) stub_test_comma.__doc__ = "test %s (w/comma)" % (description) stub_test_no_comma.__doc__ = "test %s (no comma)" % (description) setattr(AARegexHasComma, 'test_comma_%d' % (i), stub_test_comma) setattr(AARegexHasComma, 'test_no_comma_%d' % (i), stub_test_no_comma) class AARegexSplitComment(unittest.TestCase): '''Tests for RE_HAS_COMMENT_SPLIT''' def _check(self, line, expected, comment=None, not_comment=None): result = aa.RE_HAS_COMMENT_SPLIT.search(line) if expected: self.assertTrue(result, 'Couldn\'t find a comment in "%s"' % line) self.assertEqual(result.group('comment'), comment, 'Expected comment "%s", got "%s"' % (comment, result.group('comment'))) self.assertEqual(result.group('not_comment'), not_comment, 'Expected not comment "%s", got "%s"' % (not_comment, result.group('not_comment'))) else: self.assertEqual(None, result, 'Found an unexpected comment "%s" in "%s"' % ("" if result is None else result.group('comment'), line )) # Tuples of (string, expected result), where expected result is False if # the string should not be considered as having a comment, or a second # tuple of the not comment and comment sections split apart regex_split_comment_testcases = [ ('dbus send # this is a comment', ('dbus send ', '# this is a comment')), ('dbus send member=no_comment', False), ('dbus send member=no_comment, ', False), ('audit "/tmp/foo, # bar" rw', False), ('audit "/tmp/foo, # bar" rw # comment', ('audit "/tmp/foo, # bar" rw ', '# comment')), ] def setup_split_comment_testcases(): i = 0 for (test_string, result) in regex_split_comment_testcases: i += 1 def stub_test(self, test_string=test_string, result=result): if result is False: self._check(test_string, False) else: self._check(test_string, True, not_comment=result[0], comment=result[1]) stub_test.__doc__ = "test '%s'" % (test_string) setattr(AARegexSplitComment, 'test_split_comment_%d' % (i), stub_test) if __name__ == '__main__': verbosity = 2 setup_has_comma_testcases() setup_split_comment_testcases() test_suite = unittest.TestSuite() test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(AARegexHasComma)) test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(AARegexSplitComment)) result = unittest.TextTestRunner(verbosity=verbosity).run(test_suite) if not result.wasSuccessful(): exit(1) apparmor-2.8.95~2430/utils/test/easyprof.conf0000644000175000017500000000020011752130454020645 0ustar sarnoldsarnold# Location of system policygroups POLICYGROUPS_DIR="./policygroups" # Location of system templates TEMPLATES_DIR="./templates" apparmor-2.8.95~2430/utils/test/cleanprof_test.out0000644000175000017500000000041012277004630021711 0ustar sarnoldsarnold#include # A simple test comment which will persist /usr/bin/a/simple/cleanprof/test/profile { #include /home/*/** r, /home/foo/** w, } /usr/bin/other/cleanprof/test/profile { /home/*/** rw, /home/foo/bar r, } apparmor-2.8.95~2430/utils/test/regex_tests.ini0000644000175000017500000000303212277004630021210 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- [/foo/**/bar/] /foo/user/tools/bar/ = True /foo/apparmor/bar/ = True /foo/apparmor/bar = False [/foo/*/bar/] /foo/apparmor/bar/ = True /foo/apparmor/tools/bar/ = False /foo/apparmor/bar = False [/foo/{foo,bar,user,other}/bar/] /foo/user/bar/ = True /foo/bar/bar/ = True /foo/wrong/bar/ = False [/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/] /foo/user/test,ca}se/aa/bar/ = True /foo/bar/test,ca}se/sd/bar/ = True /foo/wrong/user/bar/ = False /foo/user/wrong/bar/ = False /foo/wrong/aa/bar/ = False [/foo/user/ba?/] /foo/user/bar/ = True /foo/user/bar/apparmor/ = False /foo/user/ba/ = False /foo/user/ba// = False [/foo/user/bar/**] /foo/user/bar/apparmor = True /foo/user/bar/apparmor/tools = True /foo/user/bar/ = False [/foo/user/bar/*] /foo/user/bar/apparmor = True /foo/user/bar/apparmor/tools = False /foo/user/bar/ = False /foo/user/bar/apparmor/ = Falseapparmor-2.8.95~2430/utils/aa-mergeprof.pod0000644000175000017500000000123312277004630020246 0ustar sarnoldsarnold=pod =head1 NAME aa-mergeprof - merge AppArmor security profiles. =head1 SYNOPSIS BmineE> IuserE> IotherE> [I<-d /path/to/profiles>]> =head1 OPTIONS B<-d --dir /path/to/profiles> Specifies where to look for the AppArmor security profile set. Defaults to /etc/apparmor.d. =head1 DESCRIPTION B =head1 BUGS If you find any bugs, please report them at L. =head1 SEE ALSO apparmor(7), apparmor.d(5), aa_change_hat(2), aa-genprof(1), aa-logprof(1), aa-enforce(1), aa-audit(1), aa-complain(1), aa-disable(1), and L. =cut apparmor-2.8.95~2430/utils/aa-cleanprof.pod0000644000175000017500000000171212277004630020233 0ustar sarnoldsarnold=pod =head1 NAME aa-cleanprof - clean an existing AppArmor security profile. =head1 SYNOPSIS BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] [I<-s>]> =head1 OPTIONS B<-d --dir /path/to/profiles> Specifies where to look for the AppArmor security profile set. Defaults to /etc/apparmor.d. B<-s --silent> Silently overwrites the profile without user prompt. =head1 DESCRIPTION B is used to perform a cleanup on one or more profiles. The tool removes any existing superfluous rules (rules that are covered under an include or another rule), reorders the rules to group similar rules together and removes all comments from the file. =head1 BUGS If you find any bugs, please report them at L. =head1 SEE ALSO apparmor(7), apparmor.d(5), aa-enforce(1), aa-complain(1), aa-disable(1), aa_change_hat(2), and L. =cut apparmor-2.8.95~2430/utils/vim/0000755000175000017500000000000012311706721015770 5ustar sarnoldsarnoldapparmor-2.8.95~2430/utils/vim/apparmor.vim.in0000644000175000017500000002232612270534667020754 0ustar sarnoldsarnold" ---------------------------------------------------------------------- " Copyright (c) 2005 Novell, Inc. All Rights Reserved. " Copyright (c) 2006-2012 Christian Boltz. All Rights Reserved. " " This program is free software; you can redistribute it and/or " modify it under the terms of version 2 of the GNU General Public " License as published by the Free Software Foundation. " " This program 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. See the " GNU General Public License for more details. " " You should have received a copy of the GNU General Public License " along with this program; if not, contact Novell, Inc. " " To contact Novell about this file by physical or electronic mail, " you may find current contact information at www.novell.com. " " To contact Christian Boltz about this file by physical or electronic " mail, you may find current contact information at www.cboltz.de/en/kontakt. " " If you want to report a bug via bugzilla.novell.com, please assign it " to suse-beta[AT]cboltz.de (replace [AT] with @). " ---------------------------------------------------------------------- " " stick this file into ~/.vim/syntax/ and add these commands into your .vimrc " to have vim automagically use this syntax file for these directories: " " autocmd BufNewFile,BufRead /etc/apparmor.d/* set syntax=apparmor " autocmd BufNewFile,BufRead /usr/share/apparmor/extra-profiles/* set syntax=apparmor " profiles are case sensitive syntax case match " color setup... " adjust colors according to the background " switching colors depending on the background color doesn't work " unfortunately, so we use colors that work with light and dark background. " Patches welcome ;-) "if &background == "light" " light background hi sdProfileName ctermfg=lightblue hi sdHatName ctermfg=darkblue hi sdExtHat ctermfg=darkblue " hi sdComment2 ctermfg=darkblue hi sdGlob ctermfg=darkmagenta hi sdAlias ctermfg=darkmagenta hi sdEntryWriteExec ctermfg=black ctermbg=yellow hi sdEntryUX ctermfg=darkred cterm=underline hi sdEntryUXe ctermfg=darkred hi sdEntryIX ctermfg=darkcyan hi sdEntryM ctermfg=darkcyan hi sdEntryPX ctermfg=darkgreen cterm=underline hi sdEntryPXe ctermfg=darkgreen hi sdEntryW ctermfg=darkyellow hi sdCap ctermfg=lightblue hi sdSetCap ctermfg=black ctermbg=yellow hi sdNetwork ctermfg=lightblue hi sdNetworkDanger ctermfg=darkred hi sdCapKey cterm=underline ctermfg=lightblue hi sdCapDanger ctermfg=darkred hi sdRLimit ctermfg=lightblue hi def link sdEntryR Normal hi def link sdEntryK Normal hi def link sdFlags Normal hi sdEntryChangeProfile ctermfg=darkgreen cterm=underline "else " dark background " hi sdProfileName ctermfg=white " hi sdHatName ctermfg=white " hi sdGlob ctermfg=magenta " hi sdEntryWriteExec ctermfg=black ctermbg=yellow " hi sdEntryUX ctermfg=red cterm=underline " hi sdEntryUXe ctermfg=red " hi sdEntryIX ctermfg=cyan " hi sdEntryM ctermfg=cyan " hi sdEntryPX ctermfg=green cterm=underline " hi sdEntryPXe ctermfg=green " hi sdEntryW ctermfg=yellow " hi sdCap ctermfg=lightblue " hi sdCapKey cterm=underline ctermfg=lightblue " hi def link sdEntryR Normal " hi def link sdFlags Normal " hi sdCapDanger ctermfg=red "endif hi def link sdInclude Include high def link sdComment Comment "high def link sdComment2 Comment high def link sdFlagKey TODO high def link sdError ErrorMsg " always sync from the start. should be relatively quick since we don't have " that many rules and profiles shouldn't be _extremely_ large... syn sync fromstart syn keyword sdFlagKey complain debug " highlight invalid syntax syn match sdError /{/ contained syn match sdError /}/ syn match sdError /^.*$/ contains=sdComment "highlight all non-valid lines as error " TODO: do not mark lines containing only whitespace as error " TODO: the sdGlob pattern is not anchored with ^ and $, so it matches all lines matching ^@{...}.* " This allows incorrect lines also and should be checked better. " This also (accidently ;-) includes variable definitions (@{FOO}=/bar) " TODO: make a separate pattern for variable definitions, then mark sdGlob as contained syn match sdGlob /\v\?|\*|\{.*,.*\}|[[^\]]\+\]|\@\{[a-zA-Z][a-zA-Z0-9_]*\}/ syn match sdAlias /\v^alias\s+@@FILENAME@@\s+-\>\s+@@FILENAME@@@@EOL@@/ contains=sdGlob " syn match sdComment /#.*/ syn cluster sdEntry contains=sdEntryWriteExec,sdEntryR,sdEntryW,sdEntryIX,sdEntryPX,sdEntryPXe,sdEntryUX,sdEntryUXe,sdEntryM,sdCap,sdSetCap,sdExtHat,sdRLimit,sdNetwork,sdNetworkDanger,sdEntryChangeProfile " TODO: support audit and deny keywords for all rules (not only for files) " TODO: higlight audit and deny keywords everywhere " Capability line " normal capabilities - really keep this list? syn match sdCap should be enough... (difference: sdCapKey words would loose underlining) syn keyword sdCapKey @@sdKapKey@@ " dangerous capabilities - highlighted separately syn keyword sdCapDanger @@sdKapKeyDanger@@ " full line. Keywords are from sdCapKey + sdCapDanger syn match sdCap /\v^\s*@@auditdeny@@capability\s+((@@sdKapKeyRegex@@)\s+)*(@@sdKapKeyRegex@@)@@EOL@@/ contains=sdCapKey,sdCapDanger,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude " all capabilities ('capability' without any keyword) syn match sdCapDanger /\v^\s*@@auditdeny@@capability@@EOL@@/ contains=sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude " Network line " Syntax: network domain (inet, ...) type (stream, ...) protocol (tcp, ...) " TODO: 'owner' isn't supported, but will be (JJ, 2011-01-11) syn match sdNetwork /\v^\s*@@auditdeny@@network(\s+(@@sdNetworkProto@@))?(\s+(stream|dgram|seqpacket|rdm|packet))?(@@sdNetworkType@@)?@@EOL@@/ contains=sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude " network rules containing 'raw' syn match sdNetworkDanger /\v^\s*@@auditdeny@@network(\s+(@@sdNetworkProto@@))?(\s+(raw))(@@sdNetworkType@@)?@@EOL@@/ contains=sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude " 'all networking' includes raw -> mark as dangerous syn match sdNetworkDanger /\v^\s*@@auditdeny@@network@@EOL@@/ contains=sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude " Change Profile " TODO: audit and deny support will be added (JJ, 2011-01-11) syn match sdEntryChangeProfile /\v^\s*change_profile\s+-\>\s+\S+@@EOL@@/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude " rlimit " TODO: audit and deny support will be added (JJ, 2011-01-11) " "syn match sdRLimit /\v^\s*rlimit\s+()@@EOL@@/ contains=sdComment syn match sdRLimit /\v^\s*set\s+rlimit\s+(nofile|ofile|nproc|rtprio)\s+\<\=\s+[0-9]+@@EOL@@/ contains=sdComment syn match sdRLimit /\v^\s*set\s+rlimit\s+(locks|sigpending)\s+\<\=\s+[0-9]+@@EOL@@/ contains=sdComment syn match sdRLimit /\v^\s*set\s+rlimit\s+(fsize|data|stack|core|rss|as|memlock|msgqueue)\s+\<\=\s+[0-9]+([KMG]B)?@@EOL@@/ contains=sdComment syn match sdRLimit /\v^\s*set\s+rlimit\s+nice\s+\<\=\s+(-1?[0-9]|-20|1?[0-9])@@EOL@@/ contains=sdComment syn match sdRLimit /\v^\s*set\s+rlimit\s+cpu\s+\<\=\s+[0-9]+(seconds|minutes|hours|days)?@@EOL@@/ contains=sdComment syn match sdRLimit /\v^\s*set\s+rlimit\s+rttime\s+\<\=\s+[0-9]+(ms|seconds|minutes)?@@EOL@@/ contains=sdComment syn match sdRLimit /\v^\s*set\s+rlimit\s+(cpu|rttime|nofile|nproc|rtprio|locks|sigpending|fsize|data|stack|core|rss|as|memlock|msgqueue|nice)\s+\<\=\s+infinity@@EOL@@/ contains=sdComment " link rules syn match sdEntryW /\v^\s+@@auditdenyowner@@link\s+(subset\s+)?@@FILENAME@@\s+-\>\s+@@FILENAME@@@@EOL@@/ contains=sdGlob syn match sdExtHat /\v^\s+(\^|profile\s+)\S+@@EOL@@/ contains=sdComment " hat without {...} syn match sdProfileName /\v^((profile\s+)?\/\S+|profile\s+([a-zA-Z0-9]\S*\s)?\S+)\s+@@flags@@=\{/ contains=sdProfileStart,sdHatName,sdFlags,sdComment,sdGlob syn match sdProfileStart /{/ contained syn match sdProfileEnd /^}\s*(#.*)?$/ contained " TODO: syn region does not (yet?) allow usage of comment in end= " TODO: Removing the $ mark from end= will allow non-comments also :-( syn match sdHatName /\v^\s+(\^|profile\s+)\S+\s+@@flags@@=\{/ contains=sdProfileStart,sdFlags,sdComment syn match sdHatStart /{/ contained syn match sdHatEnd /}/ contained " TODO: allow comments + [same as for syn match sdProfileEnd] syn match sdFlags /\v@@flags@@/ contained contains=sdFlagKey syn match sdComment /\s*#.*$/ " NOTE: contains=sdComment changes #include highlighting to comment color. " NOTE: Comment highlighting still works without contains=sdComment. syn match sdInclude /\s*#include\s<\S*>/ " TODO: doesn't check until $ syn match sdInclude /\s*include\s<\S*>/ " TODO: doesn't check until $ " basic profile block... " \s+ does not work in end=, therefore using \s\s* syn region Normal start=/\v^(profile\s+)?\S+\s+@@flags@@=\{/ matchgroup=sdProfileEnd end=/^}\s*$/ contains=sdProfileName,Hat,@sdEntry,sdComment,sdError,sdInclude syn region Hat start=/\v^\s+(\^|profile\s+)\S+\s+@@flags@@=\{/ matchgroup=sdHatEnd end=/^\s\s*}\s*$/ contains=sdHatName,@sdEntry,sdComment,sdError,sdInclude " file permissions apparmor-2.8.95~2430/utils/vim/create-apparmor.vim.py0000644000175000017500000001702512272276704022234 0ustar sarnoldsarnold#!/usr/bin/python # # Copyright (C) 2012 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # Written by Steve Beattie , based on work by # Christian Boltz from __future__ import with_statement import re import subprocess import sys # dangerous capabilities danger_caps = ["audit_control", "audit_write", "mac_override", "mac_admin", "set_fcap", "sys_admin", "sys_module", "sys_rawio"] def cmd(command, input=None, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, stdin=None, timeout=None): '''Try to execute given command (array) and return its stdout, or return a textual error if it failed.''' try: sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, universal_newlines=True) except OSError as ex: return [127, str(ex)] out, outerr = sp.communicate(input) # Handle redirection of stdout if out is None: out = '' # Handle redirection of stderr if outerr is None: outerr = '' return [sp.returncode, out + outerr] # get capabilities list (rc, output) = cmd(['make', '-s', '--no-print-directory', 'list_capabilities']) if rc != 0: sys.stderr.write("make list_capabilities failed: " + output) exit(rc) capabilities = re.sub('CAP_', '', output.strip()).lower().split(" ") benign_caps = [] for cap in capabilities: if cap not in danger_caps: benign_caps.append(cap) # get network protos list (rc, output) = cmd(['make', '-s', '--no-print-directory', 'list_af_names']) if rc != 0: sys.stderr.write("make list_af_names failed: " + output) exit(rc) af_names = [] af_pairs = re.sub('AF_', '', output.strip()).lower().split(",") for af_pair in af_pairs: af_name = af_pair.lstrip().split(" ")[0] # skip max af name definition if len(af_name) > 0 and af_name != "max": af_names.append(af_name) # TODO: does a "debug" flag exist? Listed in apparmor.vim.in sdFlagKey, # but not in aa_flags... # -> currently (2011-01-11) not, but might come back aa_network_types = r'\s+tcp|\s+udp|\s+icmp' aa_flags = ['complain', 'audit', 'attach_disconnected', 'no_attach_disconnected', 'chroot_attach', 'chroot_no_attach', 'chroot_relative', 'namespace_relative'] filename = r'(\/|\@\{\S*\})\S*' aa_regex_map = { 'FILENAME': filename, 'FILE': r'\v^\s*(audit\s+)?(deny\s+|allow\s+)?(owner\s+|other\s+)?' + filename + r'\s+', # Start of a file rule # (whitespace_+_, owner etc. flag_?_, filename pattern, whitespace_+_) 'DENYFILE': r'\v^\s*(audit\s+)?deny\s+(owner\s+|other\s+)?' + filename + r'\s+', # deny, otherwise like FILE 'auditdenyowner': r'(audit\s+)?(deny\s+|allow\s+)?(owner\s+|other\s+)?', 'audit_DENY_owner': r'(audit\s+)?deny\s+(owner\s+|other\s+)?', # must include "deny", otherwise like auditdenyowner 'auditdeny': r'(audit\s+)?(deny\s+|allow\s+)?', 'EOL': r'\s*,(\s*$|(\s*#.*$)\@=)', # End of a line (whitespace_?_, comma, whitespace_?_ comment.*) 'TRANSITION': r'(\s+-\>\s+\S+)?', 'sdKapKey': " ".join(benign_caps), 'sdKapKeyDanger': " ".join(danger_caps), 'sdKapKeyRegex': "|".join(capabilities), 'sdNetworkType': aa_network_types, 'sdNetworkProto': "|".join(af_names), 'flags': r'((flags\s*\=\s*)?\(\s*(' + '|'.join(aa_flags) + r')(\s*,\s*(' + '|'.join(aa_flags) + r'))*\s*\)\s+)', } def my_repl(matchobj): matchobj.group(1) if matchobj.group(1) in aa_regex_map: return aa_regex_map[matchobj.group(1)] return matchobj.group(0) def create_file_rule(highlighting, permissions, comment, denyrule=0): if denyrule == 0: keywords = '@@auditdenyowner@@' else: keywords = '@@audit_DENY_owner@@' # TODO: not defined yet, will be '(audit\s+)?deny\s+(owner\s+)?' sniplet = '' sniplet = sniplet + "\n" + '" ' + comment + "\n" prefix = r'syn match ' + highlighting + r' /\v^\s*' + keywords suffix = r'@@EOL@@/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude' + "\n" # filename without quotes sniplet = sniplet + prefix + r'@@FILENAME@@\s+' + permissions + suffix # filename with quotes sniplet = sniplet + prefix + r'"@@FILENAME@@"\s+' + permissions + suffix # filename without quotes, reverse syntax sniplet = sniplet + prefix + permissions + r'\s+@@FILENAME@@' + suffix # filename with quotes, reverse syntax sniplet = sniplet + prefix + permissions + r'\s+"@@FILENAME@@"+' + suffix return sniplet filerule = '' filerule = filerule + create_file_rule('sdEntryWriteExec ', r'(l|r|w|a|m|k|[iuUpPcC]x)+@@TRANSITION@@', 'write + exec/mmap - danger! (known bug: accepts aw to keep things simple)') filerule = filerule + create_file_rule('sdEntryUX', r'(r|m|k|ux|pux)+@@TRANSITION@@', 'ux(mr) - unconstrained entry, flag the line red. also includes pux which is unconstrained if no profile exists') filerule = filerule + create_file_rule('sdEntryUXe', r'(r|m|k|Ux|PUx)+@@TRANSITION@@', 'Ux(mr) and PUx(mr) - like ux + clean environment') filerule = filerule + create_file_rule('sdEntryPX', r'(r|m|k|px|cx|pix|cix)+@@TRANSITION@@', 'px/cx/pix/cix(mrk) - standard exec entry, flag the line blue') filerule = filerule + create_file_rule('sdEntryPXe', r'(r|m|k|Px|Cx|Pix|Cix)+@@TRANSITION@@', 'Px/Cx/Pix/Cix(mrk) - like px/cx + clean environment') filerule = filerule + create_file_rule('sdEntryIX', r'(r|m|k|ix)+', 'ix(mr) - standard exec entry, flag the line green') filerule = filerule + create_file_rule('sdEntryM', r'(r|m|k)+', 'mr - mmap with PROT_EXEC') filerule = filerule + create_file_rule('sdEntryM', r'(r|m|k|x)+', 'special case: deny x is allowed (does not need to be ix, px, ux or cx)', 1) #syn match sdEntryM /@@DENYFILE@@(r|m|k|x)+@@EOL@@/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude filerule = filerule + create_file_rule('sdError', r'\S*(w\S*a|a\S*w)\S*', 'write + append is an error') filerule = filerule + create_file_rule('sdEntryW', r'(l|r|w|k)+', 'write entry, flag the line yellow') filerule = filerule + create_file_rule('sdEntryW', r'(l|r|a|k)+', 'append entry, flag the line yellow') filerule = filerule + create_file_rule('sdEntryK', r'[rlk]+', 'read entry + locking, currently no highlighting') filerule = filerule + create_file_rule('sdEntryR', r'[rl]+', 'read entry, no highlighting') # " special case: deny x is allowed (doesn't need to be ix, px, ux or cx) # syn match sdEntryM /@@DENYFILE@@(r|m|k|x)+@@EOL@@/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude # " TODO: Support filenames enclosed in quotes ("/home/foo/My Documents/") - ideally by only allowing quotes pair-wise regex = "@@(" + "|".join(aa_regex_map) + ")@@" sys.stdout.write('" generated from apparmor.vim.in by create-apparmor.vim.py\n') sys.stdout.write('" do not edit this file - edit apparmor.vim.in or create-apparmor.vim.py instead' + "\n\n") with open("apparmor.vim.in") as template: for line in template: line = re.sub(regex, my_repl, line.rstrip()) sys.stdout.write('%s\n' % line) sys.stdout.write("\n\n\n\n") sys.stdout.write('" file rules added with create_file_rule()\n') sys.stdout.write(re.sub(regex, my_repl, filerule) + '\n') apparmor-2.8.95~2430/utils/vim/apparmor.vim.pod0000644000175000017500000000366612216646723021133 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, # 2008, 2009 # NOVELL (All rights reserved) # # Copyright (c) 2010 - 2012 # Canonical Ltd. (All rights reserved) # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Canonical, Ltd. # ---------------------------------------------------------------------- =pod =head1 NAME apparmor.vim - vim syntax highlighting file for AppArmor profiles =head1 SYNOPSIS Your system may be configured to automatically use syntax highlighting for installed AppArmor policies. If not, you can enable syntax highlighting in a specific vim session by performing: :set syntax=apparmor =head1 DESCRIPTION B provides syntax highlighting rules for the vim(1) text editor; the rules provide an easy visual method to inspect AppArmor profiles for syntax correctness and semantics. The colors indicate the relative severity of granting a specific set of privileges. Ranking access with colors is necessarily generic and vague, but it may help you understand your profiles better. =head1 BUGS B does not properly detect dark versus light backgrounds. Patches accepted. If you find any bugs, please report them at L. =head1 SEE ALSO vim(1), apparmor(7), apparmor.d(5), aa_change_hat(2), and L. =cut apparmor-2.8.95~2430/utils/vim/Makefile0000644000175000017500000000137212306136145017434 0ustar sarnoldsarnoldCOMMONDIR=../../common/ all: include common/Make.rules COMMONDIR_EXISTS=$(strip $(shell [ -d ${COMMONDIR} ] && echo true)) ifeq ($(COMMONDIR_EXISTS), true) common/Make.rules: $(COMMONDIR)/Make.rules ln -sf $(COMMONDIR) . endif MANPAGES=apparmor.vim.5 VIM_INSTALL_PATH=${DESTDIR}/usr/share/apparmor all: apparmor.vim manpages apparmor.vim: apparmor.vim.in Makefile create-apparmor.vim.py ${PYTHON} create-apparmor.vim.py > apparmor.vim manpages: $(MANPAGES) install: apparmor.vim manpages install -d $(VIM_INSTALL_PATH) install -m 644 $< $(VIM_INSTALL_PATH) $(MAKE) install_manpages DESTDIR=${DESTDIR} .PHONY: check check: #Testing with all pythons $(call pyalldo, create-apparmor.vim.py > /dev/null) clean: rm -f apparmor.vim common $(MANPAGES) apparmor-2.8.95~2430/utils/aa-exec.pod0000644000175000017500000000574212216646723017225 0ustar sarnoldsarnold# This publication is intellectual property of Canonical Ltd. Its contents # can be duplicated, either in part or in whole, provided that a copyright # label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither Canonical Ltd, the authors, nor the translators shall be held # liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. Canonical Ltd # essentially adheres to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-exec - confine a program with the specified AppArmor profile =head1 SYNOPSIS B [options] [--] [IcommandE> ...] =head1 DESCRIPTION B is used to launch a program confined by the specified profile and or namespace. If both a profile and namespace are specified command will be confined by profile in the new policy namespace. If only a namespace is specified, the profile name of the current confinement will be used. If neither a profile or namespace is specified command will be run using standard profile attachment (ie. as if run without the aa-exec command). If the arguments are to be pasted to the IcommandE> being invoked by aa-exec then -- should be used to separate aa-exec arguments from the command. aa-exec -p profile1 -- ls -l =head1 OPTIONS B accepts the following arguments: =over 4 =item -p PROFILE, --profile=PROFILE confine IcommandE> with PROFILE. If the PROFILE is not specified use the current profile name (likely unconfined). =item -n NAMESPACE, --namespace=NAMESPACE use profiles in NAMESPACE. This will result in confinement transitioning to using the new profile namespace. =item -f FILE, --file=FILE a file or directory containing profiles to load before confining the program. =item -i, --immediate transition to PROFILE before doing executing IcommandE>. This subjects the running of IcommandE> to the exec transition rules of the current profile. =item -v, --verbose show commands being performed =item -d, --debug show commands and error codes =item -- Signal the end of options and disables further option processing. Any arguments after the -- are treated as arguments of the command. This is useful when passing arguments to the IcommandE> being invoked by aa-exec. =back =head1 BUGS If you find any bugs, please report them at L. =head1 SEE ALSO aa-stack(8), aa-namespace(8), apparmor(7), apparmor.d(5), aa_change_profile(3), aa_change_onexec(3) and L. =cut apparmor-2.8.95~2430/utils/aa-mergeprof0000755000175000017500000010747312277515713017516 0ustar sarnoldsarnold#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import re import apparmor.aa import apparmor.aamode import apparmor.severity import apparmor.cleanprofile as cleanprofile # setup module translations from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_('Perform a 3way merge on the given profiles')) parser.add_argument('mine', type=str, help=_('your profile')) parser.add_argument('base', type=str, help=_('base profile')) parser.add_argument('other', type=str, help=_('other profile')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-a', '--auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts')) args = parser.parse_args() profiles = [args.mine, args.base, args.other] def main(): mergeprofiles = Merge(profiles) #Get rid of common/superfluous stuff mergeprofiles.clear_common() if not args.auto: mergeprofiles.ask_the_questions('other') mergeprofiles.clear_common() mergeprofiles.ask_the_questions('base') q = apparmor.aa.hasher() q['title'] = 'Changed Local Profiles' q['headers'] = [] q['explanation'] = _('The following local profiles were changed. Would you like to save them?') q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT'] q['default'] = 'CMD_VIEW_CHANGES' q['options'] = [] q['selected'] = 0 ans = '' arg = None programs = list(mergeprofiles.user.aa.keys()) program = programs[0] while ans != 'CMD_SAVE_CHANGES': ans, arg = apparmor.aa.UI_PromptUser(q) if ans == 'CMD_SAVE_CHANGES': apparmor.aa.write_profile_ui_feedback(program) apparmor.aa.reload_base(program) elif ans == 'CMD_VIEW_CHANGES': for program in programs: apparmor.aa.original_aa[program] = apparmor.aa.deepcopy(apparmor.aa.aa[program]) #oldprofile = apparmor.serialize_profile(apparmor.original_aa[program], program, '') newprofile = apparmor.aa.serialize_profile(mergeprofiles.user.aa[program], program, '') apparmor.aa.display_changes_with_comments(mergeprofiles.user.filename, newprofile) class Merge(object): def __init__(self, profiles): user, base, other = profiles #Read and parse base profile and save profile data, include data from it and reset them apparmor.aa.read_profile(base, True) self.base = cleanprofile.Prof(base) self.reset() #Read and parse other profile and save profile data, include data from it and reset them apparmor.aa.read_profile(other, True) self.other = cleanprofile.Prof(other) self.reset() #Read and parse user profile apparmor.aa.read_profile(profiles[0], True) self.user = cleanprofile.Prof(user) def reset(self): apparmor.aa.aa = apparmor.aa.hasher() apparmor.aa.filelist = apparmor.aa.hasher() apparmor.aa.include = dict() apparmor.aa.existing_profiles = apparmor.aa.hasher() apparmor.aa.original_aa = apparmor.aa.hasher() def clear_common(self): deleted = 0 #Remove off the parts in other profile which are common/superfluous from user profile user_other = cleanprofile.CleanProf(False, self.user, self.other) deleted += user_other.compare_profiles() #Remove off the parts in base profile which are common/superfluous from user profile user_base = cleanprofile.CleanProf(False, self.user, self.base) deleted += user_base.compare_profiles() #Remove off the parts in other profile which are common/superfluous from base profile # base_other = cleanprofile.CleanProf(False, self.base, self.other) # XXX base_other not used? deleted += user_base.compare_profiles() def conflict_mode(self, profile, hat, allow, path, mode, new_mode, old_mode): m = new_mode o = old_mode new_mode = apparmor.aa.flatten_mode(new_mode) old_mode = apparmor.aa.flatten_mode(old_mode) conflict_modes = set('uUpPcCiIxX') conflict_x= (old_mode | new_mode) & conflict_modes if conflict_x: #We may have conflicting x modes if conflict_x & set('x'): conflict_x.remove('x') if conflict_x & set('X'): conflict_x.remove('X') if len(conflict_x) > 1: q = apparmor.aa.hasher() q['headers'] = [_('Path'), path] q['headers'] += [_('Select the appropriate mode'), ''] options = [] options.append('%s: %s' %(mode, apparmor.aa.mode_to_str_user(new_mode)))# - (old_mode & conflict_x)))) options.append('%s: %s' %(mode, apparmor.aa.mode_to_str_user(old_mode)))#(old_mode | new_mode) - (new_mode & conflict_x)))) q['options'] = options q['functions'] = ['CMD_ALLOW', 'CMD_ABORT'] done = False while not done: ans, selected = apparmor.aa.UI_PromptUser(q) if ans == 'CMD_ALLOW': if selected == 0: self.user.aa[profile][hat][allow]['path'][path][mode] = m#apparmor.aa.owner_flatten_mode(new_mode)#(old_mode | new_mode) - (old_mode & conflict_x) return m elif selected == 1: return o pass#self.user.aa[profile][hat][allow][path][mode] = (old_mode | new_mode) - (new_mode & conflict_x) else: raise apparmor.aa.AppArmorException(_('Unknown selection')) done = True def ask_the_questions(self, other): if other == 'other': other = self.other else: other = self.base #print(other.aa) #Add the file-wide includes from the other profile to the user profile done = False options = list(map(lambda inc: '#include <%s>' %inc, sorted(other.filelist[other.filename]['include'].keys()))) q = apparmor.aa.hasher() q['options'] = options default_option = 1 q['selected'] = default_option - 1 q['headers'] = [_('File includes'), _('Select the ones you wish to add')] q['functions'] = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] q['default'] = 'CMD_ALLOW' while not done and options: ans, selected = apparmor.aa.UI_PromptUser(q) if ans == 'CMD_IGNORE_ENTRY': done = True elif ans == 'CMD_ALLOW': selection = options[selected] inc = apparmor.aa.re_match_include(selection) self.user.filelist[self.user.filename]['include'][inc] = True options.pop(selected) apparmor.aa.UI_Info(_('Adding %s to the file.') % selection) sev_db = apparmor.aa.sev_db if not sev_db: sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown')) for profile in sorted(other.aa.keys()): for hat in sorted(other.aa[profile].keys()): #Add the includes from the other profile to the user profile done = False options = list(map(lambda inc: '#include <%s>' %inc, sorted(other.aa[profile][hat]['include'].keys()))) q = apparmor.aa.hasher() q['options'] = options default_option = 1 q['selected'] = default_option - 1 q['headers'] = [_('File includes'), _('Select the ones you wish to add')] q['functions'] = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] q['default'] = 'CMD_ALLOW' while not done and options: ans, selected = apparmor.aa.UI_PromptUser(q) if ans == 'CMD_IGNORE_ENTRY': done = True elif ans == 'CMD_ALLOW': selection = options[selected] inc = apparmor.aa.re_match_include(selection) deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) self.user.aa[profile][hat]['include'][inc] = True options.pop(selected) apparmor.aa.UI_Info(_('Adding %s to the file.') % selection) if deleted: apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) #Add the capabilities for allow in ['allow', 'deny']: if other.aa[profile][hat].get(allow, False): continue for capability in sorted(other.aa[profile][hat][allow]['capability'].keys()): severity = sev_db.rank('CAP_%s' % capability) default_option = 1 options = [] newincludes = apparmor.aa.match_cap_includes(self.user.aa[profile][hat], capability) q = apparmor.aa.hasher() if newincludes: options += list(map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes)))) if options: options.append('capability %s' % capability) q['options'] = [options] q['selected'] = default_option - 1 q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)] q['headers'] += [_('Capability'), capability] q['headers'] += [_('Severity'), severity] audit_toggle = 0 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] q['default'] = 'CMD_ALLOW' done = False while not done: ans, selected = apparmor.aa.UI_PromptUser(q) # Ignore the log entry if ans == 'CMD_IGNORE_ENTRY': done = True break if ans == 'CMD_ALLOW': selection = '' if options: selection = options[selected] match = apparmor.aa.re_match_include(selection) if match: deleted = False inc = match deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) self.user.aa[profile][hat]['include'][inc] = True apparmor.aa.UI_Info(_('Adding %s to profile.') % selection) if deleted: apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) self.user.aa[profile][hat]['allow']['capability'][capability]['set'] = True self.user.aa[profile][hat]['allow']['capability'][capability]['audit'] = other.aa[profile][hat]['allow']['capability'][capability]['audit'] apparmor.aa.changed[profile] = True apparmor.aa.UI_Info(_('Adding capability %s to profile.'), capability) done = True elif ans == 'CMD_DENY': self.user.aa[profile][hat]['deny']['capability'][capability]['set'] = True apparmor.aa.changed[profile] = True apparmor.aa.UI_Info(_('Denying capability %s to profile.') % capability) done = True else: done = False # Process all the path entries. for allow in ['allow', 'deny']: for path in sorted(other.aa[profile][hat][allow]['path'].keys()): #print(path, other.aa[profile][hat][allow]['path'][path]) mode = other.aa[profile][hat][allow]['path'][path]['mode'] if self.user.aa[profile][hat][allow]['path'].get(path, False): mode = self.conflict_mode(profile, hat, allow, path, 'mode', other.aa[profile][hat][allow]['path'][path]['mode'], self.user.aa[profile][hat][allow]['path'][path]['mode']) self.conflict_mode(profile, hat, allow, path, 'audit', other.aa[profile][hat][allow]['path'][path]['audit'], self.user.aa[profile][hat][allow]['path'][path]['audit']) apparmor.aa.changed[profile] = True continue # Lookup modes from profile allow_mode = set() allow_audit = set() deny_mode = set() deny_audit = set() fmode, famode, fm = apparmor.aa.rematchfrag(self.user.aa[profile][hat], 'allow', path) if fmode: allow_mode |= fmode if famode: allow_audit |= famode cm, cam, m = apparmor.aa.rematchfrag(self.user.aa[profile][hat], 'deny', path) if cm: deny_mode |= cm if cam: deny_audit |= cam imode, iamode, im = apparmor.aa.match_prof_incs_to_path(self.user.aa[profile][hat], 'allow', path) if imode: allow_mode |= imode if iamode: allow_audit |= iamode cm, cam, m = apparmor.aa.match_prof_incs_to_path(self.user.aa[profile][hat], 'deny', path) if cm: deny_mode |= cm if cam: deny_audit |= cam if deny_mode & apparmor.aa.AA_MAY_EXEC: deny_mode |= apparmor.aamode.ALL_AA_EXEC_TYPE # Mask off the denied modes mode = mode - deny_mode # If we get an exec request from some kindof event that generates 'PERMITTING X' # check if its already in allow_mode # if not add ix permission if mode & apparmor.aa.AA_MAY_EXEC: # Remove all type access permission mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE if not allow_mode & apparmor.aa.AA_MAY_EXEC: mode |= apparmor.aa.str_to_mode('ix') # m is not implied by ix ### If we get an mmap request, check if we already have it in allow_mode ##if mode & AA_EXEC_MMAP: ## # ix implies m, so we don't need to add m if ix is present ## if contains(allow_mode, 'ix'): ## mode = mode - AA_EXEC_MMAP if not mode: continue matches = [] if fmode: matches += fm if imode: matches += im if not apparmor.aa.mode_contains(allow_mode, mode): default_option = 1 options = [] newincludes = [] include_valid = False for incname in apparmor.aa.include.keys(): include_valid = False # If already present skip if self.user.aa[profile][hat][incname]: continue if incname.startswith(apparmor.aa.profile_dir): incname = incname.replace(apparmor.aa.profile_dir+'/', '', 1) include_valid = apparmor.aa.valid_include('', incname) if not include_valid: continue cm, am, m = apparmor.aa.match_include_to_path(incname, 'allow', path) if cm and apparmor.aa.mode_contains(cm, mode): dm = apparmor.aa.match_include_to_path(incname, 'deny', path)[0] # If the mode is denied if not mode & dm: if not list(filter(lambda s: '/**' == s, m)): newincludes.append(incname) # Add new includes to the options if newincludes: options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes)))) # We should have literal the path in options list too options.append(path) # Add any the globs matching path from logprof globs = apparmor.aa.glob_common(path) if globs: matches += globs # Add any user entered matching globs for user_glob in apparmor.aa.user_globs: if apparmor.aa.matchliteral(user_glob, path): matches.append(user_glob) matches = list(set(matches)) if path in matches: matches.remove(path) options += apparmor.aa.order_globs(matches, path) default_option = len(options) sev_db.unload_variables() sev_db.load_variables(apparmor.aa.get_profile_filename(profile)) severity = sev_db.rank(path, apparmor.aa.mode_to_str(mode)) sev_db.unload_variables() audit_toggle = 0 owner_toggle = 0 if apparmor.aa.cfg['settings']['default_owner_prompt']: owner_toggle = apparmor.aa.cfg['settings']['default_owner_prompt'] done = False while not done: q = apparmor.aa.hasher() q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat), _('Path'), path] if allow_mode: mode |= allow_mode tail = '' s = '' prompt_mode = None if owner_toggle == 0: prompt_mode = apparmor.aa.flatten_mode(mode) tail = ' ' + _('(owner permissions off)') elif owner_toggle == 1: prompt_mode = mode elif owner_toggle == 2: prompt_mode = allow_mode | apparmor.aa.owner_flatten_mode(mode - allow_mode) tail = ' ' + _('(force new perms to owner)') else: prompt_mode = apparmor.aa.owner_flatten_mode(mode) tail = ' ' + _('(force all rule perms to owner)') if audit_toggle == 1: s = apparmor.aa.mode_to_str_user(allow_mode) if allow_mode: s += ', ' s += 'audit ' + apparmor.aa.mode_to_str_user(prompt_mode - allow_mode) + tail elif audit_toggle == 2: s = 'audit ' + apparmor.aa.mode_to_str_user(prompt_mode) + tail else: s = apparmor.aa.mode_to_str_user(prompt_mode) + tail q['headers'] += [_('Old Mode'), apparmor.aa.mode_to_str_user(allow_mode), _('New Mode'), s] else: s = '' tail = '' prompt_mode = None if audit_toggle: s = 'audit' if owner_toggle == 0: prompt_mode = apparmor.aa.flatten_mode(mode) tail = ' ' + _('(owner permissions off)') elif owner_toggle == 1: prompt_mode = mode else: prompt_mode = apparmor.aa.owner_flatten_mode(mode) tail = ' ' + _('(force perms to owner)') s = apparmor.aa.mode_to_str_user(prompt_mode) q['headers'] += [_('Mode'), s] q['headers'] += [_('Severity'), severity] q['options'] = options q['selected'] = default_option - 1 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_ABORT', 'CMD_FINISHED', 'CMD_OTHER'] q['default'] = 'CMD_ALLOW' ans, selected = apparmor.aa.UI_PromptUser(q) if ans == 'CMD_IGNORE_ENTRY': done = True break if ans == 'CMD_OTHER': audit_toggle, owner_toggle = apparmor.aa.UI_ask_mode_toggles(audit_toggle, owner_toggle, allow_mode) elif ans == 'CMD_USER_TOGGLE': owner_toggle += 1 if not allow_mode and owner_toggle == 2: owner_toggle += 1 if owner_toggle > 3: owner_toggle = 0 elif ans == 'CMD_ALLOW': path = options[selected] done = True match = apparmor.aa.re_match_include(path) if match: inc = match deleted = 0 deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) self.user.aa[profile][hat]['include'][inc] = True apparmor.aa.changed[profile] = True apparmor.aa.UI_Info(_('Adding %s to profile.') % path) if deleted: apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: if self.user.aa[profile][hat]['allow']['path'][path].get('mode', False): mode |= self.user.aa[profile][hat]['allow']['path'][path]['mode'] deleted = [] for entry in self.user.aa[profile][hat]['allow']['path'].keys(): if path == entry: continue if apparmor.aa.matchregexp(path, entry): if apparmor.aa.mode_contains(mode, self.user.aa[profile][hat]['allow']['path'][entry]['mode']): deleted.append(entry) for entry in deleted: self.user.aa[profile][hat]['allow']['path'].pop(entry) deleted = len(deleted) if owner_toggle == 0: mode = apparmor.aa.flatten_mode(mode) #elif owner_toggle == 1: # mode = mode elif owner_toggle == 2: mode = allow_mode | apparmor.aa.owner_flatten_mode(mode - allow_mode) elif owner_toggle == 3: mode = apparmor.aa.owner_flatten_mode(mode) if not self.user.aa[profile][hat]['allow'].get(path, False): self.user.aa[profile][hat]['allow']['path'][path]['mode'] = self.user.aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode tmpmode = set() if audit_toggle == 1: tmpmode = mode- allow_mode elif audit_toggle == 2: tmpmode = mode self.user.aa[profile][hat]['allow']['path'][path]['audit'] = self.user.aa[profile][hat]['allow']['path'][path].get('audit', set()) | tmpmode apparmor.aa.changed[profile] = True apparmor.aa.UI_Info(_('Adding %s %s to profile') % (path, apparmor.aa.mode_to_str_user(mode))) if deleted: apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) elif ans == 'CMD_DENY': path = options[selected].strip() # Add new entry? self.user.aa[profile][hat]['deny']['path'][path]['mode'] = self.user.aa[profile][hat]['deny']['path'][path].get('mode', set()) | (mode - allow_mode) self.user.aa[profile][hat]['deny']['path'][path]['audit'] = self.user.aa[profile][hat]['deny']['path'][path].get('audit', set()) apparmor.aa.changed[profile] = True done = True elif ans == 'CMD_NEW': arg = options[selected] if not apparmor.aa.re_match_include(arg): ans = apparmor.aa.UI_GetString(_('Enter new path: '), arg) # if ans: # if not matchliteral(ans, path): # ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %s\n Entered Path: %s\nDo you really want to use this path?') % (path,ans) # key = apparmor.aa.UI_YesNo(ynprompt, 'n') # if key == 'n': # continue apparmor.aa.user_globs.append(ans) options.append(ans) default_option = len(options) elif ans == 'CMD_GLOB': newpath = options[selected].strip() if not apparmor.aa.re_match_include(newpath): newpath = apparmor.aa.glob_path(newpath) if newpath not in options: options.append(newpath) default_option = len(options) else: default_option = options.index(newpath) + 1 elif ans == 'CMD_GLOBEXT': newpath = options[selected].strip() if not apparmor.aa.re_match_include(newpath): newpath = apparmor.aa.glob_path_withext(newpath) if newpath not in options: options.append(newpath) default_option = len(options) else: default_option = options.index(newpath) + 1 elif re.search('\d', ans): default_option = ans # for allow in ['allow', 'deny']: for family in sorted(other.aa[profile][hat][allow]['netdomain']['rule'].keys()): # severity handling for net toggles goes here for sock_type in sorted(other.aa[profile][hat][allow]['netdomain']['rule'][family].keys()): if apparmor.aa.profile_known_network(self.user.aa[profile][hat], family, sock_type): continue default_option = 1 options = [] newincludes = apparmor.aa.match_net_includes(self.user.aa[profile][hat], family, sock_type) q = apparmor.aa.hasher() if newincludes: options += list(map(lambda s: '#include <%s>'%s, sorted(set(newincludes)))) if True:#options: options.append('network %s %s' % (family, sock_type)) q['options'] = options q['selected'] = default_option - 1 q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)] q['headers'] += [_('Network Family'), family] q['headers'] += [_('Socket Type'), sock_type] audit_toggle = 0 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'] q['default'] = 'CMD_ALLOW' done = False while not done: ans, selected = apparmor.aa.UI_PromptUser(q) if ans == 'CMD_IGNORE_ENTRY': done = True break if ans.startswith('CMD_AUDIT'): audit_toggle = not audit_toggle audit = '' if audit_toggle: audit = 'audit' q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF', 'CMD_ABORT', 'CMD_FINISHED'] else: q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'] q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)] q['headers'] += [_('Network Family'), audit + family] q['headers'] += [_('Socket Type'), sock_type] elif ans == 'CMD_ALLOW': #print(options, selected) selection = options[selected] done = True if apparmor.aa.re_match_include(selection): #re.search('#include\s+<.+>$', selection): inc = apparmor.aa.re_match_include(selection) #re.search('#include\s+<(.+)>$', selection).groups()[0] deleted = 0 deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) self.user.aa[profile][hat]['include'][inc] = True apparmor.aa.changed[profile] = True apparmor.aa.UI_Info(_('Adding %s to profile') % selection) if deleted: apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: self.user.aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle self.user.aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True apparmor.aa.changed[profile] = True apparmor.aa.UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type)) elif ans == 'CMD_DENY': done = True self.user.aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True apparmor.aa.changed[profile] = True apparmor.aa.UI_Info(_('Denying network access %s %s to profile') % (family, sock_type)) else: done = False if __name__ == '__main__': main() apparmor-2.8.95~2430/utils/po/0000755000175000017500000000000012311706707015617 5ustar sarnoldsarnoldapparmor-2.8.95~2430/utils/po/fr.po0000644000175000017500000005735712267276306016616 0ustar sarnoldsarnold# Copyright (C) 2006 SuSE Linux Products GmbH, Nuernberg # This file is distributed under the same license as the package. # msgid "" msgstr "" "Project-Id-Version: apparmor-utils\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-13 16:44-0800\n" "PO-Revision-Date: 2013-11-15 02:39+0000\n" "Last-Translator: Novell Language \n" "Language-Team: Novell Language \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2013-11-16 05:14+0000\n" "X-Generator: Launchpad (build 16831)\n" "Language: fr\n" #: ../aa-genprof:72 ../aa-unconfined:54 msgid "" "AppArmor does not appear to be started. Please enable AppArmor and try again." msgstr "" "Le sous-domaine semble ne pas être démarré. Activez le sous-domaine et " "réessayez." #: ../aa-genprof:86 msgid "Please enter the program to profile: " msgstr "Saisissez le programme pour lequel créer un profil : " #: ../aa-genprof:105 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' in the other window in order to find the " "fully-qualified path." msgstr "" "%s introuvable dans la liste des chemins du système. Si le nom de " "l'application est correct, exécutez 'which %s' dans l'autre fenêtre pour " "trouver le chemin complet." #: ../aa-genprof:107 ../aa-autodep:110 ../aa-audit:120 ../aa-complain:119 #: ../aa-enforce:130 ../aa-disable:140 #, perl-format msgid "%s does not exist, please double-check the path." msgstr "%s n'existe pas, vérifiez le chemin." #: ../aa-genprof:141 msgid "" "\n" "Before you begin, you may wish to check if a\n" "profile already exists for the application you\n" "wish to confine. See the following wiki page for\n" "more information:\n" "http://wiki.apparmor.net/index.php/Profiles" msgstr "" #: ../aa-genprof:143 msgid "" "Please start the application to be profiled in \n" "another window and exercise its functionality now.\n" "\n" "Once completed, select the \"Scan\" button below in \n" "order to scan the system logs for AppArmor events. \n" "\n" "For each AppArmor event, you will be given the \n" "opportunity to choose whether the access should be \n" "allowed or denied." msgstr "" "Démarrez l'application pour lequel créer un profil dans une \n" "autre fenêtre et utilisez sa fonctionnalité maintenant.\n" " \n" " Lorsque vous avez terminé, sélectionnez le bouton \"Analyser\" ci-dessous \n" "afin d'analyser les journaux système et détecter les événements AppArmor. \n" " \n" "Pour chaque événement AppArmor, vous aurez la \n" "possibilité de choisir si l'accès doit être \n" "autorisé ou refusé." #: ../aa-genprof:163 msgid "Profiling" msgstr "Création de profil" #: ../aa-genprof:197 msgid "Reloaded AppArmor profiles in enforce mode." msgstr "Profils de sous-domaine rechargés en mode imposition." #: ../aa-genprof:198 msgid "" "\n" "Please consider contributing your new profile! See\n" "the following wiki page for more information:\n" "http://wiki.apparmor.net/index.php/Profiles\n" msgstr "" #: ../aa-genprof:199 #, perl-format msgid "Finished generating profile for %s." msgstr "Génération du profil terminé pour %s." #: ../aa-genprof:203 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ program to " "profile ]" msgstr "" "Utilisation : %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ " "programme pour lequel créer un profil ]" #: ../aa-logprof:69 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ -m \"mark in " "log to start processing after\"" msgstr "" "Utilisation : %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ -m " "\"marquer dans le journal pour commencer le traitement après\"" #: ../aa-autodep:61 #, perl-format msgid "Can't find AppArmor profiles in %s." msgstr "Impossible de trouver les profils de sous-domaine dans %s." #: ../aa-autodep:69 msgid "Please enter the program to create a profile for: " msgstr "Entrez le programme pour lequel créer un nouveau profil : " #: ../aa-autodep:93 ../Immunix/AppArmor.pm:6339 #, perl-format msgid "" "%s is currently marked as a program that should not have it's own profile. " "Usually, programs are marked this way if creating a profile for them is " "likely to break the rest of the system. If you know what you're doing and " "are certain you want to create a profile for this program, edit the " "corresponding entry in the [qualifiers] section in " "/etc/apparmor/logprof.conf." msgstr "" "%s est pour l'instant marqué en tant que programme qui ne devrait pas avoir " "son propre profil. Les programmes sont habituellement marqués ainsi si la " "création d'un profil pour eux risque fortement de corrompre le reste du " "système. Si vous savez ce que vous faites et si vous êtes sûr de vouloir " "créer un profil pour ce programme, modifiez l'entrée correspondante dans la " "section [qualifiers] du fichier /etc/apparmor/logprof.conf." #: ../aa-autodep:100 #, perl-format msgid "Profile for %s already exists - skipping." msgstr "Il existe déjà un profil pour %s - ignoré." #: ../aa-autodep:107 ../aa-audit:117 ../aa-complain:116 ../aa-enforce:127 #: ../aa-disable:137 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' as a user with the correct PATH environment " "set up in order to find the fully-qualified path." msgstr "" "Impossible de trouver %s dans la liste de chemins système. Si le nom de " "l'application est correct, exécutez which %s en tant qu'utilisateur dans " "l'environnement PATH correct pour trouver le chemin complet." #: ../aa-audit:104 #, perl-format msgid "Setting %s to audit mode." msgstr "Définition de %s en mode audit." #: ../aa-audit:129 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to audit mode ]" msgstr "" "Utilisation : %s [ -d /path/to/profiles ] [ programme à basculer en mode " "audit ]" #: ../aa-complain:61 msgid "Please enter the program to switch to complain mode: " msgstr "Entrez le programme à basculer en mode réclamation : " #: ../aa-complain:103 ../Immunix/AppArmor.pm:621 ../Immunix/AppArmor.pm:941 #, perl-format msgid "Setting %s to complain mode." msgstr "Définition de %s en mode réclamation." #: ../aa-complain:128 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to complain mode ]" msgstr "" "Utilisation : %s [ -d /path/to/profiles ] [ programme à basculer en mode " "réclamation ]" #: ../aa-enforce:62 msgid "Please enter the program to switch to enforce mode: " msgstr "Entrez le programme à basculer en mode mise en vigueur : " #: ../aa-enforce:103 ../Immunix/AppArmor.pm:634 #, perl-format msgid "Setting %s to enforce mode." msgstr "Définition de %s en mode imposition." #: ../aa-enforce:139 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to enforce mode ]" msgstr "" "Utilisation : %s [ -d /path/to/profiles ] [ programme à basculer en mode " "définition ]" #: ../aa-unconfined:48 #, perl-format msgid "Usage: %s [ --paranoid ]\n" msgstr "Utilisation : %s [ --paranoid ]\n" #: ../aa-unconfined:59 msgid "Can't read /proc\n" msgstr "Impossible de lire /proc\n" #: ../aa-unconfined:97 ../aa-unconfined:99 msgid "not confined\n" msgstr "non restreint(e)\n" #: ../aa-unconfined:108 ../aa-unconfined:110 msgid "confined by" msgstr "restreint(e) par" #: ../aa-disable:69 msgid "Please enter the program whose profile should be disabled: " msgstr "" #: ../aa-disable:113 #, perl-format msgid "Could not find basename for %s." msgstr "" #: ../aa-disable:117 #, perl-format msgid "Disabling %s." msgstr "" #: ../aa-disable:123 #, perl-format msgid "Could not create %s symlink." msgstr "" #: ../aa-disable:149 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to have profile disabled ]" msgstr "" #: ../Immunix/AppArmor.pm:619 ../Immunix/AppArmor.pm:632 #, perl-format msgid "Can't find %s." msgstr "%s introuvable." #: ../Immunix/AppArmor.pm:819 ../Immunix/AppArmor.pm:3326 msgid "Connecting to repository....." msgstr "Connexion au référentiel..." #: ../Immunix/AppArmor.pm:828 #, perl-format msgid "" "WARNING: Error fetching profiles from the repository:\n" "%s\n" msgstr "" "ATTENTION : erreur lors de la récupération des profils depuis de dépôt :\n" "%s\n" #: ../Immunix/AppArmor.pm:837 msgid "Inactive local profile for " msgstr "Profil local inactif pour " #: ../Immunix/AppArmor.pm:874 ../Immunix/AppArmor.pm:1900 #: ../Immunix/AppArmor.pm:2188 ../Immunix/AppArmor.pm:3453 #: ../Immunix/AppArmor.pm:3486 ../Immunix/AppArmor.pm:3686 #: ../Immunix/AppArmor.pm:3952 ../Immunix/AppArmor.pm:4004 msgid "Profile" msgstr "Profil" #: ../Immunix/AppArmor.pm:908 msgid "Profile submitted by" msgstr "Profil soumis par" #: ../Immunix/AppArmor.pm:949 #, perl-format msgid "Error activating profiles: %s\n" msgstr "Erreur lors de l'activation des profils : %s\n" #: ../Immunix/AppArmor.pm:1098 ../Immunix/AppArmor.pm:1151 #, perl-format msgid "" "WARNING: Error syncronizing profiles with the repository:\n" "%s\n" msgstr "" "ATTENTION : erreur de synchronisation des profils avec le dépôt :\n" "%s\n" #: ../Immunix/AppArmor.pm:1178 msgid "New profiles" msgstr "Nouveaux profils" #: ../Immunix/AppArmor.pm:1180 msgid "" "Please choose the newly created profiles that you would like\n" "to store in the repository" msgstr "" "Choisissez les nouveaux profils que vous voulez\n" "stocker dans le référentiel" #: ../Immunix/AppArmor.pm:1187 msgid "Submit newly created profiles to the repository" msgstr "Soumettre les profils nouvellement créés dans le référentiel" #: ../Immunix/AppArmor.pm:1189 msgid "Would you like to upload the newly created profiles?" msgstr "Voulez-vous charger les profils nouvellement créés ?" #: ../Immunix/AppArmor.pm:1202 msgid "" "Select which of the changed profiles you would like to upload\n" "to the repository" msgstr "" "Sélectionnez les profils modifiés que vous voulez charger \n" "dans le référentiel" #: ../Immunix/AppArmor.pm:1204 msgid "Changed profiles" msgstr "Profils modifiés" #: ../Immunix/AppArmor.pm:1210 msgid "Submit changed profiles to the repository" msgstr "Soumettre les profils modifiés au référentiel" #: ../Immunix/AppArmor.pm:1212 msgid "" "The following profiles from the repository were changed.\n" "Would you like to upload your changes?" msgstr "" "Les profils suivants du référentiel ont été modifiés.\n" "Voulez-vous charger vos modifications ?" #: ../Immunix/AppArmor.pm:1279 ../Immunix/AppArmor.pm:1359 #, perl-format msgid "" "WARNING: An error occured while uploading the profile %s\n" "%s\n" msgstr "" "ATTENTION : une erreur s'est produite lors de l'envoi du profil %s\n" "%s\n" #: ../Immunix/AppArmor.pm:1284 msgid "Uploaded changes to repository." msgstr "Modifications chargées dans le référentiel." #: ../Immunix/AppArmor.pm:1306 ../Immunix/AppArmor.pm:3185 #: ../Immunix/AppArmor.pm:3215 msgid "Repository" msgstr "" #: ../Immunix/AppArmor.pm:1333 msgid "Changelog Entry: " msgstr "Entrée du journal de modification : " #: ../Immunix/AppArmor.pm:1354 #, perl-format msgid "Uploaded %s to repository." msgstr "Chargement de %s dans le référentiel." #: ../Immunix/AppArmor.pm:1365 msgid "" "Repository Error\n" "Registration or Signin was unsuccessful. User login\n" "information is required to upload profiles to the\n" "repository. These changes have not been sent.\n" msgstr "" "Erreur de dépôt\n" "L'enregistrement ou l'identification a échoué. Les informations\n" "de login sont nécessaires pour envoyer les profils au dépôt.\n" "Les modifications n'ont pas été envoyées.\n" #: ../Immunix/AppArmor.pm:1422 ../Immunix/AppArmor.pm:1462 msgid "(Y)es" msgstr "(O)ui" #: ../Immunix/AppArmor.pm:1423 ../Immunix/AppArmor.pm:1463 msgid "(N)o" msgstr "(N)on" #: ../Immunix/AppArmor.pm:1426 ../Immunix/AppArmor.pm:1467 msgid "Invalid hotkey for" msgstr "Raccourci clavier non valide pour" #: ../Immunix/AppArmor.pm:1464 msgid "(C)ancel" msgstr "(A)nnuler" #: ../Immunix/AppArmor.pm:1789 msgid "" "Are you sure you want to abandon this set of profile changes and exit?" msgstr "" "Êtes-vous sûr de vouloir abandonner ce jeu de modifications de profil et de " "vouloir sortir ?" #: ../Immunix/AppArmor.pm:1791 msgid "Abandoning all changes." msgstr "Abandonner tous les changements ?" #: ../Immunix/AppArmor.pm:1902 msgid "Default Hat" msgstr "Hat par défaut" #: ../Immunix/AppArmor.pm:1904 msgid "Requested Hat" msgstr "Hat demandé" #: ../Immunix/AppArmor.pm:2190 msgid "Program" msgstr "Programme" #: ../Immunix/AppArmor.pm:2195 msgid "Execute" msgstr "Exécuter" #: ../Immunix/AppArmor.pm:2196 ../Immunix/AppArmor.pm:3455 #: ../Immunix/AppArmor.pm:3488 ../Immunix/AppArmor.pm:3741 msgid "Severity" msgstr "Gravité" #: ../Immunix/AppArmor.pm:2241 msgid "Enter profile name to transition to: " msgstr "Saisissez le nom du profil vers lequel exécuter la transition : " #: ../Immunix/AppArmor.pm:2249 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but some applications depend on the presence\n" "of LD_PRELOAD or LD_LIBRARY_PATH." msgstr "" "AppArmor doit-il expurger les éléments de l'environnement lors\n" "du changement de profil ?\n" "\n" "Cette opération garantit une meilleure sécurité\n" "mais certaines applications nécessitent\n" "LD_PRELOAD ou LD_LIBRARY_PATH." #: ../Immunix/AppArmor.pm:2251 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but this application appears to use LD_PRELOAD\n" "or LD_LIBRARY_PATH and clearing these could\n" "cause functionality problems." msgstr "" "AppArmor doit-il expurger les éléments de l'environnement lors\n" "du changement de profil ?\n" "\n" "Cette opération garantit une meilleure sécurité\n" "mais cette application semble utiliser LD_PRELOAD\n" "ou LD_LIBRARY_PATH. Leur nettoyage peut\n" "entraîner des problèmes de fonctionnement." #: ../Immunix/AppArmor.pm:2260 #, perl-format msgid "" "Launching processes in an unconfined state is a very\n" "dangerous operation and can cause serious security holes.\n" "\n" "Are you absolutely certain you wish to remove all\n" "AppArmor protection when executing %s?" msgstr "" "Le lancement de processus non restreints est une opération très\n" "dangereuse. Il peut entraîner d'importantes failles de sécurité.\n" "\n" "Voulez-vous vraiment annuler complètement\n" "la protection AppArmor lors de l'exécution de %s ?" #: ../Immunix/AppArmor.pm:2262 msgid "" "Should AppArmor sanitize the environment when\n" "running this program unconfined?\n" "\n" "Not sanitizing the environment when unconfining\n" "a program opens up significant security holes\n" "and should be avoided if at all possible." msgstr "" "AppArmor doit-il expurger les éléments de l'environnement lors\n" "de l'exécution de ce programme non restreint ?\n" "\n" "La non-réalisation de cette opération,\n" "entraîne l'ouverture d'importantes failles de sécurité.\n" "Si possible, évitez le problème." #: ../Immunix/AppArmor.pm:2352 #, perl-format msgid "A profile for %s does not exist. Create one?" msgstr "" #: ../Immunix/AppArmor.pm:2379 #, perl-format msgid "A local profile for %s does not exist. Create one?" msgstr "" #: ../Immunix/AppArmor.pm:2584 ../Immunix/AppArmor.pm:6733 #: ../Immunix/AppArmor.pm:6738 #, perl-format msgid "Log contains unknown mode %s." msgstr "Le journal contient un mode inconnu %s." #: ../Immunix/AppArmor.pm:3068 msgid "" "An updated version of this profile has been found in the profile repository. " " Would you like to use it?" msgstr "" "Une version à jour de ce profil a été trouvée dans le référentiel de " "profils. Voulez-vous l'utiliser ?" #: ../Immunix/AppArmor.pm:3098 #, perl-format msgid "Updated profile %s to revision %s." msgstr "Profil mis à jour %s vers la révision %s." #: ../Immunix/AppArmor.pm:3105 msgid "Error parsing repository profile." msgstr "Erreur lors de l'analyse du profil du référentiel." #: ../Immunix/AppArmor.pm:3141 msgid "Create New User?" msgstr "Créer un nouvel utilisateur ?" #: ../Immunix/AppArmor.pm:3142 msgid "Username: " msgstr "Nom d'utilisateur : " #: ../Immunix/AppArmor.pm:3143 msgid "Password: " msgstr "Mot de passe : " #: ../Immunix/AppArmor.pm:3144 msgid "Email Addr: " msgstr "Adresse électronique : " #: ../Immunix/AppArmor.pm:3146 msgid "Save Configuration? " msgstr "Enregistrer la configuration ? " #: ../Immunix/AppArmor.pm:3155 msgid "The Profile Repository server returned the following error:" msgstr "Le serveur du référentiel de profils a retourné l'erreur suivante :" #: ../Immunix/AppArmor.pm:3157 msgid "" "Please re-enter registration information or contact the administrator." msgstr "" "Saisissez à nouveau les informations d'enregistrement ou contactez " "l'administrateur." #: ../Immunix/AppArmor.pm:3158 msgid "Login Error\n" msgstr "Erreur de login\n" #: ../Immunix/AppArmor.pm:3165 msgid "" "Login failure\n" " Please check username and password and try again." msgstr "" "Échec de login.\n" " Vérifiez le nom d'utilisateur et le mot de passe, puis réessayez." #: ../Immunix/AppArmor.pm:3187 msgid "" "Would you like to enable access to the\n" "profile repository?" msgstr "" "Voulez-vous activer l'accès au référentiel de profils\n" " ?" #: ../Immunix/AppArmor.pm:3218 msgid "" "Would you like to upload newly created and changed profiles to\n" " the profile repository?" msgstr "" "Voulez-vous charger les profils nouvellement créés et modifiés vers\n" " le référentiel de profils ?" #: ../Immunix/AppArmor.pm:3337 #, perl-format msgid "" "WARNING: Profile update check failed\n" "Error Detail:\n" "%s" msgstr "" "ATTENTION : échec du contrôle des mises à jour de profils\n" "Détail de l'erreur :\n" "%s" #: ../Immunix/AppArmor.pm:3351 msgid "Change mode modifiers" msgstr "Modifier les modificateurs de mode" #: ../Immunix/AppArmor.pm:3395 msgid "Complain-mode changes:" msgstr "Changements en mode réclamation :" #: ../Immunix/AppArmor.pm:3397 msgid "Enforce-mode changes:" msgstr "Changement en mode imposition :" #: ../Immunix/AppArmor.pm:3403 #, perl-format msgid "Invalid mode found: %s" msgstr "Mode incorrect : %s" #: ../Immunix/AppArmor.pm:3454 ../Immunix/AppArmor.pm:3487 msgid "Capability" msgstr "Mode compatibilité" #: ../Immunix/AppArmor.pm:3507 ../Immunix/AppArmor.pm:3781 #: ../Immunix/AppArmor.pm:4028 #, perl-format msgid "Adding #include <%s> to profile." msgstr "Ajout de #include <%s> au profil." #: ../Immunix/AppArmor.pm:3510 ../Immunix/AppArmor.pm:3782 #: ../Immunix/AppArmor.pm:3822 ../Immunix/AppArmor.pm:4032 #, perl-format msgid "Deleted %s previous matching profile entries." msgstr "%s entrées de profil précédemment correspondantes supprimées." #: ../Immunix/AppArmor.pm:3521 #, perl-format msgid "Adding capability %s to profile." msgstr "Ajout de capacité %s au profil." #: ../Immunix/AppArmor.pm:3526 #, perl-format msgid "Denying capability %s to profile." msgstr "Refus de capacité %s au profil." #: ../Immunix/AppArmor.pm:3687 msgid "Path" msgstr "Chemin d'accès" #: ../Immunix/AppArmor.pm:3698 ../Immunix/AppArmor.pm:3730 msgid "(owner permissions off)" msgstr "(autorisations du propriétaire désactivées)" #: ../Immunix/AppArmor.pm:3704 msgid "(force new perms to owner)" msgstr "(forcer les nouvelles autorisations pour le propriétaire)" #: ../Immunix/AppArmor.pm:3707 msgid "(force all rule perms to owner)" msgstr "(forcer toutes les autorisations de règles pour le propriétaire)" #: ../Immunix/AppArmor.pm:3719 msgid "Old Mode" msgstr "Ancien mode" #: ../Immunix/AppArmor.pm:3720 msgid "New Mode" msgstr "Nouveau mode" #: ../Immunix/AppArmor.pm:3736 msgid "(force perms to owner)" msgstr "(forcer les autorisations pour le propriétaire)" #: ../Immunix/AppArmor.pm:3739 msgid "Mode" msgstr "Mode" #: ../Immunix/AppArmor.pm:3821 #, perl-format msgid "Adding %s %s to profile." msgstr "Ajout de %s %s au profil." #: ../Immunix/AppArmor.pm:3837 msgid "Enter new path: " msgstr "Saisissez le nouveau chemin : " #: ../Immunix/AppArmor.pm:3840 msgid "The specified path does not match this log entry:" msgstr "La carte spécifiée n'existe pas." #: ../Immunix/AppArmor.pm:3841 msgid "Log Entry" msgstr "Entrée de journal" #: ../Immunix/AppArmor.pm:3842 msgid "Entered Path" msgstr "Chemin entré" #: ../Immunix/AppArmor.pm:3843 msgid "Do you really want to use this path?" msgstr "Voulez-vous vraiment utiliser ce chemin ?" #: ../Immunix/AppArmor.pm:3955 ../Immunix/AppArmor.pm:4007 msgid "Network Family" msgstr "Famille d'adresses réseau" #: ../Immunix/AppArmor.pm:3958 ../Immunix/AppArmor.pm:4010 msgid "Socket Type" msgstr "Type de socket" #: ../Immunix/AppArmor.pm:4058 #, perl-format msgid "Adding network access %s %s to profile." msgstr "Ajout de l'accès réseau %s %s au profil." #: ../Immunix/AppArmor.pm:4077 #, perl-format msgid "Denying network access %s %s to profile." msgstr "Refus de l'accès réseau %s %s au profil." #: ../Immunix/AppArmor.pm:4285 #, perl-format msgid "Reading log entries from %s." msgstr "Lecture des entrées de journal depuis %s." #: ../Immunix/AppArmor.pm:4286 #, perl-format msgid "Updating AppArmor profiles in %s." msgstr "Mise à jour des profils AppArmor dans %s." #: ../Immunix/AppArmor.pm:4290 msgid "unknown\n" msgstr "inconnu(e)\n" #: ../Immunix/AppArmor.pm:4324 msgid "" "The profile analyzer has completed processing the log files.\n" "\n" "All updated profiles will be reloaded" msgstr "" "L'analyseur de profil a terminé le traitement des fichiers journaux.\n" "\n" " Tous les profils mis à jour seront rechargés" #: ../Immunix/AppArmor.pm:4330 msgid "No unhandled AppArmor events were found in the system log." msgstr "" "Aucun événement AppArmor non géré n'a été trouvé dans le journal système." #: ../Immunix/AppArmor.pm:4391 msgid "" "Select which profile changes you would like to save to the\n" "local profile set" msgstr "" "Sélectionnez les modifications de profil que vous voulez enregistrer dans " "l'ensemble\n" "de profils locaux" #: ../Immunix/AppArmor.pm:4392 msgid "Local profile changes" msgstr "Modifications de profil local" #: ../Immunix/AppArmor.pm:4419 msgid "" "The following local profiles were changed. Would you like to save them?" msgstr "" "Les profils locaux suivants ont été modifiés. Voulez-vous les enregistrer ?" #: ../Immunix/AppArmor.pm:4516 msgid "Profile Changes" msgstr "Modifications de profil" #: ../Immunix/AppArmor.pm:5139 ../Immunix/AppArmor.pm:5155 #: ../Immunix/AppArmor.pm:5166 ../Immunix/AppArmor.pm:5174 #: ../Immunix/AppArmor.pm:5195 ../Immunix/AppArmor.pm:5215 #: ../Immunix/AppArmor.pm:5224 ../Immunix/AppArmor.pm:5256 #: ../Immunix/AppArmor.pm:5334 ../Immunix/AppArmor.pm:5382 #, perl-format msgid "%s contains syntax errors." msgstr "%s contient des erreurs de syntaxe." #: ../Immunix/AppArmor.pm:5275 #, perl-format msgid "Profile %s contains invalid regexp %s." msgstr "Le profil %s contient une regexp %s incorrecte." #: ../Immunix/AppArmor.pm:5280 #, perl-format msgid "Profile %s contains invalid mode %s." msgstr "Le profil %s contient le mode non valide %s." #: ../Immunix/AppArmor.pm:5430 #, perl-format msgid "%s contains syntax errors. Line [%s]" msgstr "%s contient des erreurs de syntaxe. Ligne [%s]" #: ../Immunix/AppArmor.pm:6022 #, perl-format msgid "Writing updated profile for %s." msgstr "Écriture du profil à jour pour %s." #: ../Immunix/AppArmor.pm:6528 msgid "Unknown command" msgstr "Commande inconnue" #: ../Immunix/AppArmor.pm:6536 msgid "Invalid hotkey in" msgstr "Raccourci clavier non valide dans" #: ../Immunix/AppArmor.pm:6546 msgid "Duplicate hotkey for" msgstr "Raccourci clavier en double pour" #: ../Immunix/AppArmor.pm:6567 msgid "Invalid hotkey in default item" msgstr "Raccourci clavier non valide dans l'élément par défaut" #: ../Immunix/AppArmor.pm:6576 msgid "Invalid default" msgstr "Valeur par défaut non valide" apparmor-2.8.95~2430/utils/po/ko.po0000644000175000017500000004024312267276306016602 0ustar sarnoldsarnold# Korean translation for apparmor # Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 # This file is distributed under the same license as the apparmor package. # FIRST AUTHOR , 2013. # msgid "" msgstr "" "Project-Id-Version: apparmor-utils\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-13 16:44-0800\n" "PO-Revision-Date: 2013-11-20 22:28+0000\n" "Last-Translator: FULL NAME \n" "Language-Team: Korean \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2013-11-21 05:11+0000\n" "X-Generator: Launchpad (build 16831)\n" "Language: ko\n" #: ../aa-genprof:72 ../aa-unconfined:54 msgid "" "AppArmor does not appear to be started. Please enable AppArmor and try again." msgstr "" #: ../aa-genprof:86 msgid "Please enter the program to profile: " msgstr "" #: ../aa-genprof:105 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' in the other window in order to find the " "fully-qualified path." msgstr "" #: ../aa-genprof:107 ../aa-autodep:110 ../aa-audit:120 ../aa-complain:119 #: ../aa-enforce:130 ../aa-disable:140 #, perl-format msgid "%s does not exist, please double-check the path." msgstr "" #: ../aa-genprof:141 msgid "" "\n" "Before you begin, you may wish to check if a\n" "profile already exists for the application you\n" "wish to confine. See the following wiki page for\n" "more information:\n" "http://wiki.apparmor.net/index.php/Profiles" msgstr "" #: ../aa-genprof:143 msgid "" "Please start the application to be profiled in \n" "another window and exercise its functionality now.\n" "\n" "Once completed, select the \"Scan\" button below in \n" "order to scan the system logs for AppArmor events. \n" "\n" "For each AppArmor event, you will be given the \n" "opportunity to choose whether the access should be \n" "allowed or denied." msgstr "" #: ../aa-genprof:163 msgid "Profiling" msgstr "" #: ../aa-genprof:197 msgid "Reloaded AppArmor profiles in enforce mode." msgstr "" #: ../aa-genprof:198 msgid "" "\n" "Please consider contributing your new profile! See\n" "the following wiki page for more information:\n" "http://wiki.apparmor.net/index.php/Profiles\n" msgstr "" #: ../aa-genprof:199 #, perl-format msgid "Finished generating profile for %s." msgstr "" #: ../aa-genprof:203 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ program to " "profile ]" msgstr "" #: ../aa-logprof:69 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ -m \"mark in " "log to start processing after\"" msgstr "" #: ../aa-autodep:61 #, perl-format msgid "Can't find AppArmor profiles in %s." msgstr "" #: ../aa-autodep:69 msgid "Please enter the program to create a profile for: " msgstr "" #: ../aa-autodep:93 ../Immunix/AppArmor.pm:6339 #, perl-format msgid "" "%s is currently marked as a program that should not have it's own profile. " "Usually, programs are marked this way if creating a profile for them is " "likely to break the rest of the system. If you know what you're doing and " "are certain you want to create a profile for this program, edit the " "corresponding entry in the [qualifiers] section in " "/etc/apparmor/logprof.conf." msgstr "" #: ../aa-autodep:100 #, perl-format msgid "Profile for %s already exists - skipping." msgstr "" #: ../aa-autodep:107 ../aa-audit:117 ../aa-complain:116 ../aa-enforce:127 #: ../aa-disable:137 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' as a user with the correct PATH environment " "set up in order to find the fully-qualified path." msgstr "" #: ../aa-audit:104 #, perl-format msgid "Setting %s to audit mode." msgstr "" #: ../aa-audit:129 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to audit mode ]" msgstr "" #: ../aa-complain:61 msgid "Please enter the program to switch to complain mode: " msgstr "" #: ../aa-complain:103 ../Immunix/AppArmor.pm:621 ../Immunix/AppArmor.pm:941 #, perl-format msgid "Setting %s to complain mode." msgstr "" #: ../aa-complain:128 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to complain mode ]" msgstr "" #: ../aa-enforce:62 msgid "Please enter the program to switch to enforce mode: " msgstr "" #: ../aa-enforce:103 ../Immunix/AppArmor.pm:634 #, perl-format msgid "Setting %s to enforce mode." msgstr "" #: ../aa-enforce:139 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to enforce mode ]" msgstr "" #: ../aa-unconfined:48 #, perl-format msgid "Usage: %s [ --paranoid ]\n" msgstr "" #: ../aa-unconfined:59 msgid "Can't read /proc\n" msgstr "" #: ../aa-unconfined:97 ../aa-unconfined:99 msgid "not confined\n" msgstr "" #: ../aa-unconfined:108 ../aa-unconfined:110 msgid "confined by" msgstr "" #: ../aa-disable:69 msgid "Please enter the program whose profile should be disabled: " msgstr "" #: ../aa-disable:113 #, perl-format msgid "Could not find basename for %s." msgstr "" #: ../aa-disable:117 #, perl-format msgid "Disabling %s." msgstr "" #: ../aa-disable:123 #, perl-format msgid "Could not create %s symlink." msgstr "" #: ../aa-disable:149 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to have profile disabled ]" msgstr "" #: ../Immunix/AppArmor.pm:619 ../Immunix/AppArmor.pm:632 #, perl-format msgid "Can't find %s." msgstr "" #: ../Immunix/AppArmor.pm:819 ../Immunix/AppArmor.pm:3326 msgid "Connecting to repository....." msgstr "" #: ../Immunix/AppArmor.pm:828 #, perl-format msgid "" "WARNING: Error fetching profiles from the repository:\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:837 msgid "Inactive local profile for " msgstr "" #: ../Immunix/AppArmor.pm:874 ../Immunix/AppArmor.pm:1900 #: ../Immunix/AppArmor.pm:2188 ../Immunix/AppArmor.pm:3453 #: ../Immunix/AppArmor.pm:3486 ../Immunix/AppArmor.pm:3686 #: ../Immunix/AppArmor.pm:3952 ../Immunix/AppArmor.pm:4004 msgid "Profile" msgstr "" #: ../Immunix/AppArmor.pm:908 msgid "Profile submitted by" msgstr "" #: ../Immunix/AppArmor.pm:949 #, perl-format msgid "Error activating profiles: %s\n" msgstr "" #: ../Immunix/AppArmor.pm:1098 ../Immunix/AppArmor.pm:1151 #, perl-format msgid "" "WARNING: Error syncronizing profiles with the repository:\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:1178 msgid "New profiles" msgstr "" #: ../Immunix/AppArmor.pm:1180 msgid "" "Please choose the newly created profiles that you would like\n" "to store in the repository" msgstr "" #: ../Immunix/AppArmor.pm:1187 msgid "Submit newly created profiles to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1189 msgid "Would you like to upload the newly created profiles?" msgstr "" #: ../Immunix/AppArmor.pm:1202 msgid "" "Select which of the changed profiles you would like to upload\n" "to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1204 msgid "Changed profiles" msgstr "" #: ../Immunix/AppArmor.pm:1210 msgid "Submit changed profiles to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1212 msgid "" "The following profiles from the repository were changed.\n" "Would you like to upload your changes?" msgstr "" #: ../Immunix/AppArmor.pm:1279 ../Immunix/AppArmor.pm:1359 #, perl-format msgid "" "WARNING: An error occured while uploading the profile %s\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:1284 msgid "Uploaded changes to repository." msgstr "" #: ../Immunix/AppArmor.pm:1306 ../Immunix/AppArmor.pm:3185 #: ../Immunix/AppArmor.pm:3215 msgid "Repository" msgstr "" #: ../Immunix/AppArmor.pm:1333 msgid "Changelog Entry: " msgstr "" #: ../Immunix/AppArmor.pm:1354 #, perl-format msgid "Uploaded %s to repository." msgstr "" #: ../Immunix/AppArmor.pm:1365 msgid "" "Repository Error\n" "Registration or Signin was unsuccessful. User login\n" "information is required to upload profiles to the\n" "repository. These changes have not been sent.\n" msgstr "" #: ../Immunix/AppArmor.pm:1422 ../Immunix/AppArmor.pm:1462 msgid "(Y)es" msgstr "" #: ../Immunix/AppArmor.pm:1423 ../Immunix/AppArmor.pm:1463 msgid "(N)o" msgstr "" #: ../Immunix/AppArmor.pm:1426 ../Immunix/AppArmor.pm:1467 msgid "Invalid hotkey for" msgstr "" #: ../Immunix/AppArmor.pm:1464 msgid "(C)ancel" msgstr "" #: ../Immunix/AppArmor.pm:1789 msgid "" "Are you sure you want to abandon this set of profile changes and exit?" msgstr "" #: ../Immunix/AppArmor.pm:1791 msgid "Abandoning all changes." msgstr "" #: ../Immunix/AppArmor.pm:1902 msgid "Default Hat" msgstr "" #: ../Immunix/AppArmor.pm:1904 msgid "Requested Hat" msgstr "" #: ../Immunix/AppArmor.pm:2190 msgid "Program" msgstr "" #: ../Immunix/AppArmor.pm:2195 msgid "Execute" msgstr "" #: ../Immunix/AppArmor.pm:2196 ../Immunix/AppArmor.pm:3455 #: ../Immunix/AppArmor.pm:3488 ../Immunix/AppArmor.pm:3741 msgid "Severity" msgstr "" #: ../Immunix/AppArmor.pm:2241 msgid "Enter profile name to transition to: " msgstr "" #: ../Immunix/AppArmor.pm:2249 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but some applications depend on the presence\n" "of LD_PRELOAD or LD_LIBRARY_PATH." msgstr "" #: ../Immunix/AppArmor.pm:2251 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but this application appears to use LD_PRELOAD\n" "or LD_LIBRARY_PATH and clearing these could\n" "cause functionality problems." msgstr "" #: ../Immunix/AppArmor.pm:2260 #, perl-format msgid "" "Launching processes in an unconfined state is a very\n" "dangerous operation and can cause serious security holes.\n" "\n" "Are you absolutely certain you wish to remove all\n" "AppArmor protection when executing %s?" msgstr "" #: ../Immunix/AppArmor.pm:2262 msgid "" "Should AppArmor sanitize the environment when\n" "running this program unconfined?\n" "\n" "Not sanitizing the environment when unconfining\n" "a program opens up significant security holes\n" "and should be avoided if at all possible." msgstr "" #: ../Immunix/AppArmor.pm:2352 #, perl-format msgid "A profile for %s does not exist. Create one?" msgstr "" #: ../Immunix/AppArmor.pm:2379 #, perl-format msgid "A local profile for %s does not exist. Create one?" msgstr "" #: ../Immunix/AppArmor.pm:2584 ../Immunix/AppArmor.pm:6733 #: ../Immunix/AppArmor.pm:6738 #, perl-format msgid "Log contains unknown mode %s." msgstr "" #: ../Immunix/AppArmor.pm:3068 msgid "" "An updated version of this profile has been found in the profile repository. " " Would you like to use it?" msgstr "" #: ../Immunix/AppArmor.pm:3098 #, perl-format msgid "Updated profile %s to revision %s." msgstr "" #: ../Immunix/AppArmor.pm:3105 msgid "Error parsing repository profile." msgstr "" #: ../Immunix/AppArmor.pm:3141 msgid "Create New User?" msgstr "" #: ../Immunix/AppArmor.pm:3142 msgid "Username: " msgstr "" #: ../Immunix/AppArmor.pm:3143 msgid "Password: " msgstr "" #: ../Immunix/AppArmor.pm:3144 msgid "Email Addr: " msgstr "" #: ../Immunix/AppArmor.pm:3146 msgid "Save Configuration? " msgstr "" #: ../Immunix/AppArmor.pm:3155 msgid "The Profile Repository server returned the following error:" msgstr "" #: ../Immunix/AppArmor.pm:3157 msgid "" "Please re-enter registration information or contact the administrator." msgstr "" #: ../Immunix/AppArmor.pm:3158 msgid "Login Error\n" msgstr "" #: ../Immunix/AppArmor.pm:3165 msgid "" "Login failure\n" " Please check username and password and try again." msgstr "" #: ../Immunix/AppArmor.pm:3187 msgid "" "Would you like to enable access to the\n" "profile repository?" msgstr "" #: ../Immunix/AppArmor.pm:3218 msgid "" "Would you like to upload newly created and changed profiles to\n" " the profile repository?" msgstr "" #: ../Immunix/AppArmor.pm:3337 #, perl-format msgid "" "WARNING: Profile update check failed\n" "Error Detail:\n" "%s" msgstr "" #: ../Immunix/AppArmor.pm:3351 msgid "Change mode modifiers" msgstr "" #: ../Immunix/AppArmor.pm:3395 msgid "Complain-mode changes:" msgstr "" #: ../Immunix/AppArmor.pm:3397 msgid "Enforce-mode changes:" msgstr "" #: ../Immunix/AppArmor.pm:3403 #, perl-format msgid "Invalid mode found: %s" msgstr "" #: ../Immunix/AppArmor.pm:3454 ../Immunix/AppArmor.pm:3487 msgid "Capability" msgstr "" #: ../Immunix/AppArmor.pm:3507 ../Immunix/AppArmor.pm:3781 #: ../Immunix/AppArmor.pm:4028 #, perl-format msgid "Adding #include <%s> to profile." msgstr "" #: ../Immunix/AppArmor.pm:3510 ../Immunix/AppArmor.pm:3782 #: ../Immunix/AppArmor.pm:3822 ../Immunix/AppArmor.pm:4032 #, perl-format msgid "Deleted %s previous matching profile entries." msgstr "" #: ../Immunix/AppArmor.pm:3521 #, perl-format msgid "Adding capability %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3526 #, perl-format msgid "Denying capability %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3687 msgid "Path" msgstr "" #: ../Immunix/AppArmor.pm:3698 ../Immunix/AppArmor.pm:3730 msgid "(owner permissions off)" msgstr "" #: ../Immunix/AppArmor.pm:3704 msgid "(force new perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3707 msgid "(force all rule perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3719 msgid "Old Mode" msgstr "" #: ../Immunix/AppArmor.pm:3720 msgid "New Mode" msgstr "" #: ../Immunix/AppArmor.pm:3736 msgid "(force perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3739 msgid "Mode" msgstr "" #: ../Immunix/AppArmor.pm:3821 #, perl-format msgid "Adding %s %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3837 msgid "Enter new path: " msgstr "" #: ../Immunix/AppArmor.pm:3840 msgid "The specified path does not match this log entry:" msgstr "" #: ../Immunix/AppArmor.pm:3841 msgid "Log Entry" msgstr "" #: ../Immunix/AppArmor.pm:3842 msgid "Entered Path" msgstr "" #: ../Immunix/AppArmor.pm:3843 msgid "Do you really want to use this path?" msgstr "" #: ../Immunix/AppArmor.pm:3955 ../Immunix/AppArmor.pm:4007 msgid "Network Family" msgstr "" #: ../Immunix/AppArmor.pm:3958 ../Immunix/AppArmor.pm:4010 msgid "Socket Type" msgstr "" #: ../Immunix/AppArmor.pm:4058 #, perl-format msgid "Adding network access %s %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:4077 #, perl-format msgid "Denying network access %s %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:4285 #, perl-format msgid "Reading log entries from %s." msgstr "" #: ../Immunix/AppArmor.pm:4286 #, perl-format msgid "Updating AppArmor profiles in %s." msgstr "" #: ../Immunix/AppArmor.pm:4290 msgid "unknown\n" msgstr "" #: ../Immunix/AppArmor.pm:4324 msgid "" "The profile analyzer has completed processing the log files.\n" "\n" "All updated profiles will be reloaded" msgstr "" #: ../Immunix/AppArmor.pm:4330 msgid "No unhandled AppArmor events were found in the system log." msgstr "" #: ../Immunix/AppArmor.pm:4391 msgid "" "Select which profile changes you would like to save to the\n" "local profile set" msgstr "" #: ../Immunix/AppArmor.pm:4392 msgid "Local profile changes" msgstr "" #: ../Immunix/AppArmor.pm:4419 msgid "" "The following local profiles were changed. Would you like to save them?" msgstr "" #: ../Immunix/AppArmor.pm:4516 msgid "Profile Changes" msgstr "" #: ../Immunix/AppArmor.pm:5139 ../Immunix/AppArmor.pm:5155 #: ../Immunix/AppArmor.pm:5166 ../Immunix/AppArmor.pm:5174 #: ../Immunix/AppArmor.pm:5195 ../Immunix/AppArmor.pm:5215 #: ../Immunix/AppArmor.pm:5224 ../Immunix/AppArmor.pm:5256 #: ../Immunix/AppArmor.pm:5334 ../Immunix/AppArmor.pm:5382 #, perl-format msgid "%s contains syntax errors." msgstr "" #: ../Immunix/AppArmor.pm:5275 #, perl-format msgid "Profile %s contains invalid regexp %s." msgstr "" #: ../Immunix/AppArmor.pm:5280 #, perl-format msgid "Profile %s contains invalid mode %s." msgstr "" #: ../Immunix/AppArmor.pm:5430 #, perl-format msgid "%s contains syntax errors. Line [%s]" msgstr "" #: ../Immunix/AppArmor.pm:6022 #, perl-format msgid "Writing updated profile for %s." msgstr "" #: ../Immunix/AppArmor.pm:6528 msgid "Unknown command" msgstr "" #: ../Immunix/AppArmor.pm:6536 msgid "Invalid hotkey in" msgstr "" #: ../Immunix/AppArmor.pm:6546 msgid "Duplicate hotkey for" msgstr "" #: ../Immunix/AppArmor.pm:6567 msgid "Invalid hotkey in default item" msgstr "" #: ../Immunix/AppArmor.pm:6576 msgid "Invalid default" msgstr "" apparmor-2.8.95~2430/utils/po/de.po0000644000175000017500000006032212266301105016542 0ustar sarnoldsarnold# Copyright (C) 2006 SuSE Linux Products GmbH, Nuernberg # Copyright (C) 2013 Christian Boltz # This file is distributed under the same license as the package. # msgid "" msgstr "" "Project-Id-Version: apparmor-utils\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-13 16:44-0800\n" "PO-Revision-Date: 2013-12-06 18:42+0000\n" "Last-Translator: Christian Boltz \n" "Language-Team: Novell Language \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2013-12-07 05:14+0000\n" "X-Generator: Launchpad (build 16869)\n" "Language: de\n" #: ../aa-genprof:72 ../aa-unconfined:54 msgid "" "AppArmor does not appear to be started. Please enable AppArmor and try again." msgstr "" "AppArmor wurde offenbar nicht gestartet. Aktivieren Sie AppArmor und " "versuchen Sie es erneut." #: ../aa-genprof:86 msgid "Please enter the program to profile: " msgstr "" "Bitte geben Sie das Programm an, für das ein Profil erstellt werden soll: " #: ../aa-genprof:105 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' in the other window in order to find the " "fully-qualified path." msgstr "" "%s wurde in der Systempfadliste nicht gefunden. Wenn der Name der Anwendung " "richtig ist, führen Sie 'which %s' im anderen Fenster aus, um nach dem " "vollständigen Pfad zu suchen." #: ../aa-genprof:107 ../aa-autodep:110 ../aa-audit:120 ../aa-complain:119 #: ../aa-enforce:130 ../aa-disable:140 #, perl-format msgid "%s does not exist, please double-check the path." msgstr "%s ist nicht vorhanden. Überprüfen Sie den Pfad." #: ../aa-genprof:141 msgid "" "\n" "Before you begin, you may wish to check if a\n" "profile already exists for the application you\n" "wish to confine. See the following wiki page for\n" "more information:\n" "http://wiki.apparmor.net/index.php/Profiles" msgstr "" #: ../aa-genprof:143 msgid "" "Please start the application to be profiled in \n" "another window and exercise its functionality now.\n" "\n" "Once completed, select the \"Scan\" button below in \n" "order to scan the system logs for AppArmor events. \n" "\n" "For each AppArmor event, you will be given the \n" "opportunity to choose whether the access should be \n" "allowed or denied." msgstr "" "Starten Sie die Anwendung, die in einem anderen Fenster\n" "als Profil dargestellt werden soll, und führen Sie die Funktionalität jetzt " "aus.\n" " \n" " Nach Abschluss dieses Vorgangs wählen Sie unten \"Durchsuchen\", um\n" "in den Systemprotokollen nach AppArmor-Ereignissen zu suchen. \n" " \n" "Für jedes AppArmor-Ereignis haben Sie die Gelegenheit,\n" "anzugeben, ob der Zugriff\n" "zugelassen oder verweigert werden soll." #: ../aa-genprof:163 msgid "Profiling" msgstr "Profilerstellung" #: ../aa-genprof:197 msgid "Reloaded AppArmor profiles in enforce mode." msgstr "AppArmor-Profile wurden im Erzwingen-Modus neu geladen." #: ../aa-genprof:198 msgid "" "\n" "Please consider contributing your new profile! See\n" "the following wiki page for more information:\n" "http://wiki.apparmor.net/index.php/Profiles\n" msgstr "" #: ../aa-genprof:199 #, perl-format msgid "Finished generating profile for %s." msgstr "Profilerstellung für %s abgeschlossen." #: ../aa-genprof:203 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ program to " "profile ]" msgstr "" "Syntax: %s [ -d /pfad/zu/profilen ] [ -f /pfad/zu/protokolldatei ] [ " "programm, für das ein profil erstellt werden soll ]" #: ../aa-logprof:69 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ -m \"mark in " "log to start processing after\"" msgstr "" "Syntax: %s [ -d /pfad/zu/profilen ] [ -f /pfad/zu/protokolldatei ] [ -m " "\"Markierng im Protokoll, nach der die Verarbeitung gestartet werden soll\"" #: ../aa-autodep:61 #, perl-format msgid "Can't find AppArmor profiles in %s." msgstr "In %s wurden keine AppArmor-Profile gefunden." #: ../aa-autodep:69 msgid "Please enter the program to create a profile for: " msgstr "Geben Sie das Programm an, für das ein Profil erstellt werden soll: " #: ../aa-autodep:93 ../Immunix/AppArmor.pm:6339 #, perl-format msgid "" "%s is currently marked as a program that should not have it's own profile. " "Usually, programs are marked this way if creating a profile for them is " "likely to break the rest of the system. If you know what you're doing and " "are certain you want to create a profile for this program, edit the " "corresponding entry in the [qualifiers] section in " "/etc/apparmor/logprof.conf." msgstr "" "%s ist momentan als Programm markiert, das nicht über ein eigenes Profil " "verfügen sollte. In der Regel werden Programme auf diese Weise markiert, " "wenn es durch die Erstellung eines Profils für dieses Programm " "wahrscheinlich zu einer Beschädigung des Systems kommt. Wenn Sie sich " "darüber im Klaren sind, was Sie tun, und sich sicher sind, dass für dieses " "Programm ein Profil erstellt werden soll, bearbeiten Sie den entsprechenden " "Eintrag im Abschnitt [qualifiers] in der Datei /etc/apparmor/logprof.conf." #: ../aa-autodep:100 #, perl-format msgid "Profile for %s already exists - skipping." msgstr "Profil für %s existiert bereits - überspringen." #: ../aa-autodep:107 ../aa-audit:117 ../aa-complain:116 ../aa-enforce:127 #: ../aa-disable:137 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' as a user with the correct PATH environment " "set up in order to find the fully-qualified path." msgstr "" "%s wurde in der Systempfadliste nicht gefunden. Wenn der Name der Anwendung " "richtig ist, führen Sie 'which %s' als Benutzer mit korrekter PATH-Umgebung " "aus, um den vollständig qualifizierten Pfad zu finden." #: ../aa-audit:104 #, perl-format msgid "Setting %s to audit mode." msgstr "%s wird in Prüfmodus versetzt." #: ../aa-audit:129 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to audit mode ]" msgstr "" "Syntax: %s [ -d /pfad/zu/profilen ] [ Programm, das in den Prüfmodus " "versetzt werden soll ]" #: ../aa-complain:61 msgid "Please enter the program to switch to complain mode: " msgstr "" "Geben Sie das Programm an, das in den Meldungsmodus versetzt werden soll: " #: ../aa-complain:103 ../Immunix/AppArmor.pm:621 ../Immunix/AppArmor.pm:941 #, perl-format msgid "Setting %s to complain mode." msgstr "%s wird in Meldungsmodus versetzt." #: ../aa-complain:128 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to complain mode ]" msgstr "" "Syntax: %s [ -d /pfad/zu/profilen ] [ Programm, das in den Meldungsmodus " "versetzt werden soll ]" #: ../aa-enforce:62 msgid "Please enter the program to switch to enforce mode: " msgstr "" "Geben Sie das Programm an, das in den Erzwingen-Modus versetzt werden soll: " #: ../aa-enforce:103 ../Immunix/AppArmor.pm:634 #, perl-format msgid "Setting %s to enforce mode." msgstr "%s wird in den Erwzingen-Modus versetzt." #: ../aa-enforce:139 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to enforce mode ]" msgstr "" "Syntax: %s [ -d /pfad/zu/profilen ] [ Programm, das in den Erzwingen-Modus " "versetzt werden soll ]" #: ../aa-unconfined:48 #, perl-format msgid "Usage: %s [ --paranoid ]\n" msgstr "Syntax: %s [ --paranoid ]\n" #: ../aa-unconfined:59 msgid "Can't read /proc\n" msgstr "/proc kann nicht gelesen werden\n" #: ../aa-unconfined:97 ../aa-unconfined:99 msgid "not confined\n" msgstr "nicht eingeschränkt\n" #: ../aa-unconfined:108 ../aa-unconfined:110 msgid "confined by" msgstr "eingeschränkt durch" #: ../aa-disable:69 msgid "Please enter the program whose profile should be disabled: " msgstr "" "Bitte geben Sie das Programm an, dessen Profil deaktiviert werden soll: " #: ../aa-disable:113 #, perl-format msgid "Could not find basename for %s." msgstr "Basisname von %s nicht gefunden." #: ../aa-disable:117 #, perl-format msgid "Disabling %s." msgstr "Deaktiviere %s." #: ../aa-disable:123 #, perl-format msgid "Could not create %s symlink." msgstr "Symlink %s konnte nicht erstellt werden." #: ../aa-disable:149 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to have profile disabled ]" msgstr "" "Syntax: %s [ -d /pfad/zu/den/profilen ] [ Programm, dessen Profil " "deaktiviert werden soll ]" #: ../Immunix/AppArmor.pm:619 ../Immunix/AppArmor.pm:632 #, perl-format msgid "Can't find %s." msgstr "%s kann nicht gefunden werden." #: ../Immunix/AppArmor.pm:819 ../Immunix/AppArmor.pm:3326 msgid "Connecting to repository....." msgstr "Verbindung zu Repository wird hergestellt..." #: ../Immunix/AppArmor.pm:828 #, perl-format msgid "" "WARNING: Error fetching profiles from the repository:\n" "%s\n" msgstr "" "ACHTUNG: Fehler beim Abrufen der Profile aus dem Repository: \n" "%s\n" #: ../Immunix/AppArmor.pm:837 msgid "Inactive local profile for " msgstr "Inaktives lokales Profil für " #: ../Immunix/AppArmor.pm:874 ../Immunix/AppArmor.pm:1900 #: ../Immunix/AppArmor.pm:2188 ../Immunix/AppArmor.pm:3453 #: ../Immunix/AppArmor.pm:3486 ../Immunix/AppArmor.pm:3686 #: ../Immunix/AppArmor.pm:3952 ../Immunix/AppArmor.pm:4004 msgid "Profile" msgstr "Profil" #: ../Immunix/AppArmor.pm:908 msgid "Profile submitted by" msgstr "Profil weitergegeben von" #: ../Immunix/AppArmor.pm:949 #, perl-format msgid "Error activating profiles: %s\n" msgstr "Fehler beim Aktivieren der Profile: %s\n" #: ../Immunix/AppArmor.pm:1098 ../Immunix/AppArmor.pm:1151 #, perl-format msgid "" "WARNING: Error syncronizing profiles with the repository:\n" "%s\n" msgstr "" "ACHTUNG: Fehler beim Synchronisieren der Profile mit dem Repository: \n" "%s\n" #: ../Immunix/AppArmor.pm:1178 msgid "New profiles" msgstr "Neue Profile" #: ../Immunix/AppArmor.pm:1180 msgid "" "Please choose the newly created profiles that you would like\n" "to store in the repository" msgstr "" "Wählen Sie die neu erstellten Profile, die Sie im Repository \n" "speichern möchten." #: ../Immunix/AppArmor.pm:1187 msgid "Submit newly created profiles to the repository" msgstr "Neu erstellte Profile an das Repository weitergeben" #: ../Immunix/AppArmor.pm:1189 msgid "Would you like to upload the newly created profiles?" msgstr "Möchten Sie die neu erstellten Profile hochladen?" #: ../Immunix/AppArmor.pm:1202 msgid "" "Select which of the changed profiles you would like to upload\n" "to the repository" msgstr "" "Wählen Sie die geänderten Profile aus, die Sie in das Repository \n" "hochladen möchten" #: ../Immunix/AppArmor.pm:1204 msgid "Changed profiles" msgstr "Geänderte Profile" #: ../Immunix/AppArmor.pm:1210 msgid "Submit changed profiles to the repository" msgstr "Geänderte Profile an das Repository weitergeben" #: ../Immunix/AppArmor.pm:1212 msgid "" "The following profiles from the repository were changed.\n" "Would you like to upload your changes?" msgstr "" "Die folgenden Profile im Repository wurden geändert.\n" "Möchten Sie Ihre Änderungen hochladen?" #: ../Immunix/AppArmor.pm:1279 ../Immunix/AppArmor.pm:1359 #, perl-format msgid "" "WARNING: An error occured while uploading the profile %s\n" "%s\n" msgstr "" "ACHTUNG: Fehler beim Hochladen von Profil %s\n" "%s\n" #: ../Immunix/AppArmor.pm:1284 msgid "Uploaded changes to repository." msgstr "Änderungen an das Repository hochgeladen." #: ../Immunix/AppArmor.pm:1306 ../Immunix/AppArmor.pm:3185 #: ../Immunix/AppArmor.pm:3215 msgid "Repository" msgstr "Repository" #: ../Immunix/AppArmor.pm:1333 msgid "Changelog Entry: " msgstr "Protokolleintrag: " #: ../Immunix/AppArmor.pm:1354 #, perl-format msgid "Uploaded %s to repository." msgstr "'%s' ins Repository hochgeladen." #: ../Immunix/AppArmor.pm:1365 msgid "" "Repository Error\n" "Registration or Signin was unsuccessful. User login\n" "information is required to upload profiles to the\n" "repository. These changes have not been sent.\n" msgstr "" "Repository-Fehler\n" "Registrierung oder Anmeldung war erfolglos. Die Anmeldeinformationen\n" "des Nutzers werden benötigt, um Profile in das Repository\n" " hochzuladen. Diese Änderungen wurden nicht gesendet.\n" #: ../Immunix/AppArmor.pm:1422 ../Immunix/AppArmor.pm:1462 msgid "(Y)es" msgstr "(J)a" #: ../Immunix/AppArmor.pm:1423 ../Immunix/AppArmor.pm:1463 msgid "(N)o" msgstr "(N)ein" #: ../Immunix/AppArmor.pm:1426 ../Immunix/AppArmor.pm:1467 msgid "Invalid hotkey for" msgstr "Ungültige Tastenkombination für" #: ../Immunix/AppArmor.pm:1464 msgid "(C)ancel" msgstr "(A)bbrechen" #: ../Immunix/AppArmor.pm:1789 msgid "" "Are you sure you want to abandon this set of profile changes and exit?" msgstr "" "Möchten Sie diese Gruppe von Profiländerungen wirklich verwerfen und den " "Vorgang beenden?" #: ../Immunix/AppArmor.pm:1791 msgid "Abandoning all changes." msgstr "Alle Änderungen verworfen." #: ../Immunix/AppArmor.pm:1902 msgid "Default Hat" msgstr "Standard-Hat" #: ../Immunix/AppArmor.pm:1904 msgid "Requested Hat" msgstr "Angeforderter Hat" #: ../Immunix/AppArmor.pm:2190 msgid "Program" msgstr "Programm" #: ../Immunix/AppArmor.pm:2195 msgid "Execute" msgstr "Ausführen" #: ../Immunix/AppArmor.pm:2196 ../Immunix/AppArmor.pm:3455 #: ../Immunix/AppArmor.pm:3488 ../Immunix/AppArmor.pm:3741 msgid "Severity" msgstr "Schweregrad" #: ../Immunix/AppArmor.pm:2241 msgid "Enter profile name to transition to: " msgstr "Profilname eingeben, zu dem gewechselt werden soll: " #: ../Immunix/AppArmor.pm:2249 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but some applications depend on the presence\n" "of LD_PRELOAD or LD_LIBRARY_PATH." msgstr "" "Soll AppArmor die Umgebung beim Profilwechsel\n" "bereinigen?\n" "\n" "Die Bereinigung wäre sicherer,\n" "einige Anwendungen hängen jedoch vom Vorhandensein \n" "der Variablen LD_PRELOAD oder LD_LIBRARY_PATH ab." #: ../Immunix/AppArmor.pm:2251 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but this application appears to use LD_PRELOAD\n" "or LD_LIBRARY_PATH and clearing these could\n" "cause functionality problems." msgstr "" "Soll AppArmor die Umgebung beim Profilwechsel\n" "bereinigen?\n" "\n" "Die Bereinigung wäre sicherer,\n" "diese Anwendung scheint jedoch LD_PRELOAD\n" "oder LD_LIBRARY_PATH zu nutzen. Das Entfernen dieser Variablen kann\n" "Funktionsprobleme nach sich ziehen." #: ../Immunix/AppArmor.pm:2260 #, perl-format msgid "" "Launching processes in an unconfined state is a very\n" "dangerous operation and can cause serious security holes.\n" "\n" "Are you absolutely certain you wish to remove all\n" "AppArmor protection when executing %s?" msgstr "" "Das Starten von Prozessen in einem uneingeschränkten Status ist ein\n" "sehr riskantes Vorgehen, das ernsthafte Sicherheitslücken zur Folge haben " "kann.\n" "\n" "Sind Sie sich absolut sicher, dass %s ohne die Schutzvorkehrungen von \n" "AppArmor ausgeführt werden darf?" #: ../Immunix/AppArmor.pm:2262 msgid "" "Should AppArmor sanitize the environment when\n" "running this program unconfined?\n" "\n" "Not sanitizing the environment when unconfining\n" "a program opens up significant security holes\n" "and should be avoided if at all possible." msgstr "" "Soll AppArmor die Umgebung bereinigen, wenn\n" "dieses Programm uneingeschränkt ausgeführt wird?\n" "\n" "Ohne Bereinigung stellen uneingeschränkt ausgeführte\n" "Programme ernsthafte Sicherheitslücken dar,\n" "die möglichst vermieden werden sollten." #: ../Immunix/AppArmor.pm:2352 #, perl-format msgid "A profile for %s does not exist. Create one?" msgstr "Ein Profil für %s existiert nicht. Möchten Sie eins erstellen?" #: ../Immunix/AppArmor.pm:2379 #, perl-format msgid "A local profile for %s does not exist. Create one?" msgstr "Für %s existiert kein lokales Profil. Möchten Sie eins erstellen?" #: ../Immunix/AppArmor.pm:2584 ../Immunix/AppArmor.pm:6733 #: ../Immunix/AppArmor.pm:6738 #, perl-format msgid "Log contains unknown mode %s." msgstr "Protokoll enthält unbekannten Modus %s." #: ../Immunix/AppArmor.pm:3068 msgid "" "An updated version of this profile has been found in the profile repository. " " Would you like to use it?" msgstr "" "Es wurde eine aktualisierte Version dieses Profils im Profil-Repository " "gefunden. Möchten Sie diese verwenden?" #: ../Immunix/AppArmor.pm:3098 #, perl-format msgid "Updated profile %s to revision %s." msgstr "Aktualisiertes Profil '%s' für Version '%s'." #: ../Immunix/AppArmor.pm:3105 msgid "Error parsing repository profile." msgstr "Fehler beim Analysieren des Repository-Profils." #: ../Immunix/AppArmor.pm:3141 msgid "Create New User?" msgstr "Neuen Benutzer erstellen?" #: ../Immunix/AppArmor.pm:3142 msgid "Username: " msgstr "Benutzername: " #: ../Immunix/AppArmor.pm:3143 msgid "Password: " msgstr "Passwort: " #: ../Immunix/AppArmor.pm:3144 msgid "Email Addr: " msgstr "E-Mail-Adresse: " #: ../Immunix/AppArmor.pm:3146 msgid "Save Configuration? " msgstr "Konfiguration speichern? " #: ../Immunix/AppArmor.pm:3155 msgid "The Profile Repository server returned the following error:" msgstr "Der Profil-Repository-Server hat folgenden Fehler zurückgegeben:" #: ../Immunix/AppArmor.pm:3157 msgid "" "Please re-enter registration information or contact the administrator." msgstr "" "Geben Sie die Registrierungsinformation erneut ein oder kontaktieren Sie den " "Administrator." #: ../Immunix/AppArmor.pm:3158 msgid "Login Error\n" msgstr "Anmeldefehler\n" #: ../Immunix/AppArmor.pm:3165 msgid "" "Login failure\n" " Please check username and password and try again." msgstr "" "Fehler bei der Anmeldung\n" "Überprüfen Sie Benutzernamen und Passwort und versuchen Sie es erneut." #: ../Immunix/AppArmor.pm:3187 msgid "" "Would you like to enable access to the\n" "profile repository?" msgstr "" "Möchten Sie den Zugriff auf das\n" "Profil-Repository aktivieren?" #: ../Immunix/AppArmor.pm:3218 msgid "" "Would you like to upload newly created and changed profiles to\n" " the profile repository?" msgstr "" "Möchten Sie neu erstellte und geänderte Profile an\n" " das Profil-Repository hochladen?" #: ../Immunix/AppArmor.pm:3337 #, perl-format msgid "" "WARNING: Profile update check failed\n" "Error Detail:\n" "%s" msgstr "" "ACHTUNG: Fehler bei Profil-Update-Prüfung\n" "Fehlerdetails:\n" "%s" #: ../Immunix/AppArmor.pm:3351 msgid "Change mode modifiers" msgstr "Modus-Modifizierer ändern" #: ../Immunix/AppArmor.pm:3395 msgid "Complain-mode changes:" msgstr "Änderungen im Meldungsmodus:" #: ../Immunix/AppArmor.pm:3397 msgid "Enforce-mode changes:" msgstr "Änderungen im Erzwingen-Modus:" #: ../Immunix/AppArmor.pm:3403 #, perl-format msgid "Invalid mode found: %s" msgstr "Ungültiger Modus gefunden: %s" #: ../Immunix/AppArmor.pm:3454 ../Immunix/AppArmor.pm:3487 msgid "Capability" msgstr "Capability" #: ../Immunix/AppArmor.pm:3507 ../Immunix/AppArmor.pm:3781 #: ../Immunix/AppArmor.pm:4028 #, perl-format msgid "Adding #include <%s> to profile." msgstr "#include <%s> zum Profil hinzugefügt." #: ../Immunix/AppArmor.pm:3510 ../Immunix/AppArmor.pm:3782 #: ../Immunix/AppArmor.pm:3822 ../Immunix/AppArmor.pm:4032 #, perl-format msgid "Deleted %s previous matching profile entries." msgstr "%s vorherige übereinstimmende Profileinträge wurden gelöscht." #: ../Immunix/AppArmor.pm:3521 #, perl-format msgid "Adding capability %s to profile." msgstr "Capability %s zum Profil hinzugefügt." #: ../Immunix/AppArmor.pm:3526 #, perl-format msgid "Denying capability %s to profile." msgstr "Capability %s wird dem Profil verweigert." #: ../Immunix/AppArmor.pm:3687 msgid "Path" msgstr "Pfad" #: ../Immunix/AppArmor.pm:3698 ../Immunix/AppArmor.pm:3730 msgid "(owner permissions off)" msgstr "(Eigentümerberechtigungen deaktiviert)" #: ../Immunix/AppArmor.pm:3704 msgid "(force new perms to owner)" msgstr "(neue Berechtigungen für Eigentümer erzwingen)" #: ../Immunix/AppArmor.pm:3707 msgid "(force all rule perms to owner)" msgstr "(alle Regelberechtigungen für Eigentümer erzwingen)" #: ../Immunix/AppArmor.pm:3719 msgid "Old Mode" msgstr "Alter Modus" #: ../Immunix/AppArmor.pm:3720 msgid "New Mode" msgstr "Neuer Modus" #: ../Immunix/AppArmor.pm:3736 msgid "(force perms to owner)" msgstr "(Berechtigungen für Eigentümer erzwingen)" #: ../Immunix/AppArmor.pm:3739 msgid "Mode" msgstr "Modus" #: ../Immunix/AppArmor.pm:3821 #, perl-format msgid "Adding %s %s to profile." msgstr "%s %s wird zum Profil hinzugefügt." #: ../Immunix/AppArmor.pm:3837 msgid "Enter new path: " msgstr "Neuen Pfad eingeben: " #: ../Immunix/AppArmor.pm:3840 msgid "The specified path does not match this log entry:" msgstr "Der angegebene Pfad stimmt nicht mit dem Protokolleintrag überein:" #: ../Immunix/AppArmor.pm:3841 msgid "Log Entry" msgstr "Protokolleintrag" #: ../Immunix/AppArmor.pm:3842 msgid "Entered Path" msgstr "Eingegebener Pfad" #: ../Immunix/AppArmor.pm:3843 msgid "Do you really want to use this path?" msgstr "Möchten Sie diesen Pfad wirklich verwenden?" #: ../Immunix/AppArmor.pm:3955 ../Immunix/AppArmor.pm:4007 msgid "Network Family" msgstr "Netzwerkfamilie" #: ../Immunix/AppArmor.pm:3958 ../Immunix/AppArmor.pm:4010 msgid "Socket Type" msgstr "Socket-Typ" #: ../Immunix/AppArmor.pm:4058 #, perl-format msgid "Adding network access %s %s to profile." msgstr "Netzwerkzugriff '%s' '%s' wird zum Profil hinzugefügt." #: ../Immunix/AppArmor.pm:4077 #, perl-format msgid "Denying network access %s %s to profile." msgstr "Netzwerkzugriff '%s' '%s' wird dem Profil verweigert." #: ../Immunix/AppArmor.pm:4285 #, perl-format msgid "Reading log entries from %s." msgstr "Protokolleinträge von %s werden eingelesen." #: ../Immunix/AppArmor.pm:4286 #, perl-format msgid "Updating AppArmor profiles in %s." msgstr "AppArmor-Profile in %s werden aktualisiert." #: ../Immunix/AppArmor.pm:4290 msgid "unknown\n" msgstr "unbekannt\n" #: ../Immunix/AppArmor.pm:4324 msgid "" "The profile analyzer has completed processing the log files.\n" "\n" "All updated profiles will be reloaded" msgstr "" "Die Verarbeitung der Protokolldateien durch die Profilanalyse ist " "abgeschlossen.\n" "\n" " Alle aktualisierten Profile werden neu geladen" #: ../Immunix/AppArmor.pm:4330 msgid "No unhandled AppArmor events were found in the system log." msgstr "" "Im Systemprotokoll wurden keine unverarbeiteten AppArmor-Ereignisse gefunden." #: ../Immunix/AppArmor.pm:4391 msgid "" "Select which profile changes you would like to save to the\n" "local profile set" msgstr "" "Wählen Sie die Profiländerungen aus, die Sie im \n" "lokalen Profilsatz speichern möchten." #: ../Immunix/AppArmor.pm:4392 msgid "Local profile changes" msgstr "Lokale Profiländerungen" #: ../Immunix/AppArmor.pm:4419 msgid "" "The following local profiles were changed. Would you like to save them?" msgstr "" "Die folgenden lokalen Profile wurden geändert. Möchten Sie sie speichern?" #: ../Immunix/AppArmor.pm:4516 msgid "Profile Changes" msgstr "Profiländerungen" #: ../Immunix/AppArmor.pm:5139 ../Immunix/AppArmor.pm:5155 #: ../Immunix/AppArmor.pm:5166 ../Immunix/AppArmor.pm:5174 #: ../Immunix/AppArmor.pm:5195 ../Immunix/AppArmor.pm:5215 #: ../Immunix/AppArmor.pm:5224 ../Immunix/AppArmor.pm:5256 #: ../Immunix/AppArmor.pm:5334 ../Immunix/AppArmor.pm:5382 #, perl-format msgid "%s contains syntax errors." msgstr "%s enthält Syntax-Fehler." #: ../Immunix/AppArmor.pm:5275 #, perl-format msgid "Profile %s contains invalid regexp %s." msgstr "Profil %s enthält ungültigen regexp %s." #: ../Immunix/AppArmor.pm:5280 #, perl-format msgid "Profile %s contains invalid mode %s." msgstr "Profil '%s' enthält ungültigen Modus '%s'." #: ../Immunix/AppArmor.pm:5430 #, perl-format msgid "%s contains syntax errors. Line [%s]" msgstr "'%s' enthält Syntaxfehler. Zeile ['%s']" #: ../Immunix/AppArmor.pm:6022 #, perl-format msgid "Writing updated profile for %s." msgstr "Aktualisiertes Profil für %s wird geschrieben." #: ../Immunix/AppArmor.pm:6528 msgid "Unknown command" msgstr "Unbekannter Befehl" #: ../Immunix/AppArmor.pm:6536 msgid "Invalid hotkey in" msgstr "Ungültige Tastenkombination in" #: ../Immunix/AppArmor.pm:6546 msgid "Duplicate hotkey for" msgstr "Doppelte Tastenkombination für" #: ../Immunix/AppArmor.pm:6567 msgid "Invalid hotkey in default item" msgstr "Ungültige Tastenkombination in Standardelement" #: ../Immunix/AppArmor.pm:6576 msgid "Invalid default" msgstr "Ungültiger Standardwert." apparmor-2.8.95~2430/utils/po/README0000644000175000017500000000060412277004630016474 0ustar sarnoldsarnoldGENERATING TRANSLATION MESSAGES To generate the messages.pot file: Run the following command in Translate. python pygettext.py ../apparmor/*.py ../Tools/aa* It will generate the messages.pot file in the Translate directory. You might need to provide the full path to pygettext.py from your python installation. It will typically be in the /path/to/python/libs/Tools/i18n/pygettext.py apparmor-2.8.95~2430/utils/po/uk.po0000644000175000017500000004025112267276306016607 0ustar sarnoldsarnold# Ukrainian translation for apparmor # Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 # This file is distributed under the same license as the apparmor package. # FIRST AUTHOR , 2013. # msgid "" msgstr "" "Project-Id-Version: apparmor-utils\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-13 16:44-0800\n" "PO-Revision-Date: 2013-11-15 06:49+0000\n" "Last-Translator: FULL NAME \n" "Language-Team: Ukrainian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2013-11-16 05:14+0000\n" "X-Generator: Launchpad (build 16831)\n" "Language: uk\n" #: ../aa-genprof:72 ../aa-unconfined:54 msgid "" "AppArmor does not appear to be started. Please enable AppArmor and try again." msgstr "" #: ../aa-genprof:86 msgid "Please enter the program to profile: " msgstr "" #: ../aa-genprof:105 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' in the other window in order to find the " "fully-qualified path." msgstr "" #: ../aa-genprof:107 ../aa-autodep:110 ../aa-audit:120 ../aa-complain:119 #: ../aa-enforce:130 ../aa-disable:140 #, perl-format msgid "%s does not exist, please double-check the path." msgstr "" #: ../aa-genprof:141 msgid "" "\n" "Before you begin, you may wish to check if a\n" "profile already exists for the application you\n" "wish to confine. See the following wiki page for\n" "more information:\n" "http://wiki.apparmor.net/index.php/Profiles" msgstr "" #: ../aa-genprof:143 msgid "" "Please start the application to be profiled in \n" "another window and exercise its functionality now.\n" "\n" "Once completed, select the \"Scan\" button below in \n" "order to scan the system logs for AppArmor events. \n" "\n" "For each AppArmor event, you will be given the \n" "opportunity to choose whether the access should be \n" "allowed or denied." msgstr "" #: ../aa-genprof:163 msgid "Profiling" msgstr "" #: ../aa-genprof:197 msgid "Reloaded AppArmor profiles in enforce mode." msgstr "" #: ../aa-genprof:198 msgid "" "\n" "Please consider contributing your new profile! See\n" "the following wiki page for more information:\n" "http://wiki.apparmor.net/index.php/Profiles\n" msgstr "" #: ../aa-genprof:199 #, perl-format msgid "Finished generating profile for %s." msgstr "" #: ../aa-genprof:203 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ program to " "profile ]" msgstr "" #: ../aa-logprof:69 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ -m \"mark in " "log to start processing after\"" msgstr "" #: ../aa-autodep:61 #, perl-format msgid "Can't find AppArmor profiles in %s." msgstr "" #: ../aa-autodep:69 msgid "Please enter the program to create a profile for: " msgstr "" #: ../aa-autodep:93 ../Immunix/AppArmor.pm:6339 #, perl-format msgid "" "%s is currently marked as a program that should not have it's own profile. " "Usually, programs are marked this way if creating a profile for them is " "likely to break the rest of the system. If you know what you're doing and " "are certain you want to create a profile for this program, edit the " "corresponding entry in the [qualifiers] section in " "/etc/apparmor/logprof.conf." msgstr "" #: ../aa-autodep:100 #, perl-format msgid "Profile for %s already exists - skipping." msgstr "" #: ../aa-autodep:107 ../aa-audit:117 ../aa-complain:116 ../aa-enforce:127 #: ../aa-disable:137 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' as a user with the correct PATH environment " "set up in order to find the fully-qualified path." msgstr "" #: ../aa-audit:104 #, perl-format msgid "Setting %s to audit mode." msgstr "" #: ../aa-audit:129 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to audit mode ]" msgstr "" #: ../aa-complain:61 msgid "Please enter the program to switch to complain mode: " msgstr "" #: ../aa-complain:103 ../Immunix/AppArmor.pm:621 ../Immunix/AppArmor.pm:941 #, perl-format msgid "Setting %s to complain mode." msgstr "" #: ../aa-complain:128 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to complain mode ]" msgstr "" #: ../aa-enforce:62 msgid "Please enter the program to switch to enforce mode: " msgstr "" #: ../aa-enforce:103 ../Immunix/AppArmor.pm:634 #, perl-format msgid "Setting %s to enforce mode." msgstr "" #: ../aa-enforce:139 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to enforce mode ]" msgstr "" #: ../aa-unconfined:48 #, perl-format msgid "Usage: %s [ --paranoid ]\n" msgstr "" #: ../aa-unconfined:59 msgid "Can't read /proc\n" msgstr "" #: ../aa-unconfined:97 ../aa-unconfined:99 msgid "not confined\n" msgstr "" #: ../aa-unconfined:108 ../aa-unconfined:110 msgid "confined by" msgstr "" #: ../aa-disable:69 msgid "Please enter the program whose profile should be disabled: " msgstr "" #: ../aa-disable:113 #, perl-format msgid "Could not find basename for %s." msgstr "" #: ../aa-disable:117 #, perl-format msgid "Disabling %s." msgstr "" #: ../aa-disable:123 #, perl-format msgid "Could not create %s symlink." msgstr "" #: ../aa-disable:149 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to have profile disabled ]" msgstr "" #: ../Immunix/AppArmor.pm:619 ../Immunix/AppArmor.pm:632 #, perl-format msgid "Can't find %s." msgstr "" #: ../Immunix/AppArmor.pm:819 ../Immunix/AppArmor.pm:3326 msgid "Connecting to repository....." msgstr "" #: ../Immunix/AppArmor.pm:828 #, perl-format msgid "" "WARNING: Error fetching profiles from the repository:\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:837 msgid "Inactive local profile for " msgstr "" #: ../Immunix/AppArmor.pm:874 ../Immunix/AppArmor.pm:1900 #: ../Immunix/AppArmor.pm:2188 ../Immunix/AppArmor.pm:3453 #: ../Immunix/AppArmor.pm:3486 ../Immunix/AppArmor.pm:3686 #: ../Immunix/AppArmor.pm:3952 ../Immunix/AppArmor.pm:4004 msgid "Profile" msgstr "" #: ../Immunix/AppArmor.pm:908 msgid "Profile submitted by" msgstr "" #: ../Immunix/AppArmor.pm:949 #, perl-format msgid "Error activating profiles: %s\n" msgstr "" #: ../Immunix/AppArmor.pm:1098 ../Immunix/AppArmor.pm:1151 #, perl-format msgid "" "WARNING: Error syncronizing profiles with the repository:\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:1178 msgid "New profiles" msgstr "" #: ../Immunix/AppArmor.pm:1180 msgid "" "Please choose the newly created profiles that you would like\n" "to store in the repository" msgstr "" #: ../Immunix/AppArmor.pm:1187 msgid "Submit newly created profiles to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1189 msgid "Would you like to upload the newly created profiles?" msgstr "" #: ../Immunix/AppArmor.pm:1202 msgid "" "Select which of the changed profiles you would like to upload\n" "to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1204 msgid "Changed profiles" msgstr "" #: ../Immunix/AppArmor.pm:1210 msgid "Submit changed profiles to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1212 msgid "" "The following profiles from the repository were changed.\n" "Would you like to upload your changes?" msgstr "" #: ../Immunix/AppArmor.pm:1279 ../Immunix/AppArmor.pm:1359 #, perl-format msgid "" "WARNING: An error occured while uploading the profile %s\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:1284 msgid "Uploaded changes to repository." msgstr "" #: ../Immunix/AppArmor.pm:1306 ../Immunix/AppArmor.pm:3185 #: ../Immunix/AppArmor.pm:3215 msgid "Repository" msgstr "" #: ../Immunix/AppArmor.pm:1333 msgid "Changelog Entry: " msgstr "" #: ../Immunix/AppArmor.pm:1354 #, perl-format msgid "Uploaded %s to repository." msgstr "" #: ../Immunix/AppArmor.pm:1365 msgid "" "Repository Error\n" "Registration or Signin was unsuccessful. User login\n" "information is required to upload profiles to the\n" "repository. These changes have not been sent.\n" msgstr "" #: ../Immunix/AppArmor.pm:1422 ../Immunix/AppArmor.pm:1462 msgid "(Y)es" msgstr "" #: ../Immunix/AppArmor.pm:1423 ../Immunix/AppArmor.pm:1463 msgid "(N)o" msgstr "" #: ../Immunix/AppArmor.pm:1426 ../Immunix/AppArmor.pm:1467 msgid "Invalid hotkey for" msgstr "" #: ../Immunix/AppArmor.pm:1464 msgid "(C)ancel" msgstr "" #: ../Immunix/AppArmor.pm:1789 msgid "" "Are you sure you want to abandon this set of profile changes and exit?" msgstr "" #: ../Immunix/AppArmor.pm:1791 msgid "Abandoning all changes." msgstr "" #: ../Immunix/AppArmor.pm:1902 msgid "Default Hat" msgstr "" #: ../Immunix/AppArmor.pm:1904 msgid "Requested Hat" msgstr "" #: ../Immunix/AppArmor.pm:2190 msgid "Program" msgstr "" #: ../Immunix/AppArmor.pm:2195 msgid "Execute" msgstr "" #: ../Immunix/AppArmor.pm:2196 ../Immunix/AppArmor.pm:3455 #: ../Immunix/AppArmor.pm:3488 ../Immunix/AppArmor.pm:3741 msgid "Severity" msgstr "" #: ../Immunix/AppArmor.pm:2241 msgid "Enter profile name to transition to: " msgstr "" #: ../Immunix/AppArmor.pm:2249 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but some applications depend on the presence\n" "of LD_PRELOAD or LD_LIBRARY_PATH." msgstr "" #: ../Immunix/AppArmor.pm:2251 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but this application appears to use LD_PRELOAD\n" "or LD_LIBRARY_PATH and clearing these could\n" "cause functionality problems." msgstr "" #: ../Immunix/AppArmor.pm:2260 #, perl-format msgid "" "Launching processes in an unconfined state is a very\n" "dangerous operation and can cause serious security holes.\n" "\n" "Are you absolutely certain you wish to remove all\n" "AppArmor protection when executing %s?" msgstr "" #: ../Immunix/AppArmor.pm:2262 msgid "" "Should AppArmor sanitize the environment when\n" "running this program unconfined?\n" "\n" "Not sanitizing the environment when unconfining\n" "a program opens up significant security holes\n" "and should be avoided if at all possible." msgstr "" #: ../Immunix/AppArmor.pm:2352 #, perl-format msgid "A profile for %s does not exist. Create one?" msgstr "" #: ../Immunix/AppArmor.pm:2379 #, perl-format msgid "A local profile for %s does not exist. Create one?" msgstr "" #: ../Immunix/AppArmor.pm:2584 ../Immunix/AppArmor.pm:6733 #: ../Immunix/AppArmor.pm:6738 #, perl-format msgid "Log contains unknown mode %s." msgstr "" #: ../Immunix/AppArmor.pm:3068 msgid "" "An updated version of this profile has been found in the profile repository. " " Would you like to use it?" msgstr "" #: ../Immunix/AppArmor.pm:3098 #, perl-format msgid "Updated profile %s to revision %s." msgstr "" #: ../Immunix/AppArmor.pm:3105 msgid "Error parsing repository profile." msgstr "" #: ../Immunix/AppArmor.pm:3141 msgid "Create New User?" msgstr "" #: ../Immunix/AppArmor.pm:3142 msgid "Username: " msgstr "" #: ../Immunix/AppArmor.pm:3143 msgid "Password: " msgstr "" #: ../Immunix/AppArmor.pm:3144 msgid "Email Addr: " msgstr "" #: ../Immunix/AppArmor.pm:3146 msgid "Save Configuration? " msgstr "" #: ../Immunix/AppArmor.pm:3155 msgid "The Profile Repository server returned the following error:" msgstr "" #: ../Immunix/AppArmor.pm:3157 msgid "" "Please re-enter registration information or contact the administrator." msgstr "" #: ../Immunix/AppArmor.pm:3158 msgid "Login Error\n" msgstr "" #: ../Immunix/AppArmor.pm:3165 msgid "" "Login failure\n" " Please check username and password and try again." msgstr "" #: ../Immunix/AppArmor.pm:3187 msgid "" "Would you like to enable access to the\n" "profile repository?" msgstr "" #: ../Immunix/AppArmor.pm:3218 msgid "" "Would you like to upload newly created and changed profiles to\n" " the profile repository?" msgstr "" #: ../Immunix/AppArmor.pm:3337 #, perl-format msgid "" "WARNING: Profile update check failed\n" "Error Detail:\n" "%s" msgstr "" #: ../Immunix/AppArmor.pm:3351 msgid "Change mode modifiers" msgstr "" #: ../Immunix/AppArmor.pm:3395 msgid "Complain-mode changes:" msgstr "" #: ../Immunix/AppArmor.pm:3397 msgid "Enforce-mode changes:" msgstr "" #: ../Immunix/AppArmor.pm:3403 #, perl-format msgid "Invalid mode found: %s" msgstr "" #: ../Immunix/AppArmor.pm:3454 ../Immunix/AppArmor.pm:3487 msgid "Capability" msgstr "" #: ../Immunix/AppArmor.pm:3507 ../Immunix/AppArmor.pm:3781 #: ../Immunix/AppArmor.pm:4028 #, perl-format msgid "Adding #include <%s> to profile." msgstr "" #: ../Immunix/AppArmor.pm:3510 ../Immunix/AppArmor.pm:3782 #: ../Immunix/AppArmor.pm:3822 ../Immunix/AppArmor.pm:4032 #, perl-format msgid "Deleted %s previous matching profile entries." msgstr "" #: ../Immunix/AppArmor.pm:3521 #, perl-format msgid "Adding capability %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3526 #, perl-format msgid "Denying capability %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3687 msgid "Path" msgstr "" #: ../Immunix/AppArmor.pm:3698 ../Immunix/AppArmor.pm:3730 msgid "(owner permissions off)" msgstr "" #: ../Immunix/AppArmor.pm:3704 msgid "(force new perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3707 msgid "(force all rule perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3719 msgid "Old Mode" msgstr "" #: ../Immunix/AppArmor.pm:3720 msgid "New Mode" msgstr "" #: ../Immunix/AppArmor.pm:3736 msgid "(force perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3739 msgid "Mode" msgstr "" #: ../Immunix/AppArmor.pm:3821 #, perl-format msgid "Adding %s %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3837 msgid "Enter new path: " msgstr "" #: ../Immunix/AppArmor.pm:3840 msgid "The specified path does not match this log entry:" msgstr "" #: ../Immunix/AppArmor.pm:3841 msgid "Log Entry" msgstr "" #: ../Immunix/AppArmor.pm:3842 msgid "Entered Path" msgstr "" #: ../Immunix/AppArmor.pm:3843 msgid "Do you really want to use this path?" msgstr "" #: ../Immunix/AppArmor.pm:3955 ../Immunix/AppArmor.pm:4007 msgid "Network Family" msgstr "" #: ../Immunix/AppArmor.pm:3958 ../Immunix/AppArmor.pm:4010 msgid "Socket Type" msgstr "" #: ../Immunix/AppArmor.pm:4058 #, perl-format msgid "Adding network access %s %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:4077 #, perl-format msgid "Denying network access %s %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:4285 #, perl-format msgid "Reading log entries from %s." msgstr "" #: ../Immunix/AppArmor.pm:4286 #, perl-format msgid "Updating AppArmor profiles in %s." msgstr "" #: ../Immunix/AppArmor.pm:4290 msgid "unknown\n" msgstr "" #: ../Immunix/AppArmor.pm:4324 msgid "" "The profile analyzer has completed processing the log files.\n" "\n" "All updated profiles will be reloaded" msgstr "" #: ../Immunix/AppArmor.pm:4330 msgid "No unhandled AppArmor events were found in the system log." msgstr "" #: ../Immunix/AppArmor.pm:4391 msgid "" "Select which profile changes you would like to save to the\n" "local profile set" msgstr "" #: ../Immunix/AppArmor.pm:4392 msgid "Local profile changes" msgstr "" #: ../Immunix/AppArmor.pm:4419 msgid "" "The following local profiles were changed. Would you like to save them?" msgstr "" #: ../Immunix/AppArmor.pm:4516 msgid "Profile Changes" msgstr "" #: ../Immunix/AppArmor.pm:5139 ../Immunix/AppArmor.pm:5155 #: ../Immunix/AppArmor.pm:5166 ../Immunix/AppArmor.pm:5174 #: ../Immunix/AppArmor.pm:5195 ../Immunix/AppArmor.pm:5215 #: ../Immunix/AppArmor.pm:5224 ../Immunix/AppArmor.pm:5256 #: ../Immunix/AppArmor.pm:5334 ../Immunix/AppArmor.pm:5382 #, perl-format msgid "%s contains syntax errors." msgstr "" #: ../Immunix/AppArmor.pm:5275 #, perl-format msgid "Profile %s contains invalid regexp %s." msgstr "" #: ../Immunix/AppArmor.pm:5280 #, perl-format msgid "Profile %s contains invalid mode %s." msgstr "" #: ../Immunix/AppArmor.pm:5430 #, perl-format msgid "%s contains syntax errors. Line [%s]" msgstr "" #: ../Immunix/AppArmor.pm:6022 #, perl-format msgid "Writing updated profile for %s." msgstr "" #: ../Immunix/AppArmor.pm:6528 msgid "Unknown command" msgstr "" #: ../Immunix/AppArmor.pm:6536 msgid "Invalid hotkey in" msgstr "" #: ../Immunix/AppArmor.pm:6546 msgid "Duplicate hotkey for" msgstr "" #: ../Immunix/AppArmor.pm:6567 msgid "Invalid hotkey in default item" msgstr "" #: ../Immunix/AppArmor.pm:6576 msgid "Invalid default" msgstr "" apparmor-2.8.95~2430/utils/po/Makefile0000644000175000017500000000211212277213004017245 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (c) 2004, 2005 NOVELL (All rights reserved) # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # ---------------------------------------------------------------------- all: # As translations get added, they will automatically be included, unless # the lang is explicitly added to DISABLED_LANGS; e.g. DISABLED_LANGS=en es DISABLED_LANGS= include ../common/Make-po.rules ../common/Make-po.rules: make -C .. common/Make.rules XGETTEXT_ARGS+=--language=perl --language=python apparmor-2.8.95~2430/utils/po/hi.po0000644000175000017500000007410512267276306016575 0ustar sarnoldsarnold# Hindi translation for apparmor # Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 # This file is distributed under the same license as the apparmor package. # FIRST AUTHOR , 2013. # msgid "" msgstr "" "Project-Id-Version: apparmor-utils\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-13 16:44-0800\n" "PO-Revision-Date: 2013-12-07 11:01+0000\n" "Last-Translator: Kshitij Gupta \n" "Language-Team: Hindi \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2013-12-08 05:18+0000\n" "X-Generator: Launchpad (build 16869)\n" "Language: hi\n" #: ../aa-genprof:72 ../aa-unconfined:54 msgid "" "AppArmor does not appear to be started. Please enable AppArmor and try again." msgstr "" #: ../aa-genprof:86 msgid "Please enter the program to profile: " msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² बनाने के लिठकृपया पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® पà¥à¤°à¤µà¤¿à¤·à¥â€à¤Ÿ करें: " #: ../aa-genprof:105 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' in the other window in order to find the " "fully-qualified path." msgstr "" "सिसà¥â€à¤Ÿà¤® पथ सूची में %s को नहीं ढूंढ सका। यदि अनà¥à¤ªà¥à¤°à¤¯à¥‹à¤— का नाम सही है, तो " "कृपया 'which %s' को पूरà¥à¤£-योगà¥â€à¤¯à¤¤à¤¾ पà¥à¤°à¤¾à¤ªà¥â€à¤¤ पथ को ढूंढने के लिहाज से दूसरे " "विंडों में चलाà¤à¤‚।" #: ../aa-genprof:107 ../aa-autodep:110 ../aa-audit:120 ../aa-complain:119 #: ../aa-enforce:130 ../aa-disable:140 #, perl-format msgid "%s does not exist, please double-check the path." msgstr "%s का असà¥à¤¤à¤¿à¤¤à¥â€à¤µ नहीं है, कृपया पथ की दोबारा जांच करें।" #: ../aa-genprof:141 msgid "" "\n" "Before you begin, you may wish to check if a\n" "profile already exists for the application you\n" "wish to confine. See the following wiki page for\n" "more information:\n" "http://wiki.apparmor.net/index.php/Profiles" msgstr "" "\n" "आरंभ करने से पहले कृपà¥à¤¯à¤¾ जांच लें की जिस अपलीकेशन की\n" "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² आप बनाना चाहते हैं उसका पहले से ही असà¥à¤¤à¤¿à¤¤à¥à¤µ ना हो।\n" "अधिक जानकारी के लिये कृपà¥à¤¯à¤¾ निमà¥à¤¨à¤²à¤¿à¤–ित विकी पेज देखें :\n" "http://wiki.apparmor.net/index.php/Profiles" #: ../aa-genprof:143 msgid "" "Please start the application to be profiled in \n" "another window and exercise its functionality now.\n" "\n" "Once completed, select the \"Scan\" button below in \n" "order to scan the system logs for AppArmor events. \n" "\n" "For each AppArmor event, you will be given the \n" "opportunity to choose whether the access should be \n" "allowed or denied." msgstr "" "जिस अपलीकेशन की पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² बनाई जा रही है कृपà¥à¤¯à¤¾ उसको किसी अनà¥à¤¯ विंडो में\n" "आरमà¥à¤­ करके उसकी कारà¥à¤¯à¤•à¥à¤·à¤®à¤¤à¤¾ का अभà¥à¤¯à¤¾à¤¸ अभी करें।\n" "\n" "à¤à¤• बार इसके पूरा हो जाने पर, AppArmor घटनाओं को सिसà¥â€à¤Ÿà¤® लॉगà¥â€à¤¸ में\n" "जांचने के लिये नीचे दी गई \"Scan\" बटन को चà¥à¤¨à¥‡à¤‚।\n" "\n" "पà¥à¤°à¤¤à¥â€à¤¯à¥‡à¤• AppArmor घटना के लिà¤, आपको यह चà¥à¤¨à¤¨à¥‡ का\n" "अवसर दिया जाà¤à¤—ा कि पहà¥à¤‚च की सà¥â€à¤µà¥€à¤•ृति या असà¥à¤µà¤¿à¤•ृति दी\n" "जानी चाहिठया नहीं।" #: ../aa-genprof:163 msgid "Profiling" msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¤¿à¤‚ग" #: ../aa-genprof:197 msgid "Reloaded AppArmor profiles in enforce mode." msgstr "AppArmor पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‡à¤‚ बाधà¥à¤¯à¤•ारी मोड में पà¥à¤¨à¤ƒ लोड र दी गई हैं।" #: ../aa-genprof:198 msgid "" "\n" "Please consider contributing your new profile! See\n" "the following wiki page for more information:\n" "http://wiki.apparmor.net/index.php/Profiles\n" msgstr "" "\n" "अपनी नई पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² का योगदान पर विचार करें!\n" "अधिक जानकारी के लिये कृपà¥à¤¯à¤¾ निमà¥à¤¨à¤²à¤¿à¤–ित विकी पेज देखें :\n" "http://wiki.apparmor.net/index.php/Profiles\n" #: ../aa-genprof:199 #, perl-format msgid "Finished generating profile for %s." msgstr "%s के लिठपà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² उतà¥â€à¤ªà¤¨à¥â€à¤¨ करना पूरà¥à¤£ हà¥à¤†à¥¤" #: ../aa-genprof:203 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ program to " "profile ]" msgstr "" "उपयोग : %s [ -d /पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‹à¤‚/का/पथ ] [ -f /लॉग/का/पथ ] [ पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² हेतॠ" "पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® ]" #: ../aa-logprof:69 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ -m \"mark in " "log to start processing after\"" msgstr "" "उपयोग : %s [ -d /पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‹à¤‚/का/पथ ] [ -f /लॉग/का/पथ ] [ -m \"लॉग में चिनà¥à¤¹ " "जिसके पशà¥à¤šà¤¾à¤¤ पà¥à¤°à¥‹à¤¸à¥‡à¤¸ करना है\"" #: ../aa-autodep:61 #, perl-format msgid "Can't find AppArmor profiles in %s." msgstr "%s में AppArmor पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‡à¤‚ नहीं ढूंढी जा सकी हैं।" #: ../aa-autodep:69 msgid "Please enter the program to create a profile for: " msgstr "जिस पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® के लिये पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² सृजित की जानी है उसे पà¥à¤°à¤µà¤¿à¤·à¥â€à¤Ÿ करें: " #: ../aa-autodep:93 ../Immunix/AppArmor.pm:6339 #, perl-format msgid "" "%s is currently marked as a program that should not have it's own profile. " "Usually, programs are marked this way if creating a profile for them is " "likely to break the rest of the system. If you know what you're doing and " "are certain you want to create a profile for this program, edit the " "corresponding entry in the [qualifiers] section in " "/etc/apparmor/logprof.conf." msgstr "" "%s को वरà¥à¤¤à¤®à¤¾à¤¨ में à¤à¤• à¤à¤¸à¥‡ पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® के रूप में चिनà¥à¤¹à¤¿à¤¤ किया गया है जिसके पास " "सà¥â€à¤µà¤¯à¤‚ अपनी पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² नहीं होनी चाहिये। आम तौर पर पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤®à¥‹à¤‚ को इस पà¥à¤°à¤•ार से " "चिनà¥à¤¹à¤¿à¤¤ किया जाता है यदि उनके लिठपà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² सृजित करने पर शेष सिसà¥â€à¤Ÿà¤® में " "वà¥â€à¤¯à¤µà¤§à¤¾à¤¨ पड़ने की संभावना हो। यदि आप जानते हैं कि आप कà¥â€à¤¯à¤¾ कर रहे हैं और " "निशà¥à¤šà¤¿à¤¤ हों कि आप इस पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® के लिठà¤à¤• पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² सृजित करना चाहते हैं, तो " "/etc/apparmor/logprof.conf में [qualifiers] खंड में संगत पà¥à¤°à¤µà¤¿à¤·à¥à¤Ÿà¤¿ को " "संपादित करें।" #: ../aa-autodep:100 #, perl-format msgid "Profile for %s already exists - skipping." msgstr "%s के लिठपà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² पहले से मौजूद है - इसे छोड़ा जा रहा है।" #: ../aa-autodep:107 ../aa-audit:117 ../aa-complain:116 ../aa-enforce:127 #: ../aa-disable:137 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' as a user with the correct PATH environment " "set up in order to find the fully-qualified path." msgstr "" "सिसà¥â€à¤Ÿà¤® पथ सूची में %s को नहीं ढूंढा जा सका। यदि अपलीकेशन का नाम सही है, तो " "'which %s' को पूरà¥à¤£-योगà¥â€à¤¯à¤¤à¤¾ पà¥à¤°à¤¾à¤ªà¥â€à¤¤ पथ को ढूंढने के लिहाज से à¤à¤• सही PATH " "वातावरण के साथ à¤à¤• उपयोगकरà¥à¤¤à¤¾ के रूप में चलाà¤à¤‚।" #: ../aa-audit:104 #, perl-format msgid "Setting %s to audit mode." msgstr "ऑडिट मोड के लिठ%s सेटिंग की जा रही है।" #: ../aa-audit:129 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to audit mode ]" msgstr "" "उपयोग : %s [ -d /पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‹à¤‚/का/पथ ] [ पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® जिसको ऑडिट मोड में लगाना है ]" #: ../aa-complain:61 msgid "Please enter the program to switch to complain mode: " msgstr "शिकायत मोड में बदलने के लिठकृपया पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® को पà¥à¤°à¤µà¤¿â€à¤·à¥â€à¤Ÿ करें : " #: ../aa-complain:103 ../Immunix/AppArmor.pm:621 ../Immunix/AppArmor.pm:941 #, perl-format msgid "Setting %s to complain mode." msgstr "%s की शिकायती मोड के लिठसेटिंग की जा रही है।" #: ../aa-complain:128 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to complain mode ]" msgstr "" "उपयोग : %s [ -d /पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‹à¤‚/का/पथ ] [ शिकायती मोड में बदलने के लिठपà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® " "]" #: ../aa-enforce:62 msgid "Please enter the program to switch to enforce mode: " msgstr "" "बाधà¥â€à¤¯à¤•ारी मोड में बदलने के लिठकृपया पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® को पà¥à¤°à¤µà¤¿â€à¤·à¥â€à¤Ÿ करें : " #: ../aa-enforce:103 ../Immunix/AppArmor.pm:634 #, perl-format msgid "Setting %s to enforce mode." msgstr "%s की बाधà¥â€à¤¯à¤•ारी मोड के लिठसेटिंग की जा रही है।" #: ../aa-enforce:139 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to enforce mode ]" msgstr "" "उपयोग : %s [ -d /पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‹à¤‚/का/पथ ] [ बाधà¥â€à¤¯à¤•ारी मोड में बदलने के लिठ" "पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® ]" #: ../aa-unconfined:48 #, perl-format msgid "Usage: %s [ --paranoid ]\n" msgstr "उपयोग : %s [ --paranoid ]\n" #: ../aa-unconfined:59 msgid "Can't read /proc\n" msgstr "/proc को नहीं पढ़ सकता\n" #: ../aa-unconfined:97 ../aa-unconfined:99 msgid "not confined\n" msgstr "सीमाबदà¥à¤§ नहीं है\n" #: ../aa-unconfined:108 ../aa-unconfined:110 msgid "confined by" msgstr "के दà¥à¤µà¤¾à¤°à¤¾ सीमाबदà¥à¤§" #: ../aa-disable:69 msgid "Please enter the program whose profile should be disabled: " msgstr "" "जिस पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® की पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² असमरà¥à¤¥ की जानी है कृपया उसे पà¥à¤°à¤µà¤¿à¤·à¥â€à¤Ÿ करें: " #: ../aa-disable:113 #, perl-format msgid "Could not find basename for %s." msgstr "%s के लिये बेसनाम नहीं ढूंढा जा सका है।" #: ../aa-disable:117 #, perl-format msgid "Disabling %s." msgstr "%s निषà¥à¤•à¥à¤°à¤¿à¤¯ किया जा राहा है।" #: ../aa-disable:123 #, perl-format msgid "Could not create %s symlink." msgstr "%s सिमलिंक नहीं बनाया जा सका है।" #: ../aa-disable:149 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to have profile disabled ]" msgstr "" "उपयोग : %s [ -d /पà¥à¤°à¥‹à¤«à¤¼à¤¾à¤‡à¤²à¥‹à¤‚/का/पथ ] [ पà¥à¤°à¥‹à¤«à¤¼à¤¾à¤‡à¤² निषà¥à¤•à¥à¤°à¤¿à¤¯ करने के लिठ" "पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® ]" #: ../Immunix/AppArmor.pm:619 ../Immunix/AppArmor.pm:632 #, perl-format msgid "Can't find %s." msgstr "%s नहीं ढूंढा जा सका है।" #: ../Immunix/AppArmor.pm:819 ../Immunix/AppArmor.pm:3326 msgid "Connecting to repository....." msgstr "" #: ../Immunix/AppArmor.pm:828 #, perl-format msgid "" "WARNING: Error fetching profiles from the repository:\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:837 msgid "Inactive local profile for " msgstr "" #: ../Immunix/AppArmor.pm:874 ../Immunix/AppArmor.pm:1900 #: ../Immunix/AppArmor.pm:2188 ../Immunix/AppArmor.pm:3453 #: ../Immunix/AppArmor.pm:3486 ../Immunix/AppArmor.pm:3686 #: ../Immunix/AppArmor.pm:3952 ../Immunix/AppArmor.pm:4004 msgid "Profile" msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²" #: ../Immunix/AppArmor.pm:908 msgid "Profile submitted by" msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² पà¥à¤°à¤¸à¥à¤¤à¥à¤¤à¤¿à¤•रà¥à¤¤à¤¾" #: ../Immunix/AppArmor.pm:949 #, perl-format msgid "Error activating profiles: %s\n" msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‹à¤‚ को सकà¥à¤°à¤¿à¤¯ करने में तà¥à¤°à¥à¤Ÿà¤¿: %s\n" #: ../Immunix/AppArmor.pm:1098 ../Immunix/AppArmor.pm:1151 #, perl-format msgid "" "WARNING: Error syncronizing profiles with the repository:\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:1178 msgid "New profiles" msgstr "नयी पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‡à¤‚" #: ../Immunix/AppArmor.pm:1180 msgid "" "Please choose the newly created profiles that you would like\n" "to store in the repository" msgstr "" #: ../Immunix/AppArmor.pm:1187 msgid "Submit newly created profiles to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1189 msgid "Would you like to upload the newly created profiles?" msgstr "" #: ../Immunix/AppArmor.pm:1202 msgid "" "Select which of the changed profiles you would like to upload\n" "to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1204 msgid "Changed profiles" msgstr "बदली गयी पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‡à¤‚" #: ../Immunix/AppArmor.pm:1210 msgid "Submit changed profiles to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1212 msgid "" "The following profiles from the repository were changed.\n" "Would you like to upload your changes?" msgstr "" #: ../Immunix/AppArmor.pm:1279 ../Immunix/AppArmor.pm:1359 #, perl-format msgid "" "WARNING: An error occured while uploading the profile %s\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:1284 msgid "Uploaded changes to repository." msgstr "" #: ../Immunix/AppArmor.pm:1306 ../Immunix/AppArmor.pm:3185 #: ../Immunix/AppArmor.pm:3215 msgid "Repository" msgstr "" #: ../Immunix/AppArmor.pm:1333 msgid "Changelog Entry: " msgstr "परिवरà¥à¤¤à¤¨à¤²à¥‰à¤— पà¥à¤°à¤µà¤¿à¤·à¥à¤Ÿà¤¿: " #: ../Immunix/AppArmor.pm:1354 #, perl-format msgid "Uploaded %s to repository." msgstr "" #: ../Immunix/AppArmor.pm:1365 msgid "" "Repository Error\n" "Registration or Signin was unsuccessful. User login\n" "information is required to upload profiles to the\n" "repository. These changes have not been sent.\n" msgstr "" #: ../Immunix/AppArmor.pm:1422 ../Immunix/AppArmor.pm:1462 msgid "(Y)es" msgstr "" #: ../Immunix/AppArmor.pm:1423 ../Immunix/AppArmor.pm:1463 msgid "(N)o" msgstr "(न)हीं" #: ../Immunix/AppArmor.pm:1426 ../Immunix/AppArmor.pm:1467 msgid "Invalid hotkey for" msgstr "" #: ../Immunix/AppArmor.pm:1464 msgid "(C)ancel" msgstr "(र)दà¥à¤¦ करें" #: ../Immunix/AppArmor.pm:1789 msgid "" "Are you sure you want to abandon this set of profile changes and exit?" msgstr "" #: ../Immunix/AppArmor.pm:1791 msgid "Abandoning all changes." msgstr "सभी बदलावों को तà¥à¤¯à¤¾à¤—ा जा रहा है।" #: ../Immunix/AppArmor.pm:1902 msgid "Default Hat" msgstr "डिफॉलà¥â€à¤Ÿ हैट" #: ../Immunix/AppArmor.pm:1904 msgid "Requested Hat" msgstr "अनà¥à¤°à¥‹à¤§à¤¿à¤¤ हैट" #: ../Immunix/AppArmor.pm:2190 msgid "Program" msgstr "पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤®" #: ../Immunix/AppArmor.pm:2195 msgid "Execute" msgstr "चलाà¤à¤" #: ../Immunix/AppArmor.pm:2196 ../Immunix/AppArmor.pm:3455 #: ../Immunix/AppArmor.pm:3488 ../Immunix/AppArmor.pm:3741 msgid "Severity" msgstr "गंभीरता" #: ../Immunix/AppArmor.pm:2241 msgid "Enter profile name to transition to: " msgstr "जिस पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² में संकà¥à¤°à¤®à¤£ करना है उसका नाम पà¥à¤°à¤µà¤¿à¤·à¥à¤Ÿ करें: " #: ../Immunix/AppArmor.pm:2249 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but some applications depend on the presence\n" "of LD_PRELOAD or LD_LIBRARY_PATH." msgstr "" "कà¥à¤¯à¤¾ AppArmor को पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² बदलते समय वातावरण को साफ\n" "कर देना चाहिये?\n" "\n" "वातावरण साफ करना अधिक सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ है, लेकिन कà¥à¤› अपलीकेशनà¥à¤¸\n" "LD_PRELOAD या LD_LIBRARY_PATH की मौजूदगी पर निरà¥à¤­à¤°\n" "करती हैं।" #: ../Immunix/AppArmor.pm:2251 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but this application appears to use LD_PRELOAD\n" "or LD_LIBRARY_PATH and clearing these could\n" "cause functionality problems." msgstr "" "कà¥à¤¯à¤¾ AppArmor को पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² बदलते समय वातावरण को साफ\n" "कर देना चाहिये?\n" "\n" "वातावरण साफ करना अधिक सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ है, लेकिन à¤à¤¸à¤¾ लगता है कि यह अपलीकेशन\n" "LD_PRELOAD या LD_LIBRARY_PATH करती पà¥à¤°à¤¤à¥€à¤¤ हो रही है और इनà¥à¤¹à¥‡à¤‚ साफ\n" "करने से कारà¥à¤¯à¤•à¥à¤·à¤®à¤¤à¤¾ में परेशानियां हो सकती हैं।" #: ../Immunix/AppArmor.pm:2260 #, perl-format msgid "" "Launching processes in an unconfined state is a very\n" "dangerous operation and can cause serious security holes.\n" "\n" "Are you absolutely certain you wish to remove all\n" "AppArmor protection when executing %s?" msgstr "" "असीमाबदà¥à¤§ सà¥à¤¥à¤¿à¤¤à¤¿ में पà¥à¤°à¤•à¥à¤°à¤¿à¤¯à¤¾à¤“ं को लांच करना à¤à¤• बहà¥à¤¤\n" "खतरनाक पà¥à¤°à¤šà¤¾à¤²à¤¨ है और गंभीर सà¥à¤°à¤•à¥à¤·à¤¾ छिदà¥à¤°à¥‹à¤‚ की वजह बन सकता है।\n" "\n" "कà¥â€à¤¯à¤¾ आप पूरी तरह निशà¥à¤šà¤¿à¤¤ हैं कि आप %s को निषà¥â€à¤ªà¤¾à¤¦à¤¿à¤¤ करते समय\n" "AppArmor की सà¥à¤°à¤•à¥à¤·à¤¾ को हटाने की इचà¥â€à¤›à¤¾ रखते हैं?" #: ../Immunix/AppArmor.pm:2262 msgid "" "Should AppArmor sanitize the environment when\n" "running this program unconfined?\n" "\n" "Not sanitizing the environment when unconfining\n" "a program opens up significant security holes\n" "and should be avoided if at all possible." msgstr "" "कà¥à¤¯à¤¾ AppArmor को इस पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® को असीमित रूप में चलाते समय \n" "वातावरण को साफ कर देना चाहिये?\n" "\n" "सीमा बढ़ाते समय वातावरण को साफ न करने से पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® महतà¥à¤µà¤ªà¥‚रà¥à¤£ सà¥à¤°à¤•à¥à¤·à¤¾ छिदà¥à¤°\n" "खोलता है और यदि संभव हो इससे बचना चाहिà¤à¥¤" #: ../Immunix/AppArmor.pm:2352 #, perl-format msgid "A profile for %s does not exist. Create one?" msgstr "%s के लिये पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² मौजूद नहीं है। निरà¥à¤®à¤¾à¤£ कर दे?" #: ../Immunix/AppArmor.pm:2379 #, perl-format msgid "A local profile for %s does not exist. Create one?" msgstr "%s के लिये लोकल पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² मौजूद नहीं है। निरà¥à¤®à¤¾à¤£ कर दे?" #: ../Immunix/AppArmor.pm:2584 ../Immunix/AppArmor.pm:6733 #: ../Immunix/AppArmor.pm:6738 #, perl-format msgid "Log contains unknown mode %s." msgstr "लॉग में अजà¥à¤žà¤¾à¤¤ मोड %s मौजूद है।" #: ../Immunix/AppArmor.pm:3068 msgid "" "An updated version of this profile has been found in the profile repository. " " Would you like to use it?" msgstr "" #: ../Immunix/AppArmor.pm:3098 #, perl-format msgid "Updated profile %s to revision %s." msgstr "" #: ../Immunix/AppArmor.pm:3105 msgid "Error parsing repository profile." msgstr "" #: ../Immunix/AppArmor.pm:3141 msgid "Create New User?" msgstr "" #: ../Immunix/AppArmor.pm:3142 msgid "Username: " msgstr "" #: ../Immunix/AppArmor.pm:3143 msgid "Password: " msgstr "" #: ../Immunix/AppArmor.pm:3144 msgid "Email Addr: " msgstr "" #: ../Immunix/AppArmor.pm:3146 msgid "Save Configuration? " msgstr "" #: ../Immunix/AppArmor.pm:3155 msgid "The Profile Repository server returned the following error:" msgstr "" #: ../Immunix/AppArmor.pm:3157 msgid "" "Please re-enter registration information or contact the administrator." msgstr "" #: ../Immunix/AppArmor.pm:3158 msgid "Login Error\n" msgstr "" #: ../Immunix/AppArmor.pm:3165 msgid "" "Login failure\n" " Please check username and password and try again." msgstr "" #: ../Immunix/AppArmor.pm:3187 msgid "" "Would you like to enable access to the\n" "profile repository?" msgstr "" #: ../Immunix/AppArmor.pm:3218 msgid "" "Would you like to upload newly created and changed profiles to\n" " the profile repository?" msgstr "" #: ../Immunix/AppArmor.pm:3337 #, perl-format msgid "" "WARNING: Profile update check failed\n" "Error Detail:\n" "%s" msgstr "" #: ../Immunix/AppArmor.pm:3351 msgid "Change mode modifiers" msgstr "मोड संशोधक को बदलें" #: ../Immunix/AppArmor.pm:3395 msgid "Complain-mode changes:" msgstr "शिकायती-मोड बदà¥à¤²à¤¾à¤µ:" #: ../Immunix/AppArmor.pm:3397 msgid "Enforce-mode changes:" msgstr "बाधà¥â€à¤¯à¤•ारी-मोड बदà¥à¤²à¤¾à¤µ:" #: ../Immunix/AppArmor.pm:3403 #, perl-format msgid "Invalid mode found: %s" msgstr "अमानà¥â€à¤¯ मोड पà¥à¤°à¤¾à¤ªà¥â€à¤¤ हà¥à¤†: %s" #: ../Immunix/AppArmor.pm:3454 ../Immunix/AppArmor.pm:3487 msgid "Capability" msgstr "कà¥à¤·à¤®à¤¤à¤¾" #: ../Immunix/AppArmor.pm:3507 ../Immunix/AppArmor.pm:3781 #: ../Immunix/AppArmor.pm:4028 #, perl-format msgid "Adding #include <%s> to profile." msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² में #include <%s> जोड़ा जा रहा है।" #: ../Immunix/AppArmor.pm:3510 ../Immunix/AppArmor.pm:3782 #: ../Immunix/AppArmor.pm:3822 ../Immunix/AppArmor.pm:4032 #, perl-format msgid "Deleted %s previous matching profile entries." msgstr "पà¥à¤°à¤¾à¤¨à¥€ मेल खाने वाली %s पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² पà¥à¤°à¤µà¤¿à¤·à¥à¤Ÿà¤¿à¤¯à¥‹à¤‚ को हटाया गया।" #: ../Immunix/AppArmor.pm:3521 #, perl-format msgid "Adding capability %s to profile." msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² के लिठ%s कà¥à¤·à¤®à¤¤à¤¾ जोड़ी जा रही है।" #: ../Immunix/AppArmor.pm:3526 #, perl-format msgid "Denying capability %s to profile." msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² के लिठ%s कà¥à¤·à¤®à¤¤à¤¾ से इंकार किया जा रहा है।" #: ../Immunix/AppArmor.pm:3687 msgid "Path" msgstr "पथ" #: ../Immunix/AppArmor.pm:3698 ../Immunix/AppArmor.pm:3730 msgid "(owner permissions off)" msgstr "" #: ../Immunix/AppArmor.pm:3704 msgid "(force new perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3707 msgid "(force all rule perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3719 msgid "Old Mode" msgstr "पà¥à¤°à¤¾à¤¨à¤¾ मोड" #: ../Immunix/AppArmor.pm:3720 msgid "New Mode" msgstr "नया मोड" #: ../Immunix/AppArmor.pm:3736 msgid "(force perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3739 msgid "Mode" msgstr "मोड" #: ../Immunix/AppArmor.pm:3821 #, perl-format msgid "Adding %s %s to profile." msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² में %s %s जोड़ा जा रहा है।" #: ../Immunix/AppArmor.pm:3837 msgid "Enter new path: " msgstr "नया पथ पà¥à¤°à¤µà¤¿à¤·à¥â€à¤Ÿ करें: " #: ../Immunix/AppArmor.pm:3840 msgid "The specified path does not match this log entry:" msgstr "निरà¥à¤¦à¤¿à¤·à¥â€à¤Ÿ किया गया पथ इस लॉग पà¥à¤°à¤µà¤¿à¤·à¥à¤Ÿà¤¿ से मेल नहीं खाता:" #: ../Immunix/AppArmor.pm:3841 msgid "Log Entry" msgstr "लॉग पà¥à¤°à¤µà¤¿à¤·à¥à¤Ÿà¤¿" #: ../Immunix/AppArmor.pm:3842 msgid "Entered Path" msgstr "पà¥à¤°à¤µà¤¿à¤·à¥à¤Ÿ किया गया पथ" #: ../Immunix/AppArmor.pm:3843 msgid "Do you really want to use this path?" msgstr "कà¥â€à¤¯à¤¾ आप सचमà¥à¤š इस पथ का उपयोग करना चाहते हैं?" #: ../Immunix/AppArmor.pm:3955 ../Immunix/AppArmor.pm:4007 msgid "Network Family" msgstr "नेटवरà¥à¤• फॅमिली" #: ../Immunix/AppArmor.pm:3958 ../Immunix/AppArmor.pm:4010 msgid "Socket Type" msgstr "सॉकेट पà¥à¤°à¤•ार" #: ../Immunix/AppArmor.pm:4058 #, perl-format msgid "Adding network access %s %s to profile." msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² में %s %s नेटवरà¥à¤• के उपयोग को जोड़ा जा रहा है।" #: ../Immunix/AppArmor.pm:4077 #, perl-format msgid "Denying network access %s %s to profile." msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² के लिठ%s %s नेटवरà¥à¤• के उपयोग से इंकार किया जा रहा है।" #: ../Immunix/AppArmor.pm:4285 #, perl-format msgid "Reading log entries from %s." msgstr "%s से लॉग पà¥à¤°à¤µà¤¿à¤·à¥à¤Ÿà¤¿à¤¯à¤¾à¤‚ पढी जा रही है ।" #: ../Immunix/AppArmor.pm:4286 #, perl-format msgid "Updating AppArmor profiles in %s." msgstr "%s में AppArmor पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‹à¤‚ को अपडेट किया जा रहा है।" #: ../Immunix/AppArmor.pm:4290 msgid "unknown\n" msgstr "अजà¥à¤žà¤¾à¤¤\n" #: ../Immunix/AppArmor.pm:4324 msgid "" "The profile analyzer has completed processing the log files.\n" "\n" "All updated profiles will be reloaded" msgstr "" "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² विशà¥â€à¤²à¥‡à¤·à¤• ने लॉग फाइलों का पà¥à¤°à¤¸à¤‚सà¥â€à¤•रण पूरा कर लिया है।\n" "\n" "सभी अपडेटेड पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‡à¤‚ फिर से लोड की जाà¤à¤‚गी" #: ../Immunix/AppArmor.pm:4330 msgid "No unhandled AppArmor events were found in the system log." msgstr "सिसà¥â€à¤Ÿà¤® लॉग में न संभाली गई कोई AppArmor घटना नहीं मिलीं थीं।" #: ../Immunix/AppArmor.pm:4391 msgid "" "Select which profile changes you would like to save to the\n" "local profile set" msgstr "" "जिन पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² बदलावों को आप लोकल पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² सेट में सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ करना\n" "चाहते हैं उनका चà¥à¤¨à¤¾à¤µ करें" #: ../Immunix/AppArmor.pm:4392 msgid "Local profile changes" msgstr "लोकल पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² बदà¥à¤²à¤¾à¤µ" #: ../Immunix/AppArmor.pm:4419 msgid "" "The following local profiles were changed. Would you like to save them?" msgstr "" "निमà¥à¤¨à¤²à¤¿à¤–ित पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¥‹à¤‚ में बदलाव हà¥à¤ हैं। कà¥à¤¯à¤¾ आप उनà¥à¤¹à¥‡ सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ करना चाहते " "हैं?" #: ../Immunix/AppArmor.pm:4516 msgid "Profile Changes" msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² बदà¥à¤²à¤¾à¤µ" #: ../Immunix/AppArmor.pm:5139 ../Immunix/AppArmor.pm:5155 #: ../Immunix/AppArmor.pm:5166 ../Immunix/AppArmor.pm:5174 #: ../Immunix/AppArmor.pm:5195 ../Immunix/AppArmor.pm:5215 #: ../Immunix/AppArmor.pm:5224 ../Immunix/AppArmor.pm:5256 #: ../Immunix/AppArmor.pm:5334 ../Immunix/AppArmor.pm:5382 #, perl-format msgid "%s contains syntax errors." msgstr "%s में सिंटैकà¥â€à¤¸ तà¥à¤°à¥à¤Ÿà¤¿à¤¯à¤¾à¤‚ निहित हैं।" #: ../Immunix/AppArmor.pm:5275 #, perl-format msgid "Profile %s contains invalid regexp %s." msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² %s में अमानà¥â€à¤¯ regexp %s निहित है।" #: ../Immunix/AppArmor.pm:5280 #, perl-format msgid "Profile %s contains invalid mode %s." msgstr "पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² %s में अमानà¥â€à¤¯ मोड %s निहित है।" #: ../Immunix/AppArmor.pm:5430 #, perl-format msgid "%s contains syntax errors. Line [%s]" msgstr "%s में सिंटैकà¥â€à¤¸ तà¥à¤°à¥à¤Ÿà¤¿à¤¯à¤¾à¤‚ निहित हैं। लाइन [%s]" #: ../Immunix/AppArmor.pm:6022 #, perl-format msgid "Writing updated profile for %s." msgstr "%s के लिठअपडेटेड पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² लिख रहा है।" #: ../Immunix/AppArmor.pm:6528 msgid "Unknown command" msgstr "अजà¥à¤žà¤¾à¤¤ निरà¥à¤¦à¥‡à¤¶" #: ../Immunix/AppArmor.pm:6536 msgid "Invalid hotkey in" msgstr "अमानà¥à¤¯ कà¥à¤‚जी" #: ../Immunix/AppArmor.pm:6546 msgid "Duplicate hotkey for" msgstr "के लिये दोहरी कà¥à¤‚जी" #: ../Immunix/AppArmor.pm:6567 msgid "Invalid hotkey in default item" msgstr "डिफॉलà¥à¤Ÿ आईटेम में अमानà¥à¤¯ कà¥à¤‚जी" #: ../Immunix/AppArmor.pm:6576 msgid "Invalid default" msgstr "अमानà¥à¤¯ डिफॉलà¥à¤Ÿ" apparmor-2.8.95~2430/utils/po/apparmor-utils.pot0000644000175000017500000001612312277213004021317 0ustar sarnoldsarnold# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR NOVELL, Inc. # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: apparmor@lists.ubuntu.com\n" "POT-Creation-Date: 2014-02-13 10:57-0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: ../aa-genprof:52 msgid "Generate profile for the given program" msgstr "" #: ../aa-genprof:53 ../aa-logprof:25 ../aa-cleanprof:24 ../aa-mergeprof:31 #: ../aa-autodep:25 ../aa-audit:25 ../aa-complain:24 ../aa-enforce:24 #: ../aa-disable:24 msgid "path to profiles" msgstr "" #: ../aa-genprof:54 ../aa-logprof:26 msgid "path to logfile" msgstr "" #: ../aa-genprof:55 msgid "name of program to profile" msgstr "" #: ../aa-genprof:65 ../aa-logprof:37 #, python-format msgid "The logfile %s does not exist. Please check the path" msgstr "" #: ../aa-genprof:71 ../aa-logprof:43 ../aa-unconfined:34 msgid "" "It seems AppArmor was not started. Please enable AppArmor and try again." msgstr "" #: ../aa-genprof:76 #, python-format msgid "%s is not a directory." msgstr "" #: ../aa-genprof:90 #, python-format msgid "" "Can't find %s in the system path list. If the name of the application\n" "is correct, please run 'which %s' as a user with correct PATH\n" "environment set up in order to find the fully-qualified path and\n" "use the full path as parameter." msgstr "" #: ../aa-genprof:92 #, python-format msgid "%s does not exists, please double-check the path." msgstr "" #: ../aa-genprof:120 msgid "" "\n" "Before you begin, you may wish to check if a\n" "profile already exists for the application you\n" "wish to confine. See the following wiki page for\n" "more information:" msgstr "" #: ../aa-genprof:122 msgid "" "Please start the application to be profiled in\n" "another window and exercise its functionality now.\n" "\n" "Once completed, select the \"Scan\" option below in \n" "order to scan the system logs for AppArmor events. \n" "\n" "For each AppArmor event, you will be given the \n" "opportunity to choose whether the access should be \n" "allowed or denied." msgstr "" #: ../aa-genprof:143 msgid "Profiling" msgstr "" #: ../aa-genprof:161 msgid "" "\n" "Reloaded AppArmor profiles in enforce mode." msgstr "" #: ../aa-genprof:162 msgid "" "\n" "Please consider contributing your new profile!\n" "See the following wiki page for more information:" msgstr "" #: ../aa-genprof:163 #, python-format msgid "Finished generating profile for %s." msgstr "" #: ../aa-logprof:24 msgid "Process log entries to generate profiles" msgstr "" #: ../aa-logprof:27 msgid "mark in the log to start processing after" msgstr "" #: ../aa-cleanprof:23 msgid "Cleanup the profiles for the given programs" msgstr "" #: ../aa-cleanprof:25 ../aa-autodep:26 ../aa-audit:27 ../aa-complain:26 #: ../aa-enforce:26 ../aa-disable:26 msgid "name of program" msgstr "" #: ../aa-cleanprof:26 msgid "Silently overwrite with a clean profile" msgstr "" #: ../aa-mergeprof:27 msgid "Perform a 3way merge on the given profiles" msgstr "" #: ../aa-mergeprof:28 msgid "your profile" msgstr "" #: ../aa-mergeprof:29 msgid "base profile" msgstr "" #: ../aa-mergeprof:30 msgid "other profile" msgstr "" #: ../aa-mergeprof:32 msgid "Automatically merge profiles, exits incase of *x conflicts" msgstr "" #: ../aa-mergeprof:53 msgid "The following local profiles were changed. Would you like to save them?" msgstr "" #: ../aa-mergeprof:131 ../aa-mergeprof:413 msgid "Path" msgstr "" #: ../aa-mergeprof:132 msgid "Select the appropriate mode" msgstr "" #: ../aa-mergeprof:149 msgid "Unknown selection" msgstr "" #: ../aa-mergeprof:166 ../aa-mergeprof:192 msgid "File includes" msgstr "" #: ../aa-mergeprof:166 ../aa-mergeprof:192 msgid "Select the ones you wish to add" msgstr "" #: ../aa-mergeprof:178 ../aa-mergeprof:205 #, python-format msgid "Adding %s to the file." msgstr "" #: ../aa-mergeprof:182 msgid "unknown" msgstr "" #: ../aa-mergeprof:207 ../aa-mergeprof:258 ../aa-mergeprof:499 #: ../aa-mergeprof:541 ../aa-mergeprof:658 #, python-format msgid "Deleted %s previous matching profile entries." msgstr "" #: ../aa-mergeprof:227 ../aa-mergeprof:412 ../aa-mergeprof:612 #: ../aa-mergeprof:639 msgid "Profile" msgstr "" #: ../aa-mergeprof:228 msgid "Capability" msgstr "" #: ../aa-mergeprof:229 ../aa-mergeprof:463 msgid "Severity" msgstr "" #: ../aa-mergeprof:256 ../aa-mergeprof:497 #, python-format msgid "Adding %s to profile." msgstr "" #: ../aa-mergeprof:265 #, python-format msgid "Adding capability %s to profile." msgstr "" #: ../aa-mergeprof:272 #, python-format msgid "Denying capability %s to profile." msgstr "" #: ../aa-mergeprof:422 ../aa-mergeprof:453 msgid "(owner permissions off)" msgstr "" #: ../aa-mergeprof:427 msgid "(force new perms to owner)" msgstr "" #: ../aa-mergeprof:430 msgid "(force all rule perms to owner)" msgstr "" #: ../aa-mergeprof:442 msgid "Old Mode" msgstr "" #: ../aa-mergeprof:443 msgid "New Mode" msgstr "" #: ../aa-mergeprof:458 msgid "(force perms to owner)" msgstr "" #: ../aa-mergeprof:461 msgid "Mode" msgstr "" #: ../aa-mergeprof:539 #, python-format msgid "Adding %s %s to profile" msgstr "" #: ../aa-mergeprof:557 msgid "Enter new path: " msgstr "" #: ../aa-mergeprof:613 ../aa-mergeprof:640 msgid "Network Family" msgstr "" #: ../aa-mergeprof:614 ../aa-mergeprof:641 msgid "Socket Type" msgstr "" #: ../aa-mergeprof:656 #, python-format msgid "Adding %s to profile" msgstr "" #: ../aa-mergeprof:666 #, python-format msgid "Adding network access %s %s to profile." msgstr "" #: ../aa-mergeprof:672 #, python-format msgid "Denying network access %s %s to profile" msgstr "" #: ../aa-autodep:23 msgid "Generate a basic AppArmor profile by guessing requirements" msgstr "" #: ../aa-autodep:24 msgid "overwrite existing profile" msgstr "" #: ../aa-audit:24 msgid "Switch the given programs to audit mode" msgstr "" #: ../aa-audit:26 msgid "remove audit mode" msgstr "" #: ../aa-audit:28 msgid "Show full trace" msgstr "" #: ../aa-complain:23 msgid "Switch the given program to complain mode" msgstr "" #: ../aa-complain:25 msgid "remove complain mode" msgstr "" #: ../aa-enforce:23 msgid "Switch the given program to enforce mode" msgstr "" #: ../aa-enforce:25 msgid "switch to complain mode" msgstr "" #: ../aa-disable:23 msgid "Disable the profile for the given programs" msgstr "" #: ../aa-disable:25 msgid "enable the profile for the given programs" msgstr "" #: ../aa-unconfined:26 msgid "Lists unconfined processes having tcp or udp ports" msgstr "" #: ../aa-unconfined:27 msgid "scan all processes from /proc" msgstr "" #: ../aa-unconfined:79 #, python-format msgid "%s %s (%s) not confined\n" msgstr "" #: ../aa-unconfined:83 #, python-format msgid "%s %s %snot confined\n" msgstr "" #: ../aa-unconfined:88 #, python-format msgid "%s %s (%s) confined by '%s'\n" msgstr "" #: ../aa-unconfined:92 #, python-format msgid "%s %s %sconfined by '%s'\n" msgstr "" apparmor-2.8.95~2430/utils/po/ru.po0000644000175000017500000006603312267276306016624 0ustar sarnoldsarnold# Russian translation for apparmor # Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 # This file is distributed under the same license as the apparmor package. # FIRST AUTHOR , 2013. # msgid "" msgstr "" "Project-Id-Version: apparmor-utils\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-13 16:44-0800\n" "PO-Revision-Date: 2013-11-19 08:39+0000\n" "Last-Translator: Eugene Marshal \n" "Language-Team: Russian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2013-11-20 05:14+0000\n" "X-Generator: Launchpad (build 16831)\n" "Language: ru\n" #: ../aa-genprof:72 ../aa-unconfined:54 msgid "" "AppArmor does not appear to be started. Please enable AppArmor and try again." msgstr "" "AppArmor не запущен. ПожалуйÑта, запуÑтите AppArmor и повторите заново." #: ../aa-genprof:86 msgid "Please enter the program to profile: " msgstr "Введите программу Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ: " #: ../aa-genprof:105 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' in the other window in order to find the " "fully-qualified path." msgstr "" #: ../aa-genprof:107 ../aa-autodep:110 ../aa-audit:120 ../aa-complain:119 #: ../aa-enforce:130 ../aa-disable:140 #, perl-format msgid "%s does not exist, please double-check the path." msgstr "%s не ÑущеÑтвует, пожалуйÑта дважды проверьте адреÑ." #: ../aa-genprof:141 msgid "" "\n" "Before you begin, you may wish to check if a\n" "profile already exists for the application you\n" "wish to confine. See the following wiki page for\n" "more information:\n" "http://wiki.apparmor.net/index.php/Profiles" msgstr "" "\n" "Переде тем как начнёте, возможно вы захотите\n" "проверить наличие Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð´Ð»Ñ Ð²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ð¾Ð³Ð¾\n" "приложениÑ. Дополнительные ÑведениÑ\n" "предÑтавлены на wiki-Ñтранице:\n" "http://wiki.apparmor.net/index.php/Profiles" #: ../aa-genprof:143 msgid "" "Please start the application to be profiled in \n" "another window and exercise its functionality now.\n" "\n" "Once completed, select the \"Scan\" button below in \n" "order to scan the system logs for AppArmor events. \n" "\n" "For each AppArmor event, you will be given the \n" "opportunity to choose whether the access should be \n" "allowed or denied." msgstr "" "ЗапуÑтите приложение, Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð³Ð¾ необходимо\n" "Ñоздать профиль и проверьте его работоÑпоÑобноÑть.\n" "\n" "Когда закончите, выберите кнопку \"Проверить\", \n" " чтобы проверить ÑиÑтемные ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ AppArmor. \n" "\n" "Ð”Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ AppArmor, ÑущеÑтвует \n" "возможноÑть разрешить или запретить доÑтуп." #: ../aa-genprof:163 msgid "Profiling" msgstr "Профилирование" #: ../aa-genprof:197 msgid "Reloaded AppArmor profiles in enforce mode." msgstr "Перезагруженные профили AppArmor в принудительном режиме." #: ../aa-genprof:198 msgid "" "\n" "Please consider contributing your new profile! See\n" "the following wiki page for more information:\n" "http://wiki.apparmor.net/index.php/Profiles\n" msgstr "" "\n" "ПожалуйÑта, поделитеÑÑŒ вашим новым профилем! \n" "Откройте Ñледующую wiki-Ñтраницу Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñведений:\n" "http://wiki.apparmor.net/index.php/Profiles\n" #: ../aa-genprof:199 #, perl-format msgid "Finished generating profile for %s." msgstr "Создание Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ %s завершено." #: ../aa-genprof:203 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ program to " "profile ]" msgstr "" "команда: %s [ -d /адреÑ/раÑположениÑ/профилей ] [ -f " "/раÑположение/файла/журнала ] [ программа, Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ ÑоздаётÑÑ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»ÑŒ ]" #: ../aa-logprof:69 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ -m \"mark in " "log to start processing after\"" msgstr "" "команда: %s [ -d /адреÑ/раÑположениÑ/профилей ] [ -f " "/раÑположение/файла/журнала ] [ -m \"отметить в журнале Ð´Ð»Ñ Ð¿Ð¾Ñледующего " "запуÑка обработки\"" #: ../aa-autodep:61 #, perl-format msgid "Can't find AppArmor profiles in %s." msgstr "Ðевозможно найти профили AppArmor в %s." #: ../aa-autodep:69 msgid "Please enter the program to create a profile for: " msgstr "Введите программу Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ ÑоздаётÑÑ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»ÑŒ: " #: ../aa-autodep:93 ../Immunix/AppArmor.pm:6339 #, perl-format msgid "" "%s is currently marked as a program that should not have it's own profile. " "Usually, programs are marked this way if creating a profile for them is " "likely to break the rest of the system. If you know what you're doing and " "are certain you want to create a profile for this program, edit the " "corresponding entry in the [qualifiers] section in " "/etc/apparmor/logprof.conf." msgstr "" "Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ %s отмечена как программа Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ профиль ещё не " "Ñоздан. Обычно, программы отмечены таким образом, еÑли при Ñоздании Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ " "они могут привеÑти к неполадкам в ÑиÑтеме. ЕÑли вы оÑознанно понимаете " "порÑдок дейÑтвий и хотите Ñоздать профиль Ð´Ð»Ñ Ñтой программы, выполните " "редактирование ÑоответÑтвующей запиÑи в разделе [qualifiers] (требующие " "уточнениÑ) файла /etc/apparmor/logprof.conf." #: ../aa-autodep:100 #, perl-format msgid "Profile for %s already exists - skipping." msgstr "Профиль %s уже ÑущеÑтвует, выполнÑетÑÑ Ð¿Ñ€Ð¾Ð¿ÑƒÑк." #: ../aa-autodep:107 ../aa-audit:117 ../aa-complain:116 ../aa-enforce:127 #: ../aa-disable:137 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' as a user with the correct PATH environment " "set up in order to find the fully-qualified path." msgstr "" #: ../aa-audit:104 #, perl-format msgid "Setting %s to audit mode." msgstr "" #: ../aa-audit:129 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to audit mode ]" msgstr "" "команда: %s [ -d /адреÑ/раÑположениÑ/профилей ] [ программа Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ " "в режим аудита ]" #: ../aa-complain:61 msgid "Please enter the program to switch to complain mode: " msgstr "Введите программу Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð² тревожный режим: " #: ../aa-complain:103 ../Immunix/AppArmor.pm:621 ../Immunix/AppArmor.pm:941 #, perl-format msgid "Setting %s to complain mode." msgstr "" #: ../aa-complain:128 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to complain mode ]" msgstr "" "команда: %s [ -d /адреÑ/раÑположениÑ/профилей ] [ программа Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ " "в тревожный режим ]" #: ../aa-enforce:62 msgid "Please enter the program to switch to enforce mode: " msgstr "" #: ../aa-enforce:103 ../Immunix/AppArmor.pm:634 #, perl-format msgid "Setting %s to enforce mode." msgstr "" #: ../aa-enforce:139 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to enforce mode ]" msgstr "" #: ../aa-unconfined:48 #, perl-format msgid "Usage: %s [ --paranoid ]\n" msgstr "Команда: %s [ --paranoid ]\n" #: ../aa-unconfined:59 msgid "Can't read /proc\n" msgstr "Ðет доÑтупа к /proc\n" #: ../aa-unconfined:97 ../aa-unconfined:99 msgid "not confined\n" msgstr "без ограничений\n" #: ../aa-unconfined:108 ../aa-unconfined:110 msgid "confined by" msgstr "ограничение" #: ../aa-disable:69 msgid "Please enter the program whose profile should be disabled: " msgstr "Введите программу, профиль которой должен быть отключён: " #: ../aa-disable:113 #, perl-format msgid "Could not find basename for %s." msgstr "" #: ../aa-disable:117 #, perl-format msgid "Disabling %s." msgstr "Отключение %s." #: ../aa-disable:123 #, perl-format msgid "Could not create %s symlink." msgstr "" #: ../aa-disable:149 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to have profile disabled ]" msgstr "" "команда: %s [ -d /адреÑ/раÑположениÑ/профилей ] [ программа Ñ Ð¾Ñ‚ÐºÐ»ÑŽÑ‡Ñ‘Ð½Ð½Ñ‹Ð¼ " "профилем ]" #: ../Immunix/AppArmor.pm:619 ../Immunix/AppArmor.pm:632 #, perl-format msgid "Can't find %s." msgstr "Ðевозможно найти %s." #: ../Immunix/AppArmor.pm:819 ../Immunix/AppArmor.pm:3326 msgid "Connecting to repository....." msgstr "Подключение к репозиторию....." #: ../Immunix/AppArmor.pm:828 #, perl-format msgid "" "WARNING: Error fetching profiles from the repository:\n" "%s\n" msgstr "" "Ð’ÐИМÐÐИЕ: Ошибка загрузки профилей из репозиториÑ:\n" "%s\n" #: ../Immunix/AppArmor.pm:837 msgid "Inactive local profile for " msgstr "ÐезадейÑтвованный локальный профиль " #: ../Immunix/AppArmor.pm:874 ../Immunix/AppArmor.pm:1900 #: ../Immunix/AppArmor.pm:2188 ../Immunix/AppArmor.pm:3453 #: ../Immunix/AppArmor.pm:3486 ../Immunix/AppArmor.pm:3686 #: ../Immunix/AppArmor.pm:3952 ../Immunix/AppArmor.pm:4004 msgid "Profile" msgstr "Профиль" #: ../Immunix/AppArmor.pm:908 msgid "Profile submitted by" msgstr "Профиль подтверждён" #: ../Immunix/AppArmor.pm:949 #, perl-format msgid "Error activating profiles: %s\n" msgstr "Ошибка задейÑÑ‚Ð²Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»ÐµÐ¹: %s\n" #: ../Immunix/AppArmor.pm:1098 ../Immunix/AppArmor.pm:1151 #, perl-format msgid "" "WARNING: Error syncronizing profiles with the repository:\n" "%s\n" msgstr "" "Ð’ÐИМÐÐИЕ: Ошибка Ñинхронизации профилей и репозиториÑ:\n" "%s\n" #: ../Immunix/AppArmor.pm:1178 msgid "New profiles" msgstr "Ðовый профили" #: ../Immunix/AppArmor.pm:1180 msgid "" "Please choose the newly created profiles that you would like\n" "to store in the repository" msgstr "" "ПожалуйÑта, выберите недавно Ñозданные профили, которые\n" "вы хотите Ñохранить в репозитории" #: ../Immunix/AppArmor.pm:1187 msgid "Submit newly created profiles to the repository" msgstr "Отправить недавно Ñозданные профили в репозиторий" #: ../Immunix/AppArmor.pm:1189 msgid "Would you like to upload the newly created profiles?" msgstr "Ð’Ñ‹ дейÑтвительно хотите выгрузить недавно Ñозданные профили?" #: ../Immunix/AppArmor.pm:1202 msgid "" "Select which of the changed profiles you would like to upload\n" "to the repository" msgstr "" "Выберите те изменённые профили, которые хотите выгрузить\n" "в репозиторий" #: ../Immunix/AppArmor.pm:1204 msgid "Changed profiles" msgstr "Изменённые профили" #: ../Immunix/AppArmor.pm:1210 msgid "Submit changed profiles to the repository" msgstr "Отправить изменённые профили в репозиторий" #: ../Immunix/AppArmor.pm:1212 msgid "" "The following profiles from the repository were changed.\n" "Would you like to upload your changes?" msgstr "" "Следующие профили из Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ñодержат изменениÑ.\n" "Ð’Ñ‹ дейÑтвительно хотите выгрузить внеÑённые изменениÑ?" #: ../Immunix/AppArmor.pm:1279 ../Immunix/AppArmor.pm:1359 #, perl-format msgid "" "WARNING: An error occured while uploading the profile %s\n" "%s\n" msgstr "" "Ð’ÐИМÐÐИЕ: Обнаружена ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð³Ñ€ÑƒÐ·ÐºÐ¸ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ %s\n" "%s\n" #: ../Immunix/AppArmor.pm:1284 msgid "Uploaded changes to repository." msgstr "Выгруженные Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² репозиторий." #: ../Immunix/AppArmor.pm:1306 ../Immunix/AppArmor.pm:3185 #: ../Immunix/AppArmor.pm:3215 msgid "Repository" msgstr "Репозиторий" #: ../Immunix/AppArmor.pm:1333 msgid "Changelog Entry: " msgstr "ЗапиÑÑŒ журнала изменений: " #: ../Immunix/AppArmor.pm:1354 #, perl-format msgid "Uploaded %s to repository." msgstr "Выгружено %s в репозиторий." #: ../Immunix/AppArmor.pm:1365 msgid "" "Repository Error\n" "Registration or Signin was unsuccessful. User login\n" "information is required to upload profiles to the\n" "repository. These changes have not been sent.\n" msgstr "" "Ошибка репозиториÑ\n" "РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¸Ð»Ð¸ вход не выполнены. СведениÑ\n" "об имени Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½ÐµÐ¾Ð±Ñ…Ð¾Ð´Ð¸Ð¼Ñ‹ Ð´Ð»Ñ Ð²Ñ‹Ð³Ñ€ÑƒÐ·ÐºÐ¸\n" "профилей в репозиторий. Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ðµ были отправлены.\n" #: ../Immunix/AppArmor.pm:1422 ../Immunix/AppArmor.pm:1462 msgid "(Y)es" msgstr "(Д)а" #: ../Immunix/AppArmor.pm:1423 ../Immunix/AppArmor.pm:1463 msgid "(N)o" msgstr "(Ð)ет" #: ../Immunix/AppArmor.pm:1426 ../Immunix/AppArmor.pm:1467 msgid "Invalid hotkey for" msgstr "" #: ../Immunix/AppArmor.pm:1464 msgid "(C)ancel" msgstr "(О)тмена" #: ../Immunix/AppArmor.pm:1789 msgid "" "Are you sure you want to abandon this set of profile changes and exit?" msgstr "Ð’Ñ‹ дейÑтвительно хотите отказатьÑÑ Ð¾Ñ‚ изменений Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¸ выйти?" #: ../Immunix/AppArmor.pm:1791 msgid "Abandoning all changes." msgstr "Отказ от вÑех изменений." #: ../Immunix/AppArmor.pm:1902 msgid "Default Hat" msgstr "" #: ../Immunix/AppArmor.pm:1904 msgid "Requested Hat" msgstr "" #: ../Immunix/AppArmor.pm:2190 msgid "Program" msgstr "Программа" #: ../Immunix/AppArmor.pm:2195 msgid "Execute" msgstr "Выполнить" #: ../Immunix/AppArmor.pm:2196 ../Immunix/AppArmor.pm:3455 #: ../Immunix/AppArmor.pm:3488 ../Immunix/AppArmor.pm:3741 msgid "Severity" msgstr "" #: ../Immunix/AppArmor.pm:2241 msgid "Enter profile name to transition to: " msgstr "" #: ../Immunix/AppArmor.pm:2249 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but some applications depend on the presence\n" "of LD_PRELOAD or LD_LIBRARY_PATH." msgstr "" "Должен ли AppArmor проводить обеззараживание окружениÑ\n" "во Ð²Ñ€ÐµÐ¼Ñ Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»ÐµÐ¹?\n" "\n" "Обеззараживание Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð½Ð°Ð¸Ð±Ð¾Ð»ÐµÐµ безопаÑно,\n" "но некоторые программы завиÑÑÑ‚ от доÑтупноÑти\n" "LD_PRELOAD или LD_LIBRARY_PATH." #: ../Immunix/AppArmor.pm:2251 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but this application appears to use LD_PRELOAD\n" "or LD_LIBRARY_PATH and clearing these could\n" "cause functionality problems." msgstr "" "Должен ли AppArmor проводить обеззараживание окружениÑ\n" "во Ð²Ñ€ÐµÐ¼Ñ Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»ÐµÐ¹?\n" "\n" "Обеззараживание Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð½Ð°Ð¸Ð±Ð¾Ð»ÐµÐµ безопаÑно,\n" "но Ñта программа иÑпользует LD_PRELOAD \n" "или LD_LIBRARY_PATH, удаление которых может\n" "привеÑти к неполадкам в работоÑпоÑобноÑти." #: ../Immunix/AppArmor.pm:2260 #, perl-format msgid "" "Launching processes in an unconfined state is a very\n" "dangerous operation and can cause serious security holes.\n" "\n" "Are you absolutely certain you wish to remove all\n" "AppArmor protection when executing %s?" msgstr "" "Выполнение процеÑÑов в режиме без ограничений крайне опаÑно \n" "и может привеÑти к поÑвлению критичеÑкой бреши в безопаÑноÑти.\n" "\n" "Ð’Ñ‹ дейÑтвительно уверены, что хотите отменить вÑÑŽ\n" "предоÑтавлÑемую AppArmor защиту при выполнении %s?" #: ../Immunix/AppArmor.pm:2262 msgid "" "Should AppArmor sanitize the environment when\n" "running this program unconfined?\n" "\n" "Not sanitizing the environment when unconfining\n" "a program opens up significant security holes\n" "and should be avoided if at all possible." msgstr "" "Должен ли AppArmor обеззараживать окружение,\n" "во Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ñтой программы в режиме без ограничений?\n" "\n" "Без Ð¾Ð±ÐµÐ·Ð·Ð°Ñ€Ð°Ð¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ, когда программа\n" "запуÑкаетÑÑ Ð±ÐµÐ· ограничений, может привеÑти к поÑвлению\n" "бреши в безопаÑноÑти и при необходимоÑти такой запуÑк\n" "производитьÑÑ Ð½Ðµ должен." #: ../Immunix/AppArmor.pm:2352 #, perl-format msgid "A profile for %s does not exist. Create one?" msgstr "Профиль %s не ÑущеÑтвует. Создать его?" #: ../Immunix/AppArmor.pm:2379 #, perl-format msgid "A local profile for %s does not exist. Create one?" msgstr "Локальный профиль %s не ÑущеÑтвует. Создать его?" #: ../Immunix/AppArmor.pm:2584 ../Immunix/AppArmor.pm:6733 #: ../Immunix/AppArmor.pm:6738 #, perl-format msgid "Log contains unknown mode %s." msgstr "" #: ../Immunix/AppArmor.pm:3068 msgid "" "An updated version of this profile has been found in the profile repository. " " Would you like to use it?" msgstr "" "ÐžÐ±Ð½Ð¾Ð²Ð»Ñ‘Ð½Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ñтого Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð° в репозитории профилей. Ð’Ñ‹ " "дейÑтвительно хотите иÑпользовать его?" #: ../Immunix/AppArmor.pm:3098 #, perl-format msgid "Updated profile %s to revision %s." msgstr "" #: ../Immunix/AppArmor.pm:3105 msgid "Error parsing repository profile." msgstr "Ошибка анализа Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ." #: ../Immunix/AppArmor.pm:3141 msgid "Create New User?" msgstr "Создать нового пользователÑ?" #: ../Immunix/AppArmor.pm:3142 msgid "Username: " msgstr "Пользователь: " #: ../Immunix/AppArmor.pm:3143 msgid "Password: " msgstr "Пароль: " #: ../Immunix/AppArmor.pm:3144 msgid "Email Addr: " msgstr "Эл. почта: " #: ../Immunix/AppArmor.pm:3146 msgid "Save Configuration? " msgstr "Сохранить наÑтройки? " #: ../Immunix/AppArmor.pm:3155 msgid "The Profile Repository server returned the following error:" msgstr "Сервер Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»ÐµÐ¹ Ñообщил Ñледующую ошибку:" #: ../Immunix/AppArmor.pm:3157 msgid "" "Please re-enter registration information or contact the administrator." msgstr "" "ПожалуйÑта, повторно введите ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ региÑтрации или ÑвÑжитеÑÑŒ Ñ " "ÑиÑтемным админиÑтратором." #: ../Immunix/AppArmor.pm:3158 msgid "Login Error\n" msgstr "Ошибка входа\n" #: ../Immunix/AppArmor.pm:3165 msgid "" "Login failure\n" " Please check username and password and try again." msgstr "" "Ошибка входа\n" " ПожалуйÑта, проверьте Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ, пароль и повторите Ñнова." #: ../Immunix/AppArmor.pm:3187 msgid "" "Would you like to enable access to the\n" "profile repository?" msgstr "" "Ð’Ñ‹ дейÑтвительно хотите задейÑтвовать доÑтуп к\n" "репозиторию профилей?" #: ../Immunix/AppArmor.pm:3218 msgid "" "Would you like to upload newly created and changed profiles to\n" " the profile repository?" msgstr "" "Ð’Ñ‹ дейÑтвительно хотите выгрузить недавно Ñозданные и \n" "недавно изменённые профили в репозиторий профилей?" #: ../Immunix/AppArmor.pm:3337 #, perl-format msgid "" "WARNING: Profile update check failed\n" "Error Detail:\n" "%s" msgstr "" "Ð’ÐИМÐÐИЕ: Ошибка проверки профилÑ\n" "Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð± ошибке:\n" "%s" #: ../Immunix/AppArmor.pm:3351 msgid "Change mode modifiers" msgstr "" #: ../Immunix/AppArmor.pm:3395 msgid "Complain-mode changes:" msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ‚Ñ€ÐµÐ²Ð¾Ð¶Ð½Ð¾Ð³Ð¾ режима:" #: ../Immunix/AppArmor.pm:3397 msgid "Enforce-mode changes:" msgstr "" #: ../Immunix/AppArmor.pm:3403 #, perl-format msgid "Invalid mode found: %s" msgstr "Обнаружен недопуÑтимый режим: %s" #: ../Immunix/AppArmor.pm:3454 ../Immunix/AppArmor.pm:3487 msgid "Capability" msgstr "" #: ../Immunix/AppArmor.pm:3507 ../Immunix/AppArmor.pm:3781 #: ../Immunix/AppArmor.pm:4028 #, perl-format msgid "Adding #include <%s> to profile." msgstr "" #: ../Immunix/AppArmor.pm:3510 ../Immunix/AppArmor.pm:3782 #: ../Immunix/AppArmor.pm:3822 ../Immunix/AppArmor.pm:4032 #, perl-format msgid "Deleted %s previous matching profile entries." msgstr "Удалены %s предыдущие Ñовпадающие запиÑи профилÑ." #: ../Immunix/AppArmor.pm:3521 #, perl-format msgid "Adding capability %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3526 #, perl-format msgid "Denying capability %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3687 msgid "Path" msgstr "РаÑположение" #: ../Immunix/AppArmor.pm:3698 ../Immunix/AppArmor.pm:3730 msgid "(owner permissions off)" msgstr "(Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†Ð° отключены)" #: ../Immunix/AppArmor.pm:3704 msgid "(force new perms to owner)" msgstr "(принудительно назначить новые Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†Ñƒ)" #: ../Immunix/AppArmor.pm:3707 msgid "(force all rule perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3719 msgid "Old Mode" msgstr "УÑтаревший режим" #: ../Immunix/AppArmor.pm:3720 msgid "New Mode" msgstr "Ðовый режим" #: ../Immunix/AppArmor.pm:3736 msgid "(force perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3739 msgid "Mode" msgstr "Режим" #: ../Immunix/AppArmor.pm:3821 #, perl-format msgid "Adding %s %s to profile." msgstr "Добавление %s %s в профиль." #: ../Immunix/AppArmor.pm:3837 msgid "Enter new path: " msgstr "Введите новый адреÑ: " #: ../Immunix/AppArmor.pm:3840 msgid "The specified path does not match this log entry:" msgstr "" #: ../Immunix/AppArmor.pm:3841 msgid "Log Entry" msgstr "Событие журнала" #: ../Immunix/AppArmor.pm:3842 msgid "Entered Path" msgstr "Введённый адреÑ" #: ../Immunix/AppArmor.pm:3843 msgid "Do you really want to use this path?" msgstr "Ð’Ñ‹ дейÑтвительно хотите иÑпользовать Ñтот адреÑ?" #: ../Immunix/AppArmor.pm:3955 ../Immunix/AppArmor.pm:4007 msgid "Network Family" msgstr "" #: ../Immunix/AppArmor.pm:3958 ../Immunix/AppArmor.pm:4010 msgid "Socket Type" msgstr "" #: ../Immunix/AppArmor.pm:4058 #, perl-format msgid "Adding network access %s %s to profile." msgstr "Добавление Ñетевого доÑтупа %s %s в профиль." #: ../Immunix/AppArmor.pm:4077 #, perl-format msgid "Denying network access %s %s to profile." msgstr "Запрет Ñетевого доÑтупа %s %s в профиль." #: ../Immunix/AppArmor.pm:4285 #, perl-format msgid "Reading log entries from %s." msgstr "Чтение Ñобытий журнала из %s." #: ../Immunix/AppArmor.pm:4286 #, perl-format msgid "Updating AppArmor profiles in %s." msgstr "Обновление профилей AppArmor в %s." #: ../Immunix/AppArmor.pm:4290 msgid "unknown\n" msgstr "" #: ../Immunix/AppArmor.pm:4324 msgid "" "The profile analyzer has completed processing the log files.\n" "\n" "All updated profiles will be reloaded" msgstr "" "Ðнализатор профилей завершил обработку файлов журнала.\n" "\n" "Ð’Ñе обновлённые профили будут перезагружены" #: ../Immunix/AppArmor.pm:4330 msgid "No unhandled AppArmor events were found in the system log." msgstr "Ðеобработанные ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ AppArmor не найдены в журнале ÑиÑтемы." #: ../Immunix/AppArmor.pm:4391 msgid "" "Select which profile changes you would like to save to the\n" "local profile set" msgstr "" "Выберите, какие Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð²Ñ‹ хотите Ñохранить в\n" "группу локального профилÑ" #: ../Immunix/AppArmor.pm:4392 msgid "Local profile changes" msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ профилÑ" #: ../Immunix/AppArmor.pm:4419 msgid "" "The following local profiles were changed. Would you like to save them?" msgstr "" "Были внеÑены Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² Ñледующие локальные профили. Ð’Ñ‹ дейÑтвительно " "хотите Ñохранить их?" #: ../Immunix/AppArmor.pm:4516 msgid "Profile Changes" msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ" #: ../Immunix/AppArmor.pm:5139 ../Immunix/AppArmor.pm:5155 #: ../Immunix/AppArmor.pm:5166 ../Immunix/AppArmor.pm:5174 #: ../Immunix/AppArmor.pm:5195 ../Immunix/AppArmor.pm:5215 #: ../Immunix/AppArmor.pm:5224 ../Immunix/AppArmor.pm:5256 #: ../Immunix/AppArmor.pm:5334 ../Immunix/AppArmor.pm:5382 #, perl-format msgid "%s contains syntax errors." msgstr "%s Ñодержит ÑинтакÑичеÑкие ошибки." #: ../Immunix/AppArmor.pm:5275 #, perl-format msgid "Profile %s contains invalid regexp %s." msgstr "" #: ../Immunix/AppArmor.pm:5280 #, perl-format msgid "Profile %s contains invalid mode %s." msgstr "" #: ../Immunix/AppArmor.pm:5430 #, perl-format msgid "%s contains syntax errors. Line [%s]" msgstr "" #: ../Immunix/AppArmor.pm:6022 #, perl-format msgid "Writing updated profile for %s." msgstr "" #: ../Immunix/AppArmor.pm:6528 msgid "Unknown command" msgstr "" #: ../Immunix/AppArmor.pm:6536 msgid "Invalid hotkey in" msgstr "" #: ../Immunix/AppArmor.pm:6546 msgid "Duplicate hotkey for" msgstr "" #: ../Immunix/AppArmor.pm:6567 msgid "Invalid hotkey in default item" msgstr "" #: ../Immunix/AppArmor.pm:6576 msgid "Invalid default" msgstr "" apparmor-2.8.95~2430/utils/po/bs.po0000644000175000017500000004027312267276306016600 0ustar sarnoldsarnold# Bosnian translation for apparmor # Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 # This file is distributed under the same license as the apparmor package. # FIRST AUTHOR , 2013. # msgid "" msgstr "" "Project-Id-Version: apparmor-utils\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-13 16:44-0800\n" "PO-Revision-Date: 2013-11-14 23:32+0000\n" "Last-Translator: Steve Beattie \n" "Language-Team: Bosnian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2013-11-16 05:14+0000\n" "X-Generator: Launchpad (build 16831)\n" "Language: bs\n" #: ../aa-genprof:72 ../aa-unconfined:54 msgid "" "AppArmor does not appear to be started. Please enable AppArmor and try again." msgstr "" #: ../aa-genprof:86 msgid "Please enter the program to profile: " msgstr "" #: ../aa-genprof:105 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' in the other window in order to find the " "fully-qualified path." msgstr "" #: ../aa-genprof:107 ../aa-autodep:110 ../aa-audit:120 ../aa-complain:119 #: ../aa-enforce:130 ../aa-disable:140 #, perl-format msgid "%s does not exist, please double-check the path." msgstr "" #: ../aa-genprof:141 msgid "" "\n" "Before you begin, you may wish to check if a\n" "profile already exists for the application you\n" "wish to confine. See the following wiki page for\n" "more information:\n" "http://wiki.apparmor.net/index.php/Profiles" msgstr "" #: ../aa-genprof:143 msgid "" "Please start the application to be profiled in \n" "another window and exercise its functionality now.\n" "\n" "Once completed, select the \"Scan\" button below in \n" "order to scan the system logs for AppArmor events. \n" "\n" "For each AppArmor event, you will be given the \n" "opportunity to choose whether the access should be \n" "allowed or denied." msgstr "" #: ../aa-genprof:163 msgid "Profiling" msgstr "Profilisanje" #: ../aa-genprof:197 msgid "Reloaded AppArmor profiles in enforce mode." msgstr "" #: ../aa-genprof:198 msgid "" "\n" "Please consider contributing your new profile! See\n" "the following wiki page for more information:\n" "http://wiki.apparmor.net/index.php/Profiles\n" msgstr "" #: ../aa-genprof:199 #, perl-format msgid "Finished generating profile for %s." msgstr "" #: ../aa-genprof:203 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ program to " "profile ]" msgstr "" #: ../aa-logprof:69 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ -m \"mark in " "log to start processing after\"" msgstr "" #: ../aa-autodep:61 #, perl-format msgid "Can't find AppArmor profiles in %s." msgstr "" #: ../aa-autodep:69 msgid "Please enter the program to create a profile for: " msgstr "" #: ../aa-autodep:93 ../Immunix/AppArmor.pm:6339 #, perl-format msgid "" "%s is currently marked as a program that should not have it's own profile. " "Usually, programs are marked this way if creating a profile for them is " "likely to break the rest of the system. If you know what you're doing and " "are certain you want to create a profile for this program, edit the " "corresponding entry in the [qualifiers] section in " "/etc/apparmor/logprof.conf." msgstr "" #: ../aa-autodep:100 #, perl-format msgid "Profile for %s already exists - skipping." msgstr "" #: ../aa-autodep:107 ../aa-audit:117 ../aa-complain:116 ../aa-enforce:127 #: ../aa-disable:137 #, perl-format msgid "" "Can't find %s in the system path list. If the name of the application is " "correct, please run 'which %s' as a user with the correct PATH environment " "set up in order to find the fully-qualified path." msgstr "" #: ../aa-audit:104 #, perl-format msgid "Setting %s to audit mode." msgstr "" #: ../aa-audit:129 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to audit mode ]" msgstr "" #: ../aa-complain:61 msgid "Please enter the program to switch to complain mode: " msgstr "" #: ../aa-complain:103 ../Immunix/AppArmor.pm:621 ../Immunix/AppArmor.pm:941 #, perl-format msgid "Setting %s to complain mode." msgstr "" #: ../aa-complain:128 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to complain mode ]" msgstr "" #: ../aa-enforce:62 msgid "Please enter the program to switch to enforce mode: " msgstr "" #: ../aa-enforce:103 ../Immunix/AppArmor.pm:634 #, perl-format msgid "Setting %s to enforce mode." msgstr "" #: ../aa-enforce:139 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to switch to enforce mode ]" msgstr "" #: ../aa-unconfined:48 #, perl-format msgid "Usage: %s [ --paranoid ]\n" msgstr "" #: ../aa-unconfined:59 msgid "Can't read /proc\n" msgstr "" #: ../aa-unconfined:97 ../aa-unconfined:99 msgid "not confined\n" msgstr "" #: ../aa-unconfined:108 ../aa-unconfined:110 msgid "confined by" msgstr "" #: ../aa-disable:69 msgid "Please enter the program whose profile should be disabled: " msgstr "" #: ../aa-disable:113 #, perl-format msgid "Could not find basename for %s." msgstr "" #: ../aa-disable:117 #, perl-format msgid "Disabling %s." msgstr "" #: ../aa-disable:123 #, perl-format msgid "Could not create %s symlink." msgstr "" #: ../aa-disable:149 #, perl-format msgid "" "usage: %s [ -d /path/to/profiles ] [ program to have profile disabled ]" msgstr "" #: ../Immunix/AppArmor.pm:619 ../Immunix/AppArmor.pm:632 #, perl-format msgid "Can't find %s." msgstr "" #: ../Immunix/AppArmor.pm:819 ../Immunix/AppArmor.pm:3326 msgid "Connecting to repository....." msgstr "" #: ../Immunix/AppArmor.pm:828 #, perl-format msgid "" "WARNING: Error fetching profiles from the repository:\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:837 msgid "Inactive local profile for " msgstr "" #: ../Immunix/AppArmor.pm:874 ../Immunix/AppArmor.pm:1900 #: ../Immunix/AppArmor.pm:2188 ../Immunix/AppArmor.pm:3453 #: ../Immunix/AppArmor.pm:3486 ../Immunix/AppArmor.pm:3686 #: ../Immunix/AppArmor.pm:3952 ../Immunix/AppArmor.pm:4004 msgid "Profile" msgstr "" #: ../Immunix/AppArmor.pm:908 msgid "Profile submitted by" msgstr "" #: ../Immunix/AppArmor.pm:949 #, perl-format msgid "Error activating profiles: %s\n" msgstr "" #: ../Immunix/AppArmor.pm:1098 ../Immunix/AppArmor.pm:1151 #, perl-format msgid "" "WARNING: Error syncronizing profiles with the repository:\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:1178 msgid "New profiles" msgstr "" #: ../Immunix/AppArmor.pm:1180 msgid "" "Please choose the newly created profiles that you would like\n" "to store in the repository" msgstr "" #: ../Immunix/AppArmor.pm:1187 msgid "Submit newly created profiles to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1189 msgid "Would you like to upload the newly created profiles?" msgstr "" #: ../Immunix/AppArmor.pm:1202 msgid "" "Select which of the changed profiles you would like to upload\n" "to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1204 msgid "Changed profiles" msgstr "" #: ../Immunix/AppArmor.pm:1210 msgid "Submit changed profiles to the repository" msgstr "" #: ../Immunix/AppArmor.pm:1212 msgid "" "The following profiles from the repository were changed.\n" "Would you like to upload your changes?" msgstr "" #: ../Immunix/AppArmor.pm:1279 ../Immunix/AppArmor.pm:1359 #, perl-format msgid "" "WARNING: An error occured while uploading the profile %s\n" "%s\n" msgstr "" #: ../Immunix/AppArmor.pm:1284 msgid "Uploaded changes to repository." msgstr "" #: ../Immunix/AppArmor.pm:1306 ../Immunix/AppArmor.pm:3185 #: ../Immunix/AppArmor.pm:3215 msgid "Repository" msgstr "" #: ../Immunix/AppArmor.pm:1333 msgid "Changelog Entry: " msgstr "" #: ../Immunix/AppArmor.pm:1354 #, perl-format msgid "Uploaded %s to repository." msgstr "" #: ../Immunix/AppArmor.pm:1365 msgid "" "Repository Error\n" "Registration or Signin was unsuccessful. User login\n" "information is required to upload profiles to the\n" "repository. These changes have not been sent.\n" msgstr "" #: ../Immunix/AppArmor.pm:1422 ../Immunix/AppArmor.pm:1462 msgid "(Y)es" msgstr "" #: ../Immunix/AppArmor.pm:1423 ../Immunix/AppArmor.pm:1463 msgid "(N)o" msgstr "" #: ../Immunix/AppArmor.pm:1426 ../Immunix/AppArmor.pm:1467 msgid "Invalid hotkey for" msgstr "" #: ../Immunix/AppArmor.pm:1464 msgid "(C)ancel" msgstr "" #: ../Immunix/AppArmor.pm:1789 msgid "" "Are you sure you want to abandon this set of profile changes and exit?" msgstr "" #: ../Immunix/AppArmor.pm:1791 msgid "Abandoning all changes." msgstr "" #: ../Immunix/AppArmor.pm:1902 msgid "Default Hat" msgstr "" #: ../Immunix/AppArmor.pm:1904 msgid "Requested Hat" msgstr "" #: ../Immunix/AppArmor.pm:2190 msgid "Program" msgstr "" #: ../Immunix/AppArmor.pm:2195 msgid "Execute" msgstr "" #: ../Immunix/AppArmor.pm:2196 ../Immunix/AppArmor.pm:3455 #: ../Immunix/AppArmor.pm:3488 ../Immunix/AppArmor.pm:3741 msgid "Severity" msgstr "" #: ../Immunix/AppArmor.pm:2241 msgid "Enter profile name to transition to: " msgstr "" #: ../Immunix/AppArmor.pm:2249 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but some applications depend on the presence\n" "of LD_PRELOAD or LD_LIBRARY_PATH." msgstr "" #: ../Immunix/AppArmor.pm:2251 msgid "" "Should AppArmor sanitize the environment when\n" "switching profiles?\n" "\n" "Sanitizing the environment is more secure,\n" "but this application appears to use LD_PRELOAD\n" "or LD_LIBRARY_PATH and clearing these could\n" "cause functionality problems." msgstr "" #: ../Immunix/AppArmor.pm:2260 #, perl-format msgid "" "Launching processes in an unconfined state is a very\n" "dangerous operation and can cause serious security holes.\n" "\n" "Are you absolutely certain you wish to remove all\n" "AppArmor protection when executing %s?" msgstr "" #: ../Immunix/AppArmor.pm:2262 msgid "" "Should AppArmor sanitize the environment when\n" "running this program unconfined?\n" "\n" "Not sanitizing the environment when unconfining\n" "a program opens up significant security holes\n" "and should be avoided if at all possible." msgstr "" #: ../Immunix/AppArmor.pm:2352 #, perl-format msgid "A profile for %s does not exist. Create one?" msgstr "" #: ../Immunix/AppArmor.pm:2379 #, perl-format msgid "A local profile for %s does not exist. Create one?" msgstr "" #: ../Immunix/AppArmor.pm:2584 ../Immunix/AppArmor.pm:6733 #: ../Immunix/AppArmor.pm:6738 #, perl-format msgid "Log contains unknown mode %s." msgstr "" #: ../Immunix/AppArmor.pm:3068 msgid "" "An updated version of this profile has been found in the profile repository. " " Would you like to use it?" msgstr "" #: ../Immunix/AppArmor.pm:3098 #, perl-format msgid "Updated profile %s to revision %s." msgstr "" #: ../Immunix/AppArmor.pm:3105 msgid "Error parsing repository profile." msgstr "" #: ../Immunix/AppArmor.pm:3141 msgid "Create New User?" msgstr "" #: ../Immunix/AppArmor.pm:3142 msgid "Username: " msgstr "" #: ../Immunix/AppArmor.pm:3143 msgid "Password: " msgstr "" #: ../Immunix/AppArmor.pm:3144 msgid "Email Addr: " msgstr "" #: ../Immunix/AppArmor.pm:3146 msgid "Save Configuration? " msgstr "" #: ../Immunix/AppArmor.pm:3155 msgid "The Profile Repository server returned the following error:" msgstr "" #: ../Immunix/AppArmor.pm:3157 msgid "" "Please re-enter registration information or contact the administrator." msgstr "" #: ../Immunix/AppArmor.pm:3158 msgid "Login Error\n" msgstr "" #: ../Immunix/AppArmor.pm:3165 msgid "" "Login failure\n" " Please check username and password and try again." msgstr "" #: ../Immunix/AppArmor.pm:3187 msgid "" "Would you like to enable access to the\n" "profile repository?" msgstr "" #: ../Immunix/AppArmor.pm:3218 msgid "" "Would you like to upload newly created and changed profiles to\n" " the profile repository?" msgstr "" #: ../Immunix/AppArmor.pm:3337 #, perl-format msgid "" "WARNING: Profile update check failed\n" "Error Detail:\n" "%s" msgstr "" #: ../Immunix/AppArmor.pm:3351 msgid "Change mode modifiers" msgstr "" #: ../Immunix/AppArmor.pm:3395 msgid "Complain-mode changes:" msgstr "" #: ../Immunix/AppArmor.pm:3397 msgid "Enforce-mode changes:" msgstr "" #: ../Immunix/AppArmor.pm:3403 #, perl-format msgid "Invalid mode found: %s" msgstr "" #: ../Immunix/AppArmor.pm:3454 ../Immunix/AppArmor.pm:3487 msgid "Capability" msgstr "" #: ../Immunix/AppArmor.pm:3507 ../Immunix/AppArmor.pm:3781 #: ../Immunix/AppArmor.pm:4028 #, perl-format msgid "Adding #include <%s> to profile." msgstr "" #: ../Immunix/AppArmor.pm:3510 ../Immunix/AppArmor.pm:3782 #: ../Immunix/AppArmor.pm:3822 ../Immunix/AppArmor.pm:4032 #, perl-format msgid "Deleted %s previous matching profile entries." msgstr "" #: ../Immunix/AppArmor.pm:3521 #, perl-format msgid "Adding capability %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3526 #, perl-format msgid "Denying capability %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3687 msgid "Path" msgstr "" #: ../Immunix/AppArmor.pm:3698 ../Immunix/AppArmor.pm:3730 msgid "(owner permissions off)" msgstr "" #: ../Immunix/AppArmor.pm:3704 msgid "(force new perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3707 msgid "(force all rule perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3719 msgid "Old Mode" msgstr "" #: ../Immunix/AppArmor.pm:3720 msgid "New Mode" msgstr "" #: ../Immunix/AppArmor.pm:3736 msgid "(force perms to owner)" msgstr "" #: ../Immunix/AppArmor.pm:3739 msgid "Mode" msgstr "" #: ../Immunix/AppArmor.pm:3821 #, perl-format msgid "Adding %s %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:3837 msgid "Enter new path: " msgstr "" #: ../Immunix/AppArmor.pm:3840 msgid "The specified path does not match this log entry:" msgstr "" #: ../Immunix/AppArmor.pm:3841 msgid "Log Entry" msgstr "" #: ../Immunix/AppArmor.pm:3842 msgid "Entered Path" msgstr "" #: ../Immunix/AppArmor.pm:3843 msgid "Do you really want to use this path?" msgstr "" #: ../Immunix/AppArmor.pm:3955 ../Immunix/AppArmor.pm:4007 msgid "Network Family" msgstr "" #: ../Immunix/AppArmor.pm:3958 ../Immunix/AppArmor.pm:4010 msgid "Socket Type" msgstr "" #: ../Immunix/AppArmor.pm:4058 #, perl-format msgid "Adding network access %s %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:4077 #, perl-format msgid "Denying network access %s %s to profile." msgstr "" #: ../Immunix/AppArmor.pm:4285 #, perl-format msgid "Reading log entries from %s." msgstr "" #: ../Immunix/AppArmor.pm:4286 #, perl-format msgid "Updating AppArmor profiles in %s." msgstr "" #: ../Immunix/AppArmor.pm:4290 msgid "unknown\n" msgstr "" #: ../Immunix/AppArmor.pm:4324 msgid "" "The profile analyzer has completed processing the log files.\n" "\n" "All updated profiles will be reloaded" msgstr "" #: ../Immunix/AppArmor.pm:4330 msgid "No unhandled AppArmor events were found in the system log." msgstr "" #: ../Immunix/AppArmor.pm:4391 msgid "" "Select which profile changes you would like to save to the\n" "local profile set" msgstr "" #: ../Immunix/AppArmor.pm:4392 msgid "Local profile changes" msgstr "" #: ../Immunix/AppArmor.pm:4419 msgid "" "The following local profiles were changed. Would you like to save them?" msgstr "" #: ../Immunix/AppArmor.pm:4516 msgid "Profile Changes" msgstr "" #: ../Immunix/AppArmor.pm:5139 ../Immunix/AppArmor.pm:5155 #: ../Immunix/AppArmor.pm:5166 ../Immunix/AppArmor.pm:5174 #: ../Immunix/AppArmor.pm:5195 ../Immunix/AppArmor.pm:5215 #: ../Immunix/AppArmor.pm:5224 ../Immunix/AppArmor.pm:5256 #: ../Immunix/AppArmor.pm:5334 ../Immunix/AppArmor.pm:5382 #, perl-format msgid "%s contains syntax errors." msgstr "" #: ../Immunix/AppArmor.pm:5275 #, perl-format msgid "Profile %s contains invalid regexp %s." msgstr "" #: ../Immunix/AppArmor.pm:5280 #, perl-format msgid "Profile %s contains invalid mode %s." msgstr "" #: ../Immunix/AppArmor.pm:5430 #, perl-format msgid "%s contains syntax errors. Line [%s]" msgstr "" #: ../Immunix/AppArmor.pm:6022 #, perl-format msgid "Writing updated profile for %s." msgstr "" #: ../Immunix/AppArmor.pm:6528 msgid "Unknown command" msgstr "" #: ../Immunix/AppArmor.pm:6536 msgid "Invalid hotkey in" msgstr "" #: ../Immunix/AppArmor.pm:6546 msgid "Duplicate hotkey for" msgstr "" #: ../Immunix/AppArmor.pm:6567 msgid "Invalid hotkey in default item" msgstr "" #: ../Immunix/AppArmor.pm:6576 msgid "Invalid default" msgstr "" apparmor-2.8.95~2430/utils/aa-decode.pod0000644000175000017500000000140211503736226017505 0ustar sarnoldsarnold=pod =head1 NAME aa-decode - decode hex-encoded in AppArmor log files =head1 SYNOPSIS B [option] =head1 DESCRIPTION B will decode hex-encoded strings as seen in AppArmor log output. It will also take an audit log on standard input and convert any hex-encoded AppArmor log entries and display them on standard output. =head1 OPTIONS =over 4 =item --help displays a short usage statement. =back =head1 EXAMPLES $ aa-decode 2F746D702F666F6F20626172 Decoded: /tmp/foo bar $ cat /var/log/kern.log | aa-decode ... denied_mask="r::" fsuid=1000 ouid=1000 name=/tmp/foo bar =head1 BUGS None. Please report any you find to Launchpad at L. =head1 SEE ALSO apparmor(7) =cut apparmor-2.8.95~2430/utils/aa-enforce.pod0000644000175000017500000000372512306150527017711 0ustar sarnoldsarnold# This publication is intellectual property of Novell Inc. and Canonical # Ltd. Its contents can be duplicated, either in part or in whole, provided # that a copyright label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators # shall be held liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. SUSE LINUX GmbH # and Canonical Ltd. essentially adhere to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-enforce - set an AppArmor security profile to I mode from being disabled or I mode. =head1 SYNOPSIS BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] =head1 OPTIONS B<-d --dir / path/to/profiles> Specifies where to look for the AppArmor security profile set. Defaults to /etc/apparmor.d. =head1 DESCRIPTION B is used to set one or more profiles to I mode. This command is only relevant in conjunction with the I utility which sets a profile to complain mode and the I utility which unloads and disables a profile. The default mode for a security policy is enforce and the I utility must be run to change this behavior. =head1 BUGS If you find any bugs, please report them at L. =head1 SEE ALSO apparmor(7), apparmor.d(5), aa-complain(1), aa-disable(1), aa_change_hat(2), and L. =cut apparmor-2.8.95~2430/utils/python-tools-setup.py0000644000175000017500000000621712277076227021406 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (c) 2012 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Canonical, Ltd. # ---------------------------------------------------------------------- # # Usage: # $ python ./python-tools-setup.py install --root=... --version=... # # Note: --version=... must be the last argument to this script # from distutils.command.install import install as _install from distutils.core import setup import os import shutil import sys class Install(_install, object): '''Override distutils to install the files where we want them.''' def run(self): # Now byte-compile everything super(Install, self).run() prefix = self.prefix if self.root != None: prefix = self.root # Install scripts, configuration files and data scripts = ['/usr/bin/aa-easyprof'] self.mkpath(prefix + os.path.dirname(scripts[0])) for s in scripts: f = prefix + s # If we have a defined python version, use it instead of the system # default if 'PYTHON' in os.environ: lines = open(os.path.basename(s)).readlines() lines[0] = '#! /usr/bin/env %s\n' % os.environ['PYTHON'] open(f, 'w').write("".join(lines)) else: self.copy_file(os.path.basename(s), f) configs = ['easyprof/easyprof.conf'] self.mkpath(prefix + "/etc/apparmor") for c in configs: self.copy_file(c, os.path.join(prefix + "/etc/apparmor", os.path.basename(c))) data = ['easyprof/templates', 'easyprof/policygroups'] self.mkpath(prefix + "/usr/share/apparmor/easyprof") for d in data: self.copy_tree(d, os.path.join(prefix + "/usr/share/apparmor/easyprof", os.path.basename(d))) if os.path.exists('staging'): shutil.rmtree('staging') shutil.copytree('apparmor', 'staging') # Support the --version=... since this will be part of a Makefile version = "unknown-version" if "--version=" in sys.argv[-1]: version=sys.argv[-1].split('=')[1] sys.argv = sys.argv[0:-1] setup (name='apparmor', version=version, description='Python libraries for AppArmor utilities', long_description='Python libraries for AppArmor utilities', author='AppArmor Developers', author_email='apparmor@lists.ubuntu.com', url='https://launchpad.net/apparmor', license='GPL-2', cmdclass={'install': Install}, package_dir={'apparmor': 'staging'}, packages=['apparmor'], py_modules=['apparmor.easyprof'] ) shutil.rmtree('staging') apparmor-2.8.95~2430/utils/aa-notify0000755000175000017500000004647311647646422017044 0ustar sarnoldsarnold#!/usr/bin/perl # ------------------------------------------------------------------ # # Copyright (C) 2009-2011 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ # # /etc/apparmor/notify.conf: # # set to 'yes' to enable AppArmor DENIED notifications # show_notifications="yes" # # # only people in use_group can run this script # use_group="admin" # # $HOME/.apparmor/notify.conf can have: # # set to 'yes' to enable AppArmor DENIED notifications # show_notifications="yes" # use strict; use warnings; no warnings qw( once ); require LibAppArmor; require POSIX; require Time::Local; require File::Basename; use Getopt::Long; my %prefs; my $conf = "/etc/apparmor/notify.conf"; my $user_conf = "$ENV{HOME}/.apparmor/notify.conf"; my $notify_exe = "/usr/bin/notify-send"; my $notify_home = ""; my $notify_display = ""; my $last_exe = "/usr/bin/last"; my $ps_exe = "/bin/ps"; my $url = "https://wiki.ubuntu.com/DebuggingApparmor"; my $nobody_user = "nobody"; my $nobody_group = "nogroup"; sub readconf; sub parse_message; sub format_message; sub format_stats; sub kill_running_daemons; sub do_notify; sub show_since; sub do_last; sub do_show_messages; sub _error; sub _warn; sub _debug; sub exitscript; sub usage; # # Main script # # Clean environment $ENV{PATH} = "/bin:/usr/bin"; $ENV{SHELL} = "/bin/sh"; defined($ENV{IFS}) and $ENV{IFS} = ' \t\n'; my $prog = File::Basename::basename($0); if ($prog !~ /^[a-zA-Z0-9_\-]+$/) { print STDERR "ERROR: bad programe name '$prog'\n"; exitscript(1); } $> == $< or die "Cannot be suid\n"; $) == $( or die "Cannot be sgid\n"; my $login; our $orig_euid = $>; my $opt_d = ''; my $opt_display = ''; my $opt_h = ''; my $opt_l = ''; my $opt_p = ''; my $opt_v = ''; my $opt_f = ''; my $opt_s = 0; my $opt_u = ''; my $opt_w = 0; GetOptions( 'debug|d' => \$opt_d, 'display=s' => \$opt_display, 'help|h' => \$opt_h, 'since-last|l' => \$opt_l, 'poll|p' => \$opt_p, 'verbose|v' => \$opt_v, 'file|f=s' => \$opt_f, 'since-days|s=n' => \$opt_s, 'user|u=s' => \$opt_u, 'wait|w=n' => \$opt_w, ); if ($opt_h) { usage; exitscript(0); } # monitor file specified with -f, else use audit.log if auditd is running, # otherwise kern.log our $logfile = "/var/log/kern.log"; if ($opt_f) { -f $opt_f or die "'$opt_f' does not exist. Aborting\n"; $logfile = $opt_f; } else { -e "/var/run/auditd.pid" and $logfile = "/var/log/audit/audit.log"; } -r $logfile or die "Cannot read '$logfile'\n"; our $logfile_inode = get_logfile_inode($logfile); our $logfile_size = get_logfile_size($logfile); open (LOGFILE, "<$logfile") or die "Could not open '$logfile'\n"; # Drop priviliges, if running as root if ($< == 0) { $login = "root"; if (defined($ENV{SUDO_UID}) and defined($ENV{SUDO_GID})) { $) = "$ENV{SUDO_GID} $ENV{SUDO_GID}" or _error("Could not change egid"); $( = $ENV{SUDO_GID} or _error("Could not change gid"); $> = $ENV{SUDO_UID} or _error("Could not change euid"); defined($ENV{SUDO_USER}) and $login = $ENV{SUDO_USER}; } else { my $drop_to = $nobody_user; if ($opt_u) { $drop_to = $opt_u; } # nobody/nogroup my $nam = scalar(getgrnam($nobody_group)); $) = "$nam $nam" or _error("Could not change egid"); $( = $nam or _error("Could not change gid"); $> = scalar(getpwnam($drop_to)) or _error("Could not change euid to '$drop_to'"); } } else { $login = getlogin(); defined $login or $login = $ENV{'USER'}; } if (-s $conf) { readconf($conf); if (defined($prefs{use_group})) { my ($name, $passwd, $gid, $members) = getgrnam($prefs{use_group}); if (not defined($members) or not defined($login) or (not grep { $_ eq $login } split(/ /, $members) and $login ne "root")) { _error("'$login' must be in '$prefs{use_group}' group. Aborting.\nAsk your admin to add you to this group or to change the group in\n$conf if you want to use aa-notify."); } } } if ($opt_p) { -x "$notify_exe" or _error("Could not find '$notify_exe'. Please install libnotify-bin. Aborting"); # we need correct values for $HOME and $DISPLAY environment variables, # otherwise $notify_exe won't be able to connect to DBUS to display the # message. Do this here to avoid excessive lookups. $notify_home = (getpwuid $>)[7]; # homedir of the user if ($opt_display ne '') { $notify_display = $opt_display; } elsif (defined($ENV{'DISPLAY'})) { $notify_display = $ENV{'DISPLAY'}; } if ($notify_display eq '') { my $sudo_warn_msg = ''; if (defined($ENV{'SUDO_USER'})) { $sudo_warn_msg = ' (or reset by sudo)'; } _warn("Environment variable \$DISPLAY not set$sudo_warn_msg."); _warn ('Desktop notifications will not work.'); if ($sudo_warn_msg ne '') { _warn ('Use sudo aa-notify -p --display "$DISPLAY" to set the environment variable.'); } else { _warn ('Use something like aa-notify -p --display :0 to set the environment variable.') } } } elsif ($opt_l) { -x "$last_exe" or _error("Could not find '$last_exe'. Aborting"); } if ($opt_s and not $opt_l) { $opt_s =~ /^[0-9]+$/ or _error("-s requires a number"); } if ($opt_w) { $opt_w =~ /^[0-9]+$/ or _error("-w requires a number"); } if ($opt_p or $opt_l) { if (-s $user_conf) { readconf($user_conf); } if (defined($prefs{show_notifications}) and $prefs{show_notifications} ne "yes") { _debug("'show_notifications' is disabled. Exiting"); exitscript(0); } } my $now = time(); if ($opt_p) { do_notify(); } elsif ($opt_l) { do_last(); } elsif ($opt_s and not $opt_p) { do_show_messages($opt_s); } else { usage; exitscript(1); } exitscript(0); # # Subroutines # sub readconf { my $cfg = $_[0]; -r $cfg or die "'$cfg' does not exist\n"; open (CFG, "<$cfg") or die "Could not open '$cfg'\n"; while () { chomp; s/#.*//; # no comments s/^\s+//; # no leading white s/\s+$//; # no trailing white next unless length; # anything left? my ($var, $value) = split(/\s*=\s*/, $_, 2); if ($var eq "show_notifications" or $var eq "use_group") { $value =~ s/^"(.*)"$/$1/g; $prefs{$var} = $value; } } close(CFG); } sub parse_message { my @params = @_; my $msg = $params[0]; chomp($msg); #_debug("processing: $msg"); my ($test) = LibAppArmorc::parse_record($msg); # Don't show logs before certain date my $date = LibAppArmor::aa_log_record::swig_epoch_get($test); my $since = 0; if (defined($date) and $#params > 0 and $params[1] =~ /^[0-9]+$/) { $since = int($params[1]); int($date) >= $since or goto err; } # ignore all but status and denied messages my $type = LibAppArmor::aa_log_record::swig_event_get($test); $type == $LibAppArmor::AA_RECORD_DENIED or goto err; my $profile = LibAppArmor::aa_log_record::swig_profile_get($test); my $operation = LibAppArmor::aa_log_record::swig_operation_get($test); my $name = LibAppArmor::aa_log_record::swig_name_get($test); my $denied = LibAppArmor::aa_log_record::swig_denied_mask_get($test); my $family = LibAppArmor::aa_log_record::swig_net_family_get($test); my $sock_type = LibAppArmor::aa_log_record::swig_net_sock_type_get($test); LibAppArmorc::free_record($test); return ($profile, $operation, $name, $denied, $family, $sock_type, $date); err: LibAppArmorc::free_record($test); return (); } sub format_message { my ($profile, $operation, $name, $denied, $family, $sock_type, $date) = @_; my $formatted = ""; defined($profile) and $formatted .= "Profile: $profile\n"; defined($operation) and $formatted .= "Operation: $operation\n"; defined($name) and $formatted .= "Name: $name\n"; defined($denied) and $formatted .= "Denied: $denied\n"; defined($family) and defined ($sock_type) and $formatted .= "Family: $family\nSocket type: $sock_type\n"; $formatted .= "Logfile: $logfile\n"; return $formatted; } sub format_stats { my $num = $_[0]; my $time = $_[1]; if ($num > 0) { print "AppArmor denial"; $num > 1 and print "s"; print ": $num (since " . scalar(localtime($time)) . ")\n"; $opt_v and print "For more information, please see: $url\n"; } } sub kill_running_daemons { # Look for other daemon instances of this script and kill them. This # can happen on logout and back in (in which case $notify_exe fails # anyway). 'ps xw' should output something like: # 9987 ? Ss 0:01 /usr/bin/perl ./bin/aa-notify -p # 10170 ? Ss 0:00 /usr/bin/perl ./bin/aa-notify -p open(PS,"$ps_exe xw|") or die "Unable to run '$ps_exe':$!\n"; while() { chomp; /$prog -[ps]/ or next; s/^\s+//; my @line = split(/\s+/, $_); if ($line[5] =~ /$prog$/ and ($line[6] eq "-p" or $line[6] eq "-s")) { if ($line[0] != $$) { _warn("killing old daemon '$line[0]'"); kill 15, ($line[0]); } } } close(PS); } sub send_message { my $msg = $_[0]; my $pid = fork(); if ($pid == 0) { # child # notify-send needs $< to be the unprivileged user $< = $>; $notify_home ne "" and $ENV{'HOME'} = $notify_home; $notify_display ne "" and $ENV{'DISPLAY'} = $notify_display; # 'system' uses execvp() so no shell metacharacters here. # $notify_exe is an absolute path so execvp won't search PATH. system "$notify_exe", "-i", "gtk-dialog-warning", "-u", "critical", "--", "AppArmor Message", "$msg"; my $exit_code = $? >> 8; exit($exit_code); } # parent waitpid($pid, 0); return $?; } sub do_notify { my %seen; my $seconds = 5; our $time_to_die = 0; print "Starting aa-notify\n"; kill_running_daemons(); # Daemonize, but not if in debug mode if (not $opt_d) { chdir('/') or die "Can't chdir to /: $!"; umask 0; open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!"; #open STDERR, '>/dev/null' or die "Can't write to /dev/null: $!"; my $pid = fork(); exit if $pid; die "Couldn't fork: $!" unless defined($pid); POSIX::setsid() or die "Can't start a new session: $!"; } sub signal_handler { $time_to_die = 1; } $SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&signal_handler; $SIG{'PIPE'} = 'IGNORE'; if ($opt_w) { sleep($opt_w); } my $count = 0; my $footer = "For more information, please see:\n$url"; my $first_run = 1; my $since = $now; if ($opt_s and int($opt_s) > 0) { $since = $since - (int($opt_s) * 60 * 60 * 24); } for (my $i=0; $time_to_die == 0; $i++) { if ($logfile_inode != get_logfile_inode($logfile)) { _warn("$logfile changed inodes, reopening"); reopen_logfile(); } elsif (get_logfile_size($logfile) < $logfile_size) { _warn("$logfile is smaller, reopening"); reopen_logfile(); } while(my $msg = ) { my @attrib; if ($first_run == 1) { if ($since != $now) { @attrib = parse_message($msg, $since); } } else { @attrib = parse_message($msg); } $#attrib > 0 or next; if ($first_run == 1) { $count++; next; } my ($profile, $operation, $name, $denied, $family, $sock_type, $date) = @attrib; # Rate limit messages by creating a hash whose keys are: # - for files: $profile|$name|$denied| # - for everything else: $profile|$operation|$name|$denied|$family|$sock_type| (as available) # The value for the key is a timestamp (epoch) and we won't show # messages whose key has a timestamp from less than 5 seconds afo my $k = ""; defined($profile) and $k .= "$profile|"; if (defined($name) and defined($denied)) { $k .= "$name|$denied|"; # for file access, don't worry about operation } else { defined($operation) and $k .= "$operation|"; defined($name) and $k .= "$name|"; defined($denied) and $k .= "$denied|"; defined($family) and defined ($sock_type) and $k .= "$family|$sock_type|"; } # don't display same message if seen in last 5 seconds if (not defined($seen{$k})) { $seen{$k} = time(); } else { my $now = time(); $now - $seen{$k} < $seconds and next; $seen{$k} = $now; } my $m = format_message(@attrib); $m ne "" or next; $m .= $footer; my $rc = send_message($m); if ($rc != 0) { _warn("'$notify_exe' exited with error '$rc'"); $time_to_die = 1; last; } } # from seek() in Programming Perl seek(LOGFILE, 0, 1); sleep(1); if ($first_run) { if ($count > 0) { my $m = "$logfile contains $count denied message"; $count > 1 and $m .= "s"; if ($opt_s) { $m .= " in the last "; if ($opt_s > 1) { $m .= "$opt_s days"; } else { $m .= "day"; } } $m .= ". "; $m .= $footer; send_message($m); } $first_run = 0; } # clean out the %seen database every 30 seconds if ($i > 30) { foreach my $k (keys %seen) { my $now = time(); $now - $seen{$k} > $seconds and delete $seen{$k} and _debug("deleted $k"); } $i = 0; _debug("done purging"); foreach my $k (keys %seen) { _debug("remaining key: $k: $seen{$k}"); } } } print STDERR "Stopping aa-notify\n"; } sub show_since { my %msg_hash; my %last_date; my @msg_list; my $count = 0; while(my $msg = ) { my @attrib = parse_message($msg, $_[0]); $#attrib > 0 or next; my $m = format_message(@attrib); $m ne "" or next; my $date = $attrib[6]; if ($opt_v) { if (exists($msg_hash{$m})) { $msg_hash{$m}++; defined($date) and $last_date{$m} = scalar(localtime($date)); } else { $msg_hash{$m} = 1; push(@msg_list, $m); } } $count++; } if ($opt_v) { foreach my $m (@msg_list) { print "$m"; if ($msg_hash{$m} gt 1) { print "($msg_hash{$m} found"; if (exists($last_date{$m})) { print ", most recent from '$last_date{$m}'"; } print ")\n"; } print "\n"; } } return $count; } sub do_last { open(LAST,"$last_exe -F -a $login|") or die "Unable to run $last_exe:$!\n"; my $time = 0; while(my $line = ) { _debug("Checking '$line'"); $line =~ /^$login/ or next; $line !~ /^$login\s+pts.*\s+:[0-9]+\.[0-9]+$/ or next; # ignore xterm and friends my @entry = split(/\s+/, $line); my ($hour, $min, $sec) = (split(/:/, $entry[5]))[0,1,2]; $time = Time::Local::timelocal($sec, $min, $hour, $entry[4], $entry[3], $entry[6]); last; } close(LAST); $time > 0 or _error("Couldn't find last login"); format_stats(show_since($time), $time); } sub do_show_messages { my $since = $now - (int($_[0]) * 60 * 60 * 24); format_stats(show_since($since), $since); } sub _warn { my $msg = $_[0]; print STDERR "aa-notify: WARN: $msg\n"; } sub _error { my $msg = $_[0]; print STDERR "aa-notify: ERROR: $msg\n"; exitscript(1); } sub _debug { $opt_d or return; my $msg = $_[0]; print STDERR "aa-notify: DEBUG: $msg\n"; } sub exitscript { my $rc = $_[0]; close(LOGFILE); exit $rc; } sub usage { my $s = <<'EOF'; USAGE: aa-notify [OPTIONS] Display AppArmor notifications or messages for DENIED entries. OPTIONS: -p, --poll poll AppArmor logs and display notifications --display $DISPLAY set the DISPLAY environment variable to $DISPLAY (might be needed if sudo resets $DISPLAY) -f FILE, --file=FILE search FILE for AppArmor messages -l, --since-last display stats since last login -s NUM, --since-days=NUM show stats for last NUM days (can be used alone or with -p) -v, --verbose show messages with stats -h, --help display this help -u USER, --user=USER user to drop privileges to when not using sudo -w NUM, --wait=NUM wait NUM seconds before displaying notifications (with -p) EOF print $s; } sub raise_privileges { my $old_euid = -1; if ($> != $<) { _debug("raising privileges to '$orig_euid'"); $old_euid = $>; $> = $orig_euid; $> == $orig_euid or die "Could not raise privileges\n"; } return $old_euid; } sub drop_privileges { my $old_euid = $_[0]; # Just exit if we didn't raise privileges $old_euid == -1 and return; _debug("dropping privileges to '$old_euid'"); $> = $old_euid; $> == $old_euid or die "Could not drop privileges\n"; } sub reopen_logfile { # reopen the logfile, temporarily switching back to starting euid for # file permissions. close(LOGFILE); my $old_euid = raise_privileges(); $logfile_inode = get_logfile_inode($logfile); $logfile_size = get_logfile_size($logfile); open (LOGFILE, "<$logfile") or die "Could not open '$logfile'\n"; drop_privileges($old_euid); } sub get_logfile_size { my $fn = $_[0]; my $size; my $dir = File::Basename::dirname($fn); # If we can't access the file, then raise privs. This can happen when # using auditd and /var/log/audit/ is 700. my $old_euid = -1; if (! -x $dir) { $old_euid = raise_privileges(); } defined(($size = (stat($fn))[7])) or (sleep(10) and defined(($size = (stat($fn))[7])) or die "'$fn' disappeared. Aborting\n"); drop_privileges($old_euid); return $size; } sub get_logfile_inode { my $fn = $_[0]; my $inode; my $dir = File::Basename::dirname($fn); # If we can't access the file, then raise privs. This can happen when # using auditd and /var/log/audit/ is 700. my $old_euid = -1; if (! -x $dir) { $old_euid = raise_privileges(); } defined(($inode = (stat($fn))[1])) or (sleep(10) and defined(($inode = (stat($fn))[1])) or die "'$fn' disappeared. Aborting\n"); drop_privileges($old_euid); return $inode; } # # end Subroutines # apparmor-2.8.95~2430/utils/aa-notify.pod0000644000175000017500000000515511622734274017606 0ustar sarnoldsarnold# This publication is intellectual property of Canonical Ltd. Its contents # can be duplicated, either in part or in whole, provided that a copyright # label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither Canonical Ltd, the authors, nor the translators shall be held # liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. Canonical Ltd # essentially adheres to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-notify - display information about logged AppArmor messages. =head1 SYNOPSIS B [option] =head1 DESCRIPTION B will display a summary or provide desktop notifications for AppArmor DENIED messages. =head1 OPTIONS B accepts the following arguments: =over 4 =item -p, --poll poll AppArmor logs and display desktop notifications. Can be used with '-s' option to display a summary on startup. =item -f FILE, --file=FILE search FILE for AppArmor messages =item -l, --since-last show summary since last login. =item -s NUM, --since-days=NUM show summary for last NUM of days. =item -u USER, --user=USER user to drop privileges to when running privileged. When used with the -p option, this should be set to the user that will receive desktop notifications. This has no effect when running under sudo. =item -w NUM, --wait=NUM wait NUM seconds before displaying notifications (for use with -p) =item -v, --verbose show messages with summaries. =item -h, --help displays a short usage statement. =back =head1 CONFIGURATION System-wide configuration for B is done via /etc/apparmor/notify.conf: # set to 'yes' to enable AppArmor DENIED notifications show_notifications="yes" # only people in use_group can use aa-notify use_group="admin" Per-user configuration is done via ~/.apparmor/notify.conf: # set to 'yes' to enable AppArmor DENIED notifications show_notifications="yes" =head1 BUGS B needs to be able to read the logfiles containing the AppArmor DENIED messages. If you find any additional bugs, please report them to Launchpad at L. =head1 SEE ALSO apparmor(7) =cut apparmor-2.8.95~2430/utils/aa-unconfined0000755000175000017500000000704612303740745017646 0ustar sarnoldsarnold#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import os import re import sys import apparmor.aa as aa import apparmor.ui as ui import apparmor.common # setup module translations from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_("Lists unconfined processes having tcp or udp ports")) parser.add_argument("--paranoid", action="store_true", help=_("scan all processes from /proc")) args = parser.parse_args() paranoid = args.paranoid aa_mountpoint = aa.check_for_apparmor() if not aa_mountpoint: raise aa.AppArmorException(_("It seems AppArmor was not started. Please enable AppArmor and try again.")) pids = [] if paranoid: pids = list(filter(lambda x: re.search(r"^\d+$", x), aa.get_subdirectories("/proc"))) else: regex_tcp_udp = re.compile(r"^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)") import subprocess if sys.version_info < (3, 0): output = subprocess.check_output("LANG=C netstat -nlp", shell=True).split("\n") else: #Python3 needs to translate a stream of bytes to string with specified encoding output = str(subprocess.check_output("LANG=C netstat -nlp", shell=True), encoding='utf8').split("\n") for line in output: match = regex_tcp_udp.search(line) if match: pids.append(match.groups()[4]) # We can safely remove duplicate pid's? pids = list(map(int, set(pids))) for pid in sorted(pids): try: prog = os.readlink("/proc/%s/exe"%pid) except OSError: continue attr = None if os.path.exists("/proc/%s/attr/current"%pid): with aa.open_file_read("/proc/%s/attr/current"%pid) as current: for line in current: if line.startswith("/") or line.startswith("null"): attr = line.strip() cmdline = apparmor.common.cmd(["cat", "/proc/%s/cmdline"%pid])[1] pname = cmdline.split("\0")[0] if '/' in pname and pname != prog: pname = "(%s)"% pname else: pname = "" regex_interpreter = re.compile(r"^(/usr)?/bin/(python|perl|bash|dash|sh)$") if not attr: if regex_interpreter.search(prog): cmdline = re.sub(r"\x00", " ", cmdline) cmdline = re.sub(r"\s+$", "", cmdline).strip() ui.UI_Info(_("%s %s (%s) not confined")%(pid, prog, cmdline)) else: if pname and pname[-1] == ')': pname += ' ' ui.UI_Info(_("%s %s %snot confined")%(pid, prog, pname)) else: if regex_interpreter.search(prog): cmdline = re.sub(r"\0", " ", cmdline) cmdline = re.sub(r"\s+$", "", cmdline).strip() ui.UI_Info(_("%s %s (%s) confined by '%s'")%(pid, prog, cmdline, attr)) else: if pname and pname[-1] == ')': pname += ' ' ui.UI_Info(_("%s %s %sconfined by '%s'")%(pid, prog, pname, attr)) apparmor-2.8.95~2430/utils/aa-decode0000755000175000017500000000501212070632670016726 0ustar sarnoldsarnold#!/bin/bash # # Copyright (C) 2009-2010, 2012 Canonical Ltd. # Copyright (C) 2012 Christian Boltz # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Canonical, Ltd. # set -e help() { cat < Decode a hex-encoded string to ASCII. It will also take an audit log on standard input and convert any hex-encoded AppArmor log entries and display them on standard output. OPTIONS: --help display this help EXAMPLES: $ aa-decode 2F746D702F666F6F20626172 Decoded: /tmp/foo bar $ cat /var/log/kern.log | aa-decode ... denied_mask="r::" fsuid=1000 ouid=1000 name=/tmp/foo bar EOM } decode() { decoded=`perl -le "\\$s = uc('$1') ; if (\\$s =~ /^[0-9A-F]*$/) { print pack 'H*', \\$s; }"` echo "$decoded" } if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then help exit fi # if have an argument, then use it, otherwise process stdin if [ -n "$1" ]; then e="$1" if ! echo "$e" | egrep -q "^[0-9A-Fa-f]+$" ; then echo "String should only contain hex characters (0-9, a-f, A-F)" exit 1 fi d=`decode $e` if [ -z "$d" ]; then echo "Could not decode string" exit 1 fi echo "Decoded: $d" exit 0 fi # For now just look at 'name=...' and 'profile=...', # so validate input against this and output based on it. # TODO: better handle other cases too while read line ; do # check if line contains encoded name= or profile= if [[ "$line" =~ \ (name|profile)=[0-9a-fA-F] ]]; then # cut the encoded filename/profile name out of the line and decode it ne=`echo "$line" | sed 's/.* name=\([^ ]*\).*$/\\1/g'` nd="$(decode ${ne/\'/\\\'})" pe=`echo "$line" | sed 's/.* profile=\([^ ]*\).*$/\\1/g'` pd="$(decode ${pe/\'/\\\'})" # replace encoded name and profile with its decoded counterparts (only if it was encoded) test -n "$nd" && line="${line/name=$ne/name=\"$nd\"}" test -n "$pd" && line="${line/profile=$pe/profile=\"$pd\"}" fi echo "$line" done apparmor-2.8.95~2430/utils/aa-disable0000755000175000017500000000222412305172074017106 0ustar sarnoldsarnold#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import apparmor.tools # setup module translations from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_('Disable the profile for the given programs')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() tool = apparmor.tools.aa_tools('disable', args) tool.cmd_disable() apparmor-2.8.95~2430/utils/aa-sandbox0000755000175000017500000000202712266162463017151 0ustar sarnoldsarnold#! /usr/bin/env python # ------------------------------------------------------------------ # # Copyright (C) 2012 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ import apparmor.sandbox from apparmor.common import error import optparse import sys if __name__ == "__main__": argv = sys.argv parser = optparse.OptionParser() apparmor.easyprof.add_parser_policy_args(parser) (opt, args) = apparmor.sandbox.parse_args(sys.argv, parser) if len(args) < 1: error("Must specify binary") binary = args[0] if not apparmor.sandbox.check_requirements(binary): sys.exit(1) if opt.withx: rc, report = apparmor.sandbox.run_xsandbox(args, opt) else: rc, report = apparmor.sandbox.run_sandbox(args, opt) apparmor.common.msg(report) sys.exit(rc) apparmor-2.8.95~2430/utils/README.md0000644000175000017500000000024312277004630016454 0ustar sarnoldsarnoldKnown Bugs: Will allow multiple letters in the () due to translation/unicode issues with regexing the key. User input will probably bug out in a different locale. apparmor-2.8.95~2430/utils/apparmor-utils.spec.in0000644000175000017500000003336011503736226021447 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (c) 2004, 2005 NOVELL (All rights reserved) # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # ---------------------------------------------------------------------- # norootforbuild %if ! %{?distro:1}0 %define distro suse %endif Summary: AppArmor userlevel utilities that are useful in creating AppArmor profiles. Name: apparmor-utils Version: @@immunix_version@@ Release: @@repo_version@@ Group: Productivity/Security Source0: %{name}-%{version}-@@repo_version@@.tar.gz License: GPL BuildRoot: %{?_tmppath:}%{!?_tmppath:/var/tmp}/%{name}-%{version}-build BuildArch: noarch Url: http://forge.novell.com/modules/xfmod/project/?apparmor %if %{distro} == "rhel4" # Bleah, on RHEL4, individual requirements on perl modules are # calculated, even if the modules are conditionally used. Requires: perl Requires: /bin/sh AutoReqProv: no Requires: perl-DateManip %else Requires: perl-TimeDate %endif Requires: perl-DBI perl-DBD-SQLite perl-File-Tail perl-gettext perl-RPC-XML perl-TermReadKey perl-libapparmor Obsoletes: subdomain-utils Provides: subdomain-utils %if 0%{?suse_version} > 1120 # openSUSE 11.3 no longer has /usr/lib/perl5/vendor_perl in @INC BuildRequires: perl-macros Requires: perl = %{perl_version} %define aa_perl_vendorlib %{perl_vendorlib} %else %define aa_perl_vendorlib %{_prefix}/lib/perl5/vendor_perl %endif %description This provides some useful programs to help create and manage AppArmor profiles. This package is part of a suite of tools that used to be named SubDomain. %prep %setup -q %build [ "${RPM_BUILD_ROOT}" != "/" ] && rm -rf ${RPM_BUILD_ROOT} %install [ "${RPM_BUILD_ROOT}" != "/" ] && rm -rf ${RPM_BUILD_ROOT} make install DESTDIR=${RPM_BUILD_ROOT} DISTRO=%{distro} \ BINDIR=${RPM_BUILD_ROOT}%{_prefix}/sbin/ \ PERLDIR=${RPM_BUILD_ROOT}%{aa_perl_vendorlib}/Immunix \ MANDIR=%{_mandir} %clean [ "${RPM_BUILD_ROOT}" != "/" ] && rm -rf ${RPM_BUILD_ROOT} %files %defattr(-,root,root) %config /etc/apparmor/* %{_prefix}/sbin/* %{aa_perl_vendorlib}/* %{_prefix}/share/locale/*/*/apparmor-utils.mo %dir /var/log/apparmor %dir /etc/apparmor %{_mandir}/man*/* %doc *.[0-9].html %doc common/apparmor.css %preun if [ -x "/usr/sbin/sd-event-dispatch.pl" -a -e "/var/run/sd-event-dispatch.init.pid" ] ; then echo "Shutting down SubDomain Event daemon" ; /sbin/killproc -p /var/run/sd-event-disptach.init.pid -TERM /usr/sbin/sd-event-dispatch.pl >& /dev/null ; fi # only do the following when uninstalling if [ "$1" = 0 ] ; then if [ -x "/usr/sbin/aa-eventd" -a -e "/var/run/aa-eventd.pid" ] ; then echo "Shutting down AppArmor Event daemon" ; /sbin/killproc -p /var/run/aa-eventd.pid -TERM /usr/sbin/aa-eventd >& /dev/null; fi fi %changelog * Wed Jul 7 22:18:14 UTC 2010 - opensuse@cboltz.de - change perl module path to perl_vendorlib macro (hardcoded path broke, bnc#619893) * Tue Nov 6 2007 - dominic_r@mercenarylinux.com - (Merged from trunk -r1015) Added handling to correctly check the result of the profile development run and reset the profile mode to enforce when the profile development run exits without an error. Addresses novell bug: https://bugzilla.novell.com/show_bug.cgi?id=328045 * Tue Nov 6 2007 - dominic_r@mercenarylinux.com - (Merged from trunk -r1014) Ignore complain flags when up|down loading profiles to|from the repository. This makes the repository agnostic to profile mode (complain/enforce) - users must manage this locally via aa-complain/aa-enforce. Addresses novell bug: https://bugzilla.novell.com/show_bug.cgi?id=328033 * Tue Nov 6 2007 - dominic_r@mercenarylinux.com - (Merged from trunk -r 1013) Modified code to check the repository for new profile when: - processing an unknown hat/execute rejection if its not already in the profile - at the start of processing all the remain events for the profile Addresses novell bug: https://bugzilla.novell.com/show_bug.cgi?id=328707 * Tue Nov 6 2007 - dominic_r@mercenarylinux.com - (Merged from trunk) Updated regex used to detect syslog messages (from bug reported against Ubuntu gutsy) * Wed Oct 17 2007 - dominic_r@mercenarylinux.com - Maintenance branch for AppArmor 2.1 * Fri Sep 28 2007 - ddrewelow@suse.de - Fix for bug #329476. The mode validation regexp has been updated to support additional values. * Fri Sep 28 2007 - dreynolds@suse.de - Don't try to read inactive profile directory if it doesn't exist. Fix based on feedback from mathiaz@ubuntu.com and from bug report: https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/141128. * Sun Sep 16 2007 - dreynolds@suse.de - Change the default repository to http://apparmor.opensuse.org/backend/api - the host for the production repository. * Sun Sep 16 2007 - dreynolds@suse.de - Update to log parsing to correctly unpack the hex encoded values passed from the module: name, name2, and profile. (fix from jmichael@suse.de) * Sun Sep 16 2007 - dreynolds@suse.de - Remove the confirmation prompt for confirm_and_finish - this was a duplicate prompt after the repository changes to save_profiles. * Sun Sep 16 2007 - dreynolds@suse.de - Add support for network toggles, append, and locking to the YaST2 EditProfile wizard. * Fri Sep 14 2007 - ddrewelow@suse.de - Fixes (#310454) to support new audit log format and new libapparmor1. * Mon Sep 10 2007 - dreynolds@suse.de - Update the logfile parsing in the tools to support syslog (in addition to LAF) as a source of AppArmor module messages. * Mon Sep 10 2007 - dreynolds@suse.de - Very basic user feedback when connections are made to a remote repository. For genprof/logprof just report "Connecting to the repository". For yast display a dialog. * Mon Sep 10 2007 - dreynolds@suse.de - Update the eventd schema to support the mode_deny mode_req types reported by the latest apparmor module in its rejection/permitting messages. * Mon Aug 20 2007 - ddrewelow@suse.de - Updated requirements for changes to libapparmor. * Mon Aug 20 2007 - ddrewelow@suse.de - Updated sqlite db and related functions to support new parser and libapparmor. * Fri Aug 17 2007 - dreynolds@suse.de - Missing shortcut fixes for german. * Fri Aug 17 2007 - dreynolds@suse.de - Minor changes to ensure that removal of the repository section in logprof.conf disables repository integration. * Fri Aug 17 2007 - dreynolds@suse.de - Remove default/required hats for ssh in logprof.conf * Thu Aug 16 2007 - dreynolds@suse.de - Fixes for "mandatory profile not found" profiling bug, empty configs in logprof.conf generating undefined value errors, repository code prompting user even if no configuration is present that specifies a default repository. * Thu Aug 16 2007 - dreynolds@suse.de - Updated translations for missing shortcuts in msgstr fields. * Wed Aug 15 2007 - seth_arnold@suse.de - new audit manpage from Mathias Gug * Wed Aug 15 2007 - dreynolds@suse.de - utitlity to look for problems in the po files. * Wed Aug 15 2007 - dreynolds@suse.de - Fix problems with missing hotkey for "(S)can for SubDomain Events" in genprof. Replace occurances of SubDomain in msgstr with AppArmor. * Wed Aug 15 2007 - dreynolds@suse.de - Added support for capablities and network toggles in #includes. * Tue Aug 14 2007 - sbeattie@suse.de - Support configurable locations of logger * Tue Apr 3 2007 - sbeattie@suse.de - Add manpages to package * Fri Mar 23 2007 - sbeattie@suse.de - ignore emacs backup files, make consistent with initscript * Wed Jan 17 2007 - sbeattie@suse.de - Fall back to Date::Manip if Date::Parse is not available * Wed Jan 17 2007 - sbeattie@suse.de - Add perl-gettext to list of dependencies * Tue Dec 12 2006 - sbeattie@suse.de - Add ksh to list of shells that should not be profiled * Thu Oct 5 2006 - 2.0-7 - add support syntax checking for profiles. * Thu Jun 01 2006 - jmichael@suse.de - add support for the new m mode (#175388) - add support for the new Px/Ux modes (#172061) - make aaeventd process all of the events in the log file, not just those that occur after it's already running. (#154239) - look for the changing_profile hint on the next AppArmor or audit line in the log file, not strictly the very next in the file. (#175421) * Wed Apr 12 2006 Steve Beattie - Move to novell forge svn; fixup build issues due to new svn layout * Mon Apr 10 2006 Steve Beattie - Get rid of obsolete license files * Sun Mar 26 2006 Jesse Michael - Move vim syntax file to the vim package * Fri Mar 3 2006 Steve Beattie 2.0-4 - switch to use perl-File-Tail and monitor both syslog and audit.log * Fri Mar 3 2006 Seth Arnold 2.0-4.1 - Add /srv to severity.db #153313 * Fri Feb 10 2006 Dominic Reynolds 2.0-3.11 - Ignore vsdo lib output from ldd for autodep - Include counter (and time) in genprof logmark * Sun Feb 5 2006 Steve Beattie 2.0-3.10 - Only kill aa-eventd on uninstall, not for upgrades - (jmichael) in reporting, enable/start aaeventd if not already - Fix signal handling problems when being shutdown * Mon Jan 30 2006 Seth Arnold 2.0-3.7 - aa-audit, aa-autodep, aa-status symlinks * Fri Jan 27 2006 Dominic Reynolds 2.0-3.6 - Changes to work with profiles located under /etc/apparmor.d. - disable AALite check - srarnold: don't drop variables * Thu Jan 26 2006 Steve Beattie 2.0-3.3 - Fix SubDomain.pm and apparmor_status to deal with module/parser renaming * Wed Jan 25 2006 Dominic Reynolds 2.0-3.2 - Renaming changes: apparmor_status, apparmor.vim, aa-eventd, and aa- prefix for profile utils * Sun Jan 22 2006 Dominic Reynolds 2.0-3.1 - Added support for read events from the audit system to: genprof/logprof and sd-event-dispatch.pl * Wed Jan 4 2006 Steve Beattie 2.0-3 - Add svn repo to tarball * Thu Dec 8 2005 Steve Beattie 2.0-2 - drewelow: fix parsing/sql errors in reports bug #137742 - dreynolds: fix unconfined to support securityfs - jmichael: fix for xattr handling in genprof/logprof - jmichael: fix genprof/logprof to support securityfs - fix textdomain() calls to reflect new package name - fix references to old package name within .po files * Wed Dec 7 2005 Steve Beattie 2.0-1 - Reset version for SUSE autobuild inclusion * Wed Nov 30 2005 Steve Beattie 1.2-26 - Rename package to apparmor-utils * Wed Nov 30 2005 Jesse Michael 1.2-25_imnx - Change license to GPL * Tue Nov 29 2005 Steve Beattie 1.2-24_imnx - Add subdomain_status script * Wed Nov 2 2005 Jesse Michael 1.2-23_imnx - make autodep, logprof, and genprof ignore any user specified $PATH settings * Wed Oct 19 2005 Jesse Michael 1.2-22_imnx - sd-event-dispatch.pl now under rcsubdomain control * Thu Sep 8 2005 Steve Beattie 1.2-21_imnx - sd-event-dispatch.pl now under rcsubdomain control * Wed Sep 2 2005 Jesse Michael 1.2-20_imnx - handle access(directory, X_OK) correctly again - stop event dispatcher if we uninstall the rpm - fix bug where event dispatcher wasn't inserting events in db sometimes * Wed Sep 1 2005 Jesse Michael 1.2-19_imnx - fork/exec tracking and localization fixes * Wed Aug 17 2005 Jesse Michael 1.2-18_imnx - move perl modules from site_perl to vendor_perl * Fri Aug 5 2005 Dominic Reynolds 1.2-17_imnx - buildcache path change * Mon May 23 2005 Steve beattie 1.2-12_imnx - "Fix" the perl module dependencies for RHEL4 * Mon May 16 2005 David Drewelow 1.2-9_imnx - Changed /etc/immunix to /etc/apparmor * Sun Apr 10 2005 Seth Arnold 1.2-9_imnx - Merge i18n support from 1.1 * Wed Mar 9 2005 Steve Beattie 1.2-3_imnx - Fix some internal handling around % distro * Mon Feb 14 2005 Steve Beattie 1.2-2_imnx - Add support for FC3/RHEL4 era redhat distros * Fri Feb 4 2005 Seth Arnold 1.2-1_imnx - Reversion to 1.2 * Thu Nov 11 2004 Steve Beattie 1.1-4_imnx - Support different toolsets on a per distro basis * Wed Nov 10 2004 Steve Beattie 1.1-3_imnx - Merge in new-style build support. * Tue Oct 12 2004 Steve Beattie 1.1-1_imnx - re-version package for shass-1.1 release * Mon Aug 30 2004 Steve Beattie 7.3-30_imnx - Fix up various copyright notices. * Fri Jul 23 2004 Steve Beattie 7.3-25_imnx - Red Hat 9 still uses vim 6.1 * Wed Jul 21 2004 Steve Beattie 7.3-24_imnx - Added the dependency on perl-TermReadKey * Wed Jul 21 2004 Steve Beattie 7.3-23_imnx - first attempt to make rpm cross-distro * Thu May 6 2004 Steve Beattie 7.3-14.SUSE_imnx - Fix to unconfined due to different ls output * Thu May 6 2004 Steve Beattie 7.3-11.SUSE_imnx - updates to autodep, logprof, and unconfined. * Mon May 3 2004 Seth Arnold 7.3-10.SUSE_imnx - Fix up the description text to no longer mention /usr/libexec/subdomain * Mon Apr 26 2004 Steve Beattie 7.3-9.SUSE_imnx - Added logprof.pl apparmor-2.8.95~2430/utils/apparmor/0000755000175000017500000000000012311706721017016 5ustar sarnoldsarnoldapparmor-2.8.95~2430/utils/apparmor/config.py0000644000175000017500000003144712277004630020647 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- from __future__ import with_statement import os import shlex import shutil import stat import sys import tempfile if sys.version_info < (3, 0): import ConfigParser as configparser # Class to provide the object[section][option] behavior in Python2 class configparser_py2(configparser.ConfigParser): def __getitem__(self, section): section_val = self.items(section) section_options = dict() for option, value in section_val: section_options[option] = value return section_options else: import configparser from apparmor.common import AppArmorException, open_file_read # , warn, msg, # CFG = None # REPO_CFG = None # SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf'] class Config(object): def __init__(self, conf_type, conf_dir='/etc/apparmor'): self.CONF_DIR = conf_dir # The type of config file that'll be read and/or written if conf_type == 'shell' or conf_type == 'ini': self.conf_type = conf_type self.input_file = None else: raise AppArmorException("Unknown configuration file type") def new_config(self): if self.conf_type == 'shell': config = {'': dict()} elif self.conf_type == 'ini': config = configparser.ConfigParser() return config def read_config(self, filename): """Reads the file and returns a config[section][attribute]=property object""" # LP: Bug #692406 # Explicitly disabled repository filepath = self.CONF_DIR + '/' + filename self.input_file = filepath if filename == "repository.conf": config = dict() config['repository'] = {'enabled': 'no'} elif self.conf_type == 'shell': config = self.read_shell(filepath) elif self.conf_type == 'ini': if sys.version_info > (3, 0): config = configparser.ConfigParser() else: config = configparser_py2() # Set the option form to string -prevents forced conversion to lowercase config.optionxform = str if sys.version_info > (3, 0): config.read(filepath) else: try: config.read(filepath) except configparser.ParsingError: tmp_filepath = py2_parser(filepath) config.read(tmp_filepath.name) ##config.__get__() return config def write_config(self, filename, config): """Writes the given config to the specified file""" filepath = self.CONF_DIR + '/' + filename permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write try: # Open a temporary file in the CONF_DIR to write the config file config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=self.CONF_DIR) if os.path.exists(self.input_file): # Copy permissions from an existing file to temporary file shutil.copymode(self.input_file, config_file.name) else: # If no existing permission set the file permissions as 0600 os.chmod(config_file.name, permission_600) if self.conf_type == 'shell': self.write_shell(filepath, config_file, config) elif self.conf_type == 'ini': self.write_configparser(filepath, config_file, config) config_file.close() except IOError: raise AppArmorException("Unable to write to %s" % filename) else: # Replace the target config file with the temporary file os.rename(config_file.name, filepath) def find_first_file(self, file_list): """Returns name of first matching file None otherwise""" filename = None for f in file_list.split(): if os.path.isfile(f): filename = f break return filename def find_first_dir(self, dir_list): """Returns name of first matching directory None otherwise""" dirname = None if dir_list: for direc in dir_list.split(): if os.path.isdir(direc): dirname = direc break return dirname def read_shell(self, filepath): """Reads the shell type conf files and returns config[''][option]=value""" config = {'': dict()} with open_file_read(filepath) as conf_file: for line in conf_file: result = shlex.split(line, True) # If not a comment of empty line if result: # option="value" or option=value type if '=' in result[0]: option, value = result[0].split('=') # option type else: option = result[0] value = None config[''][option] = value return config def write_shell(self, filepath, f_out, config): """Writes the config object in shell file format""" # All the options in the file options = [key for key in config[''].keys()] # If a previous file exists modify it keeping the comments if os.path.exists(self.input_file): with open_file_read(self.input_file) as f_in: for line in f_in: result = shlex.split(line, True) # If line is not empty or comment if result: # If option=value or option="value" type if '=' in result[0]: option, value = result[0].split('=') if '#' in line: comment = value.split('#', 1)[1] comment = '#' + comment else: comment = '' # If option exists in the new config file if option in options: # If value is different if value != config[''][option]: value_new = config[''][option] if value_new is not None: # Update value if '"' in line: value_new = '"' + value_new + '"' line = option + '=' + value_new + comment + '\n' else: # If option changed to option type from option=value type line = option + comment + '\n' f_out.write(line) # Remove from remaining options list options.remove(option) else: # If option type option = result[0] value = None # If option exists in the new config file if option in options: # If its no longer option type if config[''][option] is not None: value = config[''][option] line = option + '=' + value + '\n' f_out.write(line) # Remove from remaining options list options.remove(option) else: # If its empty or comment copy as it is f_out.write(line) # If any new options are present if options: for option in options: value = config[''][option] # option type entry if value is None: line = option + '\n' # option=value type entry else: line = option + '=' + value + '\n' f_out.write(line) def write_configparser(self, filepath, f_out, config): """Writes/updates the given file with given config object""" # All the sections in the file sections = config.sections() write = True section = None options = [] # If a previous file exists modify it keeping the comments if os.path.exists(self.input_file): with open_file_read(self.input_file) as f_in: for line in f_in: # If its a section if line.lstrip().startswith('['): # If any options from preceding section remain write them if options: for option in options: line_new = ' ' + option + ' = ' + config[section][option] + '\n' f_out.write(line_new) options = [] if section in sections: # Remove the written section from the list sections.remove(section) section = line.strip()[1:-1] if section in sections: # enable write for all entries in that section write = True options = config.options(section) # write the section f_out.write(line) else: # disable writing until next valid section write = False # If write enabled elif write: value = shlex.split(line, True) # If the line is empty or a comment if not value: f_out.write(line) else: option, value = line.split('=', 1) try: # split any inline comments value, comment = value.split('#', 1) comment = '#' + comment except ValueError: comment = '' if option.strip() in options: if config[section][option.strip()] != value.strip(): value = value.replace(value, config[section][option.strip()]) line = option + '=' + value + comment f_out.write(line) options.remove(option.strip()) # If any options remain from the preceding section if options: for option in options: line = ' ' + option + ' = ' + config[section][option] + '\n' f_out.write(line) options = [] # If any new sections are present if section in sections: sections.remove(section) for section in sections: f_out.write('\n[%s]\n' % section) options = config.options(section) for option in options: line = ' ' + option + ' = ' + config[section][option] + '\n' f_out.write(line) def py2_parser(filename): """Returns the de-dented ini file from the new format ini""" tmp = tempfile.NamedTemporaryFile('rw') f_out = open(tmp.name, 'w') if os.path.exists(filename): with open_file_read(filename) as f_in: for line in f_in: # The ini format allows for multi-line entries, with the subsequent # entries being indented deeper hence simple lstrip() is not appropriate if line[:2] == ' ': line = line[2:] elif line[0] == '\t': line = line[1:] f_out.write(line) f_out.flush() return tmp apparmor-2.8.95~2430/utils/apparmor/ui.py0000644000175000017500000003460512302721754020020 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import sys import re from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast from apparmor.common import readkey, AppArmorException, DebugLogger # setup module translations from apparmor.translations import init_translation _ = init_translation() # Set up UI logger for separate messages from UI module debug_logger = DebugLogger('UI') # The operating mode: yast or text, text by default UI_mode = 'text' ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'} def getkey(): key = readkey() if key == '\x1B': key = readkey() if key == '[': key = readkey() if(ARROWS.get(key, False)): key = ARROWS[key] return key.strip() def UI_Info(text): debug_logger.info(text) if UI_mode == 'text': sys.stdout.write(text + '\n') else: yastLog(text) def UI_Important(text): debug_logger.debug(text) if UI_mode == 'text': sys.stdout.write('\n' + text + '\n') else: SendDataToYast({'type': 'dialog-error', 'message': text }) path, yarg = GetDataFromYast() def get_translated_hotkey(translated, cmsg=''): msg = 'PromptUser: ' + _('Invalid hotkey for') # Originally (\S) was used but with translations it would not work :( if re.search('\((\S+)\)', translated, re.LOCALE): return re.search('\((\S+)\)', translated, re.LOCALE).groups()[0] else: if cmsg: raise AppArmorException(cmsg) else: raise AppArmorException('%s %s' % (msg, translated)) def UI_YesNo(text, default): debug_logger.debug('UI_YesNo: %s: %s %s' % (UI_mode, text, default)) default = default.lower() ans = None if UI_mode == 'text': yes = _('(Y)es') no = _('(N)o') yeskey = get_translated_hotkey(yes).lower() nokey = get_translated_hotkey(no).lower() ans = 'XXXINVALIDXXX' while ans not in ['y', 'n']: sys.stdout.write('\n' + text + '\n') if default == 'y': sys.stdout.write('\n[%s] / %s\n' % (yes, no)) else: sys.stdout.write('\n%s / [%s]\n' % (yes, no)) ans = getkey() if ans: # Get back to english from localised answer ans = ans.lower() if ans == yeskey: ans = 'y' elif ans == nokey: ans = 'n' elif ans == 'left': default = 'y' elif ans == 'right': default = 'n' else: ans = 'XXXINVALIDXXX' continue # If user presses any other button ask again else: ans = default else: SendDataToYast({'type': 'dialog-yesno', 'question': text }) ypath, yarg = GetDataFromYast() ans = yarg['answer'] if not ans: ans = default return ans def UI_YesNoCancel(text, default): debug_logger.debug('UI_YesNoCancel: %s: %s %s' % (UI_mode, text, default)) default = default.lower() ans = None if UI_mode == 'text': yes = _('(Y)es') no = _('(N)o') cancel = _('(C)ancel') yeskey = get_translated_hotkey(yes).lower() nokey = get_translated_hotkey(no).lower() cancelkey = get_translated_hotkey(cancel).lower() ans = 'XXXINVALIDXXX' while ans not in ['c', 'n', 'y']: sys.stdout.write('\n' + text + '\n') if default == 'y': sys.stdout.write('\n[%s] / %s / %s\n' % (yes, no, cancel)) elif default == 'n': sys.stdout.write('\n%s / [%s] / %s\n' % (yes, no, cancel)) else: sys.stdout.write('\n%s / %s / [%s]\n' % (yes, no, cancel)) ans = getkey() if ans: # Get back to english from localised answer ans = ans.lower() if ans == yeskey: ans = 'y' elif ans == nokey: ans = 'n' elif ans == cancelkey: ans = 'c' elif ans == 'left': if default == 'n': default = 'y' elif default == 'c': default = 'n' elif ans == 'right': if default == 'y': default = 'n' elif default == 'n': default = 'c' else: ans = default else: SendDataToYast({'type': 'dialog-yesnocancel', 'question': text }) ypath, yarg = GetDataFromYast() ans = yarg['answer'] if not ans: ans = default return ans def UI_GetString(text, default): debug_logger.debug('UI_GetString: %s: %s %s' % (UI_mode, text, default)) string = default if UI_mode == 'text': sys.stdout.write('\n' + text) string = sys.stdin.readline() else: SendDataToYast({'type': 'dialog-getstring', 'label': text, 'default': default }) ypath, yarg = GetDataFromYast() string = yarg['string'] return string.strip() def UI_GetFile(file): debug_logger.debug('UI_GetFile: %s' % UI_mode) filename = None if UI_mode == 'text': sys.stdout.write(file['description'] + '\n') filename = sys.stdin.read() else: file['type'] = 'dialog-getfile' SendDataToYast(file) ypath, yarg = GetDataFromYast() if yarg['answer'] == 'okay': filename = yarg['filename'] return filename def UI_BusyStart(message): debug_logger.debug('UI_BusyStart: %s' % UI_mode) if UI_mode == 'text': UI_Info(message) else: SendDataToYast({'type': 'dialog-busy-start', 'message': message }) ypath, yarg = GetDataFromYast() def UI_BusyStop(): debug_logger.debug('UI_BusyStop: %s' % UI_mode) if UI_mode != 'text': SendDataToYast({'type': 'dialog-busy-stop'}) ypath, yarg = GetDataFromYast() CMDS = {'CMD_ALLOW': _('(A)llow'), 'CMD_OTHER': _('(M)ore'), 'CMD_AUDIT_NEW': _('Audi(t)'), 'CMD_AUDIT_OFF': _('Audi(t) off'), 'CMD_AUDIT_FULL': _('Audit (A)ll'), #'CMD_OTHER': '(O)pts', 'CMD_USER_ON': _('(O)wner permissions on'), 'CMD_USER_OFF': _('(O)wner permissions off'), 'CMD_DENY': _('(D)eny'), 'CMD_ABORT': _('Abo(r)t'), 'CMD_FINISHED': _('(F)inish'), 'CMD_ix': _('(I)nherit'), 'CMD_px': _('(P)rofile'), 'CMD_px_safe': _('(P)rofile Clean Exec'), 'CMD_cx': _('(C)hild'), 'CMD_cx_safe': _('(C)hild Clean Exec'), 'CMD_nx': _('(N)amed'), 'CMD_nx_safe': _('(N)amed Clean Exec'), 'CMD_ux': _('(U)nconfined'), 'CMD_ux_safe': _('(U)nconfined Clean Exec'), 'CMD_pix': _('(P)rofile Inherit'), 'CMD_pix_safe': _('(P)rofile Inherit Clean Exec'), 'CMD_cix': _('(C)hild Inherit'), 'CMD_cix_safe': _('(C)hild Inherit Clean Exec'), 'CMD_nix': _('(N)amed Inherit'), 'CMD_nix_safe': _('(N)amed Inherit Clean Exec'), 'CMD_EXEC_IX_ON': _('(X) ix On'), 'CMD_EXEC_IX_OFF': _('(X) ix Off'), 'CMD_SAVE': _('(S)ave Changes'), 'CMD_CONTINUE': _('(C)ontinue Profiling'), 'CMD_NEW': _('(N)ew'), 'CMD_GLOB': _('(G)lob'), 'CMD_GLOBEXT': _('Glob with (E)xtension'), 'CMD_ADDHAT': _('(A)dd Requested Hat'), 'CMD_USEDEFAULT': _('(U)se Default Hat'), 'CMD_SCAN': _('(S)can system log for AppArmor events'), 'CMD_HELP': _('(H)elp'), 'CMD_VIEW_PROFILE': _('(V)iew Profile'), 'CMD_USE_PROFILE': _('(U)se Profile'), 'CMD_CREATE_PROFILE': _('(C)reate New Profile'), 'CMD_UPDATE_PROFILE': _('(U)pdate Profile'), 'CMD_IGNORE_UPDATE': _('(I)gnore Update'), 'CMD_SAVE_CHANGES': _('(S)ave Changes'), 'CMD_SAVE_SELECTED': _('Save Selec(t)ed Profile'), 'CMD_UPLOAD_CHANGES': _('(U)pload Changes'), 'CMD_VIEW_CHANGES': _('(V)iew Changes'), 'CMD_VIEW_CHANGES_CLEAN': _('View Changes b/w (C)lean profiles'), 'CMD_VIEW': _('(V)iew'), 'CMD_ENABLE_REPO': _('(E)nable Repository'), 'CMD_DISABLE_REPO': _('(D)isable Repository'), 'CMD_ASK_NEVER': _('(N)ever Ask Again'), 'CMD_ASK_LATER': _('Ask Me (L)ater'), 'CMD_YES': _('(Y)es'), 'CMD_NO': _('(N)o'), 'CMD_ALL_NET': _('Allow All (N)etwork'), 'CMD_NET_FAMILY': _('Allow Network Fa(m)ily'), 'CMD_OVERWRITE': _('(O)verwrite Profile'), 'CMD_KEEP': _('(K)eep Profile'), 'CMD_CONTINUE': _('(C)ontinue'), 'CMD_IGNORE_ENTRY': _('(I)gnore') } def UI_PromptUser(q, params=''): cmd = None arg = None if UI_mode == 'text': cmd, arg = Text_PromptUser(q) else: q['type'] = 'wizard' SendDataToYast(q) ypath, yarg = GetDataFromYast() if not cmd: cmd = 'CMD_ABORT' arg = yarg['selected'] if cmd == 'CMD_ABORT': confirm_and_abort() cmd = 'XXXINVALIDXXX' return (cmd, arg) def confirm_and_abort(): ans = UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n') if ans == 'y': UI_Info(_('Abandoning all changes.')) #shutdown_yast() #for prof in created: # delete_profile(prof) sys.exit(0) def UI_ShortMessage(title, message): SendDataToYast({'type': 'short-dialog-message', 'headline': title, 'message': message }) ypath, yarg = GetDataFromYast() def UI_LongMessage(title, message): SendDataToYast({'type': 'long-dialog-message', 'headline': title, 'message': message }) ypath, yarg = GetDataFromYast() def Text_PromptUser(question): title = question['title'] explanation = question['explanation'] headers = question['headers'] functions = question['functions'] default = question['default'] options = question['options'] selected = question.get('selected', 0) helptext = question['helptext'] if helptext: functions.append('CMD_HELP') menu_items = [] keys = dict() for cmd in functions: if not CMDS.get(cmd, False): raise AppArmorException(_('PromptUser: Unknown command %s') % cmd) menutext = CMDS[cmd] key = get_translated_hotkey(menutext).lower() # Duplicate hotkey if keys.get(key, False): raise AppArmorException(_('PromptUser: Duplicate hotkey for %s: %s ') % (cmd, menutext)) keys[key] = cmd if default and default == cmd: menutext = '[%s]' % menutext menu_items.append(menutext) default_key = 0 if default and CMDS[default]: defaulttext = CMDS[default] defmsg = _('PromptUser: Invalid hotkey in default item') default_key = get_translated_hotkey(defaulttext, defmsg).lower() if not keys.get(default_key, False): raise AppArmorException(_('PromptUser: Invalid default %s') % default) widest = 0 header_copy = headers[:] while header_copy: header = header_copy.pop(0) header_copy.pop(0) if len(header) > widest: widest = len(header) widest += 1 formatstr = '%-' + str(widest) + 's %s\n' function_regexp = '^(' function_regexp += '|'.join(keys.keys()) if options: function_regexp += '|\d' function_regexp += ')$' ans = 'XXXINVALIDXXX' while not re.search(function_regexp, ans, flags=re.IGNORECASE): prompt = '\n' if title: prompt += '= %s =\n\n' % title if headers: header_copy = headers[:] while header_copy: header = header_copy.pop(0) value = header_copy.pop(0) prompt += formatstr % (header + ':', value) prompt += '\n' if explanation: prompt += explanation + '\n\n' if options: for index, option in enumerate(options): if selected == index: format_option = ' [%s - %s]' else: format_option = ' %s - %s ' prompt += format_option % (index + 1, option) prompt += '\n' prompt += ' / '.join(menu_items) sys.stdout.write(prompt + '\n') ans = getkey().lower() if ans: if ans == 'up': if options and selected > 0: selected -= 1 ans = 'XXXINVALIDXXX' elif ans == 'down': if options and selected < len(options) - 1: selected += 1 ans = 'XXXINVALIDXXX' # elif keys.get(ans, False) == 'CMD_HELP': # sys.stdout.write('\n%s\n' %helptext) # ans = 'XXXINVALIDXXX' elif is_number(ans) == 10: # If they hit return choose default option ans = default_key elif options and re.search('^\d$', ans): ans = int(ans) if ans > 0 and ans <= len(options): selected = ans - 1 ans = 'XXXINVALIDXXX' if keys.get(ans, False) == 'CMD_HELP': sys.stdout.write('\n%s\n' % helptext) ans = 'again' if keys.get(ans, False): ans = keys[ans] return ans, selected def is_number(number): try: return int(number) except: return False apparmor-2.8.95~2430/utils/apparmor/sandbox.py0000644000175000017500000006445512274766564021067 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2011-2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ from apparmor.common import AppArmorException, debug, error, msg, cmd import apparmor.easyprof import optparse import os import pwd import re import signal import socket import sys import tempfile import time def check_requirements(binary): '''Verify necessary software is installed''' exes = ['xset', # for detecting free X display 'aa-easyprof', # for templates 'aa-exec', # for changing profile 'sudo', # eventually get rid of this 'pkexec', # eventually get rid of this binary] for e in exes: debug("Searching for '%s'" % e) rc, report = cmd(['which', e]) if rc != 0: error("Could not find '%s'" % e, do_exit=False) return False return True def parse_args(args=None, parser=None): '''Parse arguments''' if parser == None: parser = optparse.OptionParser() parser.add_option('-X', '--with-x', dest='withx', default=False, help='Run in isolated X server', action='store_true') parser.add_option('--with-xserver', dest='xserver', default='xpra', help='Nested X server to use: xpra (default), xpra3d, xephyr') parser.add_option('--with-clipboard', dest='with_clipboard', default=False, help='Allow clipboard access', action='store_true') parser.add_option('--with-xauthority', dest='xauthority', default=None, help='Specify Xauthority file to use') parser.add_option('-d', '--debug', dest='debug', default=False, help='Show debug messages', action='store_true') parser.add_option('--with-xephyr-geometry', dest='xephyr_geometry', default=None, help='Geometry for Xephyr window') parser.add_option('--profile', dest='profile', default=None, help='Specify an existing profile (see aa-status)') (my_opt, my_args) = parser.parse_args() if my_opt.debug: apparmor.common.DEBUGGING = True valid_xservers = ['xpra', 'xpra3d', 'xephyr'] if my_opt.withx and my_opt.xserver.lower() not in valid_xservers: error("Invalid server '%s'. Use one of: %s" % (my_opt.xserver, \ ", ".join(valid_xservers))) if my_opt.withx: if my_opt.xephyr_geometry and my_opt.xserver.lower() != "xephyr": error("Invalid option --with-xephyr-geometry with '%s'" % my_opt.xserver) elif my_opt.with_clipboard and my_opt.xserver.lower() == "xephyr": error("Clipboard not supported with '%s'" % my_opt.xserver) if my_opt.template == "default": if my_opt.withx: my_opt.template = "sandbox-x" else: my_opt.template = "sandbox" return (my_opt, my_args) def gen_policy_name(binary): '''Generate a temporary policy based on the binary name''' return "sandbox-%s%s" % (pwd.getpwuid(os.geteuid())[0], re.sub(r'/', '_', binary)) def set_environ(env): keys = env.keys() keys.sort() for k in keys: msg("Using: %s=%s" % (k, env[k])) os.environ[k] = env[k] def aa_exec(command, opt, environ={}, verify_rules=[]): '''Execute binary under specified policy''' if opt.profile != None: policy_name = opt.profile else: opt.ensure_value("template_var", None) opt.ensure_value("name", None) opt.ensure_value("comment", None) opt.ensure_value("author", None) opt.ensure_value("copyright", None) binary = command[0] policy_name = gen_policy_name(binary) easyp = apparmor.easyprof.AppArmorEasyProfile(binary, opt) params = apparmor.easyprof.gen_policy_params(policy_name, opt) policy = easyp.gen_policy(**params) debug("\n%s" % policy) tmp = tempfile.NamedTemporaryFile(prefix = '%s-' % policy_name) if sys.version_info[0] >= 3: tmp.write(bytes(policy, 'utf-8')) else: tmp.write(policy) tmp.flush() debug("using '%s' template" % opt.template) # TODO: get rid of this if opt.withx: rc, report = cmd(['pkexec', 'apparmor_parser', '-r', '%s' % tmp.name]) else: rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) if rc != 0: raise AppArmorException("Could not load policy") rc, report = cmd(['sudo', 'apparmor_parser', '-p', tmp.name]) if rc != 0: raise AppArmorException("Could not dump policy") # Make sure the dynamic profile has the appropriate line for X for r in verify_rules: found = False for line in report.splitlines(): line = line.strip() if r == line: found = True break if not found: raise AppArmorException("Could not find required rule: %s" % r) set_environ(environ) args = ['aa-exec', '-p', policy_name, '--'] + command rc, report = cmd(args) return rc, report def run_sandbox(command, opt): '''Run application''' # aa-exec rc, report = aa_exec(command, opt) return rc, report class SandboxXserver(): def __init__(self, title, geometry=None, driver=None, xauth=None, clipboard=False): self.geometry = geometry self.title = title self.pids = [] self.driver = driver self.clipboard = clipboard self.tempfiles = [] self.timeout = 5 # used by xauth and for server starts # preserve our environment self.old_environ = dict() for env in ['DISPLAY', 'XAUTHORITY', 'UBUNTU_MENUPROXY', 'QT_X11_NO_NATIVE_MENUBAR', 'LIBOVERLAY_SCROLLBAR']: if env in os.environ: self.old_environ[env] = os.environ[env] # prepare the new environment self.display, self.xauth = self.find_free_x_display() if xauth: abs_xauth = os.path.expanduser(xauth) if os.path.expanduser("~/.Xauthority") == abs_xauth: raise AppArmorException("Trusted Xauthority file specified. Aborting") self.xauth = abs_xauth self.new_environ = dict() self.new_environ['DISPLAY'] = self.display self.new_environ['XAUTHORITY'] = self.xauth # Disable the global menu for now self.new_environ["UBUNTU_MENUPROXY"] = "" self.new_environ["QT_X11_NO_NATIVE_MENUBAR"] = "1" # Disable the overlay scrollbar for now-- they don't track correctly self.new_environ["LIBOVERLAY_SCROLLBAR"] = "0" def cleanup(self): '''Cleanup our forked pids, reset the environment, etc''' self.pids.reverse() debug(self.pids) for pid in self.pids: # Kill server with TERM debug("kill %d" % pid) os.kill(pid, signal.SIGTERM) for pid in self.pids: # Shoot the server dead debug("kill -9 %d" % pid) os.kill(pid, signal.SIGKILL) for t in self.tempfiles: if os.path.exists(t): os.unlink(t) if os.path.exists(self.xauth): os.unlink(self.xauth) # Reset our environment set_environ(self.old_environ) def find_free_x_display(self): '''Find a free X display''' old_lang = None if 'LANG' in os.environ: old_lang = os.environ['LANG'] os.environ['LANG'] = 'C' display = "" current = self.old_environ["DISPLAY"] for i in range(1,257): # TODO: this puts an artificial limit of 256 # sandboxed applications tmp = ":%d" % i os.environ["DISPLAY"] = tmp rc, report = cmd(['xset', '-q']) if rc != 0 and 'Invalid MIT-MAGIC-COOKIE-1' not in report: display = tmp break if old_lang: os.environ['LANG'] = old_lang os.environ["DISPLAY"] = current if display == "": raise AppArmorException("Could not find available X display") # Use dedicated .Xauthority file xauth = os.path.join(os.path.expanduser('~'), \ '.Xauthority-sandbox%s' % display.split(':')[1]) return display, xauth def generate_title(self): return "(Sandbox%s) %s" % (self.display, self.title) def verify_host_setup(self): '''Make sure we have everything we need''' old_lang = None if 'LANG' in os.environ: old_lang = os.environ['LANG'] os.environ['LANG'] = 'C' rc, report = cmd(['xhost']) if old_lang: os.environ['LANG'] = old_lang if rc != 0: raise AppArmorException("'xhost' exited with error") if 'access control enabled' not in report: raise AppArmorException("Access control currently disabled. Please enable with 'xhost -'") username = pwd.getpwuid(os.geteuid())[0] if ':localuser:%s' % username in report: raise AppArmorException("Access control allows '%s' full access. Please see 'man aa-sandbox' for details" % username) def start(self): '''Start a nested X server (need to override)''' # clean up the old one if os.path.exists(self.xauth): os.unlink(self.xauth) rc, cookie = cmd(['mcookie']) if rc != 0: raise AppArmorException("Could not generate magic cookie") rc, out = cmd(['xauth', '-f', self.xauth, \ 'add', \ self.display, \ 'MIT-MAGIC-COOKIE-1', \ cookie.strip()]) if rc != 0: raise AppArmorException("Could not generate '%s'" % self.display) class SandboxXephyr(SandboxXserver): def start(self): for e in ['Xephyr', 'matchbox-window-manager']: debug("Searching for '%s'" % e) rc, report = cmd(['which', e]) if rc != 0: raise AppArmorException("Could not find '%s'" % e) '''Run any setup code''' SandboxXserver.start(self) '''Start a Xephyr server''' listener_x = os.fork() if listener_x == 0: # TODO: break into config file? Which are needed? x_exts = ['-extension', 'GLX', '-extension', 'MIT-SHM', '-extension', 'RENDER', '-extension', 'SECURITY', '-extension', 'DAMAGE' ] # verify_these x_extra_args = ['-host-cursor', # less secure? '-fakexa', # for games? seems not needed '-nodri', # more secure? ] if not self.geometry: self.geometry = "640x480" x_args = ['-nolisten', 'tcp', '-screen', self.geometry, '-br', # black background '-reset', # reset after last client exists '-terminate', # terminate at server reset '-title', self.generate_title(), ] + x_exts + x_extra_args args = ['/usr/bin/Xephyr'] + x_args + [self.display] debug(" ".join(args)) os.execv(args[0], args) sys.exit(0) self.pids.append(listener_x) time.sleep(1) # FIXME: detect if running # Next, start the window manager sys.stdout.flush() os.chdir(os.environ["HOME"]) listener_wm = os.fork() if listener_wm == 0: # update environment set_environ(self.new_environ) args = ['/usr/bin/matchbox-window-manager', '-use_titlebar', 'no'] debug(" ".join(args)) cmd(args) sys.exit(0) self.pids.append(listener_wm) time.sleep(1) # FIXME: detect if running class SandboxXpra(SandboxXserver): def cleanup(self): sys.stderr.flush() listener = os.fork() if listener == 0: args = ['/usr/bin/xpra', 'stop', self.display] debug(" ".join(args)) os.execv(args[0], args) sys.exit(0) time.sleep(2) # Annoyingly, xpra doesn't clean up itself well if the application # failed for some reason. Try to account for that. rc, report = cmd(['ps', 'auxww']) for line in report.splitlines(): if '-for-Xpra-%s' % self.display in line: self.pids.append(int(line.split()[1])) SandboxXserver.cleanup(self) def _get_xvfb_args(self): '''Setup xvfb arguments''' # Debugging tip (can also use glxinfo): # $ xdpyinfo > /tmp/native # $ aa-sandbox -X -t sandbox-x /usr/bin/xdpyinfo > /tmp/nested # $ diff -Naur /tmp/native /tmp/nested xvfb_args = [] if self.driver == None: # The default from the man page, but be explicit in what we enable xvfb_args.append('--xvfb=Xvfb') xvfb_args.append('-screen 0 3840x2560x24+32') xvfb_args.append('-nolisten tcp') xvfb_args.append('-noreset') xvfb_args.append('-auth %s' % self.new_environ['XAUTHORITY']) xvfb_args.append('+extension Composite') xvfb_args.append('+extension SECURITY') xvfb_args.append('-extension GLX') elif self.driver == 'xdummy': # The dummy driver allows us to use GLX, etc. See: # http://xpra.org/Xdummy.html conf = '''# /usr/share/doc/xpra/examples/dummy.xorg.conf.gz # http://xpra.org/Xdummy.html ##Xdummy:## Section "ServerFlags" Option "DontVTSwitch" "true" Option "AllowMouseOpenFail" "true" Option "PciForceNone" "true" Option "AutoEnableDevices" "false" Option "AutoAddDevices" "false" EndSection ##Xdummy:## Section "InputDevice" Identifier "NoMouse" Option "CorePointer" "true" Driver "void" EndSection Section "InputDevice" Identifier "NoKeyboard" Option "CoreKeyboard" "true" Driver "void" EndSection ##Xdummy:## Section "Device" Identifier "Videocard0" Driver "dummy" # In kByte #VideoRam 4096000 #VideoRam 256000 # This should be good for 3840*2560*32bpp: http://winswitch.org/trac/ticket/140 VideoRam 64000 EndSection ##Xdummy:## Section "Monitor" Identifier "Monitor0" HorizSync 10.0 - 300.0 VertRefresh 10.0 - 200.0 DisplaySize 4335 1084 #The following modeline is invalid (calculator overflowed): #Modeline "32000x32000@0" -38917.43 32000 32032 -115848 -115816 32000 32775 32826 33601 Modeline "16384x8192@10" 2101.93 16384 16416 24400 24432 8192 8390 8403 8602 Modeline "8192x4096@10" 424.46 8192 8224 9832 9864 4096 4195 4202 4301 Modeline "5120x3200@10" 199.75 5120 5152 5904 5936 3200 3277 3283 3361 Modeline "3840x2880@10" 133.43 3840 3872 4376 4408 2880 2950 2955 3025 Modeline "3840x2560@10" 116.93 3840 3872 4312 4344 2560 2622 2627 2689 Modeline "3840x2048@10" 91.45 3840 3872 4216 4248 2048 2097 2101 2151 Modeline "2048x2048@10" 49.47 2048 2080 2264 2296 2048 2097 2101 2151 Modeline "2560x1600@10" 47.12 2560 2592 2768 2800 1600 1639 1642 1681 Modeline "1920x1200@10" 26.28 1920 1952 2048 2080 1200 1229 1231 1261 Modeline "1920x1080@10" 23.53 1920 1952 2040 2072 1080 1106 1108 1135 Modeline "1680x1050@10" 20.08 1680 1712 1784 1816 1050 1075 1077 1103 Modeline "1600x900@20" 33.92 1600 1632 1760 1792 900 921 924 946 Modeline "1440x900@20" 30.66 1440 1472 1584 1616 900 921 924 946 Modeline "1360x768@20" 24.49 1360 1392 1480 1512 768 786 789 807 #common resolutions for android devices (both orientations): Modeline "800x1280@20" 25.89 800 832 928 960 1280 1310 1315 1345 Modeline "1280x800@20" 24.15 1280 1312 1400 1432 800 819 822 841 Modeline "720x1280@25" 30.22 720 752 864 896 1280 1309 1315 1345 Modeline "1280x720@25" 27.41 1280 1312 1416 1448 720 737 740 757 Modeline "768x1024@25" 24.93 768 800 888 920 1024 1047 1052 1076 Modeline "1024x768@25" 23.77 1024 1056 1144 1176 768 785 789 807 Modeline "600x1024@25" 19.90 600 632 704 736 1024 1047 1052 1076 Modeline "1024x600@25" 18.26 1024 1056 1120 1152 600 614 617 631 Modeline "536x960@25" 16.74 536 568 624 656 960 982 986 1009 Modeline "960x536@25" 15.23 960 992 1048 1080 536 548 551 563 Modeline "600x800@25" 15.17 600 632 688 720 800 818 822 841 Modeline "800x600@25" 14.50 800 832 880 912 600 614 617 631 Modeline "480x854@25" 13.34 480 512 560 592 854 873 877 897 Modeline "848x480@25" 12.09 848 880 920 952 480 491 493 505 Modeline "480x800@25" 12.43 480 512 552 584 800 818 822 841 Modeline "800x480@25" 11.46 800 832 872 904 480 491 493 505 Modeline "320x480@50" 10.73 320 352 392 424 480 490 494 505 Modeline "480x320@50" 9.79 480 512 544 576 320 327 330 337 Modeline "240x400@50" 6.96 240 272 296 328 400 408 412 421 Modeline "400x240@50" 6.17 400 432 448 480 240 245 247 253 Modeline "240x320@50" 5.47 240 272 288 320 320 327 330 337 Modeline "320x240@50" 5.10 320 352 368 400 240 245 247 253 #resolutions for android devices (both orientations) #minus the status bar #38px status bar (and width rounded up) Modeline "800x1242@20" 25.03 800 832 920 952 1242 1271 1275 1305 Modeline "1280x762@20" 22.93 1280 1312 1392 1424 762 780 783 801 Modeline "720x1242@25" 29.20 720 752 856 888 1242 1271 1276 1305 Modeline "1280x682@25" 25.85 1280 1312 1408 1440 682 698 701 717 Modeline "768x986@25" 23.90 768 800 888 920 986 1009 1013 1036 Modeline "1024x730@25" 22.50 1024 1056 1136 1168 730 747 750 767 Modeline "600x986@25" 19.07 600 632 704 736 986 1009 1013 1036 Modeline "1024x562@25" 17.03 1024 1056 1120 1152 562 575 578 591 Modeline "536x922@25" 16.01 536 568 624 656 922 943 947 969 Modeline "960x498@25" 14.09 960 992 1040 1072 498 509 511 523 Modeline "600x762@25" 14.39 600 632 680 712 762 779 783 801 Modeline "800x562@25" 13.52 800 832 880 912 562 575 578 591 Modeline "480x810@25" 12.59 480 512 552 584 810 828 832 851 Modeline "848x442@25" 11.09 848 880 920 952 442 452 454 465 Modeline "480x762@25" 11.79 480 512 552 584 762 779 783 801 Modeline "800x442@25" 10.51 800 832 864 896 442 452 454 465 #32px status bar (no need for rounding): Modeline "320x448@50" 9.93 320 352 384 416 448 457 461 471 Modeline "480x288@50" 8.75 480 512 544 576 288 294 297 303 #24px status bar: Modeline "240x376@50" 6.49 240 272 296 328 376 384 387 395 Modeline "400x216@50" 5.50 400 432 448 480 216 220 222 227 Modeline "240x296@50" 5.02 240 272 288 320 296 302 305 311 Modeline "320x216@50" 4.55 320 352 368 400 216 220 222 227 EndSection ##Xdummy:## Section "Screen" Identifier "Screen0" Device "Videocard0" Monitor "Monitor0" DefaultDepth 24 SubSection "Display" Viewport 0 0 Depth 24 Modes "32000x32000" "16384x8192" "8192x4096" "5120x3200" "3840x2880" "3840x2560" "3840x2048" "2048x2048" "2560x1600" "1920x1440" "1920x1200" "1920x1080" "1600x1200" "1680x1050" "1600x900" "1400x1050" "1440x900" "1280x1024" "1366x768" "1280x800" "1024x768" "1024x600" "800x600" "320x200" #Virtual 32000 32000 #Virtual 16384 8192 #Virtual 8192 4096 # http://winswitch.org/trac/ticket/140 Virtual 3840 2560 EndSubSection EndSection Section "ServerLayout" Identifier "dummy_layout" Screen "screen0" InputDevice "NoMouse" InputDevice "NoKeyboard" EndSection ''' tmp, xorg_conf = tempfile.mkstemp(prefix='aa-sandbox-xorg.conf-') self.tempfiles.append(xorg_conf) if sys.version_info[0] >= 3: os.write(tmp, bytes(conf, 'utf-8')) else: os.write(tmp, conf) os.close(tmp) xvfb_args.append('--xvfb=Xorg') xvfb_args.append('-dpi 96') # https://www.xpra.org/trac/ticket/163 xvfb_args.append('-nolisten tcp') xvfb_args.append('-noreset') xvfb_args.append('-logfile %s' % os.path.expanduser('~/.xpra/%s.log' % self.display)) xvfb_args.append('-auth %s' % self.new_environ['XAUTHORITY']) xvfb_args.append('-config %s' % xorg_conf) extensions = ['Composite', 'GLX', 'RANDR', 'RENDER', 'SECURITY'] for i in extensions: xvfb_args.append('+extension %s' % i) else: raise AppArmorException("Unsupported X driver '%s'" % self.driver) return xvfb_args def start(self): debug("Searching for '%s'" % 'xpra') rc, report = cmd(['which', 'xpra']) if rc != 0: raise AppArmorException("Could not find '%s'" % 'xpra') if self.driver == "xdummy": # FIXME: is there a better way we can detect this? drv = "/usr/lib/xorg/modules/drivers/dummy_drv.so" debug("Searching for '%s'" % drv) if not os.path.exists(drv): raise AppArmorException("Could not find '%s'" % drv) '''Run any setup code''' SandboxXserver.start(self) xvfb_args = self._get_xvfb_args() listener_x = os.fork() if listener_x == 0: os.environ['XAUTHORITY'] = self.xauth # This will clean out any dead sessions cmd(['xpra', 'list']) x_args = ['--no-daemon', #'--no-mmap', # for security? '--no-pulseaudio'] if not self.clipboard: x_args.append('--no-clipboard') if xvfb_args != '': x_args.append(" ".join(xvfb_args)) args = ['/usr/bin/xpra', 'start', self.display] + x_args debug(" ".join(args)) sys.stderr.flush() os.execv(args[0], args) sys.exit(0) self.pids.append(listener_x) started = False # We need to wait for the xpra socket to exist before attaching fn = os.path.join(os.environ['HOME'], '.xpra', '%s-%s' % \ (socket.gethostname(), self.display.split(':')[1])) for i in range(self.timeout * 2): # up to self.timeout seconds to start if os.path.exists(fn): debug("Found '%s'! Proceeding to attach" % fn) break debug("'%s' doesn't exist yet, waiting" % fn) time.sleep(0.5) if not os.path.exists(fn): sys.stdout.flush() self.cleanup() raise AppArmorException("Could not start xpra (try again with -d)") for i in range(self.timeout): # Up to self.timeout seconds to start rc, out = cmd(['xpra', 'list']) if 'DEAD session at %s' % self.display in out: error("xpra session at '%s' died" % self.display, do_exit=False) break search = 'LIVE session at %s' % self.display if search in out: started = True break time.sleep(0.5) debug("Could not find '%s' in:\n" % search) debug(out) if not started: sys.stdout.flush() self.cleanup() raise AppArmorException("Could not start xpra (try again with -d)") # Next, attach to xpra sys.stdout.flush() os.chdir(os.environ["HOME"]) listener_attach = os.fork() if listener_attach == 0: args = ['/usr/bin/xpra', 'attach', self.display, '--title=%s' % self.generate_title(), #'--no-mmap', # for security? '--no-tray', '--no-pulseaudio'] if not self.clipboard: args.append('--no-clipboard') debug(" ".join(args)) sys.stderr.flush() os.execv(args[0], args) sys.exit(0) self.pids.append(listener_attach) # Make sure that a client has attached for i in range(self.timeout): # up to self.timeout seconds to attach time.sleep(1) rc, out = cmd (['xpra', 'info', self.display]) search = 'clients=1' if search in out: debug("Client successfully attached!") break debug("Could not find '%s' in:\n" % search) debug(out) msg("TODO: filter '~/.xpra/run-xpra'") def run_xsandbox(command, opt): '''Run X application in a sandbox''' old_cwd = os.getcwd() # first, start X if opt.xserver.lower() == "xephyr": x = SandboxXephyr(command[0], geometry=opt.xephyr_geometry, xauth=opt.xauthority) elif opt.xserver.lower() == "xpra3d": x = SandboxXpra(command[0], geometry=None, driver="xdummy", xauth=opt.xauthority, clipboard=opt.with_clipboard) else: x = SandboxXpra(command[0], geometry=None, xauth=opt.xauthority, clipboard=opt.with_clipboard) x.verify_host_setup() # Debug: show old environment keys = x.old_environ.keys() keys.sort() for k in keys: debug ("Old: %s=%s" % (k, x.old_environ[k])) try: x.start() except Exception as e: error(e) os.chdir(old_cwd) if not opt.read_path: opt.read_path = [] opt.read_path.append(x.xauth) # Only used with dynamic profiles required_rules = ['audit deny @{HOME}/.Xauthority mrwlk,'] # aa-exec try: rc, report = aa_exec(command, opt, x.new_environ, required_rules) except Exception as e: x.cleanup() raise x.cleanup() return rc, report apparmor-2.8.95~2430/utils/apparmor/easyprof.py0000644000175000017500000011570512277273444021245 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2011-2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ from __future__ import with_statement import codecs import copy import glob import json import optparse import os import re import shutil import subprocess import sys import tempfile # # TODO: move this out to the common library # #from apparmor import AppArmorException class AppArmorException(Exception): '''This class represents AppArmor exceptions''' def __init__(self, value): self.value = value def __str__(self): return repr(self.value) # # End common # DEBUGGING = False # # TODO: move this out to a utilities library # def error(out, exit_code=1, do_exit=True): '''Print error message and exit''' try: sys.stderr.write("ERROR: %s\n" % (out)) except IOError: pass if do_exit: sys.exit(exit_code) def warn(out): '''Print warning message''' try: sys.stderr.write("WARN: %s\n" % (out)) except IOError: pass def msg(out, output=sys.stdout): '''Print message''' try: sys.stdout.write("%s\n" % (out)) except IOError: pass def cmd(command): '''Try to execute the given command.''' debug(command) try: sp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except OSError as ex: return [127, str(ex)] out = sp.communicate()[0] return [sp.returncode, out] def cmd_pipe(command1, command2): '''Try to pipe command1 into command2.''' try: sp1 = subprocess.Popen(command1, stdout=subprocess.PIPE) sp2 = subprocess.Popen(command2, stdin=sp1.stdout) except OSError as ex: return [127, str(ex)] out = sp2.communicate()[0] return [sp2.returncode, out] def debug(out): '''Print debug message''' if DEBUGGING: try: sys.stderr.write("DEBUG: %s\n" % (out)) except IOError: pass def valid_binary_path(path): '''Validate name''' try: a_path = os.path.abspath(path) except Exception: debug("Could not find absolute path for binary") return False if path != a_path: debug("Binary should use a normalized absolute path") return False if not os.path.exists(a_path): return True r_path = os.path.realpath(path) if r_path != a_path: debug("Binary should not be a symlink") return False return True def valid_variable(v): '''Validate variable name''' debug("Checking '%s'" % v) try: (key, value) = v.split('=') except Exception: return False if not re.search(r'^@\{[a-zA-Z0-9_]+\}$', key): return False if '/' in value: rel_ok = False if not value.startswith('/'): rel_ok = True if not valid_path(value, relative_ok=rel_ok): return False if '"' in value: return False # If we made it here, we are safe return True def valid_path(path, relative_ok=False): '''Valid path''' m = "Invalid path: %s" % (path) if not relative_ok and not path.startswith('/'): debug("%s (relative)" % (m)) return False if '"' in path: # We double quote elsewhere debug("%s (quote)" % (m)) return False if '../' in path: debug("%s (../ path escape)" % (m)) return False try: p = os.path.normpath(path) except Exception: debug("%s (could not normalize)" % (m)) return False if p != path: debug("%s (normalized path != path (%s != %s))" % (m, p, path)) return False # If we made it here, we are safe return True def _is_safe(s): '''Known safe regex''' if re.search(r'^[a-zA-Z_0-9\-\.]+$', s): return True return False def valid_policy_vendor(s): '''Verify the policy vendor''' return _is_safe(s) def valid_policy_version(v): '''Verify the policy version''' try: float(v) except ValueError: return False if float(v) < 0: return False return True def valid_template_name(s, strict=False): '''Verify the template name''' if not strict and s.startswith('/'): if not valid_path(s): return False return True return _is_safe(s) def valid_abstraction_name(s): '''Verify the template name''' return _is_safe(s) def valid_profile_name(s): '''Verify the profile name''' # profile name specifies path if s.startswith('/'): if not valid_path(s): return False return True # profile name does not specify path # alpha-numeric and Debian version, plus '_' if re.search(r'^[a-zA-Z0-9][a-zA-Z0-9_\+\-\.:~]+$', s): return True return False def valid_policy_group_name(s): '''Verify policy group name''' return _is_safe(s) def get_directory_contents(path): '''Find contents of the given directory''' if not valid_path(path): return None files = [] for f in glob.glob(path + "/*"): files.append(f) files.sort() return files def open_file_read(path): '''Open specified file read-only''' try: orig = codecs.open(path, 'r', "UTF-8") except Exception: raise return orig def verify_policy(policy): '''Verify policy compiles''' exe = "/sbin/apparmor_parser" if not os.path.exists(exe): rc, exe = cmd(['which', 'apparmor_parser']) if rc != 0: warn("Could not find apparmor_parser. Skipping verify") return True fn = "" # if policy starts with '/' and is one line, assume it is a path if len(policy.splitlines()) == 1 and valid_path(policy): fn = policy else: f, fn = tempfile.mkstemp(prefix='aa-easyprof') if not isinstance(policy, bytes): policy = policy.encode('utf-8') os.write(f, policy) os.close(f) rc, out = cmd([exe, '-p', fn]) os.unlink(fn) if rc == 0: return True return False # # End utility functions # class AppArmorEasyProfile: '''Easy profile class''' def __init__(self, binary, opt): verify_options(opt) opt.ensure_value("conffile", "/etc/apparmor/easyprof.conf") self.conffile = os.path.abspath(opt.conffile) self.dirs = dict() if os.path.isfile(self.conffile): self._get_defaults() if opt.templates_dir and os.path.isdir(opt.templates_dir): self.dirs['templates'] = os.path.abspath(opt.templates_dir) elif not opt.templates_dir and \ opt.template and \ os.path.isfile(opt.template) and \ valid_path(opt.template): # If we specified the template and it is an absolute path, just set # the templates directory to the parent of the template so we don't # have to require --template-dir with absolute paths. self.dirs['templates'] = os.path.abspath(os.path.dirname(opt.template)) if opt.policy_groups_dir and os.path.isdir(opt.policy_groups_dir): self.dirs['policygroups'] = os.path.abspath(opt.policy_groups_dir) self.policy_version = None self.policy_vendor = None if (opt.policy_version and not opt.policy_vendor) or \ (opt.policy_vendor and not opt.policy_version): raise AppArmorException("Must specify both policy version and vendor") if opt.policy_version and opt.policy_vendor: self.policy_vendor = opt.policy_vendor self.policy_version = str(opt.policy_version) for i in ['templates', 'policygroups']: d = os.path.join(self.dirs[i], \ self.policy_vendor, \ self.policy_version) if not os.path.isdir(d): raise AppArmorException( "Could not find %s directory '%s'" % (i, d)) self.dirs[i] = d if not 'templates' in self.dirs: raise AppArmorException("Could not find templates directory") if not 'policygroups' in self.dirs: raise AppArmorException("Could not find policygroups directory") self.aa_topdir = "/etc/apparmor.d" self.binary = binary if binary: if not valid_binary_path(binary): raise AppArmorException("Invalid path for binary: '%s'" % binary) if opt.manifest: self.set_template(opt.template, allow_abs_path=False) else: self.set_template(opt.template) self.set_policygroup(opt.policy_groups) if opt.name: self.set_name(opt.name) elif self.binary != None: self.set_name(self.binary) self.templates = [] for f in get_directory_contents(self.dirs['templates']): if os.path.isfile(f): self.templates.append(f) self.policy_groups = [] for f in get_directory_contents(self.dirs['policygroups']): if os.path.isfile(f): self.policy_groups.append(f) def _get_defaults(self): '''Read in defaults from configuration''' if not os.path.exists(self.conffile): raise AppArmorException("Could not find '%s'" % self.conffile) # Read in the configuration f = open_file_read(self.conffile) pat = re.compile(r'^\w+=".*"?') for line in f: if not pat.search(line): continue if line.startswith("POLICYGROUPS_DIR="): d = re.split(r'=', line.strip())[1].strip('["\']') self.dirs['policygroups'] = d elif line.startswith("TEMPLATES_DIR="): d = re.split(r'=', line.strip())[1].strip('["\']') self.dirs['templates'] = d f.close() keys = self.dirs.keys() if 'templates' not in keys: raise AppArmorException("Could not find TEMPLATES_DIR in '%s'" % self.conffile) if 'policygroups' not in keys: raise AppArmorException("Could not find POLICYGROUPS_DIR in '%s'" % self.conffile) for k in self.dirs.keys(): if not os.path.isdir(self.dirs[k]): raise AppArmorException("Could not find '%s'" % self.dirs[k]) def set_name(self, name): '''Set name of policy''' self.name = name def get_template(self): '''Get contents of current template''' return open(self.template).read() def set_template(self, template, allow_abs_path=True): '''Set current template''' if "../" in template: raise AppArmorException('template "%s" contains "../" escape path' % (template)) elif template.startswith('/') and not allow_abs_path: raise AppArmorException("Cannot use an absolute path template '%s'" % template) if template.startswith('/'): self.template = template else: self.template = os.path.join(self.dirs['templates'], template) if not os.path.exists(self.template): raise AppArmorException('%s does not exist' % (self.template)) def get_templates(self): '''Get list of all available templates by filename''' return self.templates def get_policygroup(self, policygroup): '''Get contents of specific policygroup''' p = policygroup if not p.startswith('/'): p = os.path.join(self.dirs['policygroups'], p) if self.policy_groups == None or not p in self.policy_groups: raise AppArmorException("Policy group '%s' does not exist" % p) return open(p).read() def set_policygroup(self, policygroups): '''Set policygroups''' self.policy_groups = [] if policygroups != None: for p in policygroups.split(','): if not p.startswith('/'): p = os.path.join(self.dirs['policygroups'], p) if not os.path.exists(p): raise AppArmorException('%s does not exist' % (p)) self.policy_groups.append(p) def get_policy_groups(self): '''Get list of all policy groups by filename''' return self.policy_groups def gen_abstraction_rule(self, abstraction): '''Generate an abstraction rule''' p = os.path.join(self.aa_topdir, "abstractions", abstraction) if not os.path.exists(p): raise AppArmorException("%s does not exist" % p) return "#include " % abstraction def gen_variable_declaration(self, dec): '''Generate a variable declaration''' if not valid_variable(dec): raise AppArmorException("Invalid variable declaration '%s'" % dec) # Make sure we always quote k, v = dec.split('=') return '%s="%s"' % (k, v) def gen_path_rule(self, path, access): rule = [] if not path.startswith('/') and not path.startswith('@'): raise AppArmorException("'%s' should not be relative path" % path) owner = "" if path.startswith('/home/') or path.startswith("@{HOME"): owner = "owner " if path.endswith('/'): rule.append("%s %s," % (path, access)) rule.append("%s%s** %s," % (owner, path, access)) elif path.endswith('/**') or path.endswith('/*'): rule.append("%s %s," % (os.path.dirname(path), access)) rule.append("%s%s %s," % (owner, path, access)) else: rule.append("%s%s %s," % (owner, path, access)) return rule def gen_policy(self, name, binary=None, profile_name=None, template_var=[], abstractions=None, policy_groups=None, read_path=[], write_path=[], author=None, comment=None, copyright=None, no_verify=False): def find_prefix(t, s): '''Calculate whitespace prefix based on occurrence of s in t''' pat = re.compile(r'^ *%s' % s) p = "" for line in t.splitlines(): if pat.match(line): p = " " * (len(line) - len(line.lstrip())) break return p policy = self.get_template() if '###ENDUSAGE###' in policy: found = False tmp = "" for line in policy.splitlines(): if not found: if line.startswith('###ENDUSAGE###'): found = True continue tmp += line + "\n" policy = tmp attachment = "" if binary: if not valid_binary_path(binary): raise AppArmorException("Invalid path for binary: '%s'" % \ binary) if profile_name: attachment = 'profile "%s" "%s"' % (profile_name, binary) else: attachment = '"%s"' % binary elif profile_name: attachment = 'profile "%s"' % profile_name else: raise AppArmorException("Must specify binary and/or profile name") policy = re.sub(r'###PROFILEATTACH###', attachment, policy) policy = re.sub(r'###NAME###', name, policy) # Fill-in various comment fields if comment != None: policy = re.sub(r'###COMMENT###', "Comment: %s" % comment, policy) if author != None: policy = re.sub(r'###AUTHOR###', "Author: %s" % author, policy) if copyright != None: policy = re.sub(r'###COPYRIGHT###', "Copyright: %s" % copyright, policy) # Fill-in rules and variables with proper indenting search = '###ABSTRACTIONS###' prefix = find_prefix(policy, search) s = "%s# No abstractions specified" % prefix if abstractions != None: s = "%s# Specified abstractions" % (prefix) t = abstractions.split(',') t.sort() for i in t: s += "\n%s%s" % (prefix, self.gen_abstraction_rule(i)) policy = re.sub(r' *%s' % search, s, policy) search = '###POLICYGROUPS###' prefix = find_prefix(policy, search) s = "%s# No policy groups specified" % prefix if policy_groups != None: s = "%s# Rules specified via policy groups" % (prefix) t = policy_groups.split(',') t.sort() for i in t: for line in self.get_policygroup(i).splitlines(): s += "\n%s%s" % (prefix, line) if i != policy_groups.split(',')[-1]: s += "\n" policy = re.sub(r' *%s' % search, s, policy) search = '###VAR###' prefix = find_prefix(policy, search) s = "%s# No template variables specified" % prefix if len(template_var) > 0: s = "%s# Specified profile variables" % (prefix) template_var.sort() for i in template_var: s += "\n%s%s" % (prefix, self.gen_variable_declaration(i)) policy = re.sub(r' *%s' % search, s, policy) search = '###READS###' prefix = find_prefix(policy, search) s = "%s# No read paths specified" % prefix if len(read_path) > 0: s = "%s# Specified read permissions" % (prefix) read_path.sort() for i in read_path: for r in self.gen_path_rule(i, 'rk'): s += "\n%s%s" % (prefix, r) policy = re.sub(r' *%s' % search, s, policy) search = '###WRITES###' prefix = find_prefix(policy, search) s = "%s# No write paths specified" % prefix if len(write_path) > 0: s = "%s# Specified write permissions" % (prefix) write_path.sort() for i in write_path: for r in self.gen_path_rule(i, 'rwk'): s += "\n%s%s" % (prefix, r) policy = re.sub(r' *%s' % search, s, policy) if no_verify: debug("Skipping policy verification") elif not verify_policy(policy): msg("\n" + policy) raise AppArmorException("Invalid policy") return policy def output_policy(self, params, count=0, dir=None): '''Output policy''' policy = self.gen_policy(**params) if not dir: if count: sys.stdout.write('### aa-easyprof profile #%d ###\n' % count) sys.stdout.write('%s\n' % policy) else: out_fn = "" if 'profile_name' in params: out_fn = params['profile_name'] elif 'binary' in params: out_fn = params['binary'] else: # should not ever reach this raise AppArmorException("Could not determine output filename") # Generate an absolute path, convertng any path delimiters to '.' out_fn = os.path.join(dir, re.sub(r'/', '.', out_fn.lstrip('/'))) if os.path.exists(out_fn): raise AppArmorException("'%s' already exists" % out_fn) if not os.path.exists(dir): os.mkdir(dir) if not os.path.isdir(dir): raise AppArmorException("'%s' is not a directory" % dir) f, fn = tempfile.mkstemp(prefix='aa-easyprof') if not isinstance(policy, bytes): policy = policy.encode('utf-8') os.write(f, policy) os.close(f) shutil.move(fn, out_fn) def gen_manifest(self, params): '''Take params list and output a JSON file''' d = dict() d['security'] = dict() d['security']['profiles'] = dict() pkey = "" if 'profile_name' in params: pkey = params['profile_name'] elif 'binary' in params: # when profile_name is not specified, the binary (path attachment) # also functions as the profile name pkey = params['binary'] else: raise AppArmorException("Must supply binary or profile name") d['security']['profiles'][pkey] = dict() # Add the template since it isn't part of 'params' template = os.path.basename(self.template) if template != 'default': d['security']['profiles'][pkey]['template'] = template # Add the policy_version since it isn't part of 'params' if self.policy_version: d['security']['profiles'][pkey]['policy_version'] = float(self.policy_version) if self.policy_vendor: d['security']['profiles'][pkey]['policy_vendor'] = self.policy_vendor for key in params: if key == 'profile_name' or \ (key == 'binary' and not 'profile_name' in params): continue # don't re-add the pkey elif key == 'binary' and not params[key]: continue # binary can by None when specifying --profile-name elif key == 'template_var': d['security']['profiles'][pkey]['template_variables'] = dict() for tvar in params[key]: if not self.gen_variable_declaration(tvar): raise AppArmorException("Malformed template_var '%s'" % tvar) (k, v) = tvar.split('=') k = k.lstrip('@').lstrip('{').rstrip('}') d['security']['profiles'][pkey]['template_variables'][k] = v elif key == 'abstractions' or key == 'policy_groups': d['security']['profiles'][pkey][key] = params[key].split(",") d['security']['profiles'][pkey][key].sort() else: d['security']['profiles'][pkey][key] = params[key] json_str = json.dumps(d, sort_keys=True, indent=2, separators=(',', ': ') ) return json_str def print_basefilenames(files): for i in files: sys.stdout.write("%s\n" % (os.path.basename(i))) def print_files(files): for i in files: with open(i) as f: sys.stdout.write(f.read()+"\n") def check_manifest_conflict_args(option, opt_str, value, parser): '''Check for -m/--manifest with conflicting args''' conflict_args = ['abstractions', 'read_path', 'write_path', # template always get set to 'default', can't conflict # 'template', 'policy_groups', 'policy_version', 'policy_vendor', 'name', 'profile_name', 'comment', 'copyright', 'author', 'template_var'] for conflict in conflict_args: if getattr(parser.values, conflict, False): raise optparse.OptionValueError("can't use --%s with --manifest " \ "argument" % conflict) setattr(parser.values, option.dest, value) def check_for_manifest_arg(option, opt_str, value, parser): '''Check for -m/--manifest with conflicting args''' if parser.values.manifest: raise optparse.OptionValueError("can't use --%s with --manifest " \ "argument" % opt_str.lstrip('-')) setattr(parser.values, option.dest, value) def check_for_manifest_arg_append(option, opt_str, value, parser): '''Check for -m/--manifest with conflicting args (with append)''' if parser.values.manifest: raise optparse.OptionValueError("can't use --%s with --manifest " \ "argument" % opt_str.lstrip('-')) parser.values.ensure_value(option.dest, []).append(value) def add_parser_policy_args(parser): '''Add parser arguments''' parser.add_option("-a", "--abstractions", action="callback", callback=check_for_manifest_arg, type=str, dest="abstractions", help="Comma-separated list of abstractions", metavar="ABSTRACTIONS") parser.add_option("--read-path", action="callback", callback=check_for_manifest_arg_append, type=str, dest="read_path", help="Path allowing owner reads", metavar="PATH") parser.add_option("--write-path", action="callback", callback=check_for_manifest_arg_append, type=str, dest="write_path", help="Path allowing owner writes", metavar="PATH") parser.add_option("-t", "--template", dest="template", help="Use non-default policy template", metavar="TEMPLATE", default='default') parser.add_option("--templates-dir", dest="templates_dir", help="Use non-default templates directory", metavar="DIR") parser.add_option("-p", "--policy-groups", action="callback", callback=check_for_manifest_arg, type=str, help="Comma-separated list of policy groups", metavar="POLICYGROUPS") parser.add_option("--policy-groups-dir", dest="policy_groups_dir", help="Use non-default policy-groups directory", metavar="DIR") parser.add_option("--policy-version", action="callback", callback=check_for_manifest_arg, type=str, dest="policy_version", help="Specify version for templates and policy groups", metavar="VERSION") parser.add_option("--policy-vendor", action="callback", callback=check_for_manifest_arg, type=str, dest="policy_vendor", help="Specify vendor for templates and policy groups", metavar="VENDOR") parser.add_option("--profile-name", action="callback", callback=check_for_manifest_arg, type=str, dest="profile_name", help="AppArmor profile name", metavar="PROFILENAME") def parse_args(args=None, parser=None): '''Parse arguments''' global DEBUGGING if parser == None: parser = optparse.OptionParser() parser.add_option("-c", "--config-file", dest="conffile", help="Use alternate configuration file", metavar="FILE") parser.add_option("-d", "--debug", help="Show debugging output", action='store_true', default=False) parser.add_option("--no-verify", help="Don't verify policy using 'apparmor_parser -p'", action='store_true', default=False) parser.add_option("--list-templates", help="List available templates", action='store_true', default=False) parser.add_option("--show-template", help="Show specified template", action='store_true', default=False) parser.add_option("--list-policy-groups", help="List available policy groups", action='store_true', default=False) parser.add_option("--show-policy-group", help="Show specified policy groups", action='store_true', default=False) parser.add_option("-n", "--name", action="callback", callback=check_for_manifest_arg, type=str, dest="name", help="Name of policy (not AppArmor profile name)", metavar="COMMENT") parser.add_option("--comment", action="callback", callback=check_for_manifest_arg, type=str, dest="comment", help="Comment for policy", metavar="COMMENT") parser.add_option("--author", action="callback", callback=check_for_manifest_arg, type=str, dest="author", help="Author of policy", metavar="COMMENT") parser.add_option("--copyright", action="callback", callback=check_for_manifest_arg, type=str, dest="copyright", help="Copyright for policy", metavar="COMMENT") parser.add_option("--template-var", action="callback", callback=check_for_manifest_arg_append, type=str, dest="template_var", help="Declare AppArmor variable", metavar="@{VARIABLE}=VALUE") parser.add_option("--output-format", action="store", dest="output_format", help="Specify output format as text (default) or json", metavar="FORMAT", default="text") parser.add_option("--output-directory", action="store", dest="output_directory", help="Output policy to this directory", metavar="DIR") # This option conflicts with any of the value arguments, e.g. name, # author, template-var, etc. parser.add_option("-m", "--manifest", action="callback", callback=check_manifest_conflict_args, type=str, dest="manifest", help="JSON manifest file", metavar="FILE") parser.add_option("--verify-manifest", action="store_true", default=False, dest="verify_manifest", help="Verify JSON manifest file") # add policy args now add_parser_policy_args(parser) (my_opt, my_args) = parser.parse_args(args) if my_opt.debug: DEBUGGING = True return (my_opt, my_args) def gen_policy_params(binary, opt): '''Generate parameters for gen_policy''' params = dict(binary=binary) if not binary and not opt.profile_name: raise AppArmorException("Must specify binary and/or profile name") if opt.profile_name: params['profile_name'] = opt.profile_name if opt.name: params['name'] = opt.name else: if opt.profile_name: params['name'] = opt.profile_name elif binary: params['name'] = os.path.basename(binary) if opt.template_var: # What about specified multiple times? params['template_var'] = opt.template_var if opt.abstractions: params['abstractions'] = opt.abstractions if opt.policy_groups: params['policy_groups'] = opt.policy_groups if opt.read_path: params['read_path'] = opt.read_path if opt.write_path: params['write_path'] = opt.write_path if opt.comment: params['comment'] = opt.comment if opt.author: params['author'] = opt.author if opt.copyright: params['copyright'] = opt.copyright if opt.policy_version and opt.output_format == "json": params['policy_version'] = opt.policy_version if opt.policy_vendor and opt.output_format == "json": params['policy_vendor'] = opt.policy_vendor return params def parse_manifest(manifest, opt_orig): '''Take a JSON manifest as a string and updates options, returning an updated binary. Note that a JSON file may contain multiple profiles.''' try: m = json.loads(manifest) except ValueError: raise AppArmorException("Could not parse manifest") if 'security' in m: top_table = m['security'] else: top_table = m if 'profiles' not in top_table: raise AppArmorException("Could not parse manifest (could not find 'profiles')") table = top_table['profiles'] # generally mirrors what is settable in gen_policy_params() valid_keys = ['abstractions', 'author', 'binary', 'comment', 'copyright', 'name', 'policy_groups', 'policy_version', 'policy_vendor', 'profile_name', 'read_path', 'template', 'template_variables', 'write_path', ] profiles = [] for profile_name in table: if not isinstance(table[profile_name], dict): raise AppArmorException("Wrong JSON structure") opt = copy.deepcopy(opt_orig) # The JSON structure is: # { # "security": { # : { # "binary": ... # ... # but because binary can be the profile name, we need to handle # 'profile_name' and 'binary' special. If a profile_name starts with # '/', then it is considered the binary. Otherwise, set the # profile_name and set the binary if it is in the JSON. binary = None if profile_name.startswith('/'): if 'binary' in table[profile_name]: raise AppArmorException("Profile name should not specify path with binary") binary = profile_name else: setattr(opt, 'profile_name', profile_name) if 'binary' in table[profile_name]: binary = table[profile_name]['binary'] setattr(opt, 'binary', binary) for key in table[profile_name]: if key not in valid_keys: raise AppArmorException("Invalid key '%s'" % key) if key == 'binary': continue # handled above elif key == 'abstractions' or key == 'policy_groups': setattr(opt, key, ",".join(table[profile_name][key])) elif key == "template_variables": t = table[profile_name]['template_variables'] vlist = [] for v in t.keys(): vlist.append("@{%s}=%s" % (v, t[v])) setattr(opt, 'template_var', vlist) else: if hasattr(opt, key): setattr(opt, key, table[profile_name][key]) profiles.append( (binary, opt) ) return profiles def verify_options(opt, strict=False): '''Make sure our options are valid''' if hasattr(opt, 'binary') and opt.binary and not valid_path(opt.binary): raise AppArmorException("Invalid binary '%s'" % opt.binary) if hasattr(opt, 'profile_name') and opt.profile_name != None and \ not valid_profile_name(opt.profile_name): raise AppArmorException("Invalid profile name '%s'" % opt.profile_name) if hasattr(opt, 'binary') and opt.binary and \ hasattr(opt, 'profile_name') and opt.profile_name != None and \ opt.profile_name.startswith('/'): raise AppArmorException("Profile name should not specify path with binary") if hasattr(opt, 'policy_vendor') and opt.policy_vendor and \ not valid_policy_vendor(opt.policy_vendor): raise AppArmorException("Invalid policy vendor '%s'" % \ opt.policy_vendor) if hasattr(opt, 'policy_version') and opt.policy_version and \ not valid_policy_version(opt.policy_version): raise AppArmorException("Invalid policy version '%s'" % \ opt.policy_version) if hasattr(opt, 'template') and opt.template and \ not valid_template_name(opt.template, strict): raise AppArmorException("Invalid template '%s'" % opt.template) if hasattr(opt, 'template_var') and opt.template_var: for i in opt.template_var: if not valid_variable(i): raise AppArmorException("Invalid variable '%s'" % i) if hasattr(opt, 'policy_groups') and opt.policy_groups: for i in opt.policy_groups.split(','): if not valid_policy_group_name(i): raise AppArmorException("Invalid policy group '%s'" % i) if hasattr(opt, 'abstractions') and opt.abstractions: for i in opt.abstractions.split(','): if not valid_abstraction_name(i): raise AppArmorException("Invalid abstraction '%s'" % i) if hasattr(opt, 'read_paths') and opt.read_paths: for i in opt.read_paths: if not valid_path(i): raise AppArmorException("Invalid read path '%s'" % i) if hasattr(opt, 'write_paths') and opt.write_paths: for i in opt.write_paths: if not valid_path(i): raise AppArmorException("Invalid write path '%s'" % i) def verify_manifest(params): '''Verify manifest for safe and unsafe options''' err_str = "" (opt, args) = parse_args() fake_easyp = AppArmorEasyProfile(None, opt) unsafe_keys = ['read_path', 'write_path'] safe_abstractions = ['base'] for k in params: debug("Examining %s=%s" % (k, params[k])) if k in unsafe_keys: err_str += "\nfound %s key" % k elif k == 'profile_name': if params['profile_name'].startswith('/') or \ '*' in params['profile_name']: err_str += "\nprofile_name '%s'" % params['profile_name'] elif k == 'abstractions': for a in params['abstractions'].split(','): if not a in safe_abstractions: err_str += "\nfound '%s' abstraction" % a elif k == "template_var": pat = re.compile(r'[*/\{\}\[\]]') for tv in params['template_var']: if not fake_easyp.gen_variable_declaration(tv): err_str += "\n%s" % tv continue tv_val = tv.split('=')[1] debug("Examining %s" % tv_val) if '..' in tv_val or pat.search(tv_val): err_str += "\n%s" % tv if err_str: warn("Manifest definition is potentially unsafe%s" % err_str) return False return True apparmor-2.8.95~2430/utils/apparmor/logparser.py0000644000175000017500000003775212277204137021411 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import os import re import sys import time import LibAppArmor from apparmor.common import AppArmorException, open_file_read, DebugLogger from apparmor.aamode import validate_log_mode, log_str_to_mode, hide_log_mode, AA_MAY_EXEC # setup module translations from apparmor.translations import init_translation _ = init_translation() class ReadLog: RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=') RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=') MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N') LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix') PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x') # Used by netdomain to identify the operation types # New socket names OPERATION_TYPES = {'create': 'net', 'post_create': 'net', 'bind': 'net', 'connect': 'net', 'listen': 'net', 'accept': 'net', 'sendmsg': 'net', 'recvmsg': 'net', 'getsockname': 'net', 'getpeername': 'net', 'getsockopt': 'net', 'setsockopt': 'net', 'sock_shutdown': 'net' } def __init__(self, pid, filename, existing_profiles, profile_dir, log): self.filename = filename self.profile_dir = profile_dir self.pid = pid self.existing_profiles = existing_profiles self.log = log self.debug_logger = DebugLogger('ReadLog') self.LOG = None self.logmark = '' self.seenmark = None self.next_log_entry = None def prefetch_next_log_entry(self): if self.next_log_entry: sys.stderr.out('A log entry already present: %s' % self.next_log_entry) self.next_log_entry = self.LOG.readline() while not self.RE_LOG_v2_6_syslog.search(self.next_log_entry) and not self.RE_LOG_v2_6_audit.search(self.next_log_entry) and not (self.logmark and self.logmark in self.next_log_entry): self.next_log_entry = self.LOG.readline() if not self.next_log_entry: break def get_next_log_entry(self): # If no next log entry fetch it if not self.next_log_entry: self.prefetch_next_log_entry() log_entry = self.next_log_entry self.next_log_entry = None return log_entry def peek_at_next_log_entry(self): # Take a peek at the next log entry if not self.next_log_entry: self.prefetch_next_log_entry() return self.next_log_entry def throw_away_next_log_entry(self): self.next_log_entry = None def parse_log_record(self, record): self.debug_logger.debug('parse_log_record: %s' % record) record_event = self.parse_event(record) return record_event def parse_event(self, msg): """Parse the event from log into key value pairs""" msg = msg.strip() self.debug_logger.info('parse_event: %s' % msg) #print(repr(msg)) if sys.version_info < (3, 0): # parse_record fails with u'foo' style strings hence typecasting to string msg = str(msg) event = LibAppArmor.parse_record(msg) ev = dict() ev['resource'] = event.info ev['active_hat'] = event.active_hat ev['aamode'] = event.event ev['time'] = event.epoch ev['operation'] = event.operation ev['profile'] = event.profile ev['name'] = event.name ev['name2'] = event.name2 ev['attr'] = event.attribute ev['parent'] = event.parent ev['pid'] = event.pid ev['task'] = event.task ev['info'] = event.info dmask = event.denied_mask rmask = event.requested_mask ev['magic_token'] = event.magic_token if ev['operation'] and self.op_type(ev['operation']) == 'net': ev['family'] = event.net_family ev['protocol'] = event.net_protocol ev['sock_type'] = event.net_sock_type LibAppArmor.free_record(event) # Map c (create) to a and d (delete) to w, logprof doesn't support c and d if rmask: rmask = rmask.replace('c', 'a') rmask = rmask.replace('d', 'w') if not validate_log_mode(hide_log_mode(rmask)): raise AppArmorException(_('Log contains unknown mode %s') % rmask) if dmask: dmask = dmask.replace('c', 'a') dmask = dmask.replace('d', 'w') if not validate_log_mode(hide_log_mode(dmask)): raise AppArmorException(_('Log contains unknown mode %s') % dmask) #print('parse_event:', ev['profile'], dmask, ev['name2']) mask, name = log_str_to_mode(ev['profile'], dmask, ev['name2']) ev['denied_mask'] = mask ev['name2'] = name mask, name = log_str_to_mode(ev['profile'], rmask, ev['name2']) ev['request_mask'] = mask ev['name2'] = name if not ev['time']: ev['time'] = int(time.time()) # Remove None keys #for key in ev.keys(): # if not ev[key] or not re.search('[\w]+', ev[key]): # ev.pop(key) if ev['aamode']: # Convert aamode values to their counter-parts mode_convertor = {0: 'UNKNOWN', 1: 'ERROR', 2: 'AUDITING', 3: 'PERMITTING', 4: 'REJECTING', 5: 'HINT', 6: 'STATUS' } try: ev['aamode'] = mode_convertor[ev['aamode']] except KeyError: ev['aamode'] = None if ev['aamode']: #debug_logger.debug(ev) return ev else: return None def add_to_tree(self, loc_pid, parent, type, event): self.debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (loc_pid, type, event)) if not self.pid.get(loc_pid, False): profile, hat = event[:2] if parent and self.pid.get(parent, False): if not hat: hat = 'null-complain-profile' arrayref = [] self.pid[parent].append(arrayref) self.pid[loc_pid] = arrayref for ia in ['fork', loc_pid, profile, hat]: arrayref.append(ia) # self.pid[parent].append(array_ref) # self.pid[loc_pid] = array_ref else: arrayref = [] self.log.append(arrayref) self.pid[loc_pid] = arrayref # self.log.append(array_ref) # self.pid[loc_pid] = array_ref self.pid[loc_pid].append([type, loc_pid] + event) #print("\n\npid",self.pid) #print("log",self.log) def add_event_to_tree(self, e): aamode = e.get('aamode', 'UNKNOWN') if e.get('type', False): if re.search('(UNKNOWN\[1501\]|APPARMOR_AUDIT|1501)', e['type']): aamode = 'AUDIT' elif re.search('(UNKNOWN\[1502\]|APPARMOR_ALLOWED|1502)', e['type']): aamode = 'PERMITTING' elif re.search('(UNKNOWN\[1503\]|APPARMOR_DENIED|1503)', e['type']): aamode = 'REJECTING' elif re.search('(UNKNOWN\[1504\]|APPARMOR_HINT|1504)', e['type']): aamode = 'HINT' elif re.search('(UNKNOWN\[1505\]|APPARMOR_STATUS|1505)', e['type']): aamode = 'STATUS' elif re.search('(UNKNOWN\[1506\]|APPARMOR_ERROR|1506)', e['type']): aamode = 'ERROR' else: aamode = 'UNKNOWN' if aamode in ['UNKNOWN', 'AUDIT', 'STATUS', 'ERROR']: return None if 'profile_set' in e['operation']: return None # Skip if AUDIT event was issued due to a change_hat in unconfined mode if not e.get('profile', False): return None # Convert new null profiles to old single level null profile if '//null-' in e['profile']: e['profile'] = 'null-complain-profile' profile = e['profile'] hat = None if '//' in e['profile']: profile, hat = e['profile'].split('//')[:2] # Filter out change_hat events that aren't from learning if e['operation'] == 'change_hat': if aamode != 'HINT' and aamode != 'PERMITTING': return None profile = e['name'] #hat = None if '//' in e['name']: profile, hat = e['name'].split('//')[:2] if not hat: hat = profile # prog is no longer passed around consistently prog = 'HINT' if profile != 'null-complain-profile' and not self.profile_exists(profile): return None if e['operation'] == 'exec': if e.get('info', False) and e['info'] == 'mandatory profile missing': self.add_to_tree(e['pid'], e['parent'], 'exec', [profile, hat, aamode, 'PERMITTING', e['denied_mask'], e['name'], e['name2']]) elif e.get('name2', False) and '\\null-/' in e['name2']: self.add_to_tree(e['pid'], e['parent'], 'exec', [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif e.get('name', False): self.add_to_tree(e['pid'], e['parent'], 'exec', [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) else: self.debug_logger.debug('add_event_to_tree: dropped exec event in %s' % e['profile']) elif 'file_' in e['operation']: self.add_to_tree(e['pid'], e['parent'], 'path', [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'rename_src', 'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link']: #print(e['operation'], e['name']) self.add_to_tree(e['pid'], e['parent'], 'path', [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif e['operation'] == 'capable': self.add_to_tree(e['pid'], e['parent'], 'capability', [profile, hat, prog, aamode, e['name'], '']) elif e['operation'] == 'setattr' or 'xattr' in e['operation']: self.add_to_tree(e['pid'], e['parent'], 'path', [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif 'inode_' in e['operation']: is_domain_change = False if e['operation'] == 'inode_permission' and (e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING': following = self.peek_at_next_log_entry() if following: entry = self.parse_log_record(following) if entry and entry.get('info', False) == 'set profile': is_domain_change = True self.throw_away_next_log_entry() if is_domain_change: self.add_to_tree(e['pid'], e['parent'], 'exec', [profile, hat, prog, aamode, e['denied_mask'], e['name'], e['name2']]) else: self.add_to_tree(e['pid'], e['parent'], 'path', [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif e['operation'] == 'sysctl': self.add_to_tree(e['pid'], e['parent'], 'path', [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif e['operation'] == 'clone': parent, child = e['pid'], e['task'] if not parent: parent = 'null-complain-profile' if not hat: hat = 'null-complain-profile' arrayref = [] if self.pid.get(parent, False): self.pid[parent].append(arrayref) else: self.log.append(arrayref) self.pid[child].append(arrayref) for ia in ['fork', child, profile, hat]: arrayref.append(ia) # if self.pid.get(parent, False): # self.pid[parent] += [arrayref] # else: # self.log += [arrayref] # self.pid[child] = arrayref elif self.op_type(e['operation']) == 'net': self.add_to_tree(e['pid'], e['parent'], 'netdomain', [profile, hat, prog, aamode, e['family'], e['sock_type'], e['protocol']]) elif e['operation'] == 'change_hat': self.add_to_tree(e['pid'], e['parent'], 'unknown_hat', [profile, hat, aamode, hat]) else: self.debug_logger.debug('UNHANDLED: %s' % e) def read_log(self, logmark): self.logmark = logmark seenmark = True if self.logmark: seenmark = False #last = None #event_type = None try: #print(self.filename) self.LOG = open_file_read(self.filename) except IOError: raise AppArmorException('Can not read AppArmor logfile: ' + self.filename) #LOG = open_file_read(log_open) line = True while line: line = self.get_next_log_entry() if not line: break line = line.strip() self.debug_logger.debug('read_log: %s' % line) if self.logmark in line: seenmark = True self.debug_logger.debug('read_log: seenmark = %s' % seenmark) if not seenmark: continue event = self.parse_log_record(line) #print(event) if event: self.add_event_to_tree(event) self.LOG.close() self.logmark = '' return self.log def op_type(self, operation): """Returns the operation type if known, unkown otherwise""" operation_type = self.OPERATION_TYPES.get(operation, 'unknown') return operation_type def profile_exists(self, program): """Returns True if profile exists, False otherwise""" # Check cache of profiles if self.existing_profiles.get(program, False): return True # Check the disk for profile prof_path = self.get_profile_filename(program) #print(prof_path) if os.path.isfile(prof_path): # Add to cache of profile self.existing_profiles[program] = prof_path return True return False def get_profile_filename(self, profile): """Returns the full profile name""" if profile.startswith('/'): # Remove leading / profile = profile[1:] else: profile = "profile_" + profile profile = profile.replace('/', '.') full_profilename = self.profile_dir + '/' + profile return full_profilename apparmor-2.8.95~2430/utils/apparmor/cleanprofile.py0000644000175000017500000001667612277004630022054 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import re import copy import apparmor class Prof(object): def __init__(self, filename): self.aa = apparmor.aa.aa self.filelist = apparmor.aa.filelist self.include = apparmor.aa.include self.filename = filename class CleanProf(object): def __init__(self, same_file, profile, other): #If same_file we're basically comparing the file against itself to check superfluous rules self.same_file = same_file self.profile = profile self.other = other def compare_profiles(self): deleted = 0 other_file_includes = list(self.other.filelist[self.other.filename]['include'].keys()) #Remove the duplicate file-level includes from other for rule in self.profile.filelist[self.profile.filename]['include'].keys(): if rule in other_file_includes: self.other.filelist[self.other.filename]['include'].pop(rule) for profile in self.profile.aa.keys(): deleted += self.remove_duplicate_rules(profile) return deleted def remove_duplicate_rules(self, program): #Process the profile of the program #Process every hat in the profile individually file_includes = list(self.profile.filelist[self.profile.filename]['include'].keys()) deleted = 0 for hat in self.profile.aa[program].keys(): #The combined list of includes from profile and the file includes = list(self.profile.aa[program][hat]['include'].keys()) + file_includes #If different files remove duplicate includes in the other profile if not self.same_file: for inc in includes: if self.other.aa[program][hat]['include'].get(inc, False): self.other.aa[program][hat]['include'].pop(inc) deleted += 1 #Clean up superfluous rules from includes in the other profile for inc in includes: if not self.profile.include.get(inc, {}).get(inc, False): apparmor.aa.load_include(inc) deleted += apparmor.aa.delete_duplicates(self.other.aa[program][hat], inc) #Clean the duplicates of caps in other profile deleted += delete_cap_duplicates(self.profile.aa[program][hat]['allow']['capability'], self.other.aa[program][hat]['allow']['capability'], self.same_file) deleted += delete_cap_duplicates(self.profile.aa[program][hat]['deny']['capability'], self.other.aa[program][hat]['deny']['capability'], self.same_file) #Clean the duplicates of path in other profile deleted += delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'allow', self.same_file) deleted += delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'deny', self.same_file) #Clean the duplicates of net rules in other profile deleted += delete_net_duplicates(self.profile.aa[program][hat]['allow']['netdomain'], self.other.aa[program][hat]['allow']['netdomain'], self.same_file) deleted += delete_net_duplicates(self.profile.aa[program][hat]['deny']['netdomain'], self.other.aa[program][hat]['deny']['netdomain'], self.same_file) return deleted def delete_path_duplicates(profile, profile_other, allow, same_profile=True): deleted = [] # Check if any individual rule makes any rule superfluous for rule in profile[allow]['path'].keys(): for entry in profile_other[allow]['path'].keys(): if rule == entry: # Check the modes cm = profile[allow]['path'][rule]['mode'] am = profile[allow]['path'][rule]['audit'] # If modes of rule are a superset of rules implied by entry we can safely remove it if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): if not same_profile: deleted.append(entry) continue if re.search('#?\s*include', rule) or re.search('#?\s*include', entry): continue # Check if the rule implies entry if apparmor.aa.matchliteral(rule, entry): # Check the modes cm = profile[allow]['path'][rule]['mode'] am = profile[allow]['path'][rule]['audit'] # If modes of rule are a superset of rules implied by entry we can safely remove it if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): deleted.append(entry) for entry in deleted: profile_other[allow]['path'].pop(entry) return len(deleted) def delete_cap_duplicates(profilecaps, profilecaps_other, same_profile=True): deleted = [] if profilecaps and profilecaps_other and not same_profile: for capname in profilecaps.keys(): if profilecaps_other[capname].get('set', False): deleted.append(capname) for capname in deleted: profilecaps_other.pop(capname) return len(deleted) def delete_net_duplicates(netrules, netrules_other, same_profile=True): deleted = 0 hasher_obj = apparmor.aa.hasher() copy_netrules_other = copy.deepcopy(netrules_other) if netrules_other and netrules: netglob = False # Delete matching rules if netrules.get('all', False): netglob = True # Iterate over a copy of the rules in the other profile for fam in copy_netrules_other['rule'].keys(): if netglob or (type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]): if not same_profile: if type(netrules_other['rule'][fam]) == type(hasher_obj): deleted += len(netrules_other['rule'][fam].keys()) else: deleted += 1 netrules_other['rule'].pop(fam) elif type(netrules_other['rule'][fam]) != type(hasher_obj) and netrules_other['rule'][fam]: if type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]: if not same_profile: netrules_other['rule'].pop(fam) deleted += 1 else: for sock_type in netrules_other['rule'][fam].keys(): if netrules['rule'].get(fam, False): if netrules['rule'][fam].get(sock_type, False): if not same_profile: netrules_other['rule'][fam].pop(sock_type) deleted += 1 return deleted apparmor-2.8.95~2430/utils/apparmor/aamode.py0000644000175000017500000001633412277004630020626 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import re def AA_OTHER(mode): other = set() for i in mode: other.add('::%s' % i) return other def AA_OTHER_REMOVE(mode): other = set() for i in mode: if '::' in i: other.add(i[2:]) return other AA_MAY_EXEC = set('x') AA_MAY_WRITE = set('w') AA_MAY_READ = set('r') AA_MAY_APPEND = set('a') AA_MAY_LINK = set('l') AA_MAY_LOCK = set('k') AA_EXEC_MMAP = set('m') AA_EXEC_UNSAFE = set(['execunsafe']) AA_EXEC_INHERIT = set('i') AA_EXEC_UNCONFINED = set('U') AA_EXEC_PROFILE = set('P') AA_EXEC_CHILD = set('C') AA_EXEC_NT = set('N') AA_LINK_SUBSET = set(['linksubset']) #AA_OTHER_SHIFT = 14 #AA_USER_MASK = 16384 - 1 AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT | AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT) ALL_AA_EXEC_TYPE = AA_EXEC_TYPE MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, 'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE, 'r': AA_MAY_READ, 'R': AA_MAY_READ, 'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND, 'l': AA_MAY_LINK, 'L': AA_MAY_LINK, 'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK, 'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP, 'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT, 'u': AA_EXEC_UNCONFINED | AA_EXEC_UNSAFE, # Unconfined + Unsafe 'U': AA_EXEC_UNCONFINED, 'p': AA_EXEC_PROFILE | AA_EXEC_UNSAFE, # Profile + unsafe 'P': AA_EXEC_PROFILE, 'c': AA_EXEC_CHILD | AA_EXEC_UNSAFE, # Child + Unsafe 'C': AA_EXEC_CHILD, 'n': AA_EXEC_NT | AA_EXEC_UNSAFE, 'N': AA_EXEC_NT } LOG_MODE_RE = re.compile('(r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix)') MODE_MAP_RE = re.compile('(r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N)') def str_to_mode(string): if not string: return set() user, other = split_log_mode(string) if not user: user = other mode = sub_str_to_mode(user) #print(string, mode) #print(string, 'other', sub_str_to_mode(other)) mode |= (AA_OTHER(sub_str_to_mode(other))) #print (string, mode) #print('str_to_mode:', mode) return mode def sub_str_to_mode(string): mode = set() if not string: return mode while string: tmp = MODE_MAP_RE.search(string) if tmp: tmp = tmp.groups()[0] string = MODE_MAP_RE.sub('', string, 1) if tmp and MODE_HASH.get(tmp, False): mode |= MODE_HASH[tmp] else: pass return mode def split_log_mode(mode): user = '' other = '' match = re.search('(.*?)::(.*)', mode) if match: user, other = match.groups() else: user = mode other = mode #print ('split_logmode:', user, mode) return user, other def mode_contains(mode, subset): # w implies a if mode & AA_MAY_WRITE: mode |= AA_MAY_APPEND if mode & (AA_OTHER(AA_MAY_WRITE)): mode |= (AA_OTHER(AA_MAY_APPEND)) return (mode & subset) == subset def contains(mode, string): return mode_contains(mode, str_to_mode(string)) def validate_log_mode(mode): if LOG_MODE_RE.search(mode): #if LOG_MODE_RE.search(mode): return True else: return False def hide_log_mode(mode): mode = mode.replace('::', '') return mode def map_log_mode(mode): return mode def print_mode(mode): user, other = split_mode(mode) string = sub_mode_to_str(user) + '::' + sub_mode_to_str(other) return string def sub_mode_to_str(mode): string = '' # w(write) implies a(append) if mode & AA_MAY_WRITE: mode = mode - AA_MAY_APPEND #string = ''.join(mode) if mode & AA_EXEC_MMAP: string += 'm' if mode & AA_MAY_READ: string += 'r' if mode & AA_MAY_WRITE: string += 'w' if mode & AA_MAY_APPEND: string += 'a' if mode & AA_MAY_LINK: string += 'l' if mode & AA_MAY_LOCK: string += 'k' # modes P and C must appear before I and U else invalid syntax if mode & (AA_EXEC_PROFILE | AA_EXEC_NT): if mode & AA_EXEC_UNSAFE: string += 'p' else: string += 'P' if mode & AA_EXEC_CHILD: if mode & AA_EXEC_UNSAFE: string += 'c' else: string += 'C' if mode & AA_EXEC_UNCONFINED: if mode & AA_EXEC_UNSAFE: string += 'u' else: string += 'U' if mode & AA_EXEC_INHERIT: string += 'i' if mode & AA_MAY_EXEC: string += 'x' return string def is_user_mode(mode): user, other = split_mode(mode) if user and not other: return True else: return False def profilemode(mode): pass def split_mode(mode): user = set() for i in mode: if not '::' in i: user.add(i) other = mode - user other = AA_OTHER_REMOVE(other) return user, other def mode_to_str(mode): mode = flatten_mode(mode) return sub_mode_to_str(mode) def flatten_mode(mode): if not mode: return set() user, other = split_mode(mode) mode = user | other mode |= (AA_OTHER(mode)) return mode def owner_flatten_mode(mode): mode = flatten_mode(mode) return mode def mode_to_str_user(mode): user, other = split_mode(mode) string = '' if not user: user = set() if not other: other = set() if user - other: if other: string = sub_mode_to_str(other) + '+' string += 'owner ' + sub_mode_to_str(user - other) elif is_user_mode(mode): string = 'owner ' + sub_mode_to_str(user) else: string = sub_mode_to_str(flatten_mode(mode)) return string def log_str_to_mode(profile, string, nt_name): mode = str_to_mode(string) # If contains nx and nix #print (profile, string, nt_name) if contains(mode, 'Nx'): # Transform to px, cx match = re.search('(.+?)//(.+?)', nt_name) if match: lprofile, lhat = match.groups() tmode = 0 if lprofile == profile: if mode & AA_MAY_EXEC: tmode = str_to_mode('Cx::') if mode & AA_OTHER(AA_MAY_EXEC): tmode |= str_to_mode('Cx') nt_name = lhat else: if mode & AA_MAY_EXEC: tmode = str_to_mode('Px::') if mode & AA_OTHER(AA_MAY_EXEC): tmode |= str_to_mode('Px') nt_name = lhat mode = mode - str_to_mode('Nx') mode |= tmode return mode, nt_name apparmor-2.8.95~2430/utils/apparmor/tools.py0000644000175000017500000002657612306151176020552 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import os import sys import apparmor.aa as apparmor import apparmor.ui as aaui from apparmor.common import user_perm, cmd # setup module translations from apparmor.translations import init_translation _ = init_translation() class aa_tools: def __init__(self, tool_name, args): self.name = tool_name self.profiledir = args.dir self.profiling = args.program self.check_profile_dir() self.silent = None if tool_name in ['audit']: self.remove = args.remove elif tool_name == 'disable': self.disabledir = apparmor.profile_dir + '/disable' self.check_disable_dir() elif tool_name == 'autodep': self.force = args.force self.aa_mountpoint = apparmor.check_for_apparmor() elif tool_name == 'cleanprof': self.silent = args.silent def check_profile_dir(self): if self.profiledir: apparmor.profile_dir = apparmor.get_full_path(self.profiledir) if not os.path.isdir(apparmor.profile_dir): raise apparmor.AppArmorException("%s is not a directory." % self.profiledir) if not user_perm(apparmor.profile_dir): raise apparmor.AppArmorException("Cannot write to profile directory: %s" % (apparmor.profile_dir)) def check_disable_dir(self): if not os.path.isdir(self.disabledir): raise apparmor.AppArmorException("Can't find AppArmor disable directory %s" % self.disabledir) def get_next_to_profile(self): '''Iterator function to walk the list of arguments passed''' for p in self.profiling: if not p: continue program = None profile = None if os.path.exists(p): fq_path = apparmor.get_full_path(p).strip() if os.path.commonprefix([apparmor.profile_dir, fq_path]) == apparmor.profile_dir: program = None profile = fq_path else: program = fq_path profile = apparmor.get_profile_filename(fq_path) else: which = apparmor.which(p) if which is not None: program = apparmor.get_full_path(which) profile = apparmor.get_profile_filename(program) elif os.path.exists(os.path.join(apparmor.profile_dir, p)): program = None profile = apparmor.get_full_path(os.path.join(apparmor.profile_dir, p)).strip() else: if '/' not in p: aaui.UI_Info(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") % (p, p)) else: aaui.UI_Info(_("%s does not exist, please double-check the path.") % p) continue yield (program, profile) def act(self): for (program, profile) in self.get_next_to_profile(): if program is None: program = profile apparmor.read_profiles() if not program or not(os.path.exists(program) or apparmor.profile_exists(program)): if program and not program.startswith('/'): program = aaui.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') else: aaui.UI_Info(_("%s does not exist, please double-check the path.") % program) sys.exit(1) if program and apparmor.profile_exists(program): if self.name == 'cleanprof': self.clean_profile(program) else: filename = apparmor.get_profile_filename(program) if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): aaui.UI_Info(_('Profile for %s not found, skipping') % program) else: # One simply does not walk in here! raise apparmor.AppArmorException('Unknown tool: %s' % self.name) cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '-R', filename]) if cmd_info[0] != 0: raise apparmor.AppArmorException(cmd_info[1]) else: if '/' not in program: aaui.UI_Info(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") % (program, program)) else: aaui.UI_Info(_("%s does not exist, please double-check the path.") % program) sys.exit(1) def cmd_disable(self): for (program, profile) in self.get_next_to_profile(): output_name = profile if program is None else program if not os.path.isfile(profile) or apparmor.is_skippable_file(profile): aaui.UI_Info(_('Profile for %s not found, skipping') % output_name) continue aaui.UI_Info(_('Disabling %s.') % output_name) self.disable_profile(profile) # FIXME: this should be a profile_remove function/method # FIXME: should ensure profile is loaded before unloading cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '-R', profile]) if cmd_info[0] != 0: raise apparmor.AppArmorException(cmd_info[1]) def cmd_enforce(self): for (program, profile) in self.get_next_to_profile(): apparmor.read_profiles() output_name = profile if program is None else program if not os.path.isfile(profile) or apparmor.is_skippable_file(profile): aaui.UI_Info(_('Profile for %s not found, skipping') % output_name) continue apparmor.set_enforce(profile, program) # FIXME: this should be a profile_reload function/method cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '-r', profile]) if cmd_info[0] != 0: raise apparmor.AppArmorException(cmd_info[1]) def cmd_complain(self): for (program, profile) in self.get_next_to_profile(): apparmor.read_profiles() output_name = profile if program is None else program if not os.path.isfile(profile) or apparmor.is_skippable_file(profile): aaui.UI_Info(_('Profile for %s not found, skipping') % output_name) continue apparmor.set_complain(profile, program) # FIXME: this should be a profile_reload function/method cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '-r', profile]) if cmd_info[0] != 0: raise apparmor.AppArmorException(cmd_info[1]) def cmd_audit(self): for (program, profile) in self.get_next_to_profile(): apparmor.read_profiles() output_name = profile if program is None else program if not os.path.isfile(profile) or apparmor.is_skippable_file(profile): aaui.UI_Info(_('Profile for %s not found, skipping') % output_name) continue # keep this to allow toggling 'audit' flags if not self.remove: aaui.UI_Info(_('Setting %s to audit mode.') % output_name) else: aaui.UI_Info(_('Removing audit mode from %s.') % output_name) apparmor.change_profile_flags(profile, program, 'audit', not self.remove) # FIXME: this should be a profile_reload function/method cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '-r', profile]) if cmd_info[0] != 0: raise apparmor.AppArmorException(cmd_info[1]) def cmd_autodep(self): for (program, profile) in self.get_next_to_profile(): if not program: aaui.UI_Info(_('Please pass an application to generate a profile for, not a profile itself - skipping %s.') % profile) continue apparmor.read_profiles() apparmor.check_qualifiers(program) if os.path.exists(apparmor.get_profile_filename(program)) and not self.force: aaui.UI_Info(_('Profile for %s already exists - skipping.') % program) else: apparmor.autodep(program) if self.aa_mountpoint: apparmor.reload(program) def clean_profile(self, program): filename = apparmor.get_profile_filename(program) import apparmor.cleanprofile as cleanprofile prof = cleanprofile.Prof(filename) cleanprof = cleanprofile.CleanProf(True, prof, prof) deleted = cleanprof.remove_duplicate_rules(program) aaui.UI_Info(_("\nDeleted %s rules.") % deleted) apparmor.changed[program] = True if filename: if not self.silent: q = apparmor.hasher() q['title'] = 'Changed Local Profiles' q['headers'] = [] q['explanation'] = _('The local profile for %s in file %s was changed. Would you like to save it?') % (program, filename) q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT'] q['default'] = 'CMD_VIEW_CHANGES' q['options'] = [] q['selected'] = 0 ans = '' arg = None while ans != 'CMD_SAVE_CHANGES': ans, arg = aaui.UI_PromptUser(q) if ans == 'CMD_SAVE_CHANGES': apparmor.write_profile_ui_feedback(program) apparmor.reload_base(program) elif ans == 'CMD_VIEW_CHANGES': #oldprofile = apparmor.serialize_profile(apparmor.original_aa[program], program, '') newprofile = apparmor.serialize_profile(apparmor.aa[program], program, '') apparmor.display_changes_with_comments(filename, newprofile) else: apparmor.write_profile_ui_feedback(program) apparmor.reload_base(program) else: raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.') % program) def enable_profile(self, filename): apparmor.delete_symlink('disable', filename) def disable_profile(self, filename): apparmor.create_symlink('disable', filename) apparmor-2.8.95~2430/utils/apparmor/translations.py0000644000175000017500000000126512277004630022116 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ import gettext TRANSLATION_DOMAIN = 'apparmor-utils' __apparmor_gettext__ = None def init_translation(): global __apparmor_gettext__ if __apparmor_gettext__ is None: t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) __apparmor_gettext__ = t.gettext return __apparmor_gettext__ apparmor-2.8.95~2430/utils/apparmor/__init__.py0000644000175000017500000000066512277072411021141 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2011-2012 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ __import__('pkg_resources').declare_namespace(__name__) apparmor-2.8.95~2430/utils/apparmor/yasti.py0000644000175000017500000000632412277607257020545 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import re import sys try: import ycp except ImportError: # ycp isn't found everywhere. ycp = None from apparmor.common import error, DebugLogger # Set up UI logger for separate messages from YaST module debug_logger = DebugLogger('YaST') def setup_yast(): # To-Do pass def shutdown_yast(): # To-Do pass def yastLog(text): ycp.y2milestone(text) def SendDataToYast(data): debug_logger.info('SendDataToYast: Waiting for YCP command') for line in sys.stdin: ycommand, ypath, yargument = ParseCommand(line) if ycommand and ycommand == 'Read': debug_logger.info('SendDataToYast: Sending--%s' % data) ycp.Return(data) return True else: debug_logger.info('SendDataToYast: Expected \'Read\' but got-- %s' % line) error('SendDataToYast: didn\'t receive YCP command before connection died') def GetDataFromYast(): debug_logger.inf('GetDataFromYast: Waiting for YCP command') for line in sys.stdin: debug_logger.info('GetDataFromYast: YCP: %s' % line) ycommand, ypath, yarg = ParseCommand(line) debug_logger.info('GetDataFromYast: Recieved--\n%s' % yarg) if ycommand and ycommand == 'Write': ycp.Return('true') return ypath, yarg else: debug_logger.info('GetDataFromYast: Expected Write but got-- %s' % line) error('GetDataFromYast: didn\'t receive YCP command before connection died') def ParseCommand(commands): term = ParseTerm(commands) if term: command = term[0] term = term[1:] else: command = '' path = '' pathref = None if term: pathref = term[0] term = term[1:] if pathref: if pathref.strip(): path = pathref.strip() elif command != 'result': ycp.y2error('The first arguement is not a path. (%s)' % pathref) argument = None if term: argument = term[0] if len(term) > 1: ycp.y2warning('Superfluous command arguments ignored') return (command, path, argument) def ParseTerm(inp): regex_term = re.compile('^\s*`?(\w*)\s*') term = regex_term.search(inp) ret = [] symbol = None if term: symbol = term.groups()[0] else: ycp.y2error('No term symbol') ret.append(symbol) inp = regex_term.sub('', inp) if not inp.startswith('('): ycp.y2error('No term parantheses') argref, err, rest = ycp.ParseYcpTermBody(inp) if err: ycp.y2error('%s (%s)' % (err, rest)) else: ret += argref return ret apparmor-2.8.95~2430/utils/apparmor/rules.py0000644000175000017500000000333312306404336020525 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ class DBUS_Rule(object): actions = set() busses = set() names = set() paths = set() interfaces = set() members = set() peer_names = set() peer_labels = set() audit = False deny = False def __init__(self, actions=[], busses=[], names=[], paths=[], interfaces=[], members=[], peer_names=[], peer_labels=[]): self.actions = set(actions) self.busses = set(busses) self.names = set(names) self.paths = set(paths) self.interfaces = set(interfaces) self.members = set(members) self.peer_name = set(peer_names) self.peer_labels = set(peer_labels) def serialize(self): out = "%s%s%s" % ('audit ' if self.audit else '', 'deny ' if self.deny else '', 'dbus') if len(self.actions) > 0: if len(self.actions) == 1: out += ' %s' % self.actions[0] else: out += ' (%s)' % (', '.join(self.actions)) out += ',' return out class Raw_DBUS_Rule(object): audit = False deny = False def __init__(self, rule): self.rule = rule def serialize(self): return "%s%s%s" % ('audit ' if self.audit else '', 'deny ' if self.deny else '', self.rule) apparmor-2.8.95~2430/utils/apparmor/common.py0000644000175000017500000002062512304207064020663 0ustar sarnoldsarnold# ------------------------------------------------------------------ # # Copyright (C) 2012 Canonical Ltd. # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ from __future__ import print_function import codecs import collections import glob import logging import os import re import subprocess import sys import termios import tty DEBUGGING = False # # Utility classes # class AppArmorException(Exception): '''This class represents AppArmor exceptions''' def __init__(self, value): self.value = value def __str__(self): return repr(self.value) # # Utility functions # def error(out, exit_code=1, do_exit=True): '''Print error message and exit''' try: print("ERROR: %s" % (out), file=sys.stderr) except IOError: pass if do_exit: sys.exit(exit_code) def warn(out): '''Print warning message''' try: print("WARN: %s" % (out), file=sys.stderr) except IOError: pass def msg(out, output=sys.stdout): '''Print message''' try: print("%s" % (out), file=output) except IOError: pass def debug(out): '''Print debug message''' global DEBUGGING if DEBUGGING: try: print("DEBUG: %s" % (out), file=sys.stderr) except IOError: pass def recursive_print(src, dpth = 0, key = ''): # print recursively in a nicely formatted way # useful for debugging, too verbose for production code ;-) # "stolen" from http://code.activestate.com/recipes/578094-recursively-print-nested-dictionaries/ # by Scott S-Allen / MIT License # (output format slightly modified) """ Recursively prints nested elements.""" tabs = lambda n: ' ' * n * 4 # or 2 or 8 or... brace = lambda s, n: '[%s]' % (s) if isinstance(src, dict): empty = True for key, value in src.iteritems(): print (tabs(dpth) + brace(key, dpth)) recursive_print(value, dpth + 1, key) empty = False if empty: print (tabs(dpth) + '[--- empty ---]') elif isinstance(src, list) or isinstance(src, tuple): empty = True for litem in src: recursive_print(litem, dpth + 2) empty = False if empty: print (tabs(dpth) + '[--- empty ---]') else: if key: print (tabs(dpth) + '%s = %s' % (key, src)) else: print (tabs(dpth) + '- %s' % src) def cmd(command): '''Try to execute the given command.''' debug(command) try: sp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except OSError as ex: return [127, str(ex)] if sys.version_info[0] >= 3: out = sp.communicate()[0].decode('ascii', 'ignore') else: out = sp.communicate()[0] return [sp.returncode, out] def cmd_pipe(command1, command2): '''Try to pipe command1 into command2.''' try: sp1 = subprocess.Popen(command1, stdout=subprocess.PIPE) sp2 = subprocess.Popen(command2, stdin=sp1.stdout) except OSError as ex: return [127, str(ex)] if sys.version_info[0] >= 3: out = sp2.communicate()[0].decode('ascii', 'ignore') else: out = sp2.communicate()[0] return [sp2.returncode, out] def valid_path(path): '''Valid path''' # No relative paths m = "Invalid path: %s" % (path) if not path.startswith('/'): debug("%s (relative)" % (m)) return False if '"' in path: # We double quote elsewhere debug("%s (contains quote)" % (m)) return False try: os.path.normpath(path) except Exception: debug("%s (could not normalize)" % (m)) return False return True def get_directory_contents(path): '''Find contents of the given directory''' if not valid_path(path): return None files = [] for f in glob.glob(path + "/*"): files.append(f) files.sort() return files def open_file_read(path, encoding='UTF-8'): '''Open specified file read-only''' try: orig = codecs.open(path, 'r', encoding) except Exception: raise return orig def open_file_write(path): '''Open specified file in write/overwrite mode''' try: orig = codecs.open(path, 'w', 'UTF-8') except Exception: raise return orig def readkey(): '''Returns the pressed key''' fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setraw(sys.stdin.fileno()) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch def hasher(): '''A neat alternative to perl's hash reference''' # Creates a dictionary for any depth and returns empty dictionary otherwise return collections.defaultdict(hasher) def convert_regexp(regexp): regex_paren = re.compile('^(.*){([^}]*)}(.*)$') regexp = regexp.strip() new_reg = re.sub(r'(? # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- from __future__ import with_statement import os import re from apparmor.common import AppArmorException, open_file_read, warn, convert_regexp # , msg, error, debug class Severity(object): def __init__(self, dbname=None, default_rank=10): """Initialises the class object""" self.PROF_DIR = '/etc/apparmor.d' # The profile directory self.severity = dict() self.severity['DATABASENAME'] = dbname self.severity['CAPABILITIES'] = {} self.severity['FILES'] = {} self.severity['REGEXPS'] = {} self.severity['DEFAULT_RANK'] = default_rank # For variable expansions for the profile self.severity['VARIABLES'] = dict() if not dbname: return None with open_file_read(dbname) as database: # open(dbname, 'r') for lineno, line in enumerate(database, start=1): line = line.strip() # or only rstrip and lstrip? if line == '' or line.startswith('#'): continue if line.startswith('/'): try: path, read, write, execute = line.split() read, write, execute = int(read), int(write), int(execute) except ValueError: raise AppArmorException("Insufficient values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) else: if read not in range(0, 11) or write not in range(0, 11) or execute not in range(0, 11): raise AppArmorException("Inappropriate values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) path = path.lstrip('/') if '*' not in path: self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute} else: ptr = self.severity['REGEXPS'] pieces = path.split('/') for index, piece in enumerate(pieces): if '*' in piece: path = '/'.join(pieces[index:]) regexp = convert_regexp(path) ptr[regexp] = {'AA_RANK': {'r': read, 'w': write, 'x': execute}} break else: ptr[piece] = ptr.get(piece, {}) ptr = ptr[piece] elif line.startswith('CAP_'): try: resource, severity = line.split() severity = int(severity) except ValueError: error_message = 'No severity value present in file: %s\n\t[Line %s]: %s' % (dbname, lineno, line) #error(error_message) raise AppArmorException(error_message) # from None else: if severity not in range(0, 11): raise AppArmorException("Inappropriate severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) self.severity['CAPABILITIES'][resource] = severity else: raise AppArmorException("Unexpected line in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) def handle_capability(self, resource): """Returns the severity of for the capability resource, default value if no match""" if resource in self.severity['CAPABILITIES'].keys(): return self.severity['CAPABILITIES'][resource] # raise ValueError("unexpected capability rank input: %s"%resource) warn("unknown capability: %s" % resource) return self.severity['DEFAULT_RANK'] def check_subtree(self, tree, mode, sev, segments): """Returns the max severity from the regex tree""" if len(segments) == 0: first = '' else: first = segments[0] rest = segments[1:] path = '/'.join([first] + rest) # Check if we have a matching directory tree to descend into if tree.get(first, False): sev = self.check_subtree(tree[first], mode, sev, rest) # If severity still not found, match against globs if sev is None: # Match against all globs at this directory level for chunk in tree.keys(): if '*' in chunk: # Match rest of the path if re.search("^" + chunk, path): # Find max rank if "AA_RANK" in tree[chunk].keys(): for m in mode: if sev is None or tree[chunk]["AA_RANK"].get(m, -1) > sev: sev = tree[chunk]["AA_RANK"].get(m, None) return sev def handle_file(self, resource, mode): """Returns the severity for the file, default value if no match found""" resource = resource[1:] # remove initial / from path pieces = resource.split('/') # break path into directory level chunks sev = None # Check for an exact match in the db if resource in self.severity['FILES'].keys(): # Find max value among the given modes for m in mode: if sev is None or self.severity['FILES'][resource].get(m, -1) > sev: sev = self.severity['FILES'][resource].get(m, None) else: # Search regex tree for matching glob sev = self.check_subtree(self.severity['REGEXPS'], mode, sev, pieces) if sev is None: # Return default rank if severity cannot be found return self.severity['DEFAULT_RANK'] else: return sev def rank(self, resource, mode=None): """Returns the rank for the resource file/capability""" if '@' in resource: # path contains variable return self.handle_variable_rank(resource, mode) elif resource[0] == '/': # file resource return self.handle_file(resource, mode) elif resource[0:4] == 'CAP_': # capability resource return self.handle_capability(resource) else: raise AppArmorException("Unexpected rank input: %s" % resource) def handle_variable_rank(self, resource, mode): """Returns the max possible rank for file resources containing variables""" regex_variable = re.compile('@{([^{.]*)}') rank = None if '@' in resource: variable = regex_variable.search(resource).groups()[0] variable = '@{%s}' % variable #variables = regex_variable.findall(resource) for replacement in self.severity['VARIABLES'][variable]: resource_replaced = self.variable_replace(variable, replacement, resource) rank_new = self.handle_variable_rank(resource_replaced, mode) #rank_new = self.handle_variable_rank(resource.replace('@{'+variable+'}', replacement), mode) if rank is None or rank_new > rank: rank = rank_new return rank else: return self.handle_file(resource, mode) def variable_replace(self, variable, replacement, resource): """Returns the expanded path for the passed variable""" leading = False trailing = False # Check for leading or trailing / that may need to be collapsed if resource.find("/" + variable) != -1 and resource.find("//" + variable) == -1: # find that a single / exists before variable or not leading = True if resource.find(variable + "/") != -1 and resource.find(variable + "//") == -1: trailing = True if replacement[0] == '/' and replacement[:2] != '//' and leading: # finds if the replacement has leading / or not replacement = replacement[1:] if replacement[-1] == '/' and replacement[-2:] != '//' and trailing: replacement = replacement[:-1] return resource.replace(variable, replacement) def load_variables(self, prof_path): """Loads the variables for the given profile""" regex_include = re.compile('^#?include\s*<(\S*)>') if os.path.isfile(prof_path): with open_file_read(prof_path) as f_in: for line in f_in: line = line.strip() # If any includes, load variables from them first match = regex_include.search(line) if match: new_path = match.groups()[0] new_path = self.PROF_DIR + '/' + new_path self.load_variables(new_path) else: # Remove any comments if '#' in line: line = line.split('#')[0].rstrip() # Expected format is @{Variable} = value1 value2 .. if line.startswith('@') and '=' in line: if '+=' in line: line = line.split('+=') try: self.severity['VARIABLES'][line[0]] += [i.strip('"') for i in line[1].split()] except KeyError: raise AppArmorException("Variable %s was not previously declared, but is being assigned additional value in file: %s" % (line[0], prof_path)) else: line = line.split('=') if line[0] in self.severity['VARIABLES'].keys(): raise AppArmorException("Variable %s was previously declared in file: %s" % (line[0], prof_path)) self.severity['VARIABLES'][line[0]] = [i.strip('"') for i in line[1].split()] def unload_variables(self): """Clears all loaded variables""" self.severity['VARIABLES'] = dict() apparmor-2.8.95~2430/utils/apparmor/aa.py0000644000175000017500000054606712311337002017763 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- # No old version logs, only 2.6 + supported from __future__ import with_statement import inspect import os import re import shutil import subprocess import sys import time import traceback import atexit import tempfile import apparmor.config import apparmor.logparser import apparmor.severity from copy import deepcopy from apparmor.common import (AppArmorException, open_file_read, valid_path, hasher, open_file_write, convert_regexp, DebugLogger) import apparmor.ui as aaui from apparmor.aamode import (str_to_mode, mode_to_str, contains, split_mode, mode_to_str_user, mode_contains, AA_OTHER, flatten_mode, owner_flatten_mode) import apparmor.rules as aarules from apparmor.yasti import SendDataToYast, GetDataFromYast, shutdown_yast # setup module translations from apparmor.translations import init_translation _ = init_translation() # Setup logging incase of debugging is enabled debug_logger = DebugLogger('aa') CONFDIR = '/etc/apparmor' running_under_genprof = False unimplemented_warning = False # The database for severity sev_db = None # The file to read log messages from ### Was our filename = None cfg = None repo_cfg = None parser = None ldd = None logger = None profile_dir = None extra_profile_dir = None ### end our # To keep track of previously included profile fragments include = dict() existing_profiles = dict() seen_events = 0 # was our # To store the globs entered by users so they can be provided again user_globs = [] ## Variables used under logprof ### Were our t = hasher() # dict() transitions = hasher() aa = hasher() # Profiles originally in sd, replace by aa original_aa = hasher() extras = hasher() # Inactive profiles from extras ### end our log = [] pid = dict() seen = hasher() # dir() profile_changes = hasher() prelog = hasher() log_dict = hasher() # dict() changed = dict() created = [] skip = hasher() helpers = dict() # Preserve this between passes # was our ### logprof ends filelist = hasher() # File level variables and stuff in config files def on_exit(): """Shutdowns the logger and records exit if debugging enabled""" debug_logger.debug('Exiting..') debug_logger.shutdown() # Register the on_exit method with atexit atexit.register(on_exit) def check_for_LD_XXX(file): """Returns True if specified program contains references to LD_PRELOAD or LD_LIBRARY_PATH to give the Px/Ux code better suggestions""" found = False if not os.path.isfile(file): return False size = os.stat(file).st_size # Limit to checking files under 100k for the sake of speed if size > 100000: return False with open_file_read(file, encoding='ascii') as f_in: for line in f_in: if 'LD_PRELOAD' in line or 'LD_LIBRARY_PATH' in line: found = True return found def fatal_error(message): # Get the traceback to the message tb_stack = traceback.format_list(traceback.extract_stack()) tb_stack = ''.join(tb_stack) # Append the traceback to message message = message + '\n' + tb_stack debug_logger.error(message) caller = inspect.stack()[1][3] # If caller is SendDataToYast or GetDatFromYast simply exit if caller == 'SendDataToYast' or caller == 'GetDatFromYast': sys.exit(1) # Else tell user what happened aaui.UI_Important(message) shutdown_yast() sys.exit(1) def check_for_apparmor(): """Finds and returns the mointpoint for apparmor None otherwise""" filesystem = '/proc/filesystems' mounts = '/proc/mounts' support_securityfs = False aa_mountpoint = None regex_securityfs = re.compile('^\S+\s+(\S+)\s+securityfs\s') if valid_path(filesystem): with open_file_read(filesystem) as f_in: for line in f_in: if 'securityfs' in line: support_securityfs = True if valid_path(mounts): with open_file_read(mounts) as f_in: for line in f_in: if support_securityfs: match = regex_securityfs.search(line) if match: mountpoint = match.groups()[0] + '/apparmor' if valid_path(mountpoint): aa_mountpoint = mountpoint # Check if apparmor is actually mounted there if not valid_path(aa_mountpoint + '/profiles'): aa_mountpoint = None return aa_mountpoint def which(file): """Returns the executable fullpath for the file, None otherwise""" if sys.version_info >= (3, 3): return shutil.which(file) env_dirs = os.getenv('PATH').split(':') for env_dir in env_dirs: env_path = env_dir + '/' + file # Test if the path is executable or not if os.access(env_path, os.X_OK): return env_path return None def get_full_path(original_path): """Return the full path after resolving any symlinks""" path = original_path link_count = 0 if not path.startswith('/'): path = os.getcwd() + '/' + path while os.path.islink(path): link_count += 1 if link_count > 64: fatal_error(_("Followed too many links while resolving %s") % (original_path)) direc, file = os.path.split(path) link = os.readlink(path) # If the link an absolute path if link.startswith('/'): path = link else: # Link is relative path path = direc + '/' + link return os.path.realpath(path) def find_executable(bin_path): """Returns the full executable path for the given executable, None otherwise""" full_bin = None if os.path.exists(bin_path): full_bin = get_full_path(bin_path) else: if '/' not in bin_path: env_bin = which(bin_path) if env_bin: full_bin = get_full_path(env_bin) if full_bin and os.path.exists(full_bin): return full_bin return None def get_profile_filename(profile): """Returns the full profile name""" if existing_profiles.get(profile, False): return existing_profiles[profile] elif profile.startswith('/'): # Remove leading / profile = profile[1:] else: profile = "profile_" + profile profile = profile.replace('/', '.') full_profilename = profile_dir + '/' + profile return full_profilename def name_to_prof_filename(prof_filename): """Returns the profile""" if prof_filename.startswith(profile_dir): profile = prof_filename.split(profile_dir, 1)[1] return (prof_filename, profile) else: bin_path = find_executable(prof_filename) if bin_path: prof_filename = get_profile_filename(bin_path) if os.path.isfile(prof_filename): return (prof_filename, bin_path) else: return None, None def complain(path): """Sets the profile to complain mode if it exists""" prof_filename, name = name_to_prof_filename(path) if not prof_filename: fatal_error(_("Can't find %s") % path) set_complain(prof_filename, name) def enforce(path): """Sets the profile to enforce mode if it exists""" prof_filename, name = name_to_prof_filename(path) if not prof_filename: fatal_error(_("Can't find %s") % path) set_enforce(prof_filename, name) def set_complain(filename, program): """Sets the profile to complain mode""" aaui.UI_Info(_('Setting %s to complain mode.') % (filename if program is None else program)) # a force-complain symlink is more packaging-friendly, but breaks caching # create_symlink('force-complain', filename) change_profile_flags(filename, program, 'complain', True) def set_enforce(filename, program): """Sets the profile to enforce mode""" aaui.UI_Info(_('Setting %s to enforce mode.') % (filename if program is None else program)) delete_symlink('force-complain', filename) delete_symlink('disable', filename) change_profile_flags(filename, program, 'complain', False) def delete_symlink(subdir, filename): path = filename link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path) if link != path and os.path.islink(link): os.remove(link) def create_symlink(subdir, filename): path = filename bname = os.path.basename(filename) if not bname: raise AppArmorException(_('Unable to find basename for %s.') % filename) #print(filename) link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path) #print(link) #link = link + '/%s'%bname #print(link) symlink_dir = os.path.dirname(link) if not os.path.exists(symlink_dir): # If the symlink directory does not exist create it os.makedirs(symlink_dir) if not os.path.exists(link): try: os.symlink(filename, link) except: raise AppArmorException(_('Could not create %s symlink to %s.') % (link, filename)) def head(file): """Returns the first/head line of the file""" first = '' if os.path.isfile(file): with open_file_read(file) as f_in: try: first = f_in.readline().rstrip() except UnicodeDecodeError: pass return first else: raise AppArmorException(_('Unable to read first line from %s: File Not Found') % file) def get_output(params): """Returns the return code output by running the program with the args given in the list""" program = params[0] # args = params[1:] ret = -1 output = [] # program is executable if os.access(program, os.X_OK): try: # Get the output of the program output = subprocess.check_output(params) except OSError as e: raise AppArmorException(_("Unable to fork: %s\n\t%s") % (program, str(e))) # If exit-codes besides 0 except subprocess.CalledProcessError as e: output = e.output output = output.decode('utf-8').split('\n') ret = e.returncode else: ret = 0 output = output.decode('utf-8').split('\n') # Remove the extra empty string caused due to \n if present if len(output) > 1: output.pop() return (ret, output) def get_reqs(file): """Returns a list of paths from ldd output""" pattern1 = re.compile('^\s*\S+ => (\/\S+)') pattern2 = re.compile('^\s*(\/\S+)') reqs = [] ret, ldd_out = get_output([ldd, file]) if ret == 0: for line in ldd_out: if 'not a dynamic executable' in line: break if 'cannot read header' in line: break if 'statically linked' in line: break match = pattern1.search(line) if match: reqs.append(match.groups()[0]) else: match = pattern2.search(line) if match: reqs.append(match.groups()[0]) return reqs def handle_binfmt(profile, path): """Modifies the profile to add the requirements""" reqs_processed = dict() reqs = get_reqs(path) while reqs: library = reqs.pop() if not reqs_processed.get(library, False): if get_reqs(library): reqs += get_reqs(library) reqs_processed[library] = True combined_mode = match_prof_incs_to_path(profile, 'allow', library) if combined_mode: continue library = glob_common(library) if not library: continue profile['allow']['path'][library]['mode'] = profile['allow']['path'][library].get('mode', set()) | str_to_mode('mr') profile['allow']['path'][library]['audit'] |= profile['allow']['path'][library].get('audit', set()) def get_inactive_profile(local_profile): if extras.get(local_profile, False): return {local_profile: extras[local_profile]} return dict() def create_new_profile(localfile): local_profile = hasher() local_profile[localfile]['flags'] = 'complain' local_profile[localfile]['include']['abstractions/base'] = 1 if os.path.exists(localfile) and os.path.isfile(localfile): hashbang = head(localfile) if hashbang.startswith('#!'): interpreter_path = get_full_path(hashbang.lstrip('#!').strip()) interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path) local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('r')) | str_to_mode('r') local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set()) local_profile[localfile]['allow']['path'][interpreter_path]['mode'] = local_profile[localfile]['allow']['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix') local_profile[localfile]['allow']['path'][interpreter_path]['audit'] = local_profile[localfile]['allow']['path'][interpreter_path].get('audit', set()) if interpreter == 'perl': local_profile[localfile]['include']['abstractions/perl'] = True elif re.search('^python([23]|[23]\.[0-9]+)?$', interpreter): local_profile[localfile]['include']['abstractions/python'] = True elif interpreter == 'ruby': local_profile[localfile]['include']['abstractions/ruby'] = True elif interpreter in ['bash', 'dash', 'sh']: local_profile[localfile]['include']['abstractions/bash'] = True handle_binfmt(local_profile[localfile], interpreter_path) else: local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('mr')) | str_to_mode('mr') local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set()) handle_binfmt(local_profile[localfile], localfile) # Add required hats to the profile if they match the localfile for hatglob in cfg['required_hats'].keys(): if re.search(hatglob, localfile): for hat in sorted(cfg['required_hats'][hatglob].split()): local_profile[hat]['flags'] = 'complain' created.append(localfile) changed[localfile] = True debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__())) return {localfile: local_profile} def delete_profile(local_prof): """Deletes the specified file from the disk and remove it from our list""" profile_file = get_profile_filename(local_prof) if os.path.isfile(profile_file): os.remove(profile_file) if aa.get(local_prof, False): aa.pop(local_prof) #prof_unload(local_prof) def confirm_and_abort(): ans = aaui.UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n') if ans == 'y': aaui.UI_Info(_('Abandoning all changes.')) shutdown_yast() for prof in created: delete_profile(prof) sys.exit(0) def get_profile(prof_name): profile_data = None distro = cfg['repository']['distro'] repo_url = cfg['repository']['url'] # local_profiles = [] profile_hash = hasher() if repo_is_enabled(): aaui.UI_BusyStart(_('Connecting to repository...')) status_ok, ret = fetch_profiles_by_name(repo_url, distro, prof_name) aaui.UI_BusyStop() if status_ok: profile_hash = ret else: aaui.UI_Important(_('WARNING: Error fetching profiles from the repository')) inactive_profile = get_inactive_profile(prof_name) if inactive_profile: uname = 'Inactive local profile for %s' % prof_name inactive_profile[prof_name][prof_name]['flags'] = 'complain' inactive_profile[prof_name][prof_name].pop('filename') profile_hash[uname]['username'] = uname profile_hash[uname]['profile_type'] = 'INACTIVE_LOCAL' profile_hash[uname]['profile'] = serialize_profile(inactive_profile[prof_name], prof_name) profile_hash[uname]['profile_data'] = inactive_profile # If no profiles in repo and no inactive profiles if not profile_hash.keys(): return None options = [] tmp_list = [] preferred_present = False preferred_user = cfg['repository'].get('preferred_user', 'NOVELL') for p in profile_hash.keys(): if profile_hash[p]['username'] == preferred_user: preferred_present = True else: tmp_list.append(profile_hash[p]['username']) if preferred_present: options.append(preferred_user) options += tmp_list q = dict() q['headers'] = ['Profile', prof_name] q['functions'] = ['CMD_VIEW_PROFILE', 'CMD_USE_PROFILE', 'CMD_CREATE_PROFILE', 'CMD_ABORT', 'CMD_FINISHED'] q['default'] = "CMD_VIEW_PROFILE" q['options'] = options q['selected'] = 0 ans = '' while 'CMD_USE_PROFILE' not in ans and 'CMD_CREATE_PROFILE' not in ans: if ans == 'CMD_FINISHED': save_profiles() return ans, arg = aaui.UI_PromptUser(q) p = profile_hash[options[arg]] q['selected'] = options.index(options[arg]) if ans == 'CMD_VIEW_PROFILE': if aaui.UI_mode == 'yast': SendDataToYast({'type': 'dialogue-view-profile', 'user': options[arg], 'profile': p['profile'], 'profile_type': p['profile_type'] }) ypath, yarg = GetDataFromYast() #else: # pager = get_pager() # proc = subprocess.Popen(pager, stdin=subprocess.PIPE) # proc.communicate('Profile submitted by %s:\n\n%s\n\n' % # (options[arg], p['profile'])) # proc.kill() elif ans == 'CMD_USE_PROFILE': if p['profile_type'] == 'INACTIVE_LOCAL': profile_data = p['profile_data'] created.append(prof_name) else: profile_data = parse_repo_profile(prof_name, repo_url, p) return profile_data def activate_repo_profiles(url, profiles, complain): read_profiles() try: for p in profiles: pname = p[0] profile_data = parse_repo_profile(pname, url, p[1]) attach_profile_data(aa, profile_data) write_profile(pname) if complain: fname = get_profile_filename(pname) set_profile_flags(profile_dir + fname, 'complain') aaui.UI_Info(_('Setting %s to complain mode.') % pname) except Exception as e: sys.stderr.write(_("Error activating profiles: %s") % e) def autodep(bin_name, pname=''): bin_full = None global repo_cfg if not bin_name and pname.startswith('/'): bin_name = pname if not repo_cfg and not cfg['repository'].get('url', False): repo_conf = apparmor.config.Config('shell', CONFDIR) repo_cfg = repo_conf.read_config('repository.conf') if not repo_cfg.get('repository', False) or repo_cfg['repository']['enabled'] == 'later': UI_ask_to_enable_repo() if bin_name: bin_full = find_executable(bin_name) #if not bin_full: # bin_full = bin_name #if not bin_full.startswith('/'): #return None # Return if exectuable path not found if not bin_full: return None else: bin_full = pname # for named profiles pname = bin_full read_inactive_profiles() profile_data = get_profile(pname) # Create a new profile if no existing profile if not profile_data: profile_data = create_new_profile(pname) file = get_profile_filename(pname) attach_profile_data(aa, profile_data) attach_profile_data(original_aa, profile_data) if os.path.isfile(profile_dir + '/tunables/global'): if not filelist.get(file, False): filelist[file] = hasher() filelist[file]['include']['tunables/global'] = True filelist[file]['profiles'][pname] = True write_profile_ui_feedback(pname) def get_profile_flags(filename, program): # To-Do # XXX If more than one profile in a file then second one is being ignored XXX # Do we return flags for both or flags = '' with open_file_read(filename) as f_in: for line in f_in: if RE_PROFILE_START.search(line): matches = RE_PROFILE_START.search(line).groups() profile = matches[1] or matches[3] flags = matches[6] if profile == program or program is None: return flags raise AppArmorException(_('%s contains no profile') % filename) def change_profile_flags(filename, program, flag, set_flag): old_flags = get_profile_flags(filename, program) newflags = [] if old_flags: # Flags maybe white-space and/or , separated old_flags = old_flags.split(',') if not isinstance(old_flags, str): for i in old_flags: newflags += i.split() else: newflags = old_flags.split() #newflags = [lambda x:x.strip(), oldflags] if set_flag: if flag not in newflags: newflags.append(flag) else: if flag in newflags: newflags.remove(flag) newflags = ','.join(newflags) set_profile_flags(filename, program, newflags) def set_profile_flags(prof_filename, program, newflags): """Reads the old profile file and updates the flags accordingly""" regex_bin_flag = re.compile('^(\s*)(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.*)\)\s+)?\{\s*(#.*)?$') regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)\s*(#.*)?$') if os.path.isfile(prof_filename): with open_file_read(prof_filename) as f_in: temp_file = tempfile.NamedTemporaryFile('w', prefix=prof_filename, suffix='~', delete=False, dir=profile_dir) shutil.copymode(prof_filename, temp_file.name) with open_file_write(temp_file.name) as f_out: for line in f_in: comment = '' if '#' in line: comment = '#' + line.split('#', 1)[1].rstrip() match = regex_bin_flag.search(line) if not line.strip() or line.strip().startswith('#'): pass elif match: matches = match.groups() space = matches[0] binary = matches[1] flag = matches[6] or 'flags=' flags = matches[7] if binary == program or program is None: if newflags: line = '%s%s %s(%s) {%s\n' % (space, binary, flag, newflags, comment) else: line = '%s%s {%s\n' % (space, binary, comment) else: match = regex_hat_flag.search(line) if match: hat, flags = match.groups()[:2] if newflags: line = '%s flags=(%s) {%s\n' % (hat, newflags, comment) else: line = '%s {%s\n' % (hat, comment) f_out.write(line) os.rename(temp_file.name, prof_filename) def profile_exists(program): """Returns True if profile exists, False otherwise""" # Check cache of profiles if existing_profiles.get(program, False): return True # Check the disk for profile prof_path = get_profile_filename(program) #print(prof_path) if os.path.isfile(prof_path): # Add to cache of profile existing_profiles[program] = prof_path return True return False def sync_profile(): user, passw = get_repo_user_pass() if not user or not passw: return None repo_profiles = [] changed_profiles = [] new_profiles = [] serialize_opts = hasher() status_ok, ret = fetch_profiles_by_user(cfg['repository']['url'], cfg['repository']['distro'], user) if not status_ok: if not ret: ret = 'UNKNOWN ERROR' aaui.UI_Important(_('WARNING: Error synchronizing profiles with the repository:\n%s\n') % ret) else: users_repo_profiles = ret serialize_opts['NO_FLAGS'] = True for prof in sorted(aa.keys()): if is_repo_profile([aa[prof][prof]]): repo_profiles.append(prof) if prof in created: p_local = serialize_profile(aa[prof], prof, serialize_opts) if not users_repo_profiles.get(prof, False): new_profiles.append(prof) new_profiles.append(p_local) new_profiles.append('') else: p_repo = users_repo_profiles[prof]['profile'] if p_local != p_repo: changed_profiles.append(prof) changed_profiles.append(p_local) changed_profiles.append(p_repo) if repo_profiles: for prof in repo_profiles: p_local = serialize_profile(aa[prof], prof, serialize_opts) if not users_repo_profiles.get(prof, False): new_profiles.append(prof) new_profiles.append(p_local) new_profiles.append('') else: p_repo = '' if aa[prof][prof]['repo']['user'] == user: p_repo = users_repo_profiles[prof]['profile'] else: status_ok, ret = fetch_profile_by_id(cfg['repository']['url'], aa[prof][prof]['repo']['id']) if status_ok: p_repo = ret['profile'] else: if not ret: ret = 'UNKNOWN ERROR' aaui.UI_Important(_('WARNING: Error synchronizing profiles with the repository\n%s') % ret) continue if p_repo != p_local: changed_profiles.append(prof) changed_profiles.append(p_local) changed_profiles.append(p_repo) if changed_profiles: submit_changed_profiles(changed_profiles) if new_profiles: submit_created_profiles(new_profiles) def fetch_profile_by_id(url, id): #To-Do return None, None def fetch_profiles_by_name(url, distro, user): #to-Do return None, None def fetch_profiles_by_user(url, distro, user): #to-Do return None, None def submit_created_profiles(new_profiles): #url = cfg['repository']['url'] if new_profiles: if aaui.UI_mode == 'yast': title = 'New Profiles' message = 'Please select the newly created profiles that you would like to store in the repository' yast_select_and_upload_profiles(title, message, new_profiles) else: title = 'Submit newly created profiles to the repository' message = 'Would you like to upload newly created profiles?' console_select_and_upload_profiles(title, message, new_profiles) def submit_changed_profiles(changed_profiles): #url = cfg['repository']['url'] if changed_profiles: if aaui.UI_mode == 'yast': title = 'Changed Profiles' message = 'Please select which of the changed profiles would you like to upload to the repository' yast_select_and_upload_profiles(title, message, changed_profiles) else: title = 'Submit changed profiles to the repository' message = 'The following profiles from the repository were changed.\nWould you like to upload your changes?' console_select_and_upload_profiles(title, message, changed_profiles) def yast_select_and_upload_profiles(title, message, profiles_up): url = cfg['repository']['url'] profile_changes = hasher() profs = profiles_up[:] for p in profs: profile_changes[p[0]] = get_profile_diff(p[2], p[1]) SendDataToYast({'type': 'dialog-select-profiles', 'title': title, 'explanation': message, 'default_select': 'false', 'disable_ask_upload': 'true', 'profiles': profile_changes }) ypath, yarg = GetDataFromYast() selected_profiles = [] changelog = None changelogs = None single_changelog = False if yarg['STATUS'] == 'cancel': return else: selected_profiles = yarg['PROFILES'] changelogs = yarg['CHANGELOG'] if changelogs.get('SINGLE_CHANGELOG', False): changelog = changelogs['SINGLE_CHANGELOG'] single_changelog = True user, passw = get_repo_user_pass() for p in selected_profiles: profile_string = serialize_profile(aa[p], p) if not single_changelog: changelog = changelogs[p] status_ok, ret = upload_profile(url, user, passw, cfg['repository']['distro'], p, profile_string, changelog) if status_ok: newprofile = ret newid = newprofile['id'] set_repo_info(aa[p][p], url, user, newid) write_profile_ui_feedback(p) else: if not ret: ret = 'UNKNOWN ERROR' aaui.UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (p, ret)) aaui.UI_Info(_('Uploaded changes to repository.')) if yarg.get('NEVER_ASK_AGAIN'): unselected_profiles = [] for p in profs: if p[0] not in selected_profiles: unselected_profiles.append(p[0]) set_profiles_local_only(unselected_profiles) def upload_profile(url, user, passw, distro, p, profile_string, changelog): # To-Do return None, None def console_select_and_upload_profiles(title, message, profiles_up): url = cfg['repository']['url'] profs = profiles_up[:] q = hasher() q['title'] = title q['headers'] = ['Repository', url] q['explanation'] = message q['functions'] = ['CMD_UPLOAD_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ASK_LATER', 'CMD_ASK_NEVER', 'CMD_ABORT'] q['default'] = 'CMD_VIEW_CHANGES' q['options'] = [i[0] for i in profs] q['selected'] = 0 ans = '' while 'CMD_UPLOAD_CHANGES' not in ans and 'CMD_ASK_NEVER' not in ans and 'CMD_ASK_LATER' not in ans: ans, arg = aaui.UI_PromptUser(q) if ans == 'CMD_VIEW_CHANGES': display_changes(profs[arg][2], profs[arg][1]) if ans == 'CMD_NEVER_ASK': set_profiles_local_only([i[0] for i in profs]) elif ans == 'CMD_UPLOAD_CHANGES': changelog = aaui.UI_GetString(_('Changelog Entry: '), '') user, passw = get_repo_user_pass() if user and passw: for p_data in profs: prof = p_data[0] prof_string = p_data[1] status_ok, ret = upload_profile(url, user, passw, cfg['repository']['distro'], prof, prof_string, changelog) if status_ok: newprof = ret newid = newprof['id'] set_repo_info(aa[prof][prof], url, user, newid) write_profile_ui_feedback(prof) aaui.UI_Info('Uploaded %s to repository' % prof) else: if not ret: ret = 'UNKNOWN ERROR' aaui.UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (prof, ret)) else: aaui.UI_Important(_('Repository Error\nRegistration or Signin was unsuccessful. User login\ninformation is required to upload profiles to the repository.\nThese changes could not be sent.')) def set_profiles_local_only(profs): for p in profs: aa[profs][profs]['repo']['neversubmit'] = True write_profile_ui_feedback(profs) def build_x_functions(default, options, exec_toggle): ret_list = [] fallback_toggle = False if exec_toggle: if 'i' in options: ret_list.append('CMD_ix') if 'p' in options: ret_list.append('CMD_pix') fallback_toggle = True if 'c' in options: ret_list.append('CMD_cix') fallback_toggle = True if 'n' in options: ret_list.append('CMD_nix') fallback_toggle = True if fallback_toggle: ret_list.append('CMD_EXEC_IX_OFF') if 'u' in options: ret_list.append('CMD_ux') else: if 'i' in options: ret_list.append('CMD_ix') if 'c' in options: ret_list.append('CMD_cx') fallback_toggle = True if 'p' in options: ret_list.append('CMD_px') fallback_toggle = True if 'n' in options: ret_list.append('CMD_nx') fallback_toggle = True if 'u' in options: ret_list.append('CMD_ux') if fallback_toggle: ret_list.append('CMD_EXEC_IX_ON') ret_list += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] return ret_list def handle_children(profile, hat, root): entries = root[:] pid = None p = None h = None prog = None aamode = None mode = None detail = None to_name = None uhat = None capability = None family = None sock_type = None protocol = None global seen_events regex_nullcomplain = re.compile('^null(-complain)*-profile$') for entry in entries: if type(entry[0]) != str: handle_children(profile, hat, entry) else: typ = entry.pop(0) if typ == 'fork': # If type is fork then we (should) have pid, profile and hat pid, p, h = entry[:3] if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): profile = p hat = h if hat: profile_changes[pid] = profile + '//' + hat else: profile_changes[pid] = profile elif typ == 'unknown_hat': # If hat is not known then we (should) have pid, profile, hat, mode and unknown hat in entry pid, p, h, aamode, uhat = entry[:5] if not regex_nullcomplain.search(p): profile = p if aa[profile].get(uhat, False): hat = uhat continue new_p = update_repo_profile(aa[profile][profile]) if new_p and UI_SelectUpdatedRepoProfile(profile, new_p) and aa[profile].get(uhat, False): hat = uhat continue default_hat = None for hatglob in cfg.options('defaulthat'): if re.search(hatglob, profile): default_hat = cfg['defaulthat'][hatglob] context = profile context = context + ' -> ^%s' % uhat ans = transitions.get(context, 'XXXINVALIDXXX') while ans not in ['CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY']: q = hasher() q['headers'] = [] q['headers'] += [_('Profile'), profile] if default_hat: q['headers'] += [_('Default Hat'), default_hat] q['headers'] += [_('Requested Hat'), uhat] q['functions'] = [] q['functions'].append('CMD_ADDHAT') if default_hat: q['functions'].append('CMD_USEDEFAULT') q['functions'] += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': q['default'] = 'CMD_ADDHAT' seen_events += 1 ans = aaui.UI_PromptUser(q) if ans == 'CMD_FINISHED': save_profiles() return transitions[context] = ans if ans == 'CMD_ADDHAT': hat = uhat aa[profile][hat]['flags'] = aa[profile][profile]['flags'] elif ans == 'CMD_USEDEFAULT': hat = default_hat elif ans == 'CMD_DENY': # As unknown hat is denied no entry for it should be made return None elif typ == 'capability': # If capability then we (should) have pid, profile, hat, program, mode, capability pid, p, h, prog, aamode, capability = entry[:6] if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): profile = p hat = h if not profile or not hat: continue prelog[aamode][profile][hat]['capability'][capability] = True elif typ == 'path' or typ == 'exec': # If path or exec then we (should) have pid, profile, hat, program, mode, details and to_name pid, p, h, prog, aamode, mode, detail, to_name = entry[:8] if not mode: mode = set() if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): profile = p hat = h if not profile or not hat or not detail: continue domainchange = 'nochange' if typ == 'exec': domainchange = 'change' # Escape special characters detail = detail.replace('[', '\[') detail = detail.replace(']', '\]') detail = detail.replace('+', '\+') detail = detail.replace('*', '\*') detail = detail.replace('{', '\{') detail = detail.replace('}', '\}') # Give Execute dialog if x access requested for something that's not a directory # For directories force an 'ix' Path dialog do_execute = False exec_target = detail if mode & str_to_mode('x'): if os.path.isdir(exec_target): mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE mode = mode | str_to_mode('ix') else: do_execute = True if mode & apparmor.aamode.AA_MAY_LINK: regex_link = re.compile('^from (.+) to (.+)$') match = regex_link.search(detail) if match: path = match.groups()[0] target = match.groups()[1] frommode = str_to_mode('lr') if prelog[aamode][profile][hat]['path'].get(path, False): frommode |= prelog[aamode][profile][hat]['path'][path] prelog[aamode][profile][hat]['path'][path] = frommode tomode = str_to_mode('lr') if prelog[aamode][profile][hat]['path'].get(target, False): tomode |= prelog[aamode][profile][hat]['path'][target] prelog[aamode][profile][hat]['path'][target] = tomode else: continue elif mode: path = detail if prelog[aamode][profile][hat]['path'].get(path, False): mode |= prelog[aamode][profile][hat]['path'][path] prelog[aamode][profile][hat]['path'][path] = mode if do_execute: if profile_known_exec(aa[profile][hat], 'exec', exec_target): continue p = update_repo_profile(aa[profile][profile]) if to_name: if UI_SelectUpdatedRepoProfile(profile, p) and profile_known_exec(aa[profile][hat], 'exec', to_name): continue else: if UI_SelectUpdatedRepoProfile(profile, p) and profile_known_exec(aa[profile][hat], 'exec', exec_target): continue context_new = profile if profile != hat: context_new = context_new + '^%s' % hat context_new = context_new + ' -> %s' % exec_target # ans_new = transitions.get(context_new, '') # XXX ans meant here? combinedmode = set() combinedaudit = set() ## Check return Value Consistency # Check if path matches any existing regexps in profile cm, am, m = rematchfrag(aa[profile][hat], 'allow', exec_target) if cm: combinedmode |= cm if am: combinedaudit |= am if combinedmode & str_to_mode('x'): nt_name = None for entr in m: if aa[profile][hat]['allow']['path'].get(entr, False): nt_name = aa[profile][hat] break if to_name and to_name != nt_name: pass elif nt_name: to_name = nt_name ## Check return value consistency # Check if the includes from profile match cm, am, m = match_prof_incs_to_path(aa[profile][hat], 'allow', exec_target) if cm: combinedmode |= cm if am: combinedaudit |= am if combinedmode & str_to_mode('x'): nt_name = None for entr in m: if aa[profile][hat]['allow']['path'][entry]['to']: nt_name = aa[profile][hat]['allow']['path'][entry]['to'] break if to_name and to_name != nt_name: pass elif nt_name: to_name = nt_name # nx is not used in profiles but in log files. # Log parsing methods will convert it to its profile form # nx is internally cx/px/cix/pix + to_name exec_mode = False if contains(combinedmode, 'pix'): if to_name: ans = 'CMD_nix' else: ans = 'CMD_pix' exec_mode = str_to_mode('pixr') elif contains(combinedmode, 'cix'): if to_name: ans = 'CMD_nix' else: ans = 'CMD_cix' exec_mode = str_to_mode('cixr') elif contains(combinedmode, 'Pix'): if to_name: ans = 'CMD_nix_safe' else: ans = 'CMD_pix_safe' exec_mode = str_to_mode('Pixr') elif contains(combinedmode, 'Cix'): if to_name: ans = 'CMD_nix_safe' else: ans = 'CMD_cix_safe' exec_mode = str_to_mode('Cixr') elif contains(combinedmode, 'ix'): ans = 'CMD_ix' exec_mode = str_to_mode('ixr') elif contains(combinedmode, 'px'): if to_name: ans = 'CMD_nx' else: ans = 'CMD_px' exec_mode = str_to_mode('px') elif contains(combinedmode, 'cx'): if to_name: ans = 'CMD_nx' else: ans = 'CMD_cx' exec_mode = str_to_mode('cx') elif contains(combinedmode, 'ux'): ans = 'CMD_ux' exec_mode = str_to_mode('ux') elif contains(combinedmode, 'Px'): if to_name: ans = 'CMD_nx_safe' else: ans = 'CMD_px_safe' exec_mode = str_to_mode('Px') elif contains(combinedmode, 'Cx'): if to_name: ans = 'CMD_nx_safe' else: ans = 'CMD_cx_safe' exec_mode = str_to_mode('Cx') elif contains(combinedmode, 'Ux'): ans = 'CMD_ux_safe' exec_mode = str_to_mode('Ux') else: options = cfg['qualifiers'].get(exec_target, 'ipcnu') if to_name: fatal_error(_('%s has transition name but not transition mode') % entry) ### If profiled program executes itself only 'ix' option ##if exec_target == profile: ##options = 'i' # Don't allow hats to cx? options.replace('c', '') # Add deny to options options += 'd' # Define the default option default = None if 'p' in options and os.path.exists(get_profile_filename(exec_target)): default = 'CMD_px' sys.stdout.write(_('Target profile exists: %s\n') % get_profile_filename(exec_target)) elif 'i' in options: default = 'CMD_ix' elif 'c' in options: default = 'CMD_cx' elif 'n' in options: default = 'CMD_nx' else: default = 'DENY' # parent_uses_ld_xxx = check_for_LD_XXX(profile) sev_db.unload_variables() sev_db.load_variables(profile) severity = sev_db.rank(exec_target, 'x') # Prompt portion starts q = hasher() q['headers'] = [] q['headers'] += [_('Profile'), combine_name(profile, hat)] if prog and prog != 'HINT': q['headers'] += [_('Program'), prog] # to_name should not exist here since, transitioning is already handeled q['headers'] += [_('Execute'), exec_target] q['headers'] += [_('Severity'), severity] q['functions'] = [] # prompt = '\n%s\n' % context_new # XXX exec_toggle = False q['functions'] += build_x_functions(default, options, exec_toggle) # options = '|'.join(options) seen_events += 1 regex_options = re.compile('^CMD_(ix|px|cx|nx|pix|cix|nix|px_safe|cx_safe|nx_safe|pix_safe|cix_safe|nix_safe|ux|ux_safe|EXEC_TOGGLE|DENY)$') ans = '' while not regex_options.search(ans): ans = aaui.UI_PromptUser(q)[0].strip() if ans.startswith('CMD_EXEC_IX_'): exec_toggle = not exec_toggle q['functions'] = [] q['functions'] += build_x_functions(default, options, exec_toggle) ans = '' continue if ans == 'CMD_FINISHED': save_profiles() return if ans == 'CMD_nx' or ans == 'CMD_nix': arg = exec_target ynans = 'n' if profile == hat: ynans = aaui.UI_YesNo(_('Are you specifying a transition to a local profile?'), 'n') if ynans == 'y': if ans == 'CMD_nx': ans = 'CMD_cx' else: ans = 'CMD_cix' else: if ans == 'CMD_nx': ans = 'CMD_px' else: ans = 'CMD_pix' to_name = aaui.UI_GetString(_('Enter profile name to transition to: '), arg) regex_optmode = re.compile('CMD_(px|cx|nx|pix|cix|nix)') if ans == 'CMD_ix': exec_mode = str_to_mode('ix') elif regex_optmode.search(ans): match = regex_optmode.search(ans).groups()[0] exec_mode = str_to_mode(match) px_default = 'n' px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.") if parent_uses_ld_xxx: px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut this application appears to be using LD_PRELOAD\nor LD_LIBRARY_PATH and sanitising the environment\ncould cause functionality problems.") ynans = aaui.UI_YesNo(px_msg, px_default) if ynans == 'y': # Disable the unsafe mode exec_mode = exec_mode - (apparmor.aamode.AA_EXEC_UNSAFE | AA_OTHER(apparmor.aamode.AA_EXEC_UNSAFE)) elif ans == 'CMD_ux': exec_mode = str_to_mode('ux') ynans = aaui.UI_YesNo(_("Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing %s ?") % exec_target, 'n') if ynans == 'y': ynans = aaui.UI_YesNo(_("Should AppArmor sanitise the environment when\nrunning this program unconfined?\n\nNot sanitising the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), 'y') if ynans == 'y': # Disable the unsafe mode exec_mode = exec_mode - (apparmor.aamode.AA_EXEC_UNSAFE | AA_OTHER(apparmor.aamode.AA_EXEC_UNSAFE)) else: ans = 'INVALID' transitions[context_new] = ans regex_options = re.compile('CMD_(ix|px|cx|nx|pix|cix|nix)') if regex_options.search(ans): # For inherit we need r if exec_mode & str_to_mode('i'): exec_mode |= str_to_mode('r') else: if ans == 'CMD_DENY': aa[profile][hat]['deny']['path'][exec_target]['mode'] = aa[profile][hat]['deny']['path'][exec_target].get('mode', str_to_mode('x')) | str_to_mode('x') aa[profile][hat]['deny']['path'][exec_target]['audit'] = aa[profile][hat]['deny']['path'][exec_target].get('audit', set()) changed[profile] = True # Skip remaining events if they ask to deny exec if domainchange == 'change': return None if ans != 'CMD_DENY': prelog['PERMITTING'][profile][hat]['path'][exec_target] = prelog['PERMITTING'][profile][hat]['path'].get(exec_target, exec_mode) | exec_mode log_dict['PERMITTING'][profile] = hasher() aa[profile][hat]['allow']['path'][exec_target]['mode'] = aa[profile][hat]['allow']['path'][exec_target].get('mode', exec_mode) aa[profile][hat]['allow']['path'][exec_target]['audit'] = aa[profile][hat]['allow']['path'][exec_target].get('audit', set()) if to_name: aa[profile][hat]['allow']['path'][exec_target]['to'] = to_name changed[profile] = True if exec_mode & str_to_mode('i'): #if 'perl' in exec_target: # aa[profile][hat]['include']['abstractions/perl'] = True #elif '/bin/bash' in exec_target or '/bin/sh' in exec_target: # aa[profile][hat]['include']['abstractions/bash'] = True hashbang = head(exec_target) if hashbang.startswith('#!'): interpreter = hashbang[2:].strip() interpreter_path = get_full_path(interpreter) interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path) aa[profile][hat]['path'][interpreter_path]['mode'] = aa[profile][hat]['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix') aa[profile][hat]['path'][interpreter_path]['audit'] = aa[profile][hat]['path'][interpreter_path].get('audit', set()) if interpreter == 'perl': aa[profile][hat]['include']['abstractions/perl'] = True elif interpreter in ['bash', 'dash', 'sh']: aa[profile][hat]['include']['abstractions/bash'] = True # Update tracking info based on kind of change if ans == 'CMD_ix': if hat: profile_changes[pid] = '%s//%s' % (profile, hat) else: profile_changes[pid] = '%s//' % profile elif re.search('^CMD_(px|nx|pix|nix)', ans): if to_name: exec_target = to_name if aamode == 'PERMITTING': if domainchange == 'change': profile = exec_target hat = exec_target profile_changes[pid] = '%s' % profile # Check profile exists for px if not os.path.exists(get_profile_filename(exec_target)): ynans = 'y' if exec_mode & str_to_mode('i'): ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n') if ynans == 'y': helpers[exec_target] = 'enforce' if to_name: autodep('', exec_target) else: autodep(exec_target, '') reload_base(exec_target) elif ans.startswith('CMD_cx') or ans.startswith('CMD_cix'): if to_name: exec_target = to_name if aamode == 'PERMITTING': if domainchange == 'change': profile_changes[pid] = '%s//%s' % (profile, exec_target) if not aa[profile].get(exec_target, False): ynans = 'y' if exec_mode & str_to_mode('i'): ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n') if ynans == 'y': hat = exec_target aa[profile][hat]['declared'] = False aa[profile][hat]['profile'] = True if profile != hat: aa[profile][hat]['flags'] = aa[profile][profile]['flags'] stub_profile = create_new_profile(hat) aa[profile][hat]['flags'] = 'complain' aa[profile][hat]['allow']['path'] = hasher() if stub_profile[hat][hat]['allow'].get('path', False): aa[profile][hat]['allow']['path'] = stub_profile[hat][hat]['allow']['path'] aa[profile][hat]['include'] = hasher() if stub_profile[hat][hat].get('include', False): aa[profile][hat]['include'] = stub_profile[hat][hat]['include'] aa[profile][hat]['allow']['netdomain'] = hasher() file_name = aa[profile][profile]['filename'] filelist[file_name]['profiles'][profile][hat] = True elif ans.startswith('CMD_ux'): profile_changes[pid] = 'unconfined' if domainchange == 'change': return None elif typ == 'netdomain': # If netdomain we (should) have pid, profile, hat, program, mode, network family, socket type and protocol pid, p, h, prog, aamode, family, sock_type, protocol = entry[:8] if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): profile = p hat = h if not hat or not profile: continue if family and sock_type: prelog[aamode][profile][hat]['netdomain'][family][sock_type] = True return None PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x') ##### Repo related functions def UI_SelectUpdatedRepoProfile(profile, p): # To-Do return False def UI_repo_signup(): # To-Do return None, None def UI_ask_to_enable_repo(): # To-Do pass def UI_ask_to_upload_profiles(): # To-Do pass def UI_ask_mode_toggles(audit_toggle, owner_toggle, oldmode): # To-Do return (audit_toggle, owner_toggle) def parse_repo_profile(fqdbin, repo_url, profile): # To-Do pass def set_repo_info(profile_data, repo_url, username, iden): # To-Do pass def is_repo_profile(profile_data): # To-Do pass def get_repo_user_pass(): # To-Do pass def get_preferred_user(repo_url): # To-Do pass def repo_is_enabled(): # To-Do return False def update_repo_profile(profile): # To-Do return None def order_globs(globs, path): """Returns the globs in sorted order, more specific behind""" # To-Do # ATM its lexicographic, should be done to allow better matches later return sorted(globs) def ask_the_questions(): found = 0 global seen_events for aamode in sorted(log_dict.keys()): # Describe the type of changes if aamode == 'PERMITTING': aaui.UI_Info(_('Complain-mode changes:')) elif aamode == 'REJECTING': aaui.UI_Info(_('Enforce-mode changes:')) else: # This is so wrong! fatal_error(_('Invalid mode found: %s') % aamode) for profile in sorted(log_dict[aamode].keys()): # Update the repo profiles p = update_repo_profile(aa[profile][profile]) if p: UI_SelectUpdatedRepoProfile(profile, p) found += 1 # Sorted list of hats with the profile name coming first hats = list(filter(lambda key: key != profile, sorted(log_dict[aamode][profile].keys()))) if log_dict[aamode][profile].get(profile, False): hats = [profile] + hats for hat in hats: for capability in sorted(log_dict[aamode][profile][hat]['capability'].keys()): # skip if capability already in profile if profile_known_capability(aa[profile][hat], capability): continue # Load variables? Don't think so. severity = sev_db.rank('CAP_%s' % capability) default_option = 1 options = [] newincludes = match_cap_includes(aa[profile][hat], capability) q = hasher() if newincludes: options += list(map(lambda inc: '#include <%s>' % inc, sorted(set(newincludes)))) if options: options.append('capability %s' % capability) q['options'] = [options] q['selected'] = default_option - 1 q['headers'] = [_('Profile'), combine_name(profile, hat)] q['headers'] += [_('Capability'), capability] q['headers'] += [_('Severity'), severity] audit_toggle = 0 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'] # In complain mode: events default to allow # In enforce mode: events default to deny q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': q['default'] = 'CMD_ALLOW' seen_events += 1 done = False while not done: ans, selected = aaui.UI_PromptUser(q) if ans == 'CMD_FINISHED': save_profiles() return # Ignore the log entry if ans == 'CMD_IGNORE_ENTRY': done = True break if ans == 'CMD_AUDIT': audit_toggle = not audit_toggle audit = '' if audit_toggle: q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_OFF', 'CMD_ABORT', 'CMD_FINISHED'] audit = 'audit' else: q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED', ] q['headers'] = [_('Profile'), combine_name(profile, hat), _('Capability'), audit + capability, _('Severity'), severity] if ans == 'CMD_ALLOW': selection = '' if options: selection = options[selected] match = re_match_include(selection) if match: deleted = False inc = match # .groups()[0] deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True aaui.UI_Info(_('Adding %s to profile.') % selection) if deleted: aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) aa[profile][hat]['allow']['capability'][capability]['set'] = True aa[profile][hat]['allow']['capability'][capability]['audit'] = audit_toggle changed[profile] = True aaui.UI_Info(_('Adding capability %s to profile.') % capability) done = True elif ans == 'CMD_DENY': aa[profile][hat]['deny']['capability'][capability]['set'] = True changed[profile] = True aaui.UI_Info(_('Denying capability %s to profile.') % capability) done = True else: done = False # Process all the path entries. for path in sorted(log_dict[aamode][profile][hat]['path'].keys()): mode = log_dict[aamode][profile][hat]['path'][path] # Lookup modes from profile allow_mode = set() allow_audit = set() deny_mode = set() deny_audit = set() fmode, famode, fm = rematchfrag(aa[profile][hat], 'allow', path) if fmode: allow_mode |= fmode if famode: allow_audit |= famode cm, cam, m = rematchfrag(aa[profile][hat], 'deny', path) if cm: deny_mode |= cm if cam: deny_audit |= cam imode, iamode, im = match_prof_incs_to_path(aa[profile][hat], 'allow', path) if imode: allow_mode |= imode if iamode: allow_audit |= iamode cm, cam, m = match_prof_incs_to_path(aa[profile][hat], 'deny', path) if cm: deny_mode |= cm if cam: deny_audit |= cam if deny_mode & apparmor.aamode.AA_MAY_EXEC: deny_mode |= apparmor.aamode.ALL_AA_EXEC_TYPE # Mask off the denied modes mode = mode - deny_mode # If we get an exec request from some kindof event that generates 'PERMITTING X' # check if its already in allow_mode # if not add ix permission if mode & apparmor.aamode.AA_MAY_EXEC: # Remove all type access permission mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE if not allow_mode & apparmor.aamode.AA_MAY_EXEC: mode |= str_to_mode('ix') # m is not implied by ix ### If we get an mmap request, check if we already have it in allow_mode ##if mode & AA_EXEC_MMAP: ## # ix implies m, so we don't need to add m if ix is present ## if contains(allow_mode, 'ix'): ## mode = mode - AA_EXEC_MMAP if not mode: continue matches = [] if fmode: matches += fm if imode: matches += im if not mode_contains(allow_mode, mode): default_option = 1 options = [] newincludes = [] include_valid = False for incname in include.keys(): include_valid = False # If already present skip if aa[profile][hat][incname]: continue if incname.startswith(profile_dir): incname = incname.replace(profile_dir + '/', '', 1) include_valid = valid_include('', incname) if not include_valid: continue cm, am, m = match_include_to_path(incname, 'allow', path) if cm and mode_contains(cm, mode): dm = match_include_to_path(incname, 'deny', path)[0] # If the mode is denied if not mode & dm: if not list(filter(lambda s: '/**' == s, m)): newincludes.append(incname) # Add new includes to the options if newincludes: options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes)))) # We should have literal the path in options list too options.append(path) # Add any the globs matching path from logprof globs = glob_common(path) if globs: matches += globs # Add any user entered matching globs for user_glob in user_globs: if matchliteral(user_glob, path): matches.append(user_glob) matches = list(set(matches)) if path in matches: matches.remove(path) options += order_globs(matches, path) default_option = len(options) sev_db.unload_variables() sev_db.load_variables(get_profile_filename(profile)) severity = sev_db.rank(path, mode_to_str(mode)) sev_db.unload_variables() audit_toggle = 0 owner_toggle = 0 if cfg['settings']['default_owner_prompt']: owner_toggle = cfg['settings']['default_owner_prompt'] done = False while not done: q = hasher() q['headers'] = [_('Profile'), combine_name(profile, hat), _('Path'), path] if allow_mode: mode |= allow_mode tail = '' s = '' prompt_mode = None if owner_toggle == 0: prompt_mode = flatten_mode(mode) tail = ' ' + _('(owner permissions off)') elif owner_toggle == 1: prompt_mode = mode elif owner_toggle == 2: prompt_mode = allow_mode | owner_flatten_mode(mode - allow_mode) tail = ' ' + _('(force new perms to owner)') else: prompt_mode = owner_flatten_mode(mode) tail = ' ' + _('(force all rule perms to owner)') if audit_toggle == 1: s = mode_to_str_user(allow_mode) if allow_mode: s += ', ' s += 'audit ' + mode_to_str_user(prompt_mode - allow_mode) + tail elif audit_toggle == 2: s = 'audit ' + mode_to_str_user(prompt_mode) + tail else: s = mode_to_str_user(prompt_mode) + tail q['headers'] += [_('Old Mode'), mode_to_str_user(allow_mode), _('New Mode'), s] else: s = '' tail = '' prompt_mode = None if audit_toggle: s = 'audit' if owner_toggle == 0: prompt_mode = flatten_mode(mode) tail = ' ' + _('(owner permissions off)') elif owner_toggle == 1: prompt_mode = mode else: prompt_mode = owner_flatten_mode(mode) tail = ' ' + _('(force perms to owner)') s = mode_to_str_user(prompt_mode) q['headers'] += [_('Mode'), s] q['headers'] += [_('Severity'), severity] q['options'] = options q['selected'] = default_option - 1 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_ABORT', 'CMD_FINISHED', 'CMD_OTHER'] q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': q['default'] = 'CMD_ALLOW' seen_events += 1 ans, selected = aaui.UI_PromptUser(q) if ans == 'CMD_FINISHED': save_profiles() return if ans == 'CMD_IGNORE_ENTRY': done = True break if ans == 'CMD_OTHER': audit_toggle, owner_toggle = UI_ask_mode_toggles(audit_toggle, owner_toggle, allow_mode) elif ans == 'CMD_USER_TOGGLE': owner_toggle += 1 if not allow_mode and owner_toggle == 2: owner_toggle += 1 if owner_toggle > 3: owner_toggle = 0 elif ans == 'CMD_ALLOW': path = options[selected] done = True match = re_match_include(path) # .search('^#include\s+<(.+)>$', path) if match: inc = match # .groups()[0] deleted = 0 deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True changed[profile] = True aaui.UI_Info(_('Adding %s to profile.') % path) if deleted: aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: if aa[profile][hat]['allow']['path'][path].get('mode', False): mode |= aa[profile][hat]['allow']['path'][path]['mode'] deleted = [] for entry in aa[profile][hat]['allow']['path'].keys(): if path == entry: continue if matchregexp(path, entry): if mode_contains(mode, aa[profile][hat]['allow']['path'][entry]['mode']): deleted.append(entry) for entry in deleted: aa[profile][hat]['allow']['path'].pop(entry) deleted = len(deleted) if owner_toggle == 0: mode = flatten_mode(mode) #elif owner_toggle == 1: # mode = mode elif owner_toggle == 2: mode = allow_mode | owner_flatten_mode(mode - allow_mode) elif owner_toggle == 3: mode = owner_flatten_mode(mode) aa[profile][hat]['allow']['path'][path]['mode'] = aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode tmpmode = set() if audit_toggle == 1: tmpmode = mode - allow_mode elif audit_toggle == 2: tmpmode = mode aa[profile][hat]['allow']['path'][path]['audit'] = aa[profile][hat]['allow']['path'][path].get('audit', set()) | tmpmode changed[profile] = True aaui.UI_Info(_('Adding %s %s to profile') % (path, mode_to_str_user(mode))) if deleted: aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) elif ans == 'CMD_DENY': path = options[selected].strip() # Add new entry? aa[profile][hat]['deny']['path'][path]['mode'] = aa[profile][hat]['deny']['path'][path].get('mode', set()) | (mode - allow_mode) aa[profile][hat]['deny']['path'][path]['audit'] = aa[profile][hat]['deny']['path'][path].get('audit', set()) changed[profile] = True done = True elif ans == 'CMD_NEW': arg = options[selected] if not re_match_include(arg): ans = aaui.UI_GetString(_('Enter new path: '), arg) if ans: if not matchliteral(ans, path): ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %s\n Entered Path: %s\nDo you really want to use this path?') % (path, ans) key = aaui.UI_YesNo(ynprompt, 'n') if key == 'n': continue user_globs.append(ans) options.append(ans) default_option = len(options) elif ans == 'CMD_GLOB': newpath = options[selected].strip() if not re_match_include(newpath): newpath = glob_path(newpath) if newpath not in options: options.append(newpath) default_option = len(options) else: default_option = options.index(newpath) + 1 elif ans == 'CMD_GLOBEXT': newpath = options[selected].strip() if not re_match_include(newpath): newpath = glob_path_withext(newpath) if newpath not in options: options.append(newpath) default_option = len(options) else: default_option = options.index(newpath) + 1 elif re.search('\d', ans): default_option = ans # for family in sorted(log_dict[aamode][profile][hat]['netdomain'].keys()): # severity handling for net toggles goes here for sock_type in sorted(log_dict[profile][profile][hat]['netdomain'][family].keys()): if profile_known_network(aa[profile][hat], family, sock_type): continue default_option = 1 options = [] newincludes = match_net_includes(aa[profile][hat], family, sock_type) q = hasher() if newincludes: options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes)))) if options: options.append('network %s %s' % (family, sock_type)) q['options'] = options q['selected'] = default_option - 1 q['headers'] = [_('Profile'), combine_name(profile, hat)] q['headers'] += [_('Network Family'), family] q['headers'] += [_('Socket Type'), sock_type] audit_toggle = 0 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'] q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': q['default'] = 'CMD_ALLOW' seen_events += 1 done = False while not done: ans, selected = aaui.UI_PromptUser(q) if ans == 'CMD_FINISHED': save_profiles() return if ans == 'CMD_IGNORE_ENTRY': done = True break if ans.startswith('CMD_AUDIT'): audit_toggle = not audit_toggle audit = '' if audit_toggle: audit = 'audit' q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF', 'CMD_ABORT', 'CMD_FINISHED'] else: q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'] q['headers'] = [_('Profile'), combine_name(profile, hat)] q['headers'] += [_('Network Family'), audit + family] q['headers'] += [_('Socket Type'), sock_type] elif ans == 'CMD_ALLOW': selection = options[selected] done = True if re_match_include(selection): # re.search('#include\s+<.+>$', selection): inc = re_match_include(selection) # re.search('#include\s+<(.+)>$', selection).groups()[0] deleted = 0 deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True changed[profile] = True aaui.UI_Info(_('Adding %s to profile') % selection) if deleted: aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True changed[profile] = True aaui.UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type)) elif ans == 'CMD_DENY': done = True aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True changed[profile] = True aaui.UI_Info(_('Denying network access %s %s to profile') % (family, sock_type)) else: done = False def glob_path(newpath): """Glob the given file path""" if newpath[-1] == '/': if newpath[-4:] == '/**/' or newpath[-3:] == '/*/': # /foo/**/ and /foo/*/ => /**/ newpath = re.sub('/[^/]+/\*{1,2}/$', '/**/', newpath) # re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath) elif re.search('/[^/]+\*\*[^/]*/$', newpath): # /foo**/ and /foo**bar/ => /**/ newpath = re.sub('/[^/]+\*\*[^/]*/$', '/**/', newpath) elif re.search('/\*\*[^/]+/$', newpath): # /**bar/ => /**/ newpath = re.sub('/\*\*[^/]+/$', '/**/', newpath) else: newpath = re.sub('/[^/]+/$', '/*/', newpath) else: if newpath[-3:] == '/**' or newpath[-2:] == '/*': # /foo/** and /foo/* => /** newpath = re.sub('/[^/]+/\*{1,2}$', '/**', newpath) elif re.search('/[^/]*\*\*[^/]+$', newpath): # /**foo and /foor**bar => /** newpath = re.sub('/[^/]*\*\*[^/]+$', '/**', newpath) elif re.search('/[^/]+\*\*$', newpath): # /foo** => /** newpath = re.sub('/[^/]+\*\*$', '/**', newpath) else: newpath = re.sub('/[^/]+$', '/*', newpath) return newpath def glob_path_withext(newpath): """Glob given file path with extension""" # match /**.ext and /*.ext match = re.search('/\*{1,2}(\.[^/]+)$', newpath) if match: # /foo/**.ext and /foo/*.ext => /**.ext newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**' + match.groups()[0], newpath) elif re.search('/[^/]+\*\*[^/]*\.[^/]+$', newpath): # /foo**.ext and /foo**bar.ext => /**.ext match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$', newpath) newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**' + match.groups()[0], newpath) elif re.search('/\*\*[^/]+\.[^/]+$', newpath): # /**foo.ext => /**.ext match = re.search('/\*\*[^/]+(\.[^/]+)$', newpath) newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**' + match.groups()[0], newpath) else: match = re.search('(\.[^/]+)$', newpath) if match: newpath = re.sub('/[^/]+(\.[^/]+)$', '/*' + match.groups()[0], newpath) return newpath def delete_net_duplicates(netrules, incnetrules): deleted = 0 hasher_obj = hasher() copy_netrules = deepcopy(netrules) if incnetrules and netrules: incnetglob = False # Delete matching rules from abstractions if incnetrules.get('all', False): incnetglob = True for fam in copy_netrules['rule'].keys(): if incnetglob or (type(incnetrules['rule'][fam]) != type(hasher_obj) and incnetrules['rule'][fam]): if type(netrules['rule'][fam]) == type(hasher_obj): deleted += len(netrules['rule'][fam].keys()) else: deleted += 1 netrules['rule'].pop(fam) elif type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]: continue else: for socket_type in copy_netrules['rule'][fam].keys(): if incnetrules['rule'][fam].get(socket_type, False): netrules['rule'][fam].pop(socket_type) deleted += 1 return deleted def delete_cap_duplicates(profilecaps, inccaps): deleted = [] if profilecaps and inccaps: for capname in profilecaps.keys(): if inccaps[capname].get('set', False) == 1: deleted.append(capname) for capname in deleted: profilecaps.pop(capname) return len(deleted) def delete_path_duplicates(profile, incname, allow): deleted = [] for entry in profile[allow]['path'].keys(): if entry == '#include <%s>' % incname: continue cm, am, m = match_include_to_path(incname, allow, entry) if cm and mode_contains(cm, profile[allow]['path'][entry]['mode']) and mode_contains(am, profile[allow]['path'][entry]['audit']): deleted.append(entry) for entry in deleted: profile[allow]['path'].pop(entry) return len(deleted) def delete_duplicates(profile, incname): deleted = 0 # Allow rules covered by denied rules shouldn't be deleted # only a subset allow rules may actually be denied if include.get(incname, False): deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain']) deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain']) deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']['capability']) deleted += delete_cap_duplicates(profile['deny']['capability'], include[incname][incname]['deny']['capability']) deleted += delete_path_duplicates(profile, incname, 'allow') deleted += delete_path_duplicates(profile, incname, 'deny') elif filelist.get(incname, False): deleted += delete_net_duplicates(profile['allow']['netdomain'], filelist[incname][incname]['allow']['netdomain']) deleted += delete_net_duplicates(profile['deny']['netdomain'], filelist[incname][incname]['deny']['netdomain']) deleted += delete_cap_duplicates(profile['allow']['capability'], filelist[incname][incname]['allow']['capability']) deleted += delete_cap_duplicates(profile['deny']['capability'], filelist[incname][incname]['deny']['capability']) deleted += delete_path_duplicates(profile, incname, 'allow') deleted += delete_path_duplicates(profile, incname, 'deny') return deleted def match_net_include(incname, family, type): includelist = [incname] checked = [] name = None if includelist: name = includelist.pop(0) while name: checked.append(name) if netrules_access_check(include[name][name]['allow']['netdomain'], family, type): return True if include[name][name]['include'].keys() and name not in checked: includelist += include[name][name]['include'].keys() if len(includelist): name = includelist.pop(0) else: name = False return False def match_cap_includes(profile, cap): newincludes = [] for incname in include.keys(): if valid_include(profile, incname) and include[incname][incname]['allow']['capability'][cap].get('set', False) == 1: newincludes.append(incname) return newincludes def re_match_include(path): """Matches the path for include and returns the include path""" regex_include = re.compile('^\s*#?include\s*<(.*)>\s*(#.*)?$') match = regex_include.search(path) if match: return match.groups()[0] else: return None def valid_include(profile, incname): if profile and profile['include'].get(incname, False): return False if cfg['settings']['custom_includes']: for incm in cfg['settings']['custom_includes'].split(): if incm == incname: return True if incname.startswith('abstractions/') and os.path.isfile(profile_dir + '/' + incname): return True return False def match_net_includes(profile, family, nettype): newincludes = [] for incname in include.keys(): if valid_include(profile, incname) and match_net_include(incname, family, type): newincludes.append(incname) return newincludes def do_logprof_pass(logmark='', passno=0, pid=pid): # set up variables for this pass # t = hasher() # transitions = hasher() # seen = hasher() # XXX global? global log log = [] global existing_profiles global sev_db # aa = hasher() # profile_changes = hasher() # prelog = hasher() # log_dict = hasher() # changed = dict() # skip = hasher() # XXX global? # filelist = hasher() aaui.UI_Info(_('Reading log entries from %s.') % filename) if not passno: aaui.UI_Info(_('Updating AppArmor profiles in %s.') % profile_dir) read_profiles() if not sev_db: sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown')) #print(pid) #print(existing_profiles) ##if not repo_cf and cfg['repostory']['url']: ## repo_cfg = read_config('repository.conf') ## if not repo_cfg['repository'].get('enabled', False) or repo_cfg['repository]['enabled'] not in ['yes', 'no']: ## UI_ask_to_enable_repo() log_reader = apparmor.logparser.ReadLog(pid, filename, existing_profiles, profile_dir, log) log = log_reader.read_log(logmark) #read_log(logmark) for root in log: handle_children('', '', root) #for root in range(len(log)): #log[root] = handle_children('', '', log[root]) #print(log) for pid in sorted(profile_changes.keys()): set_process(pid, profile_changes[pid]) collapse_log() ask_the_questions() if aaui.UI_mode == 'yast': # To-Do pass finishing = False # Check for finished save_profiles() ##if not repo_cfg['repository'].get('upload', False) or repo['repository']['upload'] == 'later': ## UI_ask_to_upload_profiles() ##if repo_enabled(): ## if repo_cgf['repository']['upload'] == 'yes': ## sync_profiles() ## created = [] # If user selects 'Finish' then we want to exit logprof if finishing: return 'FINISHED' else: return 'NORMAL' def save_profiles(): # Ensure the changed profiles are actual active profiles for prof_name in changed.keys(): if not is_active_profile(prof_name): changed.pop(prof_name) changed_list = sorted(changed.keys()) if changed_list: if aaui.UI_mode == 'yast': # To-Do # selected_profiles = [] # XXX selected_profiles_ref? profile_changes = dict() for prof in changed_list: oldprofile = serialize_profile(original_aa[prof], prof) newprofile = serialize_profile(aa[prof], prof) profile_changes[prof] = get_profile_diff(oldprofile, newprofile) explanation = _('Select which profile changes you would like to save to the\nlocal profile set.') title = _('Local profile changes') SendDataToYast({'type': 'dialog-select-profiles', 'title': title, 'explanation': explanation, 'dialog_select': 'true', 'get_changelog': 'false', 'profiles': profile_changes }) ypath, yarg = GetDataFromYast() if yarg['STATUS'] == 'cancel': return None else: selected_profiles_ref = yarg['PROFILES'] for profile_name in selected_profiles_ref: write_profile_ui_feedback(profile_name) reload_base(profile_name) else: q = hasher() q['title'] = 'Changed Local Profiles' q['headers'] = [] q['explanation'] = _('The following local profiles were changed. Would you like to save them?') q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_SAVE_SELECTED', 'CMD_VIEW_CHANGES', 'CMD_VIEW_CHANGES_CLEAN', 'CMD_ABORT'] q['default'] = 'CMD_VIEW_CHANGES' q['options'] = changed q['selected'] = 0 ans = '' arg = None while ans != 'CMD_SAVE_CHANGES': if not changed: return ans, arg = aaui.UI_PromptUser(q) if ans == 'CMD_SAVE_SELECTED': profile_name = list(changed.keys())[arg] write_profile_ui_feedback(profile_name) reload_base(profile_name) #changed.pop(profile_name) #q['options'] = changed elif ans == 'CMD_VIEW_CHANGES': which = list(changed.keys())[arg] oldprofile = None if aa[which][which].get('filename', False): oldprofile = aa[which][which]['filename'] else: oldprofile = get_profile_filename(which) newprofile = serialize_profile_from_old_profile(aa[which], which, '') display_changes_with_comments(oldprofile, newprofile) elif ans == 'CMD_VIEW_CHANGES_CLEAN': which = list(changed.keys())[arg] oldprofile = serialize_profile(original_aa[which], which, '') newprofile = serialize_profile(aa[which], which, '') display_changes(oldprofile, newprofile) for profile_name in changed_list: write_profile_ui_feedback(profile_name) reload_base(profile_name) def get_pager(): pass def generate_diff(oldprofile, newprofile): oldtemp = tempfile.NamedTemporaryFile('w') oldtemp.write(oldprofile) oldtemp.flush() newtemp = tempfile.NamedTemporaryFile('w') newtemp.write(newprofile) newtemp.flush() difftemp = tempfile.NamedTemporaryFile('w', delete=False) subprocess.call('diff -u -p %s %s > %s' % (oldtemp.name, newtemp.name, difftemp.name), shell=True) oldtemp.close() newtemp.close() return difftemp def get_profile_diff(oldprofile, newprofile): difftemp = generate_diff(oldprofile, newprofile) diff = [] with open_file_read(difftemp.name) as f_in: for line in f_in: if not (line.startswith('---') and line .startswith('+++') and line.startswith('@@')): diff.append(line) difftemp.delete = True difftemp.close() return ''.join(diff) def display_changes(oldprofile, newprofile): if aaui.UI_mode == 'yast': aaui.UI_LongMessage(_('Profile Changes'), get_profile_diff(oldprofile, newprofile)) else: difftemp = generate_diff(oldprofile, newprofile) subprocess.call('less %s' % difftemp.name, shell=True) difftemp.delete = True difftemp.close() def display_changes_with_comments(oldprofile, newprofile): """Compare the new profile with the existing profile inclusive of all the comments""" if not os.path.exists(oldprofile): raise AppArmorException(_("Can't find existing profile %s to compare changes.") % oldprofile) if aaui.UI_mode == 'yast': #To-Do pass else: newtemp = tempfile.NamedTemporaryFile('w') newtemp.write(newprofile) newtemp.flush() difftemp = tempfile.NamedTemporaryFile('w') subprocess.call('diff -u -p %s %s > %s' % (oldprofile, newtemp.name, difftemp.name), shell=True) newtemp.close() subprocess.call('less %s' % difftemp.name, shell=True) difftemp.close() def set_process(pid, profile): # If process not running don't do anything if not os.path.exists('/proc/%s/attr/current' % pid): return None process = None try: process = open_file_read('/proc/%s/attr/current' % pid) except IOError: return None current = process.readline().strip() process.close() if not re.search('^null(-complain)*-profile$', current): return None stats = None try: stats = open_file_read('/proc/%s/stat' % pid) except IOError: return None stat = stats.readline().strip() stats.close() match = re.search('^\d+ \((\S+)\) ', stat) if not match: return None try: process = open_file_write('/proc/%s/attr/current' % pid) except IOError: return None process.write('setprofile %s' % profile) process.close() def collapse_log(): for aamode in prelog.keys(): for profile in prelog[aamode].keys(): for hat in prelog[aamode][profile].keys(): for path in prelog[aamode][profile][hat]['path'].keys(): mode = prelog[aamode][profile][hat]['path'][path] combinedmode = set() # Is path in original profile? if aa[profile][hat]['allow']['path'].get(path, False): combinedmode |= aa[profile][hat]['allow']['path'][path]['mode'] # Match path to regexps in profile combinedmode |= rematchfrag(aa[profile][hat], 'allow', path)[0] # Match path from includes combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path)[0] if not combinedmode or not mode_contains(combinedmode, mode): if log_dict[aamode][profile][hat]['path'].get(path, False): mode |= log_dict[aamode][profile][hat]['path'][path] log_dict[aamode][profile][hat]['path'][path] = mode for capability in prelog[aamode][profile][hat]['capability'].keys(): # If capability not already in profile if not aa[profile][hat]['allow']['capability'][capability].get('set', False): log_dict[aamode][profile][hat]['capability'][capability] = True nd = prelog[aamode][profile][hat]['netdomain'] for family in nd.keys(): for sock_type in nd[family].keys(): if not profile_known_network(aa[profile][hat], family, sock_type): log_dict[aamode][profile][hat]['netdomain'][family][sock_type] = True def validate_profile_mode(mode, allow, nt_name=None): if allow == 'deny': pattern = '^(%s)+$' % PROFILE_MODE_DENY_RE.pattern if re.search(pattern, mode): return True else: return False elif nt_name: pattern = '^(%s)+$' % PROFILE_MODE_NT_RE.pattern if re.search(pattern, mode): return True else: return False else: pattern = '^(%s)+$' % PROFILE_MODE_RE.pattern if re.search(pattern, mode): return True else: return False # rpm backup files, dotfiles, emacs backup files should not be processed # The skippable files type needs be synced with apparmor initscript def is_skippable_file(path): """Returns True if filename matches something to be skipped""" if (re.search('(^|/)\.[^/]*$', path) or re.search('\.rpm(save|new)$', path) or re.search('\.dpkg-(old|new)$', path) or re.search('\.swp$', path) or path[-1] == '~' or path == 'README'): return True def is_skippable_dir(path): if re.search('(disable|cache|force-complain|lxc)', path): return True return False def check_include_syntax(errors): # To-Do pass def check_profile_syntax(errors): # To-Do pass def read_profiles(): try: os.listdir(profile_dir) except: fatal_error(_("Can't read AppArmor profiles in %s") % profile_dir) for file in os.listdir(profile_dir): if os.path.isfile(profile_dir + '/' + file): if is_skippable_file(file): continue else: read_profile(profile_dir + '/' + file, True) def read_inactive_profiles(): if not os.path.exists(extra_profile_dir): return None try: os.listdir(profile_dir) except: fatal_error(_("Can't read AppArmor profiles in %s") % extra_profile_dir) for file in os.listdir(profile_dir): if os.path.isfile(extra_profile_dir + '/' + file): if is_skippable_file(file): continue else: read_profile(extra_profile_dir + '/' + file, False) def read_profile(file, active_profile): data = None try: with open_file_read(file) as f_in: data = f_in.readlines() except IOError: debug_logger.debug("read_profile: can't read %s - skipping" % file) return None profile_data = parse_profile_data(data, file, 0) if profile_data and active_profile: attach_profile_data(aa, profile_data) attach_profile_data(original_aa, profile_data) elif profile_data: attach_profile_data(extras, profile_data) def attach_profile_data(profiles, profile_data): # Make deep copy of data to avoid changes to # arising due to mutables for p in profile_data.keys(): profiles[p] = deepcopy(profile_data[p]) ## Profile parsing regex RE_PROFILE_START = re.compile('^\s*(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)?\{\s*(#.*)?$') RE_PROFILE_END = re.compile('^\s*\}\s*(#.*)?$') RE_PROFILE_CAP = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') RE_PROFILE_LINK = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') RE_PROFILE_CHANGE_PROFILE = re.compile('^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$') RE_PROFILE_ALIAS = re.compile('^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$') RE_PROFILE_RLIMIT = re.compile('^\s*set\s+rlimit\s+(.+)\s+(<=)?\s*(.+)\s*,(#.*)?$') RE_PROFILE_BOOLEAN = re.compile('^\s*(\$\{?\w*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$', flags=re.IGNORECASE) RE_PROFILE_VARIABLE = re.compile('^\s*(@\{?\w+\}?)\s*(\+?=)\s*(@*.+?)\s*,?\s*(#.*)?$') RE_PROFILE_CONDITIONAL = re.compile('^\s*if\s+(not\s+)?(\$\{?\w*\}?)\s*\{\s*(#.*)?$') RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^\s*if\s+(not\s+)?defined\s+(@\{?\w+\}?)\s*\{\s*(#.*)?$') RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$') RE_PROFILE_PATH_ENTRY = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(owner\s+)?([\"@/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$') RE_PROFILE_NETWORK = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?network(.*)\s*(#.*)?$') RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$') RE_PROFILE_HAT_DEF = re.compile('^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') RE_NETWORK_FAMILY_TYPE = re.compile('\s+(\S+)\s+(\S+)\s*,$') RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$') RE_PROFILE_DBUS = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(dbus[^#]*\s*,)\s*(#.*)?$') # match anything that's not " or #, or matching quotes with anything except quotes inside __re_no_or_quoted_hash = '([^#"]|"[^"]*")*' RE_RULE_HAS_COMMA = re.compile('^' + __re_no_or_quoted_hash + ',\s*(#.*)?$') # match comma plus any trailing comment RE_HAS_COMMENT_SPLIT = re.compile('^(?P' + __re_no_or_quoted_hash + ')' + # store in 'not_comment' group '(?P#.*)$') # match trailing comment and store in 'comment' group def parse_profile_data(data, file, do_include): profile_data = hasher() profile = None hat = None in_contained_hat = None repo_data = None parsed_profiles = [] initial_comment = '' lastline = None if do_include: profile = file hat = file for lineno, line in enumerate(data): line = line.strip() if not line: continue # we're dealing with a multiline statement if lastline: line = '%s %s' % (lastline, line) lastline = None # Starting line of a profile if RE_PROFILE_START.search(line): matches = RE_PROFILE_START.search(line).groups() if profile: #print(profile, hat) if profile != hat or not matches[3]: raise AppArmorException(_('%s profile in %s contains syntax errors in line: %s.') % (profile, file, lineno + 1)) # Keep track of the start of a profile if profile and profile == hat and matches[3]: # local profile hat = matches[3] in_contained_hat = True profile_data[profile][hat]['profile'] = True else: if matches[1]: profile = matches[1] else: profile = matches[3] #print(profile) if len(profile.split('//')) >= 2: profile, hat = profile.split('//')[:2] else: hat = None in_contained_hat = False if hat: profile_data[profile][hat]['external'] = True else: hat = profile # Profile stored existing_profiles[profile] = file flags = matches[6] profile = strip_quotes(profile) if hat: hat = strip_quotes(hat) # save profile name and filename profile_data[profile][hat]['name'] = profile profile_data[profile][hat]['filename'] = file filelist[file]['profiles'][profile][hat] = True profile_data[profile][hat]['flags'] = flags profile_data[profile][hat]['allow']['netdomain'] = hasher() profile_data[profile][hat]['allow']['path'] = hasher() profile_data[profile][hat]['allow']['dbus'] = list() # Save the initial comment if initial_comment: profile_data[profile][hat]['initial_comment'] = initial_comment initial_comment = '' if repo_data: profile_data[profile][profile]['repo']['url'] = repo_data['url'] profile_data[profile][profile]['repo']['user'] = repo_data['user'] elif RE_PROFILE_END.search(line): # If profile ends and we're not in one if not profile: raise AppArmorException(_('Syntax Error: Unexpected End of Profile reached in file: %s line: %s') % (file, lineno + 1)) if in_contained_hat: hat = profile in_contained_hat = False else: parsed_profiles.append(profile) profile = None initial_comment = '' elif RE_PROFILE_CAP.search(line): matches = RE_PROFILE_CAP.search(line).groups() if not profile: raise AppArmorException(_('Syntax Error: Unexpected capability entry found in file: %s line: %s') % (file, lineno + 1)) audit = False if matches[0]: audit = True allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' capability = matches[2] profile_data[profile][hat][allow]['capability'][capability]['set'] = True profile_data[profile][hat][allow]['capability'][capability]['audit'] = audit elif RE_PROFILE_LINK.search(line): matches = RE_PROFILE_LINK.search(line).groups() if not profile: raise AppArmorException(_('Syntax Error: Unexpected link entry found in file: %s line: %s') % (file, lineno + 1)) audit = False if matches[0]: audit = True allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' subset = matches[3] link = strip_quotes(matches[6]) value = strip_quotes(matches[7]) profile_data[profile][hat][allow]['link'][link]['to'] = value profile_data[profile][hat][allow]['link'][link]['mode'] = profile_data[profile][hat][allow]['link'][link].get('mode', set()) | apparmor.aamode.AA_MAY_LINK if subset: profile_data[profile][hat][allow]['link'][link]['mode'] |= apparmor.aamode.AA_LINK_SUBSET if audit: profile_data[profile][hat][allow]['link'][link]['audit'] = profile_data[profile][hat][allow]['link'][link].get('audit', set()) | apparmor.aamode.AA_LINK_SUBSET else: profile_data[profile][hat][allow]['link'][link]['audit'] = set() elif RE_PROFILE_CHANGE_PROFILE.search(line): matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() if not profile: raise AppArmorException(_('Syntax Error: Unexpected change profile entry found in file: %s line: %s') % (file, lineno + 1)) cp = strip_quotes(matches[0]) profile_data[profile][hat]['changes_profile'][cp] = True elif RE_PROFILE_ALIAS.search(line): matches = RE_PROFILE_ALIAS.search(line).groups() from_name = strip_quotes(matches[0]) to_name = strip_quotes(matches[1]) if profile: profile_data[profile][hat]['alias'][from_name] = to_name else: if not filelist.get(file, False): filelist[file] = hasher() filelist[file]['alias'][from_name] = to_name elif RE_PROFILE_RLIMIT.search(line): matches = RE_PROFILE_RLIMIT.search(line).groups() if not profile: raise AppArmorException(_('Syntax Error: Unexpected rlimit entry found in file: %s line: %s') % (file, lineno + 1)) from_name = matches[0] to_name = matches[2] profile_data[profile][hat]['rlimit'][from_name] = to_name elif RE_PROFILE_BOOLEAN.search(line): matches = RE_PROFILE_BOOLEAN.search(line) if not profile: raise AppArmorException(_('Syntax Error: Unexpected boolean definition found in file: %s line: %s') % (file, lineno + 1)) bool_var = matches[0] value = matches[1] profile_data[profile][hat]['lvar'][bool_var] = value elif RE_PROFILE_VARIABLE.search(line): # variable additions += and = matches = RE_PROFILE_VARIABLE.search(line).groups() list_var = strip_quotes(matches[0]) var_operation = matches[1] value = strip_quotes(matches[2]) if profile: if not profile_data[profile][hat].get('lvar', False): profile_data[profile][hat]['lvar'][list_var] = [] store_list_var(profile_data[profile]['lvar'], list_var, value, var_operation) else: if not filelist[file].get('lvar', False): filelist[file]['lvar'][list_var] = [] store_list_var(filelist[file]['lvar'], list_var, value, var_operation) elif RE_PROFILE_CONDITIONAL.search(line): # Conditional Boolean pass elif RE_PROFILE_CONDITIONAL_VARIABLE.search(line): # Conditional Variable defines pass elif RE_PROFILE_CONDITIONAL_BOOLEAN.search(line): # Conditional Boolean defined pass elif RE_PROFILE_PATH_ENTRY.search(line): matches = RE_PROFILE_PATH_ENTRY.search(line).groups() if not profile: raise AppArmorException(_('Syntax Error: Unexpected path entry found in file: %s line: %s') % (file, lineno + 1)) audit = False if matches[0]: audit = True allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' user = False if matches[2]: user = True path = matches[3].strip() mode = matches[4] nt_name = matches[6] if nt_name: nt_name = nt_name.strip() p_re = convert_regexp(path) try: re.compile(p_re) except: raise AppArmorException(_('Syntax Error: Invalid Regex %s in file: %s line: %s') % (path, file, lineno + 1)) if not validate_profile_mode(mode, allow, nt_name): raise AppArmorException(_('Invalid mode %s in file: %s line: %s') % (mode, file, lineno + 1)) tmpmode = set() if user: tmpmode = str_to_mode('%s::' % mode) else: tmpmode = str_to_mode(mode) profile_data[profile][hat][allow]['path'][path]['mode'] = profile_data[profile][hat][allow]['path'][path].get('mode', set()) | tmpmode if nt_name: profile_data[profile][hat][allow]['path'][path]['to'] = nt_name if audit: profile_data[profile][hat][allow]['path'][path]['audit'] = profile_data[profile][hat][allow]['path'][path].get('audit', set()) | tmpmode else: profile_data[profile][hat][allow]['path'][path]['audit'] = set() elif re_match_include(line): # Include files include_name = re_match_include(line) if include_name.startswith('local/'): profile_data[profile][hat]['localinclude'][include_name] = True if profile: profile_data[profile][hat]['include'][include_name] = True else: if not filelist.get(file): filelist[file] = hasher() filelist[file]['include'][include_name] = True # If include is a directory if os.path.isdir(profile_dir + '/' + include_name): for path in os.listdir(profile_dir + '/' + include_name): path = path.strip() if is_skippable_file(path): continue if os.path.isfile(profile_dir + '/' + include_name + '/' + path): file_name = include_name + '/' + path file_name = file_name.replace(profile_dir + '/', '') if not include.get(file_name, False): load_include(file_name) else: if not include.get(include_name, False): load_include(include_name) elif RE_PROFILE_NETWORK.search(line): matches = RE_PROFILE_NETWORK.search(line).groups() if not profile: raise AppArmorException(_('Syntax Error: Unexpected network entry found in file: %s line: %s') % (file, lineno + 1)) audit = False if matches[0]: audit = True allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' network = matches[2] if RE_NETWORK_FAMILY_TYPE.search(network): nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups() fam, typ = nmatch[:2] ##Simply ignore any type subrules if family has True (seperately for allow and deny) ##This will lead to those type specific rules being lost when written #if type(profile_data[profile][hat][allow]['netdomain']['rule'].get(fam, False)) == dict: profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = 1 profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit elif RE_NETWORK_FAMILY.search(network): fam = RE_NETWORK_FAMILY.search(network).groups()[0] profile_data[profile][hat][allow]['netdomain']['rule'][fam] = True profile_data[profile][hat][allow]['netdomain']['audit'][fam] = audit else: profile_data[profile][hat][allow]['netdomain']['rule']['all'] = True profile_data[profile][hat][allow]['netdomain']['audit']['all'] = audit # True elif RE_PROFILE_DBUS.search(line): matches = RE_PROFILE_DBUS.search(line).groups() if not profile: raise AppArmorException(_('Syntax Error: Unexpected dbus entry found in file: %s line: %s') % (file, lineno + 1)) audit = False if matches[0]: audit = True allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' dbus = matches[2] #parse_dbus_rule(profile_data[profile], dbus, audit, allow) dbus_rule = parse_dbus_rule(dbus) dbus_rule.audit = audit dbus_rule.deny = (allow == 'deny') dbus_rules = profile_data[profile][hat][allow].get('dbus', list()) dbus_rules.append(dbus_rule) profile_data[profile][hat][allow]['dbus'] = dbus_rules elif RE_PROFILE_CHANGE_HAT.search(line): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() if not profile: raise AppArmorException(_('Syntax Error: Unexpected change hat declaration found in file: %s line: %s') % (file, lineno + 1)) hat = matches[0] hat = strip_quotes(hat) if not profile_data[profile][hat].get('declared', False): profile_data[profile][hat]['declared'] = True elif RE_PROFILE_HAT_DEF.search(line): # An embedded hat syntax definition starts matches = RE_PROFILE_HAT_DEF.search(line).groups() if not profile: raise AppArmorException(_('Syntax Error: Unexpected hat definition found in file: %s line: %s') % (file, lineno + 1)) in_contained_hat = True hat = matches[0] hat = strip_quotes(hat) flags = matches[3] profile_data[profile][hat]['flags'] = flags profile_data[profile][hat]['declared'] = False #profile_data[profile][hat]['allow']['path'] = hasher() #profile_data[profile][hat]['allow']['netdomain'] = hasher() if initial_comment: profile_data[profile][hat]['initial_comment'] = initial_comment initial_comment = '' if filelist[file]['profiles'][profile].get(hat, False): raise AppArmorException(_('Error: Multiple definitions for hat %s in profile %s.') % (hat, profile)) filelist[file]['profiles'][profile][hat] = True elif line[0] == '#': # Handle initial comments if not profile: if line.startswith('# Last Modified:'): continue elif line.startswith('# REPOSITORY:'): # TODO: allow any number of spaces/tabs parts = line.split() if len(parts) == 3 and parts[2] == 'NEVERSUBMIT': repo_data = {'neversubmit': True} elif len(parts) == 5: repo_data = {'url': parts[2], 'user': parts[3], 'id': parts[4]} else: aaui.UI_Important(_('Warning: invalid "REPOSITORY:" line in %s, ignoring.') % file) initial_comment = initial_comment + line + '\n' else: initial_comment = initial_comment + line + '\n' elif not RE_RULE_HAS_COMMA.search(line): # Bah, line continues on to the next line if RE_HAS_COMMENT_SPLIT.search(line): # filter trailing comments lastline = RE_HAS_COMMENT_SPLIT.search(line).group('not_comment') else: lastline = line else: raise AppArmorException(_('Syntax Error: Unknown line found in file: %s line: %s') % (file, lineno + 1)) # Below is not required I'd say if not do_include: for hatglob in cfg['required_hats'].keys(): for parsed_prof in sorted(parsed_profiles): if re.search(hatglob, parsed_prof): for hat in cfg['required_hats'][hatglob].split(): if not profile_data[parsed_prof].get(hat, False): profile_data[parsed_prof][hat] = hasher() # End of file reached but we're stuck in a profile if profile and not do_include: raise AppArmorException(_("Syntax Error: Missing '}' . Reached end of file %s while inside profile %s") % (file, profile)) return profile_data # RE_DBUS_ENTRY = re.compile('^dbus\s*()?,\s*$') # use stuff like '(?P(send|write|w|receive|read|r|rw))' def parse_dbus_rule(line): # XXX Do real parsing here return aarules.Raw_DBUS_Rule(line) #matches = RE_DBUS_ENTRY.search(line).groups() #if len(matches) == 1: # XXX warn? # matched nothing # print('no matches') # return aarules.DBUS_Rule() #print(line) def separate_vars(vs): """Returns a list of all the values for a variable""" data = [] #data = [i.strip('"') for i in vs.split()] RE_VARS = re.compile('\s*((\".+?\")|([^\"]\S+))\s*(.*)$') while RE_VARS.search(vs): matches = RE_VARS.search(vs).groups() data.append(strip_quotes(matches[0])) vs = matches[3] return data def is_active_profile(pname): if aa.get(pname, False): return True else: return False def store_list_var(var, list_var, value, var_operation): """Store(add new variable or add values to variable) the variables encountered in the given list_var""" vlist = separate_vars(value) if var_operation == '=': if not var.get(list_var, False): var[list_var] = set(vlist) else: #print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var]) raise AppArmorException(_('An existing variable redefined: %s') % list_var) elif var_operation == '+=': if var.get(list_var, False): var[list_var] = set(var[list_var] + vlist) else: raise AppArmorException(_('Values added to a non-existing variable: %s') % list_var) else: raise AppArmorException(_('Unknown variable operation: %s') % var_operation) def strip_quotes(data): if data[0] + data[-1] == '""': return data[1:-1] else: return data def quote_if_needed(data): # quote data if it contains whitespace if ' ' in data: data = '"' + data + '"' return data def escape(escape): escape = strip_quotes(escape) escape = re.sub('((?') def write_change_profile(prof_data, depth): return write_single(prof_data, depth, '', 'change_profile', 'change_profile -> ', ',') def write_alias(prof_data, depth): return write_pair(prof_data, depth, '', 'alias', 'alias ', ' -> ', ',', quote_if_needed) def write_rlimits(prof_data, depth): return write_pair(prof_data, depth, '', 'rlimit', 'set rlimit ', ' <= ', ',', quote_if_needed) def var_transform(ref): data = [] for value in ref: data.append(quote_if_needed(value)) return ' '.join(data) def write_list_vars(prof_data, depth): return write_pair(prof_data, depth, '', 'lvar', '', ' = ', '', var_transform) def write_cap_rules(prof_data, depth, allow): pre = ' ' * depth data = [] allowstr = set_allow_str(allow) if prof_data[allow].get('capability', False): for cap in sorted(prof_data[allow]['capability'].keys()): audit = '' if prof_data[allow]['capability'][cap].get('audit', False): audit = 'audit ' if prof_data[allow]['capability'][cap].get('set', False): data.append('%s%s%scapability %s,' % (pre, audit, allowstr, cap)) data.append('') return data def write_capabilities(prof_data, depth): #data = write_single(prof_data, depth, '', 'set_capability', 'set capability ', ',') data = write_cap_rules(prof_data, depth, 'deny') data += write_cap_rules(prof_data, depth, 'allow') return data def write_net_rules(prof_data, depth, allow): pre = ' ' * depth data = [] allowstr = set_allow_str(allow) audit = '' if prof_data[allow].get('netdomain', False): if prof_data[allow]['netdomain'].get('rule', False) == 'all': if prof_data[allow]['netdomain']['audit'].get('all', False): audit = 'audit ' data.append('%s%snetwork,' % (pre, audit)) else: for fam in sorted(prof_data[allow]['netdomain']['rule'].keys()): if prof_data[allow]['netdomain']['rule'][fam] is True: if prof_data[allow]['netdomain']['audit'][fam]: audit = 'audit' data.append('%s%s%snetwork %s' % (pre, audit, allowstr, fam)) else: for typ in sorted(prof_data[allow]['netdomain']['rule'][fam].keys()): if prof_data[allow]['netdomain']['audit'][fam].get(typ, False): audit = 'audit' data.append('%s%s%snetwork %s %s,' % (pre, audit, allowstr, fam, typ)) if prof_data[allow].get('netdomain', False): data.append('') return data def write_netdomain(prof_data, depth): data = write_net_rules(prof_data, depth, 'deny') data += write_net_rules(prof_data, depth, 'allow') return data def write_dbus_rules(prof_data, depth, allow): pre = ' ' * depth data = [] # no dbus rules, so return if not prof_data[allow].get('dbus', False): return data for dbus_rule in prof_data[allow]['dbus']: data.append('%s%s' % (pre, dbus_rule.serialize())) data.append('') return data def write_dbus(prof_data, depth): data = write_dbus_rules(prof_data, depth, 'deny') data += write_net_rules(prof_data, depth, 'allow') return data def write_link_rules(prof_data, depth, allow): pre = ' ' * depth data = [] allowstr = set_allow_str(allow) if prof_data[allow].get('link', False): for path in sorted(prof_data[allow]['link'].keys()): to_name = prof_data[allow]['link'][path]['to'] subset = '' if prof_data[allow]['link'][path]['mode'] & apparmor.aamode.AA_LINK_SUBSET: subset = 'subset' audit = '' if prof_data[allow]['link'][path].get('audit', False): audit = 'audit ' path = quote_if_needed(path) to_name = quote_if_needed(to_name) data.append('%s%s%slink %s%s -> %s,' % (pre, audit, allowstr, subset, path, to_name)) data.append('') return data def write_links(prof_data, depth): data = write_link_rules(prof_data, depth, 'deny') data += write_link_rules(prof_data, depth, 'allow') return data def write_path_rules(prof_data, depth, allow): pre = ' ' * depth data = [] allowstr = set_allow_str(allow) if prof_data[allow].get('path', False): for path in sorted(prof_data[allow]['path'].keys()): mode = prof_data[allow]['path'][path]['mode'] audit = prof_data[allow]['path'][path]['audit'] tail = '' if prof_data[allow]['path'][path].get('to', False): tail = ' -> %s' % prof_data[allow]['path'][path]['to'] user, other = split_mode(mode) user_audit, other_audit = split_mode(audit) while user or other: ownerstr = '' tmpmode = 0 tmpaudit = False if user - other: # if no other mode set ownerstr = 'owner ' tmpmode = user - other tmpaudit = user_audit user = user - tmpmode else: if user_audit - other_audit & user: ownerstr = 'owner ' tmpaudit = user_audit - other_audit & user tmpmode = user & tmpaudit user = user - tmpmode else: ownerstr = '' tmpmode = user | other tmpaudit = user_audit | other_audit user = user - tmpmode other = other - tmpmode if tmpmode & tmpaudit: modestr = mode_to_str(tmpmode & tmpaudit) path = quote_if_needed(path) data.append('%saudit %s%s%s %s%s,' % (pre, allowstr, ownerstr, path, modestr, tail)) tmpmode = tmpmode - tmpaudit if tmpmode: modestr = mode_to_str(tmpmode) path = quote_if_needed(path) data.append('%s%s%s%s %s%s,' % (pre, allowstr, ownerstr, path, modestr, tail)) data.append('') return data def write_paths(prof_data, depth): data = write_path_rules(prof_data, depth, 'deny') data += write_path_rules(prof_data, depth, 'allow') return data def write_rules(prof_data, depth): data = write_alias(prof_data, depth) data += write_list_vars(prof_data, depth) data += write_includes(prof_data, depth) data += write_rlimits(prof_data, depth) data += write_capabilities(prof_data, depth) data += write_netdomain(prof_data, depth) data += write_dbus(prof_data, depth) data += write_links(prof_data, depth) data += write_paths(prof_data, depth) data += write_change_profile(prof_data, depth) return data def write_piece(profile_data, depth, name, nhat, write_flags): pre = ' ' * depth data = [] wname = None inhat = False if name == nhat: wname = name else: wname = name + '//' + nhat name = nhat inhat = True data += write_header(profile_data[name], depth, wname, False, write_flags) data += write_rules(profile_data[name], depth + 1) pre2 = ' ' * (depth + 1) # External hat declarations for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if profile_data[hat].get('declared', False): data.append('%s^%s,' % (pre2, hat)) if not inhat: # Embedded hats for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if not profile_data[hat]['external'] and not profile_data[hat]['declared']: data.append('') if profile_data[hat]['profile']: data += list(map(str, write_header(profile_data[hat], depth + 1, hat, True, write_flags))) else: data += list(map(str, write_header(profile_data[hat], depth + 1, '^' + hat, True, write_flags))) data += list(map(str, write_rules(profile_data[hat], depth + 2))) data.append('%s}' % pre2) data.append('%s}' % pre) # External hats for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if name == nhat and profile_data[hat].get('external', False): data.append('') data += list(map(lambda x: ' %s' % x, write_piece(profile_data, depth - 1, name, nhat, write_flags))) data.append(' }') return data def serialize_profile(profile_data, name, options): string = '' include_metadata = False include_flags = True data = [] if options: # and type(options) == dict: if options.get('METADATA', False): include_metadata = True if options.get('NO_FLAGS', False): include_flags = False if include_metadata: string = '# Last Modified: %s\n' % time.asctime() if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): repo = profile_data[name]['repo'] string += '# REPOSITORY: %s %s %s\n' % (repo['url'], repo['user'], repo['id']) elif profile_data[name]['repo']['neversubmit']: string += '# REPOSITORY: NEVERSUBMIT\n' # if profile_data[name].get('initial_comment', False): # comment = profile_data[name]['initial_comment'] # comment.replace('\\n', '\n') # string += comment + '\n' prof_filename = get_profile_filename(name) if filelist.get(prof_filename, False): data += write_alias(filelist[prof_filename], 0) data += write_list_vars(filelist[prof_filename], 0) data += write_includes(filelist[prof_filename], 0) #Here should be all the profiles from the files added write after global/common stuff for prof in sorted(filelist[prof_filename]['profiles'].keys()): if prof != name: if original_aa[prof][prof].get('initial_comment', False): comment = original_aa[prof][prof]['initial_comment'] comment.replace('\\n', '\n') data += [comment + '\n'] data += write_piece(original_aa[prof], 0, prof, prof, include_flags) else: if profile_data[name].get('initial_comment', False): comment = profile_data[name]['initial_comment'] comment.replace('\\n', '\n') data += [comment + '\n'] data += write_piece(profile_data, 0, name, name, include_flags) string += '\n'.join(data) return string + '\n' def serialize_profile_from_old_profile(profile_data, name, options): data = [] string = '' include_metadata = False include_flags = True prof_filename = get_profile_filename(name) write_filelist = deepcopy(filelist[prof_filename]) write_prof_data = deepcopy(profile_data) if options: # and type(options) == dict: if options.get('METADATA', False): include_metadata = True if options.get('NO_FLAGS', False): include_flags = False if include_metadata: string = '# Last Modified: %s\n' % time.asctime() if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): repo = profile_data[name]['repo'] string += '# REPOSITORY: %s %s %s\n' % (repo['url'], repo['user'], repo['id']) elif profile_data[name]['repo']['neversubmit']: string += '# REPOSITORY: NEVERSUBMIT\n' if not os.path.isfile(prof_filename): raise AppArmorException(_("Can't find existing profile to modify")) # profiles_list = filelist[prof_filename].keys() # XXX with open_file_read(prof_filename) as f_in: profile = None hat = None write_methods = {'alias': write_alias, 'lvar': write_list_vars, 'include': write_includes, 'rlimit': write_rlimits, 'capability': write_capabilities, 'netdomain': write_netdomain, 'dbus': write_dbus, 'link': write_links, 'path': write_paths, 'change_profile': write_change_profile, } # prof_correct = True # XXX correct? segments = {'alias': False, 'lvar': False, 'include': False, 'rlimit': False, 'capability': False, 'netdomain': False, 'dbus': False, 'link': False, 'path': False, 'change_profile': False, 'include_local_started': False, } #data.append('reading prof') for line in f_in: correct = True line = line.rstrip('\n') #data.append(' ')#data.append('read: '+line) if RE_PROFILE_START.search(line): matches = RE_PROFILE_START.search(line).groups() if profile and profile == hat and matches[3]: hat = matches[3] in_contained_hat = True if write_prof_data[profile][hat]['profile']: pass else: if matches[1]: profile = matches[1] else: profile = matches[3] if len(profile.split('//')) >= 2: profile, hat = profile.split('//')[:2] else: hat = None in_contained_hat = False if hat and not write_prof_data[profile][hat]['external']: correct = False else: hat = profile flags = matches[6] profile = strip_quotes(profile) if hat: hat = strip_quotes(hat) if not write_prof_data[hat]['name'] == profile: correct = False if not write_filelist['profiles'][profile][hat] is True: correct = False if not write_prof_data[hat]['flags'] == flags: correct = False #Write the profile start if correct: if write_filelist: data += write_alias(write_filelist, 0) data += write_list_vars(write_filelist, 0) data += write_includes(write_filelist, 0) data.append(line) else: if write_prof_data[hat]['name'] == profile: depth = len(line) - len(line.lstrip()) data += write_header(write_prof_data[name], int(depth / 2), name, False, include_flags) elif RE_PROFILE_END.search(line): # DUMP REMAINDER OF PROFILE if profile: depth = len(line) - len(line.lstrip()) if True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) data += write_alias(write_prof_data[name], depth) data += write_list_vars(write_prof_data[name], depth) data += write_includes(write_prof_data[name], depth) data += write_rlimits(write_prof_data, depth) data += write_capabilities(write_prof_data[name], depth) data += write_netdomain(write_prof_data[name], depth) data += write_dbus(write_prof_data[name], depth) data += write_links(write_prof_data[name], depth) data += write_paths(write_prof_data[name], depth) data += write_change_profile(write_prof_data[name], depth) write_prof_data.pop(name) #Append local includes data.append(line) if not in_contained_hat: # Embedded hats depth = int((len(line) - len(line.lstrip())) / 2) pre2 = ' ' * (depth + 1) for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if not profile_data[hat]['external'] and not profile_data[hat]['declared']: data.append('') if profile_data[hat]['profile']: data += list(map(str, write_header(profile_data[hat], depth + 1, hat, True, include_flags))) else: data += list(map(str, write_header(profile_data[hat], depth + 1, '^' + hat, True, include_flags))) data += list(map(str, write_rules(profile_data[hat], depth + 2))) data.append('%s}' % pre2) # External hats for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if profile_data[hat].get('external', False): data.append('') data += list(map(lambda x: ' %s' % x, write_piece(profile_data, depth - 1, name, name, include_flags))) data.append(' }') if in_contained_hat: #Hat processed, remove it hat = profile in_contained_hat = False else: profile = None elif RE_PROFILE_CAP.search(line): matches = RE_PROFILE_CAP.search(line).groups() audit = False if matches[0]: audit = matches[0] allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' capability = matches[2] if not write_prof_data[hat][allow]['capability'][capability].get('set', False): correct = False if not write_prof_data[hat][allow]['capability'][capability].get(audit, False) == audit: correct = False if correct: if not segments['capability'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) segments['capability'] = True write_prof_data[hat][allow]['capability'].pop(capability) data.append(line) #write_prof_data[hat][allow]['capability'][capability].pop(audit) #Remove this line else: # To-Do pass elif RE_PROFILE_LINK.search(line): matches = RE_PROFILE_LINK.search(line).groups() audit = False if matches[0]: audit = True allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' subset = matches[3] link = strip_quotes(matches[6]) value = strip_quotes(matches[7]) if not write_prof_data[hat][allow]['link'][link]['to'] == value: correct = False if not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_MAY_LINK: correct = False if subset and not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_LINK_SUBSET: correct = False if audit and not write_prof_data[hat][allow]['link'][link]['audit'] & apparmor.aamode.AA_LINK_SUBSET: correct = False if correct: if not segments['link'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) segments['link'] = True write_prof_data[hat][allow]['link'].pop(link) data.append(line) else: # To-Do pass elif RE_PROFILE_CHANGE_PROFILE.search(line): matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() cp = strip_quotes(matches[0]) if not write_prof_data[hat]['changes_profile'][cp] is True: correct = False if correct: if not segments['change_profile'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) segments['change_profile'] = True write_prof_data[hat]['change_profile'].pop(cp) data.append(line) else: #To-Do pass elif RE_PROFILE_ALIAS.search(line): matches = RE_PROFILE_ALIAS.search(line).groups() from_name = strip_quotes(matches[0]) to_name = strip_quotes(matches[1]) if profile: if not write_prof_data[hat]['alias'][from_name] == to_name: correct = False else: if not write_filelist['alias'][from_name] == to_name: correct = False if correct: if not segments['alias'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) segments['alias'] = True if profile: write_prof_data[hat]['alias'].pop(from_name) else: write_filelist['alias'].pop(from_name) data.append(line) else: #To-Do pass elif RE_PROFILE_RLIMIT.search(line): matches = RE_PROFILE_RLIMIT.search(line).groups() from_name = matches[0] to_name = matches[2] if not write_prof_data[hat]['rlimit'][from_name] == to_name: correct = False if correct: if not segments['rlimit'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) segments['rlimit'] = True write_prof_data[hat]['rlimit'].pop(from_name) data.append(line) else: #To-Do pass elif RE_PROFILE_BOOLEAN.search(line): matches = RE_PROFILE_BOOLEAN.search(line).groups() bool_var = matches[0] value = matches[1] if not write_prof_data[hat]['lvar'][bool_var] == value: correct = False if correct: if not segments['lvar'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) segments['lvar'] = True write_prof_data[hat]['lvar'].pop(bool_var) data.append(line) else: #To-Do pass elif RE_PROFILE_VARIABLE.search(line): matches = RE_PROFILE_VARIABLE.search(line).groups() list_var = strip_quotes(matches[0]) var_operation = matches[1] value = strip_quotes(matches[2]) var_set = hasher() if profile: store_list_var(var_set, list_var, value, var_operation) if not var_set[list_var] == write_prof_data['lvar'].get(list_var, False): correct = False else: store_list_var(var_set, list_var, value, var_operation) if not var_set[list_var] == write_filelist['lvar'].get(list_var, False): correct = False if correct: if not segments['lvar'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) segments['lvar'] = True if profile: write_prof_data[hat]['lvar'].pop(list_var) else: write_filelist['lvar'].pop(list_var) data.append(line) else: #To-Do pass elif RE_PROFILE_PATH_ENTRY.search(line): matches = RE_PROFILE_PATH_ENTRY.search(line).groups() audit = False if matches[0]: audit = True allow = 'allow' if matches[1] and matches[1].split() == 'deny': allow = 'deny' user = False if matches[2]: user = True path = matches[3].strip() mode = matches[4] nt_name = matches[6] if nt_name: nt_name = nt_name.strip() tmpmode = set() if user: tmpmode = str_to_mode('%s::' % mode) else: tmpmode = str_to_mode(mode) if not write_prof_data[hat][allow]['path'][path].get('mode', set()) & tmpmode: correct = False if nt_name and not write_prof_data[hat][allow]['path'][path].get('to', False) == nt_name: correct = False if audit and not write_prof_data[hat][allow]['path'][path].get('audit', set()) & tmpmode: correct = False if correct: if not segments['path'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) segments['path'] = True write_prof_data[hat][allow]['path'].pop(path) data.append(line) else: #To-Do pass elif re_match_include(line): include_name = re_match_include(line) if profile: if write_prof_data[hat]['include'].get(include_name, False): if not segments['include'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) segments['include'] = True write_prof_data[hat]['include'].pop(include_name) data.append(line) else: if write_filelist['include'].get(include_name, False): write_filelist['include'].pop(include_name) data.append(line) elif RE_PROFILE_NETWORK.search(line): matches = RE_PROFILE_NETWORK.search(line).groups() audit = False if matches[0]: audit = True allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' network = matches[2] if RE_NETWORK_FAMILY_TYPE.search(network): nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups() fam, typ = nmatch[:2] if write_prof_data[hat][allow]['netdomain']['rule'][fam][typ] and write_prof_data[hat][allow]['netdomain']['audit'][fam][typ] == audit: write_prof_data[hat][allow]['netdomain']['rule'][fam].pop(typ) write_prof_data[hat][allow]['netdomain']['audit'][fam].pop(typ) data.append(line) else: correct = False elif RE_NETWORK_FAMILY.search(network): fam = RE_NETWORK_FAMILY.search(network).groups()[0] if write_prof_data[hat][allow]['netdomain']['rule'][fam] and write_prof_data[hat][allow]['netdomain']['audit'][fam] == audit: write_prof_data[hat][allow]['netdomain']['rule'].pop(fam) write_prof_data[hat][allow]['netdomain']['audit'].pop(fam) data.append(line) else: correct = False else: if write_prof_data[hat][allow]['netdomain']['rule']['all'] and write_prof_data[hat][allow]['netdomain']['audit']['all'] == audit: write_prof_data[hat][allow]['netdomain']['rule'].pop('all') write_prof_data[hat][allow]['netdomain']['audit'].pop('all') data.append(line) else: correct = False if correct: if not segments['netdomain'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) segments['netdomain'] = True elif RE_PROFILE_CHANGE_HAT.search(line): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() hat = matches[0] hat = strip_quotes(hat) if not write_prof_data[hat]['declared']: correct = False if correct: data.append(line) else: #To-Do pass elif RE_PROFILE_HAT_DEF.search(line): matches = RE_PROFILE_HAT_DEF.search(line).groups() in_contained_hat = True hat = matches[0] hat = strip_quotes(hat) flags = matches[3] if not write_prof_data[hat]['flags'] == flags: correct = False if not write_prof_data[hat]['declared'] is False: correct = False if not write_filelist['profile'][profile][hat]: correct = False if correct: data.append(line) else: #To-Do pass else: if correct: data.append(line) else: #To-Do pass # data.append('prof done') # if write_filelist: # data += write_alias(write_filelist, 0) # data += write_list_vars(write_filelist, 0) # data += write_includes(write_filelist, 0) # data.append('from filelist over') # data += write_piece(write_prof_data, 0, name, name, include_flags) string += '\n'.join(data) return string + '\n' def write_profile_ui_feedback(profile): aaui.UI_Info(_('Writing updated profile for %s.') % profile) write_profile(profile) def write_profile(profile): prof_filename = None if aa[profile][profile].get('filename', False): prof_filename = aa[profile][profile]['filename'] else: prof_filename = get_profile_filename(profile) newprof = tempfile.NamedTemporaryFile('w', suffix='~', delete=False, dir=profile_dir) if os.path.exists(prof_filename): shutil.copymode(prof_filename, newprof.name) else: #permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write #os.chmod(newprof.name, permission_600) pass serialize_options = {} serialize_options['METADATA'] = True profile_string = serialize_profile(aa[profile], profile, serialize_options) newprof.write(profile_string) newprof.close() os.rename(newprof.name, prof_filename) changed.pop(profile) original_aa[profile] = deepcopy(aa[profile]) def matchliteral(aa_regexp, literal): p_regexp = '^' + convert_regexp(aa_regexp) + '$' match = False try: match = re.search(p_regexp, literal) except: return None return match def profile_known_exec(profile, typ, exec_target): if typ == 'exec': cm = None am = None m = [] cm, am, m = rematchfrag(profile, 'deny', exec_target) if cm & apparmor.aamode.AA_MAY_EXEC: return -1 cm, am, m = match_prof_incs_to_path(profile, 'deny', exec_target) if cm & apparmor.aamode.AA_MAY_EXEC: return -1 cm, am, m = rematchfrag(profile, 'allow', exec_target) if cm & apparmor.aamode.AA_MAY_EXEC: return 1 cm, am, m = match_prof_incs_to_path(profile, 'allow', exec_target) if cm & apparmor.aamode.AA_MAY_EXEC: return 1 return 0 def profile_known_capability(profile, capname): if profile['deny']['capability'][capname].get('set', False): return -1 if profile['allow']['capability'][capname].get('set', False): return 1 for incname in profile['include'].keys(): if include[incname][incname]['deny']['capability'][capname].get('set', False): return -1 if include[incname][incname]['allow']['capability'][capname].get('set', False): return 1 return 0 def profile_known_network(profile, family, sock_type): if netrules_access_check(profile['deny']['netdomain'], family, sock_type): return -1 if netrules_access_check(profile['allow']['netdomain'], family, sock_type): return 1 for incname in profile['include'].keys(): if netrules_access_check(include[incname][incname]['deny']['netdomain'], family, sock_type): return -1 if netrules_access_check(include[incname][incname]['allow']['netdomain'], family, sock_type): return 1 return 0 def netrules_access_check(netrules, family, sock_type): if not netrules: return 0 all_net = False all_net_family = False net_family_sock = False if netrules['rule'].get('all', False): all_net = True if netrules['rule'].get(family, False) is True: all_net_family = True if (netrules['rule'].get(family, False) and type(netrules['rule'][family]) == dict and netrules['rule'][family][sock_type]): net_family_sock = True if all_net or all_net_family or net_family_sock: return True else: return False def reload_base(bin_path): if not check_for_apparmor(): return None prof_filename = get_profile_filename(bin_path) subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" % (prof_filename, parser, profile_dir), shell=True) def reload(bin_path): bin_path = find_executable(bin_path) if not bin_path: return None return reload_base(bin_path) def get_include_data(filename): data = [] filename = profile_dir + '/' + filename if os.path.exists(filename): with open_file_read(filename) as f_in: data = f_in.readlines() else: raise AppArmorException(_('File Not Found: %s') % filename) return data def load_include(incname): load_includeslist = [incname] if include.get(incname, {}).get(incname, False): return 0 while load_includeslist: incfile = load_includeslist.pop(0) if os.path.isfile(profile_dir + '/' + incfile): data = get_include_data(incfile) incdata = parse_profile_data(data, incfile, True) #print(incdata) if not incdata: # If include is empty, simply push in a placeholder for it # because other profiles may mention them incdata = hasher() incdata[incname] = hasher() attach_profile_data(include, incdata) #If the include is a directory means include all subfiles elif os.path.isdir(profile_dir + '/' + incfile): load_includeslist += list(map(lambda x: incfile + '/' + x, os.listdir(profile_dir + '/' + incfile))) return 0 def rematchfrag(frag, allow, path): combinedmode = set() combinedaudit = set() matches = [] if not frag: return combinedmode, combinedaudit, matches for entry in frag[allow]['path'].keys(): match = matchliteral(entry, path) if match: #print(frag[allow]['path'][entry]['mode']) combinedmode |= frag[allow]['path'][entry].get('mode', set()) combinedaudit |= frag[allow]['path'][entry].get('audit', set()) matches.append(entry) return combinedmode, combinedaudit, matches def match_include_to_path(incname, allow, path): combinedmode = set() combinedaudit = set() matches = [] includelist = [incname] while includelist: incfile = str(includelist.pop(0)) # ret = load_include(incfile) load_include(incfile) if not include.get(incfile, {}): continue cm, am, m = rematchfrag(include[incfile].get(incfile, {}), allow, path) #print(incfile, cm, am, m) if cm: combinedmode |= cm combinedaudit |= am matches += m if include[incfile][incfile][allow]['path'][path]: combinedmode |= include[incfile][incfile][allow]['path'][path]['mode'] combinedaudit |= include[incfile][incfile][allow]['path'][path]['audit'] if include[incfile][incfile]['include'].keys(): includelist += include[incfile][incfile]['include'].keys() return combinedmode, combinedaudit, matches def match_prof_incs_to_path(frag, allow, path): combinedmode = set() combinedaudit = set() matches = [] includelist = list(frag['include'].keys()) while includelist: incname = includelist.pop(0) cm, am, m = match_include_to_path(incname, allow, path) if cm: combinedmode |= cm combinedaudit |= am matches += m return combinedmode, combinedaudit, matches def suggest_incs_for_path(incname, path, allow): combinedmode = set() combinedaudit = set() matches = [] includelist = [incname] while includelist: inc = includelist.pop(0) cm, am, m = rematchfrag(include[inc][inc], 'allow', path) if cm: combinedmode |= cm combinedaudit |= am matches += m if include[inc][inc]['allow']['path'].get(path, False): combinedmode |= include[inc][inc]['allow']['path'][path]['mode'] combinedaudit |= include[inc][inc]['allow']['path'][path]['audit'] if include[inc][inc]['include'].keys(): includelist += include[inc][inc]['include'].keys() return combinedmode, combinedaudit, matches def check_qualifiers(program): if cfg['qualifiers'].get(program, False): if cfg['qualifiers'][program] != 'p': fatal_error(_("%s is currently marked as a program that should not have its own\nprofile. Usually, programs are marked this way if creating a profile for \nthem is likely to break the rest of the system. If you know what you\'re\ndoing and are certain you want to create a profile for this program, edit\nthe corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.") % program) return False def get_subdirectories(current_dir): """Returns a list of all directories directly inside given directory""" if sys.version_info < (3, 0): return os.walk(current_dir).next()[1] else: return os.walk(current_dir).__next__()[1] def loadincludes(): incdirs = get_subdirectories(profile_dir) for idir in incdirs: if is_skippable_dir(idir): continue for dirpath, dirname, files in os.walk(profile_dir + '/' + idir): if is_skippable_dir(dirpath): continue for fi in files: if is_skippable_file(fi): continue else: fi = dirpath + '/' + fi fi = fi.replace(profile_dir + '/', '', 1) load_include(fi) def glob_common(path): globs = [] if re.search('[\d\.]+\.so$', path) or re.search('\.so\.[\d\.]+$', path): libpath = path libpath = re.sub('[\d\.]+\.so$', '*.so', libpath) libpath = re.sub('\.so\.[\d\.]+$', '.so.*', libpath) if libpath != path: globs.append(libpath) for glob in cfg['globs']: if re.search(glob, path): globbedpath = path globbedpath = re.sub(glob, cfg['globs'][glob], path) if globbedpath != path: globs.append(globbedpath) return sorted(set(globs)) def combine_name(name1, name2): if name1 == name2: return name1 else: return '%s^%s' % (name1, name2) def split_name(name): names = name.split('^') if len(names) == 1: return name, name else: return names[0], names[1] def commonprefix(new, old): match = re.search(r'^([^\0]*)[^\0]*(\0\1[^\0]*)*$', '\0'.join([new, old])) if match: return match.groups()[0] return match def commonsuffix(new, old): match = commonprefix(new[-1::-1], old[-1::-1]) if match: return match[-1::-1] def matchregexp(new, old): if re.search('\{.*(\,.*)*\}', old): return None # if re.search('\[.+\]', old) or re.search('\*', old) or re.search('\?', old): # # new_reg = convert_regexp(new) # old_reg = convert_regexp(old) # # pref = commonprefix(new, old) # if pref: # if convert_regexp('(*,**)$') in pref: # pref = pref.replace(convert_regexp('(*,**)$'), '') # new = new.replace(pref, '', 1) # old = old.replace(pref, '', 1) # # suff = commonsuffix(new, old) # if suffix: # pass new_reg = convert_regexp(new) if re.search(new_reg, old): return True return None ######Initialisations###### conf = apparmor.config.Config('ini', CONFDIR) cfg = conf.read_config('logprof.conf') #print(cfg['settings']) #if 'default_owner_prompt' in cfg['settings']: if cfg['settings'].get('default_owner_prompt', False): cfg['settings']['default_owner_prompt'] = '' profile_dir = conf.find_first_dir(cfg['settings']['profiledir']) or '/etc/apparmor.d' if not os.path.isdir(profile_dir): raise AppArmorException('Can\'t find AppArmor profiles') extra_profile_dir = conf.find_first_dir(cfg['settings']['inactive_profiledir']) or '/etc/apparmor/profiles/extras/' parser = conf.find_first_file(cfg['settings']['parser']) or '/sbin/apparmor_parser' if not os.path.isfile(parser) or not os.access(parser, os.EX_OK): raise AppArmorException('Can\'t find apparmor_parser') filename = conf.find_first_file(cfg['settings']['logfiles']) or '/var/log/syslog' if not os.path.isfile(filename): raise AppArmorException('Can\'t find system log.') ldd = conf.find_first_file(cfg['settings']['ldd']) or '/usr/bin/ldd' if not os.path.isfile(ldd) or not os.access(ldd, os.EX_OK): raise AppArmorException('Can\'t find ldd') logger = conf.find_first_file(cfg['settings']['logger']) or '/bin/logger' if not os.path.isfile(logger) or not os.access(logger, os.EX_OK): raise AppArmorException('Can\'t find logger') apparmor-2.8.95~2430/utils/logprof.conf.pod0000644000175000017500000001000612216646723020303 0ustar sarnoldsarnold# This publication is intellectual property of Novell Inc. and Canonical # Ltd. Its contents can be duplicated, either in part or in whole, provided # that a copyright label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators # shall be held liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. SUSE LINUX GmbH # and Canonical Ltd. essentially adhere to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME logprof.conf - configuration file for expert options that modify the behavior of the AppArmor aa-logprof(1) program. =head1 DESCRIPTION The aa-logprof(1) program can be configured to have certain default behavior by the contents of logprof.conf. The B<[qualifiers]> section lists specific programs that should have a subset of the full ix/px/ux list when asking what mode to execute it using. Since creating a separate profile for /bin/bash is dangerous, we can specify that for /bin/bash, only (I)nherit, (U)nconstrained, and (D)eny should be allowed options and only those will show up in the prompt when we're asking about adding that to a profile. Likewise, if someone currently exec's /bin/mount in ix or px mode, things won't work, so we can provide only (U)nconstrained and (D)eny as options. And certain apps like grep, awk, sed, cp, and mkdir should always inherit the parent profile rather than having their own profile or running unconfined, so for them we can specify that only (I)nherit and (D)eny are the allowed options. Any programs that are not listed in the qualifiers section get the full (I)nherit / (P)rofile / (U)nconstrained / (D)eny option set. If the user is doing something tricky and wants different behavior, they can tweak or remove the corresponding line in the conf file. The B<[defaulthat]> section lists changehat-aware programs and what hat aa-logprof(1) will collapse the entries to for that program if the user specifies that the access should be allowed, but should not have it's own hat. The B<[globs]> section allows modification of the logprof rule engine with respect to globbing suggestions that the user will be prompted with. The format of each line is-- " = ". When aa-logprof(1) asks about a specific path, if the perl glob matches the path, it replaces the part of the path that matched with the corresponding apparmor glob and adds it to the list of globbing suggestions. Lines starting with # are comments and are ignored. =head1 EXAMPLE [qualifiers] # things will very likely be painfully broken if bash has it's own profile /bin/bash = iu # mount doesn't work if it's confined /bin/mount = u # these helper utilities should inherit the parent profile and # shouldn't have their own profiles /bin/awk = i /bin/grep = i /bin/sed = i [defaulthat] /usr/sbin/sshd = EXEC /usr/sbin/httpd2 = DEFAULT_URI /usr/sbin/httpd2-prefork = DEFAULT_URI [globs] # /foo/bar/lib/libbaz.so -> /foo/bar/lib/lib* /lib/lib[^\/]+so[^\/]*$ = /lib/lib*so* # strip kernel version numbers from kernel module accesses ^/lib/modules/[^\/]+\/ = /lib/modules/*/ # strip pid numbers from /proc accesses ^/proc/\d+/ = /proc/*/ =head1 BUGS If you find any bugs, please report them at L. =head1 SEE ALSO apparmor(7), apparmor.d(5), aa-enforce(1), aa-complain(1), aa-disable(1), aa_change_hat(2), aa-logprof(1), aa-genprof(1), and L. =cut apparmor-2.8.95~2430/utils/Makefile0000644000175000017500000000650612303753534016651 0ustar sarnoldsarnold# ---------------------------------------------------------------------- # Copyright (c) 1999, 2004-2009 NOVELL (All rights reserved) # Copyright (c) 2010-2011 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # ---------------------------------------------------------------------- NAME = apparmor-utils all: COMMONDIR=../common/ include common/Make.rules COMMONDIR_EXISTS=$(strip $(shell [ -d ${COMMONDIR} ] && echo true)) ifeq ($(COMMONDIR_EXISTS), true) common/Make.rules: $(COMMONDIR)/Make.rules ln -sf $(COMMONDIR) . endif PERLTOOLS = aa-exec aa-notify PYTOOLS = aa-easyprof aa-genprof aa-logprof aa-cleanprof aa-mergeprof \ aa-autodep aa-audit aa-complain aa-enforce aa-disable \ aa-status aa-unconfined TOOLS = ${PERLTOOLS} ${PYTOOLS} aa-decode PYSETUP = python-tools-setup.py MANPAGES = ${TOOLS:=.8} logprof.conf.5 all: ${MANPAGES} ${HTMLMANPAGES} $(MAKE) -C po all $(MAKE) -C vim all # need some better way of determining this DESTDIR=/ BINDIR=${DESTDIR}/usr/sbin CONFDIR=${DESTDIR}/etc/apparmor PYPREFIX=/usr po/${NAME}.pot: ${TOOLS} $(MAKE) -C po ${NAME}.pot NAME=${NAME} SOURCES="${TOOLS} ${MODULES}" .PHONY: install install: ${MANPAGES} ${HTMLMANPAGES} install -d ${CONFDIR} install -m 644 logprof.conf severity.db notify.conf ${CONFDIR} install -d ${BINDIR} ln -sf aa-status ${BINDIR}/apparmor_status install -m 755 ${TOOLS} ${BINDIR} $(MAKE) -C po install DESTDIR=${DESTDIR} NAME=${NAME} $(MAKE) install_manpages DESTDIR=${DESTDIR} $(MAKE) -C vim install DESTDIR=${DESTDIR} ln -sf aa-status.8 ${DESTDIR}/${MANDIR}/man8/apparmor_status.8 ${PYTHON} ${PYSETUP} install --prefix=${PYPREFIX} --root=${DESTDIR} --version=${VERSION} .PHONY: clean ifndef VERBOSE .SILENT: clean endif clean: _clean rm -f core core.* *.o *.s *.a *~ rm -f Make.rules $(MAKE) -C po clean $(MAKE) -C vim clean $(MAKE) -C test clean rm -rf staging/ build/ rm -f apparmor/*.pyc rm -rf apparmor/__pycache__/ # ${CAPABILITIES} is defined in common/Make.rules .PHONY: check_severity_db .SILENT: check_severity_db check_severity_db: /usr/include/linux/capability.h severity.db # The sed statement is based on the one in the parser's makefile RC=0 ; for cap in ${CAPABILITIES} ; do \ if ! grep -q -w $${cap} severity.db ; then \ echo "Warning! capability $${cap} not found in severity.db" ; \ RC=1 ; \ fi ;\ done ; \ test "$$RC" -eq 0 .PHONY: check .SILENT: check check: check_severity_db for i in ${MODULES} ${PERLTOOLS} ; do \ perl -c $$i || exit 1; \ done tmpfile=$$(mktemp --tmpdir aa-pyflakes-XXXXXX); \ for i in ${PYTOOLS} apparmor test/*.py; do \ echo Checking $$i; \ pyflakes $$i 2>&1 | grep -v "undefined name '_'" > $$tmpfile; \ test -s $$tmpfile && cat $$tmpfile && rm -f $$tmpfile && exit 1; \ done || true; \ rm -f $$tmpfile $(MAKE) -C test check $(MAKE) -C vim check apparmor-2.8.95~2430/utils/aa-unconfined.pod0000644000175000017500000000443612277004630020420 0ustar sarnoldsarnold# This publication is intellectual property of Novell Inc. and Canonical # Ltd. Its contents can be duplicated, either in part or in whole, provided # that a copyright label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators # shall be held liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. SUSE LINUX GmbH # and Canonical Ltd. essentially adhere to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-unconfined - output a list of processes with tcp or udp ports that do not have AppArmor profiles loaded =head1 SYNOPSIS B]> =head1 OPTIONS B<--paranoid> Displays all processes from F filesystem with tcp or udp ports that do not have AppArmor profiles loaded. =head1 DESCRIPTION B will use netstat(8) to determine which processes have open network sockets and do not have AppArmor profiles loaded into the kernel. =head1 BUGS B must be run as root to retrieve the process executable link from the F filesystem. This program is susceptible to race conditions of several flavours: an unlinked executable will be mishandled; an executable started before an AppArmor profile is loaded will not appear in the output, despite running without confinement; a process that dies between the netstat(8) and further checks will be mishandled. This program only lists processes using TCP and UDP. In short, this program is unsuitable for forensics use and is provided only as an aid to profiling all network-accessible processes in the lab. If you find any bugs, please report them at L. =head1 SEE ALSO netstat(8), apparmor(7), apparmor.d(5), aa_change_hat(2), and L. =cut apparmor-2.8.95~2430/utils/aa-logprof.pod0000644000175000017500000001510112277004630017727 0ustar sarnoldsarnold# This publication is intellectual property of Novell Inc. and Canonical # Ltd. Its contents can be duplicated, either in part or in whole, provided # that a copyright label is visibly located on each copy. # # All information found in this book has been compiled with utmost # attention to detail. However, this does not guarantee complete accuracy. # Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators # shall be held liable for possible errors or the consequences thereof. # # Many of the software and hardware descriptions cited in this book # are registered trademarks. All trade names are subject to copyright # restrictions and may be registered trade marks. SUSE LINUX GmbH # and Canonical Ltd. essentially adhere to the manufacturer's spelling. # # Names of products and trademarks appearing in this book (with or without # specific notation) are likewise subject to trademark and trade protection # laws and may thus fall under copyright restrictions. # =pod =head1 NAME aa-logprof - utility for updating AppArmor security profiles =head1 SYNOPSIS B] [I<-f /path/to/logfile>] [I<-m Emark in logfileE>]> =head1 OPTIONS B<-d --dir /path/to/profiles> Specifies where to look for the AppArmor security profile set. Defaults to /etc/apparmor.d. B<-f --file /path/to/logfile> Specifies the location of logfile that contains AppArmor security events. Default locations are read from F. Typical defaults are: /var/log/audit/audit.log /var/log/syslog /var/log/messages B< -m --logmark "mark"> aa-logprof will ignore all events in the system log before the specified mark is seen. If the mark contains spaces, it must be surrounded with quotes to work correctly. =head1 DESCRIPTION B is an interactive tool used to review AppArmor generated messages and update AppArmor security profiles. Running aa-logprof will scan the log file and if there are new AppArmor events that are not covered by the existing profile set, the user will be prompted with suggested modifications to augment the profile. When aa-logprof exits profile changes are saved to disk. If AppArmor is running, the updated profiles are reloaded and if any processes that generated AppArmor events are still running in the null-complain-profile, those processes are set to run under their proper profiles. =head2 Responding to AppArmor Events B will generate a list of suggested profile changes that the user can choose from, or they can create their own, to modifiy the permission set of the profile so that the generated access violation will not re-occur. The user is then presented with info about the access including profile, path, old mode if there was a previous entry in the profile for this path, new mode, the suggestion list, and given these options: (A)llow, (D)eny, (I)gnore, (N)ew, (G)lob last piece, (Q)uit If the AppArmor profile was in complain mode when the event was generated, the default for this option is (A)llow, otherwise, it's (D)eny. The (D)eny option adds a "deny" rule to the AppArmor profile, which silences logging. The (I)gnore option allows user to ignore the event, without making any changes to the AppArmor profile. The suggestion list is presented as a numbered list with includes at the top, the literal path in the middle, and the suggested globs at the bottom. If any globs are being suggested, the shortest glob is the selected option, otherwise, the literal path is selected. Picking includes from the list must be done manually. Hitting a numbered key will change the selected option to the corresponding numbered entry in the list. If the user selects (N)ew, they'll be prompted to enter their own globbed entry to match the path. If the user-entered glob does not match the path for this event, they'll be informed and have the option to fix it. If the user selects (G)lob last piece then, taking the currently selected option, aa-logprof will remove the last path element and replace it with /*. If the last path element already was /*, aa-logprof will go up a directory level and replace it with /**. This new globbed entry is then added to the suggestion list and marked as the selected option. So /usr/share/themes/foo/bar/baz.gif can be turned into /usr/share/themes/** by hitting "g" three times. If the user selects (A)llow, aa-logprof will take the current selection and add it to the profile, deleting other entries in the profile that are matched by the new entry. Adding r access to /usr/share/themes/** would delete an entry for r access to /usr/share/themes/foo/*.gif if it exists in the profile. If (Q)uit is selected at this point, aa-logprof will ignore all new pending accesses. After all of the accesses have been handled, logrof will write all updated profiles to the disk and reload them if AppArmor is running. =head2 New Process (Execution) Events If there are unhandled x accesses generated by the execve(2) of a new process, aa-logprof will display the parent profile and the target program that's being executed and prompt the user to select and execute modifier. These modifiers will allow a choice for the target to: have it's own profile (px), inherit the parent's profile (ix), run unconstrained (ux), or deny access for the target. See apparmor.d(5) for details. If there is a corresponding entry for the target in the qualifiers section of /etc/apparmor/logprof.conf, the presented list will contain only the allowed modes. The default option for this question is selected using this logic-- # if px mode is allowed and profile exists for the target # px is default. # else if ix mode is allowed # ix is default # else # deny is default aa-logprof will never suggest "ux" as the default. =head2 ChangeHat Events If unknown aa_change_hat(2) events are found, the user is prompted to add a new hat, if the events should go into the default hat for this profile based on the corresponding entry in the defaulthat section of logprof.conf, or if the following events that run under that hat should be denied altogether. =head2 Capability Events If there are capability accesses, the user is shown each capability access and asked if the capability should be allowed, denied, or if the user wants to quit. See capability(7) for details. =head1 BUGS If you find any bugs, please report them at L. =head1 SEE ALSO klogd(8), auditd(8), apparmor(7), apparmor.d(5), aa_change_hat(2), logprof.conf(5), aa-genprof(1), aa-enforce(1), aa-complain(1), aa-disable(1), and L. =cut apparmor-2.8.95~2430/utils/aa-exec0000755000175000017500000000514012163071055016426 0ustar sarnoldsarnold#!/usr/bin/perl # ------------------------------------------------------------------ # # Copyright (C) 2011-2013 Canonical Ltd. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # ------------------------------------------------------------------ use strict; use warnings; use Errno; require LibAppArmor; require POSIX; my $opt_d = ''; my $opt_h = ''; my $opt_p = ''; my $opt_n = ''; my $opt_i = ''; my $opt_v = ''; my $opt_f = ''; sub _warn { my $msg = $_[0]; print STDERR "aa-exec: WARN: $msg\n"; } sub _error { my $msg = $_[0]; print STDERR "aa-exec: ERROR: $msg\n"; exit 1 } sub _debug { $opt_d or return; my $msg = $_[0]; print STDERR "aa-exec: DEBUG: $msg\n"; } sub _verbose { $opt_v or return; my $msg = $_[0]; print STDERR "$msg\n"; } sub usage() { my $s = <<'EOF'; USAGE: aa-exec [OPTIONS] Confine with the specified PROFILE. OPTIONS: -p PROFILE, --profile=PROFILE PROFILE to confine with -n NAMESPACE, --namespace=NAMESPACE NAMESPACE to confine in -f FILE, --file FILE profile file to load -i, --immediate change profile immediately instead of at exec -v, --verbose show messages with stats -h, --help display this help EOF print $s; } use Getopt::Long; GetOptions( 'debug|d' => \$opt_d, 'help|h' => \$opt_h, 'profile|p=s' => \$opt_p, 'namespace|n=s' => \$opt_n, 'file|f=s' => \$opt_f, 'immediate|i' => \$opt_i, 'verbose|v' => \$opt_v, ); if ($opt_h) { usage(); exit(0); } if ($opt_n || $opt_p) { my $test; my $prof; if ($opt_n) { $prof = ":$opt_n:"; } $prof .= $opt_p; if ($opt_f) { system("apparmor_parser", "-r", "$opt_f") == 0 or _error("\'aborting could not load $opt_f\'"); } if ($opt_i) { _verbose("aa_change_profile(\"$prof\")"); $test = LibAppArmor::aa_change_profile($prof); _debug("$test = aa_change_profile(\"$prof\"); $!"); } else { _verbose("aa_change_onexec(\"$prof\")"); $test = LibAppArmor::aa_change_onexec($prof); _debug("$test = aa_change_onexec(\"$prof\"); $!"); } if ($test != 0) { if ($!{ENOENT} || $!{EACCESS}) { my $pre = ($opt_p) ? "profile" : "namespace"; _error("$pre \'$prof\' does not exist\n"); } elsif ($!{EINVAL}) { _error("AppArmor interface not available\n"); } else { _error("$!\n"); } } } _verbose("exec @ARGV"); exec @ARGV; apparmor-2.8.95~2430/utils/aa-audit0000755000175000017500000000270712306150740016614 0ustar sarnoldsarnold#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import traceback import apparmor.tools # setup module translations from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_('Switch the given programs to audit mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-r', '--remove', action='store_true', help=_('remove audit mode')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) parser.add_argument('--trace', action='store_true', help=_('Show full trace')) args = parser.parse_args() try: tool = apparmor.tools.aa_tools('audit', args) tool.cmd_audit() except Exception as e: if not args.trace: print(e.value + "\n") else: traceback.print_exc() apparmor-2.8.95~2430/tests/0000755000175000017500000000000012311706720015176 5ustar sarnoldsarnoldapparmor-2.8.95~2430/tests/regression/0000755000175000017500000000000012311706720017356 5ustar sarnoldsarnoldapparmor-2.8.95~2430/tests/regression/apparmor/0000755000175000017500000000000012311706722021201 5ustar sarnoldsarnoldapparmor-2.8.95~2430/tests/regression/apparmor/sd_flags.sh0000755000175000017500000000755511703541307023336 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME sd_flags #=DESCRIPTION Verify that the profile flags are enforced (or not) properly. pwd=$(dirname $0) pwd=$(cd $pwd ; /bin/pwd) bin=$pwd . $bin/prologue.inc settest open file=$tmpdir/file okperm=rw badperm1=r badperm2=w touch $file chmod 600 $file # PASS TEST (noflags) genprofile $file:$okperm runchecktest "SD_FLAGS OPEN RW (noflags)" pass $file # audit alone # PASS TEST (audit) genprofile $file:$okperm flag:audit runchecktest "SD_FLAGS OPEN RW (audit)" pass $file # FAILURE TEST (audit) genprofile $file:$badperm1 flag:audit runchecktest "SD_FLAGS OPEN R (audit)" fail $file # complain alone # PASS TEST (complain) genprofile $file:$okperm flag:complain runchecktest "SD_FLAGS OPEN RW (complain)" pass $file # PASS TEST (complain) 2 genprofile flag:complain runchecktest "SD_FLAGS OPEN noaccess (complain)" pass $file # need a way to verify that audit is actually auditing... # PASS TEST (audit,complain,debug) genprofile flag:audit flag:complain runchecktest "SD_FLAGS OPEN noaccess (audit,complain)" pass $file # check for flags on hats... settest changehat_wrapper # audit alone # PASS TEST (noflags) genprofile hat:open addimage:${bin}/open $file:$okperm runchecktest "SD_FLAGS HAT/OPEN RW (noflags)" pass open $file # PASS TEST 1 (audit) genprofile flag:audit hat:open addimage:${bin}/open $file:$okperm runchecktest "SD_FLAGS HAT/OPEN RW (audit)" pass open $file # PASS TEST 2 (audit) genprofile hat:open addimage:${bin}/open $file:$okperm flag:audit runchecktest "SD_FLAGS HAT/OPEN RW (audit)" pass open $file # PASS TEST 3 (audit) genprofile flag:audit hat:open addimage:${bin}/open $file:$okperm flag:audit runchecktest "SD_FLAGS HAT/OPEN RW (audit)" pass open $file # FAILURE TEST 1 (audit) genprofile flag:audit hat:open addimage:${bin}/open $file:$badperm1 runchecktest "SD_FLAGS HAT/OPEN R (audit)" fail open $file # FAILURE TEST 2 (audit) genprofile hat:open addimage:${bin}/open $file:$badperm1 flag:audit runchecktest "SD_FLAGS HAT/OPEN R (audit)" fail open $file # FAILURE TEST 3 (audit) genprofile flag:audit hat:open addimage:${bin}/open $file:$badperm1 flag:audit runchecktest "SD_FLAGS HAT/OPEN R (audit)" fail open $file # complain alone # PASS TEST 1 (complain) genprofile flag:complain hat:open addimage:${bin}/open $file:$okperm runchecktest "SD_FLAGS HAT/OPEN RW (complain)" pass open $file # PASS TEST 2 (complain) genprofile hat:open addimage:${bin}/open $file:$okperm flag:complain runchecktest "SD_FLAGS HAT/OPEN RW (complain)" pass open $file # PASS TEST 3 (complain) genprofile flag:complain hat:open addimage:${bin}/open $file:$okperm flag:complain runchecktest "SD_FLAGS HAT/OPEN RW (complain)" pass open $file # FAILURE TEST 1 (complain) genprofile flag:complain hat:open addimage:${bin}/open $file:$badperm1 runchecktest "SD_FLAGS HAT/OPEN R (complain)" fail open $file # PASS TEST 4 (complain) genprofile hat:open addimage:${bin}/open $file:$badperm1 flag:complain runchecktest "SD_FLAGS HAT/OPEN R (complain)" pass open $file # PASS TEST 5 (complain) genprofile flag:complain hat:open addimage:${bin}/open $file:$badperm1 flag:complain runchecktest "SD_FLAGS HAT/OPEN R (complain)" pass open $file # PASS TEST 6 (complain) no hat defined genprofile flag:complain runchecktest "SD_FLAGS HAT/OPEN R (complain)" pass open $file # audit + complain # PASS TEST 3 (audit+complain) genprofile flag:audit hat:open addimage:${bin}/open $file:$badperm1 flag:complain runchecktest "SD_FLAGS HAT/OPEN RW (audit+complain)" pass open $file # FAILURE TEST 3 (complain+audit) genprofile flag:complain hat:open addimage:${bin}/open $file:$badperm1 flag:audit runchecktest "SD_FLAGS HAT/OPEN R (complain+audit)" fail open $file apparmor-2.8.95~2430/tests/regression/apparmor/onexec.c0000644000175000017500000000235011734065364022636 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include "changehat.h" int main(int argc, char *argv[]) { int rc = 0; extern char **environ; if (argc < 3){ fprintf(stderr, "usage: %s profile executable args\n", argv[0]); return 1; } /* change profile if profile name != nochange */ if (strcmp(argv[1], "nochange") != 0){ rc = aa_change_onexec(argv[1]); if (rc == -1){ fprintf(stderr, "FAIL: change_onexec %s failed - %s\n", argv[1], strerror(errno)); exit(errno); } } /* stop after onexec and wait to for continue before exec so * caller can introspect task */ (void)kill(getpid(), SIGSTOP); (void)execve(argv[2], &argv[2], environ); /* exec failed, kill outselves to flag parent */ rc = errno; fprintf(stderr, "FAIL: exec to '%s' failed\n", argv[2]); return rc; } apparmor-2.8.95~2430/tests/regression/apparmor/link_subset.c0000644000175000017500000001470111503736226023676 0ustar sarnoldsarnold/* * Copyright (C) 2002-2007 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. */ #include #include #include #include #include #include "changehat.h" /* actual mapping does not match kernel, just needed for bit manging */ #define AA_MAY_EXEC 0x001 #define AA_MAY_WRITE 0x002 #define AA_MAY_READ 0x004 #define AA_MAY_APPEND 0x008 #define AA_MAY_LINK 0x0010 #define AA_MAY_LOCK 0x0020 #define AA_MAY_MOUNT 0x0040 #define AA_EXEC_MMAP 0x0080 #define AA_EXEC_UNSAFE 0x0100 #define AA_EXEC_INHERIT 0x0200 #define AA_EXEC_MOD_0 0x0400 #define AA_EXEC_MOD_1 0x0800 #define AA_EXEC_MOD_2 0x1000 #define AA_EXEC_MOD_3 0x2000 #define AA_EXEC_MODIFIERS (AA_EXEC_MOD_0 | AA_EXEC_MOD_1 | \ AA_EXEC_MOD_2 | AA_EXEC_MOD_3) #define AA_EXEC_TYPE (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT | \ AA_EXEC_MODIFIERS) #define AA_EXEC_UNCONFINED AA_EXEC_MOD_0 #define AA_EXEC_PROFILE AA_EXEC_MOD_1 #define AA_EXEC_LOCAL (AA_EXEC_MOD_0 | AA_EXEC_MOD_1) #define MAX_PERM (AA_EXEC_MOD_2) #define MAX_PERM_LEN 10 /* Set up permission subset test as a seperate binary to reduce the time * as the shell based versions takes for ever */ /* test if link_perm is a subset of target_perm */ int valid_link_perm_subset(int tperm, int lperm) { /* link must always have link bit set */ if (!(lperm & AA_MAY_LINK)) return 0; lperm = lperm & ~AA_MAY_LINK; /* an empty permission set is always a subset of target */ if (!lperm) return 1; /* ix implies mix */ if (lperm & AA_EXEC_INHERIT) lperm |= AA_EXEC_MMAP; if (tperm & AA_EXEC_INHERIT) tperm |= AA_EXEC_MMAP; /* w implies a */ if (lperm & AA_MAY_WRITE) lperm |= AA_MAY_APPEND; if (tperm & AA_MAY_WRITE) tperm |= AA_MAY_APPEND; /* currently no such thing as a safe ix - probably should be * depending on how the rule is written */ // if ((tperm & AA_EXEC_MODIFIERS) == AA_EXEC_INHERIT && !(tperm & AA_EXEC_UNSAFE)) // tperm |= AA_EXEC_UNSAFE; /* treat safe exec as subset of unsafe exec */ if (!(lperm & AA_EXEC_UNSAFE)) lperm |= AA_EXEC_UNSAFE & tperm; /* check that exec mode, if present, matches */ if ((lperm & AA_MAY_EXEC) && ((lperm & AA_EXEC_TYPE) != (tperm & AA_EXEC_TYPE))) return 0; return !(lperm & ~tperm); } void permstring(char *buffer, int mask) { char *b = buffer; if (mask & AA_EXEC_MMAP) *b++ = 'm'; if (mask & AA_MAY_READ) *b++ = 'r'; if (mask & AA_MAY_WRITE) *b++ = 'w'; else if (mask & AA_MAY_APPEND) *b++ = 'a'; if (mask & AA_MAY_EXEC) { if (mask & AA_EXEC_UNSAFE) { switch(mask & AA_EXEC_MODIFIERS) { case AA_EXEC_UNCONFINED: *b++ = 'u'; break; case AA_EXEC_PROFILE: *b++ = 'p'; break; case AA_EXEC_LOCAL: *b++ = 'c'; break; default: *b++ = 'y'; } } else { switch(mask & AA_EXEC_MODIFIERS) { case AA_EXEC_UNCONFINED: *b++ = 'U'; break; case AA_EXEC_PROFILE: *b++ = 'P'; break; case AA_EXEC_LOCAL: *b++ = 'C'; break; default: *b++ = 'Y'; } } if (mask & AA_EXEC_INHERIT) *b++ = 'i'; *b++ = 'x'; } if (mask & AA_MAY_LINK) *b++ = 'l'; if (mask & AA_MAY_LOCK) *b++ = 'k'; *b++ = '\0'; } /* generate the filename based off of perm set. */ void build_filename(const char *name, int perm, char *buffer) { char perms[10]; permstring(perms, perm); sprintf(buffer, "%s%s", name, perms); } int is_valid_perm_set(int perm) { if (AA_EXEC_TYPE & perm) { /* exec mods need the perm bit set */ if (!(perm & AA_MAY_EXEC)) return 0; /* unconfined can't inherit */ if (((perm & AA_EXEC_MODIFIERS) == AA_EXEC_UNCONFINED) && (perm & AA_EXEC_INHERIT)) return 0; /* no such thing as an unsafe ix */ if ((perm & AA_EXEC_MODIFIERS) == 0 && (perm & AA_EXEC_INHERIT) && (perm & AA_EXEC_UNSAFE)) return 0; /* check exec_modifiers in range */ if (!((perm & AA_EXEC_MODIFIERS) > 0 && (perm & AA_EXEC_MODIFIERS) < AA_EXEC_MOD_2)) return 0; } /* only 1 of append or write should be set */ if ((perm & AA_MAY_WRITE) && (perm & AA_MAY_APPEND)) return 0; /* not using mount yet, how should mount perms affect link? */ if (perm & AA_MAY_MOUNT) return 0; return 1; } int main(int argc, char *argv[]) { int fail = 0, pass = 0; int tperm, lperm; char *lname, *tname; int res; if (argc != 3){ fprintf(stderr, "usage: %s target_file link_file\n", argv[0]); return 1; } if (strcmp(argv[1], "--filenames") == 0) { /* just output the filename and permissions used */ char perms[10]; char *b, *buffer = malloc(MAX_PERM * (strlen(argv[2] + 2*MAX_PERM_LEN +2))); if (!buffer) goto fail; b = buffer; lname = malloc(strlen(argv[2]) + 2*MAX_PERM_LEN + 2); if (!lname) goto fail; for (lperm = 1; lperm < MAX_PERM; lperm++) { if (!is_valid_perm_set(lperm)) continue; if (lperm) *b++ = ' '; permstring(perms, lperm); sprintf(lname, "%s%s:%s", argv[2], perms, perms); strcpy(b, lname); b += strlen(lname); } printf("%s", buffer); return 0; } tname = malloc(strlen(argv[1]) + 2*MAX_PERM_LEN + 2); lname = malloc(strlen(argv[2]) + 2*MAX_PERM_LEN + 2); /* no perms on link or target - no target file */ for (tperm = 0; tperm < MAX_PERM; tperm++) { if (!is_valid_perm_set(tperm)) continue; build_filename(argv[1], tperm, tname); for (lperm = 0; lperm < MAX_PERM; lperm++) { if (!is_valid_perm_set(lperm)) continue; build_filename(argv[2], lperm, lname); errno = 0; res = link(tname, lname) == 0; if (valid_link_perm_subset(tperm, lperm) != res) { printf("FAIL(%s) - link %s to %s (%s expected to %s)\n", strerror(errno), lname, tname, res ? "passed" : "failed", res ? "fail" : "pass"); fail++; // if (fail > 5) // return 1; } else { pass++; } if (res) { if (change_hat("remove_link", SD_ID_MAGIC+1) == -1) printf("FAIL(%s) - failed change_hat remove_link\n", strerror(errno)); if (unlink(lname) != 0) { printf("FAIL(%s) - failed to remove link file %s\n", strerror(errno), lname); } if (change_hat(NULL, SD_ID_MAGIC+1) == -1) printf("FAIL(%s) - failed change_hat NULL\n", strerror(errno)); } } } if (fail) printf("FAIL - %d of %d link subset tests failed\n", fail, fail + pass); else printf("PASS\n"); return 0; fail: printf("FAIL - %s\n", strerror(errno)); return errno; } apparmor-2.8.95~2430/tests/regression/apparmor/coredump.c0000644000175000017500000000067011423345764023176 0ustar sarnoldsarnold#include int *ptr; /* * Copyright (C) 2002-2005 Novell/SUSE * Copyright (C) 2010 Canonical, Ltd * * 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. */ int main(int argc, char *argv[]) { printf("This will cause a sigsegv\n"); ptr=0; *ptr=0xdeadbeef; return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/changehat_misc.sh0000755000175000017500000000764311703541307024507 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME changehat_misc #=DESCRIPTION # Variety of tests verifying entry to subprofiles and return back to parent. # AppArmor has rigid requirements around the correct use of the magic# token # passed to changehat. #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc file=$tmpdir/file subfile=$tmpdir/file2 okperm=rw subtest=sub subtest2=sub2 touch $file $subfile # NO CHANGEHAT TEST genprofile $file:$okperm runchecktest "NO CHANGEHAT (access parent file)" pass nochange $file runchecktest "NO CHANGEHAT (access sub file)" fail nochange $subfile # CHANGEHAT TEST - test if can enter and exit a subprofile genprofile $file:$okperm hat:$subtest $subfile:$okperm runchecktest "CHANGEHAT (access parent file)" pass $subtest $file runchecktest "CHANGEHAT (access sub file)" fail $subtest $subfile # CHANGEHAT - test enter subprofile -> fork -> exit subprofile settest changehat_misc2 genprofile $file:$okperm hat:$subtest $subfile:$okperm runchecktest "FORK BETWEEN CHANGEHATS (access parent file)" pass $subtest $file runchecktest "FORK BETWEEN CHANGEHATS (access sub file)" fail $subtest $subfile # CHANGEHAT FROM ONE SUBPROFILE TO ANOTHER settest changehat_twice genprofile hat:$subtest $subfile:$okperm hat:$subtest2 $file:$okperm runchecktest "CHANGEHAT (subprofile->subprofile)" pass $subtest $subtest2 goodmagic $file echo echo "*** A 'Killed' message from bash is expected for the following test" runchecktest "CHANGEHAT (subprofile->subprofile w/ bad magic)" signal9 $subtest $subtest2 badmagic $file # 1. ATTEMPT TO CHANGEHAT TO AN INVALID PROFILE, SHOULD PUT US INTO A NULL # PROFILE # 2. ATTEMPT TO CHANGEHAT OUT WITH BAD TOKEN settest changehat_fail genprofile hat:$subtest runchecktest "CHANGEHAT (bad subprofile)" fail ${subtest2} echo echo "*** A 'Killed' message from bash is expected for the following test" runchecktest "CHANGEHAT (bad token)" signal9 ${subtest} settest changehat_wrapper genprofile hat:open addimage:${bin}/open ${file}:${okperm} runchecktest "CHANGEHAT (noexit subprofile (token=0))" pass --token=0 open ${file} runchecktest "CHANGEHAT (exit noexit subprofile (token=0))" fail --token=0 --exit_hat open ${file} if [ -f /proc/self/attr/current ] ; then # do some tests of writing directly to /proc/self/attr/current, skipping # libimmunx. Only do these tests if the extended attribute exists. runchecktest "CHANGEHAT (subprofile/write to /proc/attr/current)" pass --manual=123456 open ${file} runchecktest "CHANGEHAT (exit subprofile/write to /proc/attr/current)" pass --manual=123456 --exit_hat open ${file} runchecktest "CHANGEHAT (noexit subprofile/write 0 to /proc/attr/current)" pass --manual=0 open ${file} runchecktest "CHANGEHAT (noexit subprofile/write 00000000 to /proc/attr/current)" pass --manual=00000000 open ${file} # verify that the kernel accepts the command "changehat ^hat\0" and # treats an empty token as 0. runchecktest "CHANGEHAT (noexit subprofile/write \"\" to /proc/attr/current)" fail --manual="" open ${file} runchecktest "CHANGEHAT (exit of noexit subprofile/write 0 to /proc/attr/current)" fail --manual=0 --exit_hat open ${file} runchecktest "CHANGEHAT (exit of noexit subprofile/write 00000000 to /proc/attr/current)" fail --manual=00000000 --exit_hat open ${file} runchecktest "CHANGEHAT (exit of noexit subprofile/write \"\" to /proc/attr/current)" fail --manual="" --exit_hat open ${file} fi # CHANGEHAT and PTHREADS: test that change_hat functions correctly in # the presence of threads settest changehat_pthread genprofile $file:$okperm "/proc/**:w" hat:fez $subfile:$okperm "/proc/**:w" runchecktest "CHANGEHAT PTHREAD (access parent file)" fail $file runchecktest "CHANGEHAT PTHREAD (access sub file)" pass $subfile apparmor-2.8.95~2430/tests/regression/apparmor/readdir.c0000644000175000017500000000202211503736226022757 0ustar sarnoldsarnold/* * Copyright (C) 2002-2006 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. */ #include #include #include #include #include #include #include #include #include int main(int argc, char *argv[]) { int fd; struct dirent dir; if (argc != 2){ fprintf(stderr, "usage: %s dir\n", argv[0]); return 1; } fd = open(argv[1], O_RDONLY, 0); if (fd == -1){ printf("FAIL - open %s\n", strerror(errno)); return 1; } /* if (fchdir(fd) == -1){ printf("FAIL - fchdir %s\n", strerror(errno)); return 1; } */ /* getdents isn't exported by glibc, so must use syscall() */ if (syscall(SYS_getdents, fd, &dir, sizeof(struct dirent)) == -1){ printf("FAIL - getdents %s\n", strerror(errno)); return 1; } printf("PASS\n"); return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/changeprofile.c0000644000175000017500000000170211503736226024157 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include "changehat.h" int main(int argc, char *argv[]) { int rc; if (argc != 3){ fprintf(stderr, "usage: %s profile file\n", argv[0]); return 1; } /* change profile if profile name != nochange */ if (strcmp(argv[1], "nochange") != 0){ rc = aa_change_profile(argv[1]); if (rc == -1){ fprintf(stderr, "FAIL: changeprofile %s failed - %s\n", argv[1], strerror(errno)); exit(errno); } } rc = do_open(argv[2]); if (rc == 0) printf("PASS\n"); return rc; } apparmor-2.8.95~2430/tests/regression/apparmor/syscall_setdomainname.c0000644000175000017500000000356511503736226025740 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include /* this implicitly tests getdomainname, too. */ #define RESET_DOMAIN_WHEN_DONE #define BUFSIZE 4096 int main(int argc, char *argv[]) { char saved_domain[BUFSIZE]; char new_domain[BUFSIZE]; size_t len = sizeof(saved_domain); size_t newlen; int error = 0; if (argc != 2) { fprintf(stderr, "usage: %s domain\n", argv[0]); return 1; } newlen = strlen(argv[1]); if (newlen <= 0 || newlen >= BUFSIZE) { fprintf(stderr, "FAIL: invalid domain '%s'\n", argv[1]); return 1; } if (getdomainname(saved_domain, len) == -1) { fprintf(stderr, "FAIL: getdomainname failed - %s\n", strerror(errno)); return 1; } /* printf("old domainname is %s\n", saved_domain ? saved_domain : "NULL"); */ if (setdomainname(argv[1], strlen(argv[1])) == -1) { fprintf(stderr, "FAIL: setdomainname failed - %s\n", strerror(errno)); return 1; } len = sizeof(new_domain); if (getdomainname(new_domain, len) == -1) { fprintf(stderr, "FAIL: getdomainname failed - %s\n", strerror(errno)); error = 1; goto cleanup; } if (strcmp(new_domain, argv[1]) != 0) { fprintf(stderr, "FAIL: attempted to set domainname to '%s', " "but '%s' was the result\n", argv[1], new_domain); error = 1; goto cleanup; } cleanup: #ifdef RESET_DOMAIN_WHEN_DONE if (setdomainname(saved_domain, strlen(saved_domain)) == -1) { fprintf(stderr, "FAIL: setdomainname failed restting to old name - %s\n", strerror(errno)); error = 1; } #endif /* RESET_DOMAIN_WHEN_DONE */ if (error == 0) printf("PASS\n"); return error; } apparmor-2.8.95~2430/tests/regression/apparmor/swap.sh0000755000175000017500000000237311503736226022523 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME swap #=DESCRIPTION # Confined processes are prohibited from executing certain system calls # entirely, including swapon(2) swapoff (2). This test verifies that # unconfined processes can call these syscalls but confined processes cannot. #=END # I made this a seperate test script because of the need to make a # swapfile before the tests run. pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc ## ## A. SWAP ## swap_file=$tmpdir/swapfile dd if=/dev/zero of=${swap_file} bs=1024 count=512 2> /dev/null /sbin/mkswap -f ${swap_file} > /dev/null # TEST 1. Make sure can enable and disable swap unconfined runchecktest "SWAPON (unconfined)" pass on ${swap_file} runchecktest "SWAPOFF (unconfined)" pass off ${swap_file} # TEST A2. confine SWAPON genprofile runchecktest "SWAPON (confined)" fail on ${swap_file} # TEST A3. confine SWAPOFF /sbin/swapon ${swap_file} runchecktest "SWAPOFF (confined)" fail off ${swap_file} # cleanup, turn off swap /sbin/swapoff ${swap_file} apparmor-2.8.95~2430/tests/regression/apparmor/fork.sh0000755000175000017500000000237211503736226022511 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME fork #=DESCRIPTION # Verifies that profiles are duplicated correctly for fork (the subtask # receives a copy of it's parents profile). The test attempts to access the # files passed as arguments for both a parent and a child. The test is # repeated for permissive and restrictive profiles. #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc file1=$tmpdir/file1 file2=$tmpdir/file2 file3=$tmpdir/file3 cap=ipc_owner okperm=rw badperm=r touch $file1 $file2 $file3 # TEST1 genprofile cap:$cap $file1:$okperm $file2:$okperm $file3:$okperm runchecktest "FORK with rw" pass $file1 $file2 $file3 # TEST2. All will fail, but parent and child will equally fail genprofile cap:$cap $file1:$badperm $file2:$badperm $file3:$badperm runchecktest "FORK w/o w" pass $file1 $file2 $file3 # TEST3. Some pass, some fail, but parent and child will equally fail genprofile cap:$cap $file1:$badperm $file2:$okperm $file3:$badperm runchecktest "FORK mixed" pass $file1 $file2 $file3 apparmor-2.8.95~2430/tests/regression/apparmor/exec_qual.c0000644000175000017500000000123711503736226023322 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include #include #include int main(int argc, char *argv[]) { extern char **environ; if (argc < 2){ fprintf(stderr, "usage: %s program [args] \n", argv[0]); return 1; } (void)execve(argv[1], &argv[1], environ); fprintf(stderr, "FAILED: exec failed %s\n", strerror(errno)); return 1; } apparmor-2.8.95~2430/tests/regression/apparmor/tcp.sh0000755000175000017500000000614611533437765022352 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME tcp #=DESCRIPTION a series of tests for tcp/netdomain. pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc port=34567 ip="127.0.0.1" #badperm1=r #badperm2=w # PASS TEST - no apparmor rules runchecktest "TCP (no apparmor)" pass $port # FAIL TEST - no network rules genprofile runchecktest "TCP (accept, connect) no network rules" fail $port # PASS TEST - allow tcp genprofile network:tcp runchecktest "TCP (accept, connect) allow tcp" pass $port # PASS TEST - allow inet genprofile network:inet runchecktest "TCP (accept, connect) allow inet" pass $port # PASS TEST - allow inet stream genprofile "network:inet stream" runchecktest "TCP (accept, connect) allow inet stream" pass $port # PASS TEST - simple / low-numbered port # you damn well better not be running telnet genprofile network:inet cap:net_bind_service runchecktest "TCP (accept, connect) low numbered port/bind cap" pass 23 # FAIL TEST - simple / low-numbered port # will always fail unless process has net_bind_service capability. # you damn well better not be running telnetd. genprofile network:inet runchecktest "TCP (accept, connect) low numbered port/no bind cap" fail 23 exit 0 # PASS TEST - accept via interface genprofile tcp_accept:via:lo tcp_connect: runchecktest "TCP (accept, connect)" pass $port # PASS TEST - accept to ip addr genprofile tcp_accept:to:${ip} tcp_connect: runchecktest "TCP (accept, connect)" pass $port # PASS TEST - accept to ip addr + cidr genprofile tcp_accept:to:127.0.0.0/24 tcp_connect: runchecktest "TCP (accept, connect)" pass $port # PASS TEST - accept to ip addr + netmask genprofile tcp_accept:to:127.0.0.0/255.255.255.0 tcp_connect: runchecktest "TCP (accept, connect)" pass $port # PASS TEST - accept to ip addr:port genprofile tcp_accept:to:${ip}::${port} tcp_connect: runchecktest "TCP (accept, connect)" pass $port # PASS TEST - accept to ip addr/cidr:port genprofile tcp_accept:to:127.0.0.0/24::${port} tcp_connect: runchecktest "TCP (accept, connect)" pass $port # PASS TEST - accept to ip addr/mask:port genprofile tcp_accept:to:127.0.0.0/255.255.192.0::${port} tcp_connect: runchecktest "TCP (accept, connect)" pass $port # PASS TEST - simple / low-numbered port # will always fail unless process has net_bind_service capability. # you damn well better not be running telnetd. genprofile tcp_accept: tcp_connect: cap:net_bind_service runchecktest "TCP (accept, connect, port 23)" pass 23 # The following tests will FAIL only if netdomain is enabled. If # netdomain is disabled, they are expected to pass. netdomain is # disabled for the SHASS 1.1 release. FIXME - sure would be nice to # detect this programmatically. #EXPECTED=fail EXPECTED=pass # FAIL TEST - needs tcp_connect genprofile tcp_accept: runchecktest "TCP (accept)" ${EXPECTED} $port # FAIL TEST - needs tcp_accept genprofile tcp_connect: runchecktest "TCP (connect)" ${EXPECTED} $port apparmor-2.8.95~2430/tests/regression/apparmor/changehat_twice.c0000644000175000017500000000241712305420207024460 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include "changehat.h" int main(int argc, char *argv[]) { int rc; unsigned long magic; if (argc != 5){ fprintf(stderr, "usage: %s profile1 profile2 goodmagic|badmagic file\n", argv[0]); return 1; } /* change hat if hatname != nochange */ if (strcmp(argv[1], "nochange") != 0){ rc = change_hat(argv[1], SD_ID_MAGIC+1); if (rc == -1){ fprintf(stderr, "FAIL: changehat %s failed - %s\n", argv[1], strerror(errno)); exit(1); } } magic=SD_ID_MAGIC+1; if (strcmp(argv[3], "badmagic") == 0){ magic^=0xdeadbeef; } if (strcmp(argv[1], "nochange") != 0){ rc = change_hat(argv[2], magic); if (rc == -1){ fprintf(stderr, "FAIL: changehat %s failed - %s\n", argv[1], strerror(errno)); exit(1); } } rc = do_open(argv[4]); if (rc != 0) return rc; printf("PASS\n"); return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/fd_inheritance.sh0000755000175000017500000000507312201101155024472 0ustar sarnoldsarnold#!/bin/bash # # Copyright (c) 2013 # Canonical, Ltd. (All rights reserved) # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License published by the Free Software Foundation. # # This program 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. or Canonical # Ltd. # #=NAME fd_inheritance #=DESCRIPTION # This test verifies that file descriptor inheritance results in the expected # delegation, or lack thereof, between various combinations of confined and # unconfined processes. #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc file=$tmpdir/file inheritor=$bin/fd_inheritor okperm=r badperm=w cat > $file << EOF 0a7eb75b2a54eaf86aa8d7b4c6cc945c 70a2265ba96d962d993c97689ff09904 d3e773e2a4a0cc9d7e28eb217a4241ce 1437d6c55ef788d3bcd27ab14e9382a9 EOF runchecktest "fd inheritance; unconfined -> unconfined" pass $file $inheritor genprofile $file:$okperm $inheritor:Ux runchecktest "fd inheritance; confined -> unconfined" pass $file $inheritor genprofile $file:$badperm $inheritor:Ux runchecktest "fd inheritance; confined (bad perm) -> unconfined" fail $file $inheritor genprofile $inheritor:Ux runchecktest "fd inheritance; confined (no perm) -> unconfined" fail $file $inheritor genprofile image=$inheritor $file:$okperm runchecktest "fd inheritance; unconfined -> confined" pass $file $inheritor genprofile image=$inheritor runchecktest "fd inheritance; unconfined -> confined (no perm)" pass $file $inheritor genprofile $file:$okperm $inheritor:Px -- image=$inheritor $file:$okperm runchecktest "fd inheritance; confined -> confined" pass $file $inheritor genprofile $file:$badperm $inheritor:Px -- image=$inheritor $file:$okperm runchecktest "fd inheritance; confined (bad perm) -> confined" fail $file $inheritor genprofile $inheritor:Px -- image=$inheritor $file:$okperm runchecktest "fd inheritance; confined (no perm) -> confined" fail $file $inheritor genprofile $file:$okperm $inheritor:Px -- image=$inheritor $file:$badperm runchecktest "fd inheritance; confined -> confined (bad perm)" fail $file $inheritor genprofile $file:$okperm $inheritor:Px -- image=$inheritor runchecktest "fd inheritance; confined -> confined (no perm)" fail $file $inheritor apparmor-2.8.95~2430/tests/regression/apparmor/dbus.inc0000755000175000017500000000456312207721065022645 0ustar sarnoldsarnold# vim:syntax=sh # # Copyright (C) 2013 Canonical, Ltd. # # 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. gendbusprofile() { genprofile --stdin </dev/null if [ $? -ne 0 ] then fatalerror "DBus daemon unexpectedly stopped" fi do_onexit="kill $bus_pid" export DBUS_SESSION_BUS_ADDRESS=$bus_addr } bus="session" dest=com.apparmor.Test path=/com/apparmor/Test iface=com.apparmor.Test # parameters: bus message_type destination path interface.member # # destination must be a connection name or "broadcast" for a broadcast signal send() { d="" if [ "$3" == "broadcast" ] then if [ "$2" != "signal" ] then fatalerror "Cannot send broadcast for message type \"$2\"" fi else d="--name=$3" fi out=$(./dbus_message --$1 --type=$2 $d $4 $5 2>&1) if [ $? -ne 0 ] then fatalerror "$out" fi } sendsignal() { send "$bus" "signal" "$dest" "$path" "${iface}.Signal" } sendbroadcastsignal() { send "$bus" "signal" "broadcast" "$path" "${iface}.Signal" } sendmethod() { send "$bus" "method_call" "$dest" "$path" "${iface}.Method" } compare_logs() { local msg local rc=0 cmp -s "$1" "$3" || rc=$? if [ $rc -ne 0 ] && [ "$2" == "eq" ] then msg="Log files \"$1\" and \"$3\" are different, but should be equal." elif [ $rc -eq 0 ] && [ "$2" == "ne" ] then msg="Log files \"$1\" and \"$3\" are the same, but should be different." else return fi echo "Error: ${testname} failed. Test '${_testdesc}' produced unexpected log contents. ${msg}" testfailed } apparmor-2.8.95~2430/tests/regression/apparmor/prologue.inc0000755000175000017500000002547512221421705023543 0ustar sarnoldsarnold# vim:syntax=sh # # Test infrastructure support. # # Copyright 2010 Canonical, Ltd. # # 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. # # This file should be included by each test case # It does a lot of hidden 'magic', Downside is that # this magic makes debugging fauling tests more difficult. # Running the test with the '-r' option can help. # # Userchangeable variables (tmpdir etc) should be specified in # uservars.inc # # Cleanup is automatically performed by epilogue.inc # # For this file, functions are first, entry point code is at end, see "MAIN" required_features() { if [ ! -e "/sys/kernel/security/apparmor/features/" ] ; then echo "Kernel feature masks not supported. Skipping tests ..." exit 0 fi for f in $@ ; do if [ ! -e "/sys/kernel/security/apparmor/features/$f" ] ; then echo "Required feature $f not available. Skipping tests ..." exit 0 fi done } requires_query_interface() { if [ ! -e "/sys/kernel/security/apparmor/.access" ] then echo "Kernel query interface not supported. Skipping tests ..." exit 0 fi } fatalerror() { # global _fatal if [ -z "$_fatal" ] then _fatal=true # avoid cascading fatal errors echo "Fatal Error ($testname): $*" >&2 exit 127 fi } testerror() { fatalerror "Unable to run test sub-executable" } testfailed() { # global num_testfailures teststatus num_testfailures=$(($num_testfailures + 1)) teststatus="fail" # if we are retaining the tmpdir we are debugging failures so # stop so it can be looked at if [ $retaintmpdir == "true" ] ; then exit 127 fi } error_handler() { #invoke exit_handler to cleanup exit_handler fatalerror "Unexpected shell error. Run with -x to debug" } # invoked whenever we exit (normally, interrupt # or exit due to testfailure()/error_handler() exit_handler() { # global bin if [ -d "$bin" ] then . $bin/epilogue.inc fi } genrunscript() { # create a log so we can run test again if -retain specified local runfile #global tmpdir profile outfile if [ "$retaintmpdir" = "true" ] then runfile=$tmpdir/runtest echo "$subdomain ${parser_args} $profile" > $runfile echo -n "$testexec " >> $runfile while [ $# -gt 0 ] ; do echo -n "\"$1\" " >> $runfile shift done echo "2>&1 > $outfile" >> $runfile echo "echo $testname: \`cat $outfile\`" >> $runfile echo "$subdomain ${parser_args} -R $profile" >> $runfile fi } runtestbg() { if [ -z "${__NO_TRAP_ERR}" ] then trap "error_handler" ERR fi # global _testdesc _pfmode _known _pid outfile _testdesc=$1 if [ ${2:0:1} == "x" ] ; then _pfmode=${2#x} _known=" (known problem)" else _pfmode=$2 _known="" fi shift 2 genrunscript "$@" $testexec "$@" > $outfile 2>&1 & _pid=$! } checktestbg() { # global _pid _rc outfile local rc wait $_pid rc=$? if [ $rc -gt 128 ] then echo "SIGNAL$(($rc - 128))" > $outfile fi checktestfg "$@" } runtestfg() { # global _testdesc _pfmode _known outfile _testdesc=$1 if [ ${2:0:1} == "x" ] ; then _pfmode=${2#x} _known=" (known problem)" else _pfmode=$2 _known="" fi shift 2 genrunscript "$@" $testexec "$@" > $outfile 2>&1 & _pid=$! wait $_pid test_rc=$? if [ $test_rc -gt 128 ] then echo "SIGNAL$(($test_rc - 128))" > $outfile fi } checktestfg() { # global _pfmode _known _testdesc outfile teststatus testname local ret expectedsig killedsig ret=`cat $outfile 2>/dev/null` teststatus=pass case "$ret" in PASS) if [ "$_pfmode" != "pass" -a -z "${_known}" ] then echo "Error: ${testname} passed. Test '${_testdesc}' was expected to '${_pfmode}'" testfailed return elif [ "$_pfmode" == "pass" -a -n "${_known}" ] then echo "Alert: ${testname} passed. Test '${_testdesc}' was marked as expected pass but known problem (xpass)" fi ;; FAIL*) if [ "$_pfmode" != "fail" -a -z "${_known}" ] then echo "Error: ${testname} failed. Test '${_testdesc}' was expected to '${_pfmode}'. Reason for failure '${ret}'" testfailed return elif [ "$_pfmode" == "fail" -a -n "${_known}" ] then echo "Alert: ${testname} failed. Test '${_testdesc}' was marked as expected fail but known problem (xfail)." fi ;; SIGNAL*) killedsig=`echo $ret | sed 's/SIGNAL//'` case "$_pfmode" in signal*) expectedsig=`echo ${_pfmode} | sed 's/signal//'` if [ -n "${expectedsig}" -a ${expectedsig} != ${killedsig} ] then echo "Error: ${testname} failed. Test '${_testdesc}' was expected to terminate with signal ${expectedsig}${_known}. Instead it terminated with signal ${killedsig}" testfailed return fi ;; *) echo "Error: ${testname} failed. Test '${_testdesc}' was expected to '${_pfmode}'${_known}. Reason for failure 'killed by signal ${killedsig}'" testfailed return ;; esac ;; *) testerror return ;; esac if [ $# -gt 0 ] then $1 if [ "$teststatus" != "pass" ] then return fi fi if [ -n "$VERBOSE" ]; then echo "ok: ${_testdesc}" fi } runchecktest() { if [ -z "${__NO_TRAP_ERR}" ] then trap "error_handler" ERR fi runtestfg "$@" checktestfg } runchecktest_errno() { local errno=$(perl -MPOSIX -e 'printf "%d\n", '$1';') shift 1 if [ -z "${__NO_TRAP_ERR}" ] then trap "error_handler" ERR fi runtestfg "$@" if [ "$test_rc" == "$errno" ] ; then checktestfg else echo "Error: ${testname} failed. Test '${_testdesc}' was expected to '${_pfmode}'${_known}. Reason for failure expect errno ${errno} != ${test_rc}" testfailed fi } emit_profile() { if [ -z "${__NO_TRAP_ERR}" ] then trap "error_handler" ERR fi #global name outfile profile profilenames name=$1; shift 1 $bin/mkprofile.pl ${mkflags} "$name" ${outfile}:w "$@" >> $profile echo $name >> $profilenames } genprofile() { if [ -z "${__NO_TRAP_ERR}" ] then trap "error_handler" ERR fi local num_emitted imagename hat args arg names1 names2 #global complainflag escapeflag nodefaults profile profilenames complainflag="" mkflags="" while /bin/true do case "$1" in "-C") complainflag="-C" ;; "-E") mkflags="${mkflags} -E" ;; "-N") mkflags="${mkflags} -N" ;; "-I") mkflags="${mkflags} -I" ;; *) break ;; esac shift done # save previous profile if [ -f $profile ] then mv $profile ${profile}.old mv $profilenames ${profilenames}.old fi num_emitted=0 while /bin/true do imagename=$test # image/subhat allows overriding of the default # imagename which is based on the testname # # it is most often used after --, in fact it is basically # mandatory after -- case "$1" in image=*) imagename=`echo $1 | sed 's/^image=\([^:]*\).*$/\1/'` if [ ! -x "$imagename" ] then fatalerror "invalid imagename specified in input '$1'" fi num_emitted=0 shift ;; subhat=*) fatalerror "'subhat=hatname' is no longer supported ('$1')" shift ;; esac num_args=0 while [ $# -gt 0 ] do arg="$1" shift # -- is the separator between profiles if [ "$arg" == "--" ] then eval emit_profile \"$imagename\" \ $(for i in $(seq 0 $((${num_args} - 1))) ; do echo \"\${args[${i}]}\" ; done) num_emitted=$((num_emitted + 1)) num_args=0 continue 2 else args[${num_args}]=${arg} num_args=$(($num_args + 1)) fi done # output what is in args, or force empty profile if [ -n "$args" -o $num_emitted -eq 0 ] ; then eval emit_profile \"$imagename\" \ $(for i in $(seq 0 $((${num_args} - 1))) ; do echo \"\${args[${i}]}\" ; done) fi break done # if old and new profiles consist of the same entries # we can do a replace, else remove/reload if [ $profileloaded -eq 1 ] then names1=$tmpdir/sorted1 names2=$tmpdir/sorted2 sort $profilenames > $names1 sort ${profilenames}.old > $names2 if cmp -s $names1 $names2 then replaceprofile else removeprofile ${profile}.old loadprofile fi rm -f $names1 $names2 else loadprofile fi if [ -e ${sys_profiles} ] ; then #check to see if the profiles are actually loaded for f in `cat $profilenames` ; do grep -Eq "^$f"' \([^)]+\)$' ${sys_profiles} rc=$? if [ $rc -ne 0 ] ; then echo "Genprofile failed to load profile \"$f\"" exit 1 fi done fi rm -f ${profile}.old ${profilenames}.old } loadprofile() { #global complainflaf profile profileloaded $subdomain ${parser_args} $complainflag $profile > /dev/null if [ $? -ne 0 ] then removeprofile fatalerror "Unable to load profile $profile" else profileloaded=1 fi } replaceprofile() { #global complainflag profile $subdomain ${parser_args} -r $complainflag $profile > /dev/null if [ $? -ne 0 ] then fatalerror "Unable to replace profile $profile" fi } removeprofile() { local remprofile #global profile profileloaded if [ -f "$1" ] then remprofile=$1 else remprofile=$profile fi $subdomain ${parser_args} -R $remprofile > /dev/null if [ $? -ne 0 ] then fatalerror "Unable to remove profile $remprofile" else profileloaded=0 fi } settest() { if [ -z "${__NO_TRAP_ERR}" ] then trap "error_handler" ERR fi #global test testname testexec outfile profileloaded #testname is the basename of the test, i,e 'open' #test is the full path to the test executable. #testexec is the path than will be run, normally this is the same # as $test, but occasionally, you may want to invoke a wrapper which # will run the test. In this case 'settest "wrapper {}' # will result in testexec invoking wrapper. {} will be replaced with # $test testname=$1 if [ $# -eq 1 ] then test=$bin/$1 testexec=$test elif [ $# -eq 2 ] then test=$bin/$1 testexec=`echo $2 | sed "s~{}~$test~"` else fatalerror "settest, illegal usage" fi outfile=$tmpdir/output.$1 # Remove any current profile if loaded if [ $profileloaded -eq 1 ] then removeprofile fi } # ---------------------------------------------------------------------------- # MAIN trap "exit_handler" EXIT trap "error_handler" ERR 2> /dev/null if [ $? -ne 0 ] then __NO_TRAP_ERR="true" fi if [ `whoami` != "root" ] then fatalerror "Must be root to run $0" fi if [ ! -d "$bin" ] then fatalerror "$0 requires \$bin pointing to binary directory" fi # parse arguments. # -r/-retain: flag to retain last failing testcase in tmpdir if [ "$1" == "-retain" -o "$1" == "-r" ] then retaintmpdir=true else retaintmpdir=false fi # load user changeable variables . $bin/uservars.inc if [ ! -x $subdomain ] then fatalerror "AppArmor parser '$subdomain' is not executable" fi profileloaded=0 tmpdir=$(mktemp -d $tmpdir-XXXXXX) chmod 755 ${tmpdir} export tmpdir #set initial testname based on name of script settest `basename $0 .sh` profile=$tmpdir/profile profilenames=$tmpdir/profile.names num_testfailures=0 # exit code of script is set to #failures apparmor-2.8.95~2430/tests/regression/apparmor/fd_inheritor.c0000644000175000017500000000254612201101155024013 0ustar sarnoldsarnold/* * Copyright (c) 2013 * Canonical, Ltd. (All rights reserved) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, contact Novell, Inc. or Canonical * Ltd. */ #include #include #include #include #include #define BUF_LEN 128 int main(int argc, char *argv[]) { char buf[BUF_LEN + 1]; int fd, rc; if (argc != 3) { fprintf(stderr, "Usage: %s \n", argv[0]); exit(1); } fd = atoi(argv[1]); rc = lseek(fd, 0, SEEK_SET); if (rc) { perror("FAIL INHERITOR - lseek"); exit(1); } memset(buf, 0, sizeof(buf)); rc = read(fd, buf, BUF_LEN); if (rc < 0) { perror("FAIL INHERITOR - read"); exit(1); } if (strcmp(argv[2], buf)) { fprintf(stderr, "FAIL INHERITOR - expected \"%s\" but read \"%s\"\n", argv[2], buf); exit(1); } printf("PASS\n"); exit(0); } apparmor-2.8.95~2430/tests/regression/apparmor/pipe.c0000644000175000017500000000474611503736226022321 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include "changehat.h" const char *data="hello world"; int do_read (int fd) { int rc; char buf[128]; if (fd < 0) { fprintf(stderr, "FAIL: read failed - no descriptor passed\n"); return 1; } rc=read(fd, buf, sizeof(buf)); if (rc != strlen(data)){ fprintf(stderr, "FAIL: read failed - %s\n", strerror(errno)); return 1; } if (memcmp(buf, data, strlen(data)) != 0){ fprintf(stderr, "FAIL: comparison failed - %s\n", strerror(errno)); return 1; } close(fd); return 0; } int do_write (int fd) { int rc; rc=write(fd, data, strlen(data)); if (rc != strlen(data)){ fprintf(stderr, "FAIL: write failed - %s\n", strerror(errno)); return 1; } close(fd); return 0; } int main(int argc, char *argv[]) { int rc; pid_t pid; int waitstatus; int filedes[2]; int read_error = 0; if (argc != 2){ fprintf(stderr, "usage: %s hatname\n", argv[0]); return 1; } /* change hat if hatname != nochange */ if (strcmp(argv[1], "nochange") != 0){ rc = change_hat(argv[1], SD_ID_MAGIC+1); if (rc == -1){ fprintf(stderr, "FAIL: changehat %s failed - %s\n", argv[1], strerror(errno)); exit(1); } } if (pipe(filedes) == -1){ fprintf(stderr, "FAIL: pipe() failed - %s\n", strerror(errno)); exit(1); } pid = fork(); if (pid == -1) { fprintf(stderr, "FAIL: fork failed - %s\n", strerror(errno)); exit(1); } else if (pid != 0) { /* parent */ read_error = do_read(filedes[0]); rc = wait(&waitstatus); if (rc == -1){ fprintf(stderr, "FAIL: wait failed - %s\n", strerror(errno)); exit(1); } } else { /* child */ exit(do_write(filedes[1])); } if ((WIFEXITED(waitstatus) != 0) && (WEXITSTATUS(waitstatus) == 0) && read_error == 0) { printf("PASS\n"); } else { return (WEXITSTATUS(waitstatus) & read_error); } return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/link_subset.sh0000644000175000017500000000266711503736226024076 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME link #=DESCRIPTION # Link requires 'l' permission on the link and that permissions on the #links rwmx perms are a subset of the targets perms, and if x is present #that the link and target have the same x qualifiers. # This test verifies matching, non-matching and missing link # permissions in a profile. #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc target=$tmpdir/target_ linkfile=$tmpdir/link_ tfiles=`$bin/link_subset --filenames $target` lfiles=`$bin/link_subset --filenames $linkfile` # unconfined test - no target file #runchecktest "unconfined - no target" fail $target $linkfile #touch $target # unconfined test #runchecktest "unconfined" pass $target $linkfile #rm -rf $target # Link no perms on link or target - no target file #genprofile #runchecktest "link no target (no perms) -> target (no perms)" fail $target $linkfile #rm -rf $linkfile touch $target for f in $tfiles ; do touch ${f%%:*} ; done # Link no perms on link or target #runchecktest "link (no perms) -> target (no perms)" fail $target $linkfile #rm -rf $linkfile genprofile $tfiles $lfiles hat:remove_link /**:rw runchecktest "link_subset" pass $target $linkfile rm -rf $linkfile apparmor-2.8.95~2430/tests/regression/apparmor/mount.c0000644000175000017500000000213511503736226022514 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include #include #include int main(int argc, char *argv[]) { if (argc != 4) { fprintf(stderr, "usage: %s [mount|umount] loopdev mountpoint\n", argv[0]); return 1; } if (strcmp(argv[1], "mount") == 0) { if (mount(argv[2], argv[3], "ext2", 0xc0ed0000 | MS_MANDLOCK, NULL ) == -1) { fprintf(stderr, "FAIL: mount %s on %s failed - %s\n", argv[2], argv[3], strerror(errno)); return errno; } } else if (strcmp(argv[1], "umount") == 0) { if (umount(argv[3]) == -1) { fprintf(stderr, "FAIL: umount %s failed - %s\n", argv[3], strerror(errno)); return errno; } } else { fprintf(stderr, "usage: %s [mount|umount] loopdev mountpoint\n", argv[0]); return 1; } printf("PASS\n"); return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/ptrace.c0000644000175000017500000001141310772754413022634 0ustar sarnoldsarnold#include #include #include #include #include #include #include #include #include #include #define NUM_CHLD_SYSCALLS 10 #define PARENT_TRACE 0 #define CHILD_TRACE 1 #define HELPER_TRACE 2 extern char **environ; int interp_status(int status) { int rc; if (WIFEXITED(status)) { if (WEXITSTATUS(status) == 0) { rc = 0; } else { rc = -WEXITSTATUS(status); } } else { rc = -ECONNABORTED; /* overload to mean child signal */ } return rc; } /* return 0 on success. Child failure -errorno, parent failure errno */ int do_parent(pid_t pid, int trace, int num_syscall) { struct user regs; int status, i; unsigned int rc; /* child is paused */ rc = alarm(5); if (rc != 0) { fprintf(stderr, "FAIL: unexpected alarm already set\n"); return errno; } //fprintf(stderr, "waiting ... "); if (waitpid(pid, &status, WUNTRACED) == -1) return errno; if (!WIFSTOPPED(status)) return interp_status(status); //fprintf(stderr, " done initial wait\n"); if (trace) { /* this sends a child SIGSTOP */ if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) { perror("FAIL: parent ptrace(PTRACE_ATTACH) failed - "); return errno; } } else { /* continue child so it can attach to parent */ kill(pid, SIGCONT); //fprintf(stderr, "waiting2 ... "); if (waitpid(pid, &status, WUNTRACED) == -1) return errno; //fprintf(stderr, " done\n"); if (!WIFSTOPPED(status)) return interp_status(status); } for (i = 0; i < num_syscall * 2; i++){ /* this will restart stopped child */ if (ptrace(PTRACE_SYSCALL, pid, NULL, 0) == -1) { perror("FAIL: parent ptrace(PTRACE_SINGLESTEP) failed - "); return errno; } //fprintf(stderr, "waiting3 ... "); if (waitpid(pid, &status, WUNTRACED) == -1) return errno; //fprintf(stderr, " done\n"); if (!WIFSTOPPED(status)) return interp_status(status); memset(®s, 0, sizeof(regs)); if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1) { perror("FAIL: parent ptrace(PTRACE_GETREGS) failed - "); return errno; } } if (ptrace(PTRACE_DETACH, pid, NULL, NULL) == -1) { perror("FAIL: parent ptrace(PTRACE_DETACH) failed - "); return errno; } return 0; } /* returns 0 on success or error code of failure */ int do_child(char *argv[], int child_trace, int helper) { if (helper) { /* for helper we want to transition before ptrace occurs * so don't stop here, let the helper do that */ if (child_trace) { putenv("_tracer=child"); } else { putenv("_tracer=parent"); } //fprintf(stderr, "child trace %d\n", child_trace); } else { /* stop child to ensure it doesn't finish before it is traced */ if (raise(SIGSTOP) != 0){ perror("FAIL: child/helper SIGSTOP itself failed -"); return errno; } if (child_trace) { if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1){ perror("FAIL: child ptrace(PTRACE_TRACEME) failed - "); return errno; } if (raise(SIGSTOP) != 0){ perror("FAIL: child SIGSTOP itself failed -"); return errno; } /* ok we're stopped, wait for parent to trace (continue) us */ } } execve(argv[0], argv, environ); perror("FAIL: child exec failed - "); return errno; } /* make pid_t global so the alarm handler can kill off children */ pid_t pid; void sigalrm_handler(int sig) { fprintf(stderr, "FAIL: parent timed out waiting for child\n"); kill(pid, SIGKILL); exit(1); } int main(int argc, char *argv[]) { int parent_trace = 1, use_helper = 0, num_syscall = NUM_CHLD_SYSCALLS, opt, ret = 0; const char *usage = "usage: %s [-c] [-n #syscall] program [args ...]\n"; char **args; if (signal(SIGALRM, sigalrm_handler) == SIG_ERR) { perror ("FAIL - signal failed: "); return(1); } opterr = 0; while (1) { opt = getopt(argc, argv, "chn:"); if (opt == -1) break; switch (opt) { case 'c': parent_trace = 0; break; case 'h': use_helper = 1; break; case 'n': num_syscall = atoi(optarg); break; default: fprintf(stderr, usage, argv[0]); break; } } if (argc < 2) { fprintf(stderr, usage, argv[0]); return 1; } args = &argv[optind]; pid = fork(); if (pid > 0){ /*parent */ int stat; ret = do_parent(pid, parent_trace, num_syscall); kill(pid, SIGKILL); if (ret >= 0) { /* wait for child */ waitpid(pid, &stat, 0); } if (ret > 0) { perror("FAIL: parent failed: "); } else if (ret == 0) { printf("PASS\n"); return 0; } else if (ret == -ECONNABORTED) { errno = -ret; perror("FAIL: child killed: "); } else { errno = -ret; perror("FAIL: child failed: "); } } else if (pid == 0) { /* child */ if (do_child(args, !parent_trace, use_helper)) return 0; } else { perror("FAIL: fork failed - "); } return ret; } apparmor-2.8.95~2430/tests/regression/apparmor/link.sh0000755000175000017500000000310411503736226022477 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME link #=DESCRIPTION # Link requires 'l' permission on the link and that permissions on the #links rwmx perms are a subset of the targets perms, and if x is present #that the link and target have the same x qualifiers. # This test verifies matching, non-matching and missing link # permissions in a profile. #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc target=$tmpdir/target linkfile=$tmpdir/linkfile okperm=rwixl badperm=rwl nolinkperm=rwix PERMS="r w m ix px ux Px Ux l rw rm rix rpx rux rPx rUx rl wm wix wpx wux \ wPx wUx wl mix mpx mux mPx mUx ml ixl pxl uxl Pxl Uxl rwm rwix rwpx \ rwux rwPx rwUx rwl rmix rmpx rmux rmPx rmUx rml wmix wmpx wmux wmPx \ wmUx wml mixl mpxl muxl mPxl mUxl rwmix rwmpx rwmux rwmPx rwmUx \ rwml wmixl wmpxl wmuxl wmPxl wmUxl rwmixl rwmpxl rwmuxl rwmPxl \ rwmUxl" # unconfined test - no target file runchecktest "unconfined - no target" fail $target $linkfile touch $target # unconfined test runchecktest "unconfined" pass $target $linkfile rm -rf $target # Link no perms on link or target - no target file genprofile runchecktest "link no target (no perms) -> target (no perms)" fail $target $linkfile rm -rf $linkfile touch $target # Link no perms on link or target runchecktest "link (no perms) -> target (no perms)" fail $target $linkfile rm -rf $linkfile apparmor-2.8.95~2430/tests/regression/apparmor/syscall_ioperm.c0000644000175000017500000000214411503736226024377 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include /* for glibc */ #include #include int main(int argc, char *argv[]) { unsigned long from, num; if (argc != 3) { fprintf(stderr, "usage: %s \n", argv[0]); return 1; } if ( (from = strtoul(argv[1], NULL, 0)) == 0) { if (errno == EINVAL) { fprintf(stderr, "FAIL: no argument in '%s'\n", argv[1]); } } if ( (num = strtoul(argv[2], NULL, 0)) == 0) { if (errno == EINVAL) { fprintf(stderr, "FAIL: no argument in '%s'\n", argv[1]); } } if (from > 0x3ff || (from + num) > 0x3ff) { fprintf(stderr, "FAIL: out of range (0x3ff)\n"); } if (ioperm(from, num, 1) == -1) { fprintf(stderr, "FAIL: ioperm failed - %s\n", strerror(errno)); return 1; } printf("PASS\n"); return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/regex.sh0000755000175000017500000001133311503736226022657 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME regex #=DESCRIPTION # This test verifies that tail globbing and regex globbing (perl regex engine) # are functioning correctly for confined processes. Single character, multi # character and character class regexes are verified. #=END # A series of tests for regex expressions. this should help verify that the # parser is handling regex's correctly, as well as verifying that the module # pcre stuff works as well. pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc file=$tmpdir/file file2=$tmpdir/filealpha okperm=rw badperm1=r badperm2=w touch $file $file2 chmod 600 $file $file2 # read/write tests settest open # PASS TEST - 'testfile genprofile ${tmpdir}/\'testfile:$okperm runchecktest "\'testfile" pass ${tmpdir}/\'testfile # PASS TEST - testfile genprofile ${tmpdir}/\>testfile:$okperm runchecktest "\>testfile" pass ${tmpdir}/\>testfile # PASS TEST - |testfile genprofile ${tmpdir}/\|testfile:$okperm runchecktest "ESCAPED PIPE" pass ${tmpdir}/\|testfile # PASS TEST - ;testfile genprofile ${tmpdir}/\;testfile:$okperm runchecktest "\;testfile" pass ${tmpdir}/\;testfile # PASS TEST - #testfile #genprofile ${tmpdir}/\\#testfile:$okperm #runchecktest "\#testfile" pass ${tmpdir}/\#testfile # PASS TEST - looking for * genprofile ${tmpdir}/\*:$okperm runchecktest "SINGLE TAILGLOB" pass $file # PASS TEST - looking for ** genprofile /\*\*:$okperm runchecktest "DOUBLE TAILGLOB" pass $file # PASS TEST - looking for { , } genprofile ${tmpdir}/\{file,nofile\}:$okperm runchecktest "CURLY BRACES" pass $file # PASS TEST - looking for [] genprofile ${tmpdir}/\[aefg\]ile:$okperm runchecktest "SQUARE BRACES 1" pass $file # PASS TEST - looking for [] genprofile ${tmpdir}/\[a-g\]ile:$okperm runchecktest "SQUARE BRACES 2" pass $file # PASS TEST - looking for ? genprofile ${tmpdir}/\?ile:$okperm runchecktest "QUESTION MARK" pass $file # FAIL TEST - looking for * genprofile ${tmpdir}/z\*:$okperm runchecktest "SINGLE TAILGLOB (fail)" fail $file # FAIL TEST - looking for ** genprofile /does_not_exist\*\*:$okperm runchecktest "DOUBLE TAILGLOB (fail)" fail $file # FAIL TEST - looking for { , } genprofile ${tmpdir}/\{elif,nofile\}:$okperm runchecktest "CURLY BRACES (fail)" fail $file # FAIL TEST - looking for [] genprofile ${tmpdir}/\[aeg\]ile:$okperm runchecktest "SQUARE BRACES 1 (fail)" fail $file # FAIL TEST - looking for [] genprofile ${tmpdir}/\[g-j\]ile:$okperm runchecktest "SQUARE BRACES 2 (fail)" fail $file # FAIL TEST - looking for ? genprofile ${tmpdir}/\?ine:$okperm runchecktest "QUESTION MARK (fail)" fail $file # FAIL TEST - looking for literal '*' followed by ** # TEST for https://bugs.wirex.com/show_bug.cgi?id=2895 genprofile "${tmpdir}/file\*/beta**:$okperm" runchecktest "GLOB FOLLOWED BY DOUBLE TAILGLOB (fail)" fail ${file} # FAIL TEST - looking for literal '*' followed by ** # TEST for https://bugs.wirex.com/show_bug.cgi?id=2895 genprofile "${tmpdir}/file\*/beta**:$okperm" runchecktest "GLOB FOLLOWED BY DOUBLE TAILGLOB (fail)" fail ${file2} settest exec file=/bin/true okperm=rix baderm=r # PASS TEST - looking for * genprofile /bin/\*:$okperm runchecktest "SINGLE TAILGLOB (exec)" pass $file # PASS TEST - looking for ** genprofile /bi\*\*:$okperm runchecktest "DOUBLE TAILGLOB (exec)" pass $file # PASS TEST - looking for { , } genprofile /bin/\{true,false\}:$okperm runchecktest "CURLY BRACES (exec)" pass $file # PASS TEST - looking for [] genprofile /bin/\[aeft\]rue:$okperm runchecktest "SQUARE BRACES 1 (exec)" pass $file # PASS TEST - looking for [] genprofile /bin/\[s-v\]rue:$okperm runchecktest "SQUARE BRACES 2 (exec)" pass $file # PASS TEST - looking for ? genprofile /bin/t\?ue:$okperm runchecktest "QUESTION MARK (exec)" pass $file # FAIL TEST - looking for * genprofile /sbin/\*:$okperm runchecktest "SINGLE TAILGLOB (exec, fail)" fail $file # FAIL TEST - looking for ** genprofile /sbi\*\*:$okperm runchecktest "DOUBLE TAILGLOB (exec, fail)" fail $file # FAIL TEST - looking for { , } genprofile /bin/\{flase,false\}:$okperm runchecktest "CURLY BRACES (exec, fail)" fail $file # FAIL TEST - looking for [] genprofile /bin/\[aef\]rue:$okperm runchecktest "SQUARE BRACES 1 (exec, fail)" fail $file # FAIL TEST - looking for [] genprofile /bin/\[u-x\]rue:$okperm runchecktest "SQUARE BRACES 2 (exec, fail)" fail $file # FAIL TEST - looking for ? genprofile /bin/b\?ue:$okperm runchecktest "QUESTION MARK (exec, fail)" fail $file apparmor-2.8.95~2430/tests/regression/apparmor/rw.sh0000755000175000017500000000275711703327712022205 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME rw #=DESCRIPTION # This test verifies read/write operation. AppArmor caches a successful open # but checks (on read/write) to see if a confined processes profile has been # replaced asynchronously. If it has, access is reevaluated. The test waits # for a signal at which point it reattempts to write, read and verify data. The # controlling script performs a profile replacement before sending the signal # for the test to reattempt the io. #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc file=$tmpdir/src okperm=rw badperm=r # PASS TEST (pt 1) genprofile $file:$okperm runtestbg "READ/WRITE pass" pass $file sleep 2 # PASS TEST (pt 2) kill -USR1 $_pid checktestbg rm -f $file # Disabled revalidation/revocation test as this can not be reliably tested # at this time ## FAILURE TEST (pt 1) # #genprofile $file:$okperm # #runtestbg "READ/WRITE fail" fail $file # #sleep 2 # ## FAILURE TEST (pt 2) # #genprofile $file:$badperm # ## problem the shell and the test program are racing, after profile replacement ## if the shell runs immediately after profile replacement instead of the ## test program it will will. We insert a small sleep to make this unlikely # #sleep 1 # #kill -USR1 $_pid # #checktestbg apparmor-2.8.95~2430/tests/regression/apparmor/rename.c0000644000175000017500000000136211503736226022622 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include #include #include int main(int argc, char *argv[]) { int retval; if (argc != 3){ fprintf(stderr, "usage: %s oldfile newfile\n", argv[0]); return 1; } retval=rename(argv[1], argv[2]); if (retval == -1){ fprintf(stderr, "FAIL: rename from %s to %s failed - %s\n", argv[1], argv[2], strerror(errno)); return errno; } printf("PASS\n"); return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/fchgrp.c0000644000175000017500000000175311503736226022630 0ustar sarnoldsarnold/* * Copyright (C) 2002-2007 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. */ #include #include #include #include #include #include #include int main(int argc, char *argv[]) { gid_t gid; int fd; if (argc != 3) { fprintf(stderr, "usage: %s file groupname|gid\n", argv[0]); return 1; } if (sscanf(argv[2], "%d", &gid) != 1) { fprintf(stderr, "FAIL: bad gid %s\n", argv[2]); return 1; } fd = open(argv[1], O_RDONLY); if (fd == -1) { fprintf(stderr, "FAIL: open %s failed - %s\n", argv[1], strerror(errno)); perror("FAIL: open"); return 1; } if (fchown(fd, -1, gid) == -1) { fprintf(stderr, "FAIL: fchgrp %s %d failed - %s\n", argv[1], gid, strerror(errno)); return 1; } printf("PASS\n"); return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/syscall_sysctl.c0000644000175000017500000000416611503736226024433 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include #include #include #define BUFSIZE 4096 int main(int argc, char *argv[]) { int save_max_threads, new_max_threads, read_new_max_threads; size_t save_sz = sizeof(save_max_threads); int name[] = {CTL_KERN, KERN_MAX_THREADS}; int readonly = 0; if ((argc > 1) && strcmp(argv[1],"ro") == 0) readonly = 1; if (sysctl(name, sizeof(name), &save_max_threads, &save_sz, NULL, 0) == -1){ fprintf(stderr, "FAIL: sysctl read failed - %s\n", strerror(errno)); return 1; } /* printf("Kernel max threads (saved) is %d\n", save_max_threads); */ if (readonly) { printf ("PASS\n"); return 0; } new_max_threads = save_max_threads + 1024; if (sysctl(name, sizeof(name), NULL, 0, &new_max_threads, save_sz) == -1){ fprintf(stderr, "FAIL: sysctl write failed - %s\n", strerror(errno)); return 1; } if (sysctl(name, sizeof(name), &read_new_max_threads, &save_sz, NULL, 0) == -1){ fprintf(stderr, "FAIL: sysctl read failed - %s\n", strerror(errno)); return 1; } /* printf("Kernel max threads (new) is %d\n", read_new_max_threads); */ if (read_new_max_threads != new_max_threads) { fprintf(stderr, "FAIL: read value does not match written values\n"); return 1; } if (sysctl(name, sizeof(name), NULL, 0, &save_max_threads, save_sz) == -1){ fprintf(stderr, "FAIL: sysctl write failed - %s\n", strerror(errno)); return 1; } if (sysctl(name, sizeof(name), &read_new_max_threads, &save_sz, NULL, 0) == -1){ fprintf(stderr, "FAIL: sysctl read failed - %s\n", strerror(errno)); return 1; } /* printf("Kernel max threads (saved) is %d\n", read_new_max_threads);*/ if (read_new_max_threads != save_max_threads) { fprintf(stderr, "FAIL: read value does not match written values\n"); return 1; } printf("PASS\n"); return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/readdir.sh0000755000175000017500000000203211730477552023161 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME readdir #=DESCRIPTION # AppArmor requires 'r' permission on a directory in order for a confined task # to be able to read the directory contents. This test verifies this. #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc dir=$tmpdir/tmpdir # x is not really needed, see chdir.sh okperm=rix badperm=ix mkdir $dir # READDIR TEST genprofile $dir/:$okperm runchecktest "READDIR" pass $dir # READDIR TEST (no perm) genprofile $dir/:$badperm runchecktest "READDIR (no perm)" fail $dir # this test is to make sure the raw 'file' rule allows access # to directories genprofile file runchecktest "READDIR 'file' dir" pass $dir # this test is to make sure the raw 'file' rule allows access # to '/' genprofile file runchecktest "READDIR 'file' '/'" pass '/' apparmor-2.8.95~2430/tests/regression/apparmor/xattrs.sh0000755000175000017500000001160611503736226023075 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME xattrs #=DESCRIPTION # This test verifies setting getting and removing xattrs on a file or symlink. # The test is run for each namespace supported by xattrs since its namespace # has its own security constraints (see man 5 attr for full details). # security: get r, set w + CAP_SYS_ADMIN # system: (acl's etc.) fs and kernel dependent (CAP_SYS_ADMIN) # trusted: CAP_SYS_ADMIN # user: for subdomain the relevent file must be in the profile, with r perm # to get xattr, w perm to set or remove xattr. The appriate cap must be # present in the profile as well #=END # User xattrs are not allowed on symlinks and special files system namespace # tests are going to take some work, have todo with acls or caps all system # tests are currently commented until new tests can be developed, then they # can be removed xattrtest() { runchecktest "$3 xattrs in namespace \"$4\" on $1 with perms=$2" $5 $1 $4 $3 } pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc tmpmount=$tmpdir/mountpoint diskimg=$tmpdir/disk.img file=$tmpmount/testfile link=$tmpmount/testlink dir=$tmpmount/testdir/ okperm=rw badperm=r # guarantee fs supports user_xattrs dd if=/dev/zero of=${diskimg} bs=4096 count=4096 2> /dev/null mkfs.ext3 -q -F ${diskimg} mkdir ${tmpmount} mount -o loop,user_xattr ${diskimg} ${tmpmount} touch $file ln -s $file $link mkdir $dir add_attrs() { #set the xattr for thos that passed above again so we can test removing it setfattr -h -n security.sdtest -v hello "$1" setfattr -h -n trusted.sdtest -v hello "$1" if [ "$1" != $link ] ; then setfattr -h -n user.sdtest -v hello "$1" fi } for var in $file $link $dir ; do #write xattr genprofile $var:$badperm xattrtest $var $badperm write security fail #xattrtest $var $badperm write system fail xattrtest $var $badperm write trusted fail if [ $var != $link ] ; then xattrtest $var $badperm write user xfail ; fi genprofile $var:$badperm capability:sys_admin xattrtest $var "$badperm+cap SYS_ADMIN" write security xfail #xattrtest $var "$badperm+cap SYS_ADMIN" write system fail xattrtest $var "$badperm+cap SYS_ADMIN" write trusted xfail if [ $var != $link ] ; then xattrtest $var "$badperm+cap SYS_ADMIN" write user xfail ; fi genprofile $var:$okperm xattrtest $var $okperm write security xpass #xattrtest $var $okperm write system fail xattrtest $var $okperm write trusted fail if [ $var != $link ] ; then xattrtest $var $okperm write user pass ; fi genprofile $var:$okperm capability:sys_admin xattrtest $var "$okperm+cap SYS_ADMIN" write security pass #xattrtest $var "$okperm+cap SYS_ADMIN" write system pass xattrtest $var "$okperm+cap SYS_ADMIN" write trusted pass if [ $var != $link ] ; then xattrtest $var "$okperm+cap SYS_ADMIN" write user pass ; fi #read xattr genprofile $var:$badperm xattrtest $var $badperm read security pass #xattrtest $var $badperm read system fail xattrtest $var $badperm read trusted fail if [ $var != $link ] ; then xattrtest $var $badperm read user pass ; fi genprofile $var:$badperm capability:sys_admin xattrtest $var "$badperm+cap SYS_ADMIN" read security pass #xattrtest $var "$badperm+cap SYS_ADMIN" read system pass xattrtest $var "$badperm+cap SYS_ADMIN" read trusted pass if [ $var != $link ] ; then xattrtest $var "$badperm+cap SYS_ADMIN" read user pass ; fi #remove xattr genprofile $var:$badperm xattrtest $var $badperm remove security fail #xattrtest $var $badperm remove system fail xattrtest $var $badperm remove trusted fail if [ $var != $link ] ; then xattrtest $var $badperm remove user xfail ; fi add_attrs $var genprofile $var:$badperm capability:sys_admin xattrtest $var "$badperm+cap SYS_ADMIN" remove security xfail #xattrtest $var "$badperm+cap SYS_ADMIN" remove system fail xattrtest $var "$badperm+cap SYS_ADMIN" remove trusted xfail if [ $var != $link ] ; then xattrtest $var "$badperm+cap SYS_ADMIN" remove user xfail ; fi add_attrs $var genprofile $var:$okperm xattrtest $var $okperm remove security xpass #xattrtest $var $okperm remove system fail xattrtest $var $okperm remove trusted fail if [ $var != $link ] ; then xattrtest $var $okperm remove user pass ; fi add_attrs $var genprofile $var:$okperm capability:sys_admin xattrtest $var "$okperm+cap SYS_ADMIN" remove security pass #xattrtest $var "$okperm+cap SYS_ADMIN" remove system pass xattrtest $var "$okperm+cap SYS_ADMIN" remove trusted pass if [ $var != $link ] ; then xattrtest $var "$okperm+cap SYS_ADMIN" remove user pass ; fi done umount ${tmpmount} apparmor-2.8.95~2430/tests/regression/apparmor/changehat_fork.sh0000755000175000017500000000431711503736226024514 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME changehat_fork #=DESCRIPTION # As 'changehat' but access checks for hats are verified across a fork #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc file=$tmpdir/file subfile=$tmpdir/file2 okperm=rw touch $file $subfile # NO CHANGEHAT TEST genprofile $file:$okperm runchecktest "NO CHANGEHAT (access parent file)" pass nochange $file runchecktest "NO CHANGEHAT (access sub file)" fail nochange $subfile # CHANGEHAT TEST # Note: As of AppArmor 2.1 (opensuse 10.3) hats are no longer atomic # to profile load/replacement so we need to remove them manually subtest=sub genprofile $file:$okperm hat:$subtest $subfile:$okperm runchecktest "CHANGEHAT (access parent file 1)" fail $subtest $file runchecktest "CHANGEHAT (access sub file)" pass $subtest $subfile echo -n "${testexec}//${subtest}" >/sys/kernel/security/apparmor/.remove # CHANGEHAT TEST -- multiple subprofiles subtest2=sub2 subtest3=sub3 genprofile $file:$okperm hat:$subtest $subfile:$okperm hat:$subtest2 $subfile:$okperm hat:$subtest3 $subfile:$okperm runchecktest "CHANGEHAT (access parent file 2)" fail $subtest $file runchecktest "CHANGEHAT (access sub file)" pass $subtest $subfile runchecktest "CHANGEHAT (access sub file)" pass $subtest2 $subfile runchecktest "CHANGEHAT (access sub file)" pass $subtest3 $subfile echo -n "${testexec}//${subtest}" >/sys/kernel/security/apparmor/.remove echo -n "${testexec}//${subtest2}" >/sys/kernel/security/apparmor/.remove echo -n "${testexec}//${subtest3}" >/sys/kernel/security/apparmor/.remove # CHANGEHAT TEST -- non-existent subprofile access # Should put us into a null-profile # NOTE: As of AppArmor 2.1 (opensuse 10.3) this test now passes as # the change_hat failes but it no longer entires the null profile genprofile $file:$okperm hat:$subtest $subfile:$okperm hat:$subtest2 $subfile:$okperm runchecktest "CHANGEHAT (access parent file 3)" pass $subtest3 $file runchecktest "CHANGEHAT (access sub file)" fail $subtest3 $subfile apparmor-2.8.95~2430/tests/regression/apparmor/syscall.sh0000755000175000017500000000754311423345351023223 0ustar sarnoldsarnold#! /bin/bash # # Copyright (C) 2002-2005 Novell/SUSE # Copyright (C) 2010 Canonical, Ltd. # # 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. #=NAME syscall #=DESCRIPTION # Confined processes are prohibited from executing certain system calls # entirely. This test checks a variety of such syscalls including ptrace, # mknod, sysctl (write), sethostname, setdomainname, ioperm, iopl and reboot. #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc ## ## A. PTRACE ## settest syscall_ptrace # TEST A1 runchecktest "PTRACE with no profile" pass sub # TEST A2. ptrace will fail genprofile runchecktest "PTRACE with confinement" fail sub ## ## B. MKNOD ## mknod_file=$tmpdir/mknod_file mknod_okperm=rw mknod_badperm=r settest syscall_mknod # TEST B1 rm -f $mknod_file runchecktest "MKNOD block (no confinement)" pass b $mknod_file # TEST B2 rm -f $mknod_file runchecktest "MKNOD char (no confinement)" pass c $mknod_file # TEST B3 rm -f $mknod_file runchecktest "MKNOD fifo (no confinement)" pass f $mknod_file # TEST B4 rm -f $mknod_file runchecktest "MKNOD sock (no confinement)" pass s $mknod_file # TEST B5 rm -f $mknod_file runchecktest "MKNOD regular file (no confinement)" pass r $mknod_file # PASS with acceptable permissions genprofile $mknod_file:$mknod_okperm cap:mknod # TEST B6 rm -f $mknod_file runchecktest "MKNOD block (confined)" pass b $mknod_file # TEST B7 rm -f $mknod_file runchecktest "MKNOD char (confined)" pass c $mknod_file genprofile $mknod_file:$mknod_okperm # TEST B8 rm -f $mknod_file runchecktest "MKNOD fifo (confined)" pass f $mknod_file # TEST B9 rm -f $mknod_file runchecktest "MKNOD sock (confined)" pass s $mknod_file # TEST B10 rm -f $mknod_file runchecktest "MKNOD regular file (confined)" pass r $mknod_file # FAIL due to permissions genprofile $mknod_file:$mknod_badperm cap:mknod # TEST B11 rm -f $mknod_file runchecktest "MKNOD block (permissions)" fail b $mknod_file # TEST B12 rm -f $mknod_file runchecktest "MKNOD char (permissions)" fail c $mknod_file # TEST B13 rm -f $mknod_file runchecktest "MKNOD regular file (permissions)" fail r $mknod_file # TEST B14 rm -f $mknod_file runchecktest "MKNOD fifo (permissions)" fail f $mknod_file # TEST B15 rm -f $mknod_file runchecktest "MKNOD sock (permissions)" fail s $mknod_file ## ## C. SYSCTL ## bash syscall_sysctl.sh ## ## D. SETHOSTNAME ## settest syscall_sethostname # TEST D1 runchecktest "SETHOSTNAME (no confinement)" pass dumb.example.com # TEST D2. sethostname will fail genprofile runchecktest "SETHOSTNAME (confinement)" fail another.dumb.example.com ## ## E. SETDOMAINNAME ## settest syscall_setdomainname # TEST D1 runchecktest "SETDOMAINNAME (no confinement)" pass example.com # TEST D2. sethostname will fail genprofile runchecktest "SETDOMAINNAME (confinement)" fail dumb.com #only do the ioperm/iopl tests for x86 derived architectures case `uname -i` in i386 | i486 | i586 | i686 | x86 | x86_64) # But don't run them on xen kernels if [ ! -d /proc/xen ] ; then ## ## F. IOPERM ## settest syscall_ioperm # TEST F1 runchecktest "IOPERM (no confinement)" pass 0 0x3ff # TEST F2. ioperm will fail genprofile runchecktest "IOPERM (confinement)" fail 0 0x3ff ## ## G. IOPL ## settest syscall_iopl # TEST G1 runchecktest "IOPL (no confinement)" pass 3 # TEST G2. iopl will fail genprofile runchecktest "IOPL (confinement)" fail 3 fi esac ## ## I. reboot - you can enable/disable the Ctrl-Alt-Del behavior ## (supposedly) through the reboot(2) syscall, so these ## tests should be safe ## settest syscall_reboot # TEST I1 runchecktest "REBOOT - disable CAD (no confinement)" pass off # TEST I2. reboot will fail genprofile runchecktest "REBOOT - disable CAD (confinement)" fail off apparmor-2.8.95~2430/tests/regression/apparmor/access.sh0000644000175000017500000000410311503736226023000 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME access #=DESCRIPTION # Verify that the access syscall is correctly managed for confined profiles #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc file=$tmpdir/file dir=$tmpdir/dir/ rperm=r rwxperm=rwix wxperm=wix touch $file chmod 777 $file # full perms so discretionary access checks succeed # PASS TEST genprofile $file:$rwxperm runchecktest "ACCESS file r (rwx)" pass $file r runchecktest "ACCESS file rx (rwx)" pass $file rx runchecktest "ACCESS file rwx (rwx)" pass $file rwx genprofile $file:$rperm runchecktest "ACCESS file r (r)" pass $file r runchecktest "ACCESS file rx (r)" xfail $file rx runchecktest "ACCESS file rwx (r)" xfail $file rwx genprofile $file:$wxperm runchecktest "ACCESS file x (wx)" pass $file x runchecktest "ACCESS file w (wx)" pass $file w runchecktest "ACCESS file wx (wx)" pass $file wx genprofile $file:$wxperm runchecktest "ACCESS file r (wx)" xfail $file r runchecktest "ACCESS file rx (wx)" xfail $file rx runchecktest "ACCESS file rwx (wx)" xfail $file rwx # wx are not necessary for directory write or traverse # only r is required mkdir $dir chmod 777 $dir # full perms so discretionary access checks succeed genprofile $dir:$rwxperm runchecktest "ACCESS dir r (rwx)" pass $dir r runchecktest "ACCESS dir rx (rwx)" pass $dir rx runchecktest "ACCESS dir rwx (rwx)" pass $dir rwx genprofile $dir:$rperm runchecktest "ACCESS dir r (r)" pass $dir r runchecktest "ACCESS dir rx (r)" pass $dir rx runchecktest "ACCESS dir rwx (r)" xfail $dir rwx genprofile $dir:$wxperm runchecktest "ACCESS dir x (wx)" pass $dir x runchecktest "ACCESS dir w (wx)" pass $dir w runchecktest "ACCESS dir wx (wx)" pass $dir wx genprofile $dir:$wxperm runchecktest "ACCESS dir r (wx)" xfail $dir r runchecktest "ACCESS dir rx (wx)" xfail $dir rx runchecktest "ACCESS dir rwx (wx)" xfail $dir rwx apparmor-2.8.95~2430/tests/regression/apparmor/syscall_reboot.c0000644000175000017500000000204711503736226024400 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include #include int main(int argc, char *argv[]) { int cmd = RB_ENABLE_CAD; if (argc != 2) { fprintf(stderr, "Usage %s [on|off|reboot]\n", argv[0]); return 1; } if (strcmp(argv[1], "off") == 0) cmd = RB_DISABLE_CAD; else if (strcmp(argv[1], "on") == 0) /* dangerous, a CAD will do a forced reboot (no shutdown) */ cmd = RB_ENABLE_CAD; else if (strcmp(argv[1], "reboot") == 0) /* Aiieee, you could lose data if you do this */ cmd = RB_AUTOBOOT; else { fprintf(stderr, "Usage %s [on|off|reboot]\n", argv[0]); return 1; } if (reboot(cmd) == -1){ fprintf(stderr, "FAIL: reboot failed - %s\n", strerror(errno)); return 1; } printf("PASS\n"); return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/query_label.c0000644000175000017500000001050112221421705023641 0ustar sarnoldsarnold#include #include #include #include #define OPT_EXPECT "--expect=" #define OPT_EXPECT_LEN strlen(OPT_EXPECT) #define OPT_LABEL "--label=" #define OPT_LABEL_LEN strlen(OPT_LABEL) #define OPT_TYPE_DBUS "--dbus=" #define OPT_TYPE_DBUS_LEN strlen(OPT_TYPE_DBUS) static char *progname = NULL; void usage(void) { fprintf(stderr, "Usage: %s --expect=EXPECTED --label=LABEL --CLASS=PERMS QUERY...\n\n", progname); fprintf(stderr, " EXPECTED\tA comma separated list of allow, audit, and/or anything.\n"); fprintf(stderr, "\t\t\"anything\" is a special keyword that matches any condition\n"); fprintf(stderr, "\t\tand cannot be used with other keywords. Additionally,\n"); fprintf(stderr, "\t\tEXPECTED can be empty to indicate neither, allow or audit,\n"); fprintf(stderr, "\t\tin the results.\n"); fprintf(stderr, " LABEL\t\tThe AppArmor label to use in the query\n"); fprintf(stderr, " CLASS\t\tThe rule class and may consist of:\n"); fprintf(stderr, "\t\t dbus\n"); fprintf(stderr, " PERMS\t\tA comma separated list of permissions. Possibilities\n"); fprintf(stderr, "\t\tfor the supported rule classes are:\n"); fprintf(stderr, "\t\t dbus: send,receive,bind\n"); fprintf(stderr, "\t\tAdditionaly, PERMS can be empty to indicate an empty mask\n"); exit(1); } static int parse_expected(int *should_allow, int *should_audit, char *expected) { char *expect; *should_allow = *should_audit = 0; expect = strtok(expected, ","); while (expect) { if (!strcmp(expect, "allow")) { *should_allow = 1; } else if (!strcmp(expect, "audit")) { *should_audit = 1; } else if (!strcmp(expect, "anything")) { *should_allow = *should_audit = -1; } else { fprintf(stderr, "FAIL: unknown expect: %s\n", expect); return 1; } expect = strtok(NULL, ","); } return 0; } static int parse_dbus_perms(uint32_t *mask, char *perms) { char *perm; *mask = 0; perm = strtok(perms, ","); while (perm) { if (!strcmp(perm, "send")) *mask |= AA_DBUS_SEND; else if (!strcmp(perm, "receive")) *mask |= AA_DBUS_RECEIVE; else if (!strcmp(perm, "bind")) *mask |= AA_DBUS_BIND; else { fprintf(stderr, "FAIL: unknown perm: %s\n", perm); return 1; } perm = strtok(NULL, ","); } return 0; } static ssize_t build_query(char **qstr, const char *label, int class, int argc, char **argv) { int size, label_size, i; char *buffer, *to; label_size = strlen(label); size = label_size + 1; for (i = 0; i < argc; i++) { if (argv[i]) size += strlen(argv[i]); } buffer = malloc(size + argc + 1 + AA_QUERY_CMD_LABEL_SIZE); if (!buffer) return -1; to = buffer + AA_QUERY_CMD_LABEL_SIZE; strcpy(to, label); to += label_size; *(to)++ = '\0'; *(to)++ = class; for (i = 0; i < argc; to++, i++) { char *arg = argv[i]; if (!arg) arg = ""; to = stpcpy(to, arg); } *qstr = buffer; /* don't include trailing \0 in size */ return size + argc + AA_QUERY_CMD_LABEL_SIZE; } int main(int argc, char **argv) { char *label, *class_str, *query; int class, should_allow, allowed, should_audit, audited, rc; uint32_t mask; ssize_t query_len; progname = argv[0]; if (argc < 5) usage(); if (!strncmp(argv[1], OPT_EXPECT, OPT_EXPECT_LEN)) { rc = parse_expected(&should_allow, &should_audit, argv[1] + OPT_EXPECT_LEN); if (rc) usage(); } if (!strncmp(argv[2], OPT_LABEL, OPT_LABEL_LEN)) label = argv[2] + OPT_LABEL_LEN; else usage(); class_str = argv[3]; if (!strncmp(class_str, OPT_TYPE_DBUS, OPT_TYPE_DBUS_LEN)) { class = AA_CLASS_DBUS; rc = parse_dbus_perms(&mask, class_str + OPT_TYPE_DBUS_LEN); if (rc) usage(); } else { fprintf(stderr, "FAIL: unknown rule class: %s\n", class_str); usage(); } query_len = build_query(&query, label, class, argc - 4, argv + 4); if (query_len < 0) { fprintf(stderr, "FAIL: failed to allocate memory for query string\n"); exit(1); } rc = aa_query_label(mask, query, query_len, &allowed, &audited); free(query); if (rc < 0) { fprintf(stderr, "FAIL: failed to perform query: %m\n"); exit(1); } if ((should_allow == -1 && should_audit == -1) || (allowed == should_allow && audited == should_audit)) { printf("PASS\n"); } else { fprintf(stderr, "FAIL: the access should %sbe allowed and should %sbe audited\n", allowed ? "" : "not ", audited ? "" : "not "); exit(1); } exit(0); } apparmor-2.8.95~2430/tests/regression/apparmor/dbus_service.c0000644000175000017500000001630612204737773024043 0ustar sarnoldsarnold/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* dbus_service.c Utility program to send messages from the command line * * Copyright (C) 2003 Philip Blundell * Copyright (C) 2013 Canonical, Ltd. * * Originally dbus-send.c from the dbus package. It has been heavily modified * to work within the regression test framework. * * 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; either version 2 of the License, or * (at your option) any later version. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "dbus_common.h" static int terminate = 0; DBusConnection *connection = NULL; DBusError error; DBusBusType type = DBUS_BUS_SESSION; const char *name = NULL; const char *path = NULL; const char *interface = NULL; const char *member = NULL; const char *address = NULL; int session_or_system = FALSE; int log_fd = -1; int lock_fd = 0; static void usage(void) { fprintf(stderr, "Usage: dbus_service [ADDRESS] --name= \n\n" " ADDRESS\t\t--system, --session (default), or --address=ADDR\n" " NAME\t\tthe well-known name to bind to\n" " path\t\tpath to object (such as /org/freedesktop/DBus)\n" " interface\t\tinterface to use (such as org.freedesktop.DBus)\n\n" " The method .Method replies with an empty method_reply message.\n" " The signal .Signal is accepted by the service.\n"); } /** * Returns -1 upon error, 0 when there are no more messages */ static int handle_messages(void) { DBusMessage *message; if (!dbus_connection_read_write(connection, 250)) { fprintf(stderr, "FAIL: Connecion is closed\n"); return -1; } for (;;) { message = dbus_connection_pop_message(connection); if (message == NULL) return 0; log_message(log_fd, "received ", message); if (dbus_message_is_signal(message, interface, "Signal")) { dbus_message_unref(message); continue; } else if (dbus_message_is_method_call (message, interface, "Method")) { DBusMessage *reply; reply = dbus_message_new_method_return(message); dbus_message_unref(message); log_message(log_fd, "sent ", reply); dbus_connection_send(connection, reply, NULL); dbus_connection_flush(connection); dbus_message_unref(reply); continue; } else if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL) { DBusMessage *reply; reply = dbus_message_new_error(message, DBUS_ERROR_UNKNOWN_METHOD, NULL); dbus_message_unref(message); log_message(log_fd, "sent ", reply); dbus_connection_send(connection, reply, NULL); dbus_connection_flush(connection); dbus_message_unref(reply); continue; } else { dbus_message_unref(message); continue; } } return 0; } void sigterm_handler(int signum) { terminate = 1; } static int setup_signal_handling(void) { struct sigaction sa; int rc; sa.sa_handler = sigterm_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; rc = sigaction(SIGTERM, &sa, NULL); if (rc < 0) { fprintf(stderr, "FAIL: Could not set up signal handling\n"); return 1; } return 0; } static int unlock_fd(void) { int rc; if (lock_fd < 0) return 0; rc = flock(lock_fd, LOCK_UN); if (rc < 0) fprintf(stderr, "FAIL: Failed to unlock lock file: %m\n"); return rc; } static int do_service(void) { int rc; rc = dbus_bus_request_name(connection, name, DBUS_NAME_FLAG_REPLACE_EXISTING, &error); if (dbus_error_is_set(&error)) { fprintf(stderr, "FAIL: %s: %s\n", error.name, error.message); dbus_error_free(&error); } if (rc != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { return 1; } if (unlock_fd()) return 1; rc = 0; while (!terminate && !rc) rc = handle_messages(); /* If we've received SIGTERM, try one last time to drain the incoming queue */ if (terminate && !rc) rc = handle_messages(); if (rc < 0) return 1; rc = dbus_bus_release_name(connection, name, &error); if (dbus_error_is_set(&error)) { fprintf(stderr, "FAIL: %s: %s\n", error.name, error.message); dbus_error_free(&error); } if (rc != DBUS_RELEASE_NAME_REPLY_RELEASED) { return 1; } return 0; } int main(int argc, char *argv[]) { int i, rc; if (argc < 3) { usage(); rc = 1; goto out; } rc = setup_signal_handling(); if (rc != 0) { fprintf(stderr, "FAIL: Couldn't set up signal handler\n"); rc = 1; goto out; } for (i = 1; i < argc && interface == NULL; i++) { char *arg = argv[i]; if (strcmp(arg, "--system") == 0) { type = DBUS_BUS_SYSTEM; session_or_system = TRUE; } else if (strcmp(arg, "--session") == 0) { type = DBUS_BUS_SESSION; session_or_system = TRUE; } else if (strstr(arg, "--address") == arg) { address = strchr(arg, '='); if (address == NULL) { fprintf(stderr, "FAIL: \"--address=\" requires an ADDRESS\n"); usage(); rc = 1; goto out; } else { address = address + 1; } } else if (strstr(arg, "--name=") == arg) name = strchr(arg, '=') + 1; else if (strstr(arg, "--log=") == arg) { char *path = strchr(arg, '=') + 1; log_fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (log_fd < 0) { fprintf(stderr, "FAIL: Couldn't open log file \"%s\"\n", path); exit(1); } } else if (strstr(arg, "--lock-fd=") == arg) { char *fd = strchr(arg, '=') + 1; lock_fd = atoi(fd); } else if (!strcmp(arg, "--help")) { usage(); rc = 0; goto out; } else if (arg[0] == '-') { usage(); rc = 1; goto out; } else if (path == NULL) path = arg; else /* interface == NULL guaranteed by the 'while' loop */ interface = arg; } if (name == NULL || path == NULL || interface == NULL || i < argc) { usage(); rc = 1; goto out; } if (session_or_system && (address != NULL)) { fprintf(stderr, "FAIL: \"--address\" may not be used with \"--system\" or \"--session\"\n"); usage(); rc = 1; goto out; } dbus_error_init(&error); if (address != NULL) connection = dbus_connection_open(address, &error); else connection = dbus_bus_get(type, &error); if (connection == NULL) { fprintf(stderr, "FAIL: Failed to open connection to \"%s\" message bus: %s\n", address ? address : ((type == DBUS_BUS_SYSTEM) ? "system" : "session"), error.message); dbus_error_free(&error); rc = 1; goto out; } else if (address != NULL) dbus_bus_register(connection, &error); rc = do_service(); out: if (connection) dbus_connection_unref(connection); unlock_fd(); if (rc == 0) printf("PASS\n"); exit(rc); } apparmor-2.8.95~2430/tests/regression/apparmor/mkprofile.pl0000755000175000017500000001766012217051110023530 0ustar sarnoldsarnold#! /usr/bin/perl -w # # mkprofile.pl - # generate a formatted profile based on passed in arguments # # Gawd, I hate writing perl. It shows, too. # my $__VERSION__=$0; use strict; use Getopt::Long; use Cwd 'realpath'; my $help = ''; my $nowarn = ''; my $nodefault; my $noimage; my $escape = ''; my $usestdin = ''; my %output_rules; my $hat = "__no_hat"; my %flags; GetOptions( 'escape|E' => \$escape, 'nowarn' => \$nowarn, 'help|h' => \$help, 'nodefault|N' => \$nodefault, 'noimage|I' => \$noimage, 'stdin' => \$usestdin, ); sub usage { print STDERR "$__VERSION__\n"; print STDERR "Usage $0 [--nowarn|--escape] execname [rules]\n"; print STDERR " $0 --help\n"; print STDERR " $0 --stdin\n"; print STDERR " nowarn: don't warn if execname does not exist\n"; print STDERR " nodefault: don't include default rules/ldd output\n"; print STDERR " escape: escape stuff that would be treated as regexs\n"; print STDERR " help: print this message\n"; } # genprofile passes in $bin:w as default rule atm &usage && exit 0 if ($help || (!$usestdin && @ARGV < 1) || ($usestdin && @ARGV != 2)); sub head ($) { my $file = shift; my $first = ""; if (open(FILE, $file)) { $first = ; close(FILE); } return $first; } sub get_output ($@) { my ($program, @args) = @_; my $ret = -1; my $pid; my @output; if (-x $program) { $pid = open(KID_TO_READ, "-|"); unless (defined $pid) { die "can't fork: $!"; } if ($pid) { while () { chomp; push @output, $_; } close(KID_TO_READ); $ret = $?; } else { ($>, $)) = ($<, $(); open(STDERR, ">&STDOUT") || die "can't dup stdout to stderr"; exec($program, @args) || die "can't exec program: $!"; # NOTREACHED } } return ($ret, @output); } sub gen_default_rules() { gen_file("/etc/ld.so.cache:r"); # give every profile access to change_hat gen_file("/proc/*/attr/current:w"); # give every profile access to /dev/urandom (propolice, etc.) gen_file("/dev/urandom:r"); } sub gen_elf_binary($) { my $bin = shift; my ($ret, @ldd) = get_output("/usr/bin/ldd", $bin); if ($ret == 0) { for my $line (@ldd) { last if $line =~ /not a dynamic executable/; last if $line =~ /cannot read header/; last if $line =~ /statically linked/; # avoid new kernel 2.6 poo next if $line =~ /linux-(gate|vdso(32|64)).so/; if ($line =~ /^\s*\S+ => (\/\S+)/) { # shared libraries gen_file(realpath($1) . ":mr") } elsif ($line =~ /^\s*(\/\S+)/) { # match loader lines like "/lib64/ld-linux-x86-64.so.2 (0x00007fbb46999000)" gen_file(realpath($1) . ":rix") } } } } sub gen_binary($) { my $bin = shift; gen_file("$bin:rix") unless $noimage; my $hashbang = head($bin); if ($hashbang && $hashbang =~ /^#!\s*(\S+)/) { my $interpreter = $1; gen_file("$interpreter:rix"); gen_elf_binary($interpreter); } else { gen_elf_binary($bin) } } sub gen_netdomain($) { my $rule = shift; # only split on single ':'s my @rules = split (/(?) { chomp; if ($_ =~ m/@\{gen_def}/) { gen_default_rules(); emit_and_clear_rules(); } elsif ($_ =~ m/@\{gen_bin\s+(.+)\}/) { gen_binary($1); emit_and_clear_rules(); } elsif ($_ =~ m/@\{gen_elf\s+(.+)\}/) { gen_elf_binary($1); emit_and_clear_rules(); } elsif ($_ =~ m/@\{gen\s+(.+)\}/) { gen_default_rules(); gen_binary($1); emit_and_clear_rules(); } else { print STDOUT "$_\n" ; } } } if ($usestdin) { gen_from_stdin(); } else { gen_from_args(); } apparmor-2.8.95~2430/tests/regression/apparmor/environ.sh0000644000175000017500000000727411703541307023227 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME environ #=DESCRIPTION # verify bprm_unsafe filtering occurs for Px and Ux. # #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc helper=$pwd/env_check setuid_helper=${tmpdir}/env_check helper_sh=$pwd/env_check.sh # TEST environment filtering on elf binaries genprofile $helper:ux runchecktest "ENVIRON (elf): ux & regular env" pass $helper FOO=BAR runchecktest "ENVIRON (elf): ux & sensitive env" pass $helper LD_LIBRARY_PATH=. genprofile $helper:Ux runchecktest "ENVIRON (elf): Ux & regular env" pass $helper FOO=BAR runchecktest "ENVIRON (elf): Ux & sensitive env" fail $helper LD_LIBRARY_PATH=. genprofile $helper:rix runchecktest "ENVIRON (elf): ix & regular env" pass $helper FOO=BAR runchecktest "ENVIRON (elf): ix & sensitive env" pass $helper LD_LIBRARY_PATH=. genprofile $helper:px -- image=$helper runchecktest "ENVIRON (elf): px & regular env" pass $helper FOO=BAR runchecktest "ENVIRON (elf): px & sensitive env" pass $helper LD_LIBRARY_PATH=. genprofile $helper:Px -- image=$helper runchecktest "ENVIRON (elf): Px & regular env" pass $helper FOO=BAR runchecktest "ENVIRON (elf): Px & sensitive env" fail $helper LD_LIBRARY_PATH=. genprofile image=$helper runchecktest "ENVIRON (elf): unconfined --> confined & regular env" pass $helper FOO=BAR runchecktest "ENVIRON (elf): unconfined --> confined & sensitive env" pass $helper LD_LIBRARY_PATH=. genprofile -C runchecktest "ENVIRON (elf): confined/complain & regular env" pass $helper FOO=BAR runchecktest "ENVIRON (elf): confined/complain & sensitive env" pass $helper LD_LIBRARY_PATH=. # TEST environment filtering on shell scripts genprofile ${helper_sh}:ux runchecktest "ENVIRON (shell script): ux & regular env" pass ${helper_sh} FOO=BAR runchecktest "ENVIRON (shell script): ux & sensitive env" pass ${helper_sh} LD_LIBRARY_PATH=. genprofile ${helper_sh}:Ux runchecktest "ENVIRON (shell script): Ux & regular env" pass ${helper_sh} FOO=BAR runchecktest "ENVIRON (shell script): Ux & sensitive env" fail ${helper_sh} LD_LIBRARY_PATH=. genprofile ${helper_sh}:px -- image=${helper_sh} runchecktest "ENVIRON (shell script): px & regular env" pass ${helper_sh} FOO=BAR runchecktest "ENVIRON (shell script): px & sensitive env" pass ${helper_sh} LD_LIBRARY_PATH=. genprofile ${helper_sh}:Px -- image=${helper_sh} runchecktest "ENVIRON (shell script): Px & regular env" pass ${helper_sh} FOO=BAR runchecktest "ENVIRON (shell script): Px & sensitive env" fail ${helper_sh} LD_LIBRARY_PATH=. genprofile addimage:${helper_sh} runchecktest "ENVIRON (shell script): ix & regular env" pass ${helper_sh} FOO=BAR runchecktest "ENVIRON (shell script): ix & sensitive env" pass ${helper_sh} LD_LIBRARY_PATH=. genprofile image=${helper_sh} runchecktest "ENVIRON (shell script): unconfined --> confined & regular env" pass ${helper_sh} FOO=BAR runchecktest "ENVIRON (shell script): unconfined --> confined & sensitive env" pass ${helper_sh} LD_LIBRARY_PATH=. genprofile -C runchecktest "ENVIRON (shell script): confined/complain & regular env" pass ${helper_sh} FOO=BAR runchecktest "ENVIRON (shell script): confined/complain & sensitive env" pass ${helper_sh} LD_LIBRARY_PATH=. # TEST environment filtering still works on setuid apps removeprofile cp $helper ${setuid_helper} chown nobody ${setuid_helper} chmod u+s ${setuid_helper} runchecktest "ENVIRON (elf): unconfined setuid helper" pass ${setuid_helper} FOO=BAR runchecktest "ENVIRON (elf): unconfined setuid helper" fail ${setuid_helper} LD_LIBRARY_PATH=. apparmor-2.8.95~2430/tests/regression/apparmor/syscall_sethostname.c0000644000175000017500000000354711503736226025446 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include /* this implicitly tests gethostname, too. */ #define RESET_HOSTNAME_WHEN_DONE #define BUFSIZE 4096 int main(int argc, char *argv[]) { char saved_hostname[BUFSIZE]; char new_hostname[BUFSIZE]; size_t len = sizeof(saved_hostname); size_t newlen; int error = 0; if (argc != 2) { fprintf(stderr, "usage: %s hostname\n", argv[0]); return 1; } newlen = strlen(argv[1]); if (newlen <= 0 || newlen >= BUFSIZE) { fprintf(stderr, "FAIL: invalid hostname '%s'\n", argv[1]); return 1; } if (gethostname(saved_hostname, len) == -1) { fprintf(stderr, "FAIL: gethostname failed - %s\n", strerror(errno)); return 1; } /* printf("old hostname is %s\n", saved_hostname); */ if (sethostname(argv[1], strlen(argv[1])) == -1) { fprintf(stderr, "FAIL: sethostname failed - %s\n", strerror(errno)); return 1; } len = sizeof(new_hostname); if (gethostname(new_hostname, len) == -1) { fprintf(stderr, "FAIL: gethostname failed - %s\n", strerror(errno)); error = 1; goto cleanup; } if (strcmp(new_hostname, argv[1]) != 0) { fprintf(stderr, "FAIL: attempted to set hostname to '%s', but " "'%s' was the result\n", argv[1], new_hostname); error = 1; goto cleanup; } cleanup: #ifdef RESET_HOSTNAME_WHEN_DONE if (sethostname(saved_hostname, strlen(saved_hostname)) == -1) { fprintf(stderr, "FAIL: sethostname failed restting to old name - %s\n", strerror(errno)); error = 1; } #endif /* RESET_HOSTNAME_WHEN_DONE */ if (error == 0) printf("PASS\n"); return error; } apparmor-2.8.95~2430/tests/regression/apparmor/AppArmor.rtf0000644000175000017500000025123410417022466023447 0ustar sarnoldsarnold{\rtf1\ansi\deff1\adeflang1025 {\fonttbl{\f0\froman\fprq2\fcharset0 Nimbus Roman No9 L{\*\falt Times New Roman};}{\f1\froman\fprq2\fcharset0 Nimbus Roman No9 L{\*\falt Times New Roman};}{\f2\froman\fprq2\fcharset0 Times New Roman;}{\f3\froman\fprq2\fcharset0 Nimbus Roman No9 L{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset0 Nimbus Sans L{\*\falt Arial};}{\f5\fswiss\fprq2\fcharset0 Arial;}{\f6\fmodern\fprq0\fcharset0 Courier New;}{\f7\fnil\fprq0\fcharset2 StarSymbol;}{\f8\froman\fprq2\fcharset2 Symbol;}{\f9\fnil\fprq2\fcharset2 Wingdings;}{\f10\fmodern\fprq0\fcharset0 Courier;}{\f11\fnil\fprq2\fcharset0 Andale Sans UI{\*\falt Arial Unicode MS};}{\f12\froman\fprq0\fcharset0 MS Mincho{\*\falt \u65325 ?\u65331 ? \u26126 ?\u26397 ?};}{\f13\fnil\fprq2\fcharset0 Lucidasans;}{\f14\fnil\fprq0\fcharset0 Lucidasans;}} {\colortbl;\red0\green0\blue0;\red128\green128\blue128;} {\stylesheet{\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\aspalpha\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033\snext1 Default;} {\s2\sa120\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033\sbasedon1\snext2 Text body;} {\s3{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af14\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033\sbasedon2\snext3 List;} {\s4\sb120\sa120\rtlch\af14\afs20\lang255\ai\ltrch\dbch\af11\afs20\langfe255\ai\loch\fs20\lang1033\i\sbasedon1\snext4 Caption;} {\s5\rtlch\af14\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033\sbasedon1\snext5 Index;} {\s6\sb240\sa120\keepn\rtlch\afs28\lang255\ltrch\dbch\afs28\langfe255\loch\f4\fs28\lang1033\sbasedon1\snext2 Heading;} {\s7\sb240\sa60\keepn\rtlch\af5\afs32\lang255\ab\ltrch\dbch\af11\afs32\langfe255\ab\loch\f5\fs32\lang1033\b\sbasedon1\snext1{\*\soutlvl0} Heading 1;} {\s8\sb240\sa60\keepn\rtlch\af5\afs28\lang255\ai\ab\ltrch\dbch\af11\afs28\langfe255\ai\ab\loch\f5\fs28\lang1033\i\b\sbasedon1\snext1{\*\soutlvl1} Heading 2;} {\s9\rtlch\af6\afs20\lang255\ltrch\dbch\af11\afs20\langfe255\loch\f6\fs20\lang1033\sbasedon1\snext9 WW-Plain Text;} {\*\cs11\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 2 1;} {\*\cs12\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 2 2;} {\*\cs13\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 2 3;} {\*\cs14\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 2 4;} {\*\cs15\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 2 5;} {\*\cs16\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 2 6;} {\*\cs17\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 2 7;} {\*\cs18\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 2 8;} {\*\cs19\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 2 9;} {\*\cs20\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 2 10;} {\*\cs21\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 1;} {\*\cs22\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 2;} {\*\cs23\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 3;} {\*\cs24\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 4;} {\*\cs25\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 5;} {\*\cs26\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 6;} {\*\cs27\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 7;} {\*\cs28\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 8;} {\*\cs29\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 9;} {\*\cs30\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 10;} {\*\cs31\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 3 1;} {\*\cs32\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 3 2;} {\*\cs33\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 3 3;} {\*\cs34\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 3 4;} {\*\cs35\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 3 5;} {\*\cs36\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 3 6;} {\*\cs37\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 3 7;} {\*\cs38\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 3 8;} {\*\cs39\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 3 9;} {\*\cs40\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 3 10;} {\*\cs41\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 1;} {\*\cs42\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 2;} {\*\cs43\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 3;} {\*\cs44\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 4;} {\*\cs45\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 5;} {\*\cs46\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 6;} {\*\cs47\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 7;} {\*\cs48\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 8;} {\*\cs49\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 9;} {\*\cs50\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 RTF_Num 3 10;} {\*\cs51\rtlch\af8\afs24\lang255\ltrch\dbch\af8\afs24\langfe255\loch\f8\fs24\lang1033 RTF_Num 5 1;} {\*\cs52\rtlch\af6\afs24\lang255\ltrch\dbch\af6\afs24\langfe255\loch\f6\fs24\lang1033 RTF_Num 5 2;} {\*\cs53\rtlch\af9\afs24\lang255\ltrch\dbch\af9\afs24\langfe255\loch\f9\fs24\lang1033 RTF_Num 5 3;} {\*\cs54\rtlch\af8\afs24\lang255\ltrch\dbch\af8\afs24\langfe255\loch\f8\fs24\lang1033 RTF_Num 5 4;} {\*\cs55\rtlch\af6\afs24\lang255\ltrch\dbch\af6\afs24\langfe255\loch\f6\fs24\lang1033 RTF_Num 5 5;} {\*\cs56\rtlch\af9\afs24\lang255\ltrch\dbch\af9\afs24\langfe255\loch\f9\fs24\lang1033 RTF_Num 5 6;} {\*\cs57\rtlch\af8\afs24\lang255\ltrch\dbch\af8\afs24\langfe255\loch\f8\fs24\lang1033 RTF_Num 5 7;} {\*\cs58\rtlch\af6\afs24\lang255\ltrch\dbch\af6\afs24\langfe255\loch\f6\fs24\lang1033 RTF_Num 5 8;} {\*\cs59\rtlch\af9\afs24\lang255\ltrch\dbch\af9\afs24\langfe255\loch\f9\fs24\lang1033 RTF_Num 5 9;} {\*\cs60\cf0\rtlch\af1\afs24\lang255\ltrch\dbch\af1\afs24\langfe255\loch\f1\fs24\lang1033 RTF_Num 5 10;} {\*\cs61\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 4 1;} {\*\cs62\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 4 2;} {\*\cs63\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 4 3;} {\*\cs64\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 4 4;} {\*\cs65\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 4 5;} {\*\cs66\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 4 6;} {\*\cs67\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 4 7;} {\*\cs68\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 4 8;} {\*\cs69\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 4 9;} {\*\cs70\rtlch\afs24\lang255\ltrch\dbch\afs24\langfe255\loch\fs24\lang1033 RTF_Num 4 10;} {\*\cs71\cf0\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\f1\fs24\lang1033 Numbering Symbols;} {\*\cs72\cf0\rtlch\af7\afs18\lang255\ltrch\dbch\af7\afs18\langfe255\loch\f7\fs18\lang1033 Bullets;} {\*\cs73\cf0\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\f8\fs24\lang1033 WW8Num1z0;} {\*\cs74\cf0\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\f6\fs24\lang1033 WW8Num1z1;} {\*\cs75\cf0\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\f9\fs24\lang1033 WW8Num1z2;} {\*\cs76\cf0\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\f1\fs24\lang1033 WW-Default Paragraph Font;} }{\*\listtable{\list\listtemplateid1 {\listlevel\levelnfc0\leveljc0\levelstartat3\levelfollow2{\leveltext \'02\'00.;}{\levelnumbers\'01;}\f8\f8\f8\fi-283\li283} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'01.;}{\levelnumbers\'01;}\f6\f6\f6\fi-283\li567} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'02.;}{\levelnumbers\'01;}\f9\f9\f9\fi-283\li850} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'03.;}{\levelnumbers\'01;}\f8\f8\f8\fi-283\li1134} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'04.;}{\levelnumbers\'01;}\f6\f6\f6\fi-283\li1417} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'05.;}{\levelnumbers\'01;}\f9\f9\f9\fi-283\li1701} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'06.;}{\levelnumbers\'01;}\f8\f8\f8\fi-283\li1984} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'07.;}{\levelnumbers\'01;}\f6\f6\f6\fi-283\li2268} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'08.;}{\levelnumbers\'01;}\f9\f9\f9\fi-283\li2551} {\*\soutlvl{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'09.;}{\levelnumbers\'01;}\fi-283\li2835}}{\listname RTF_Num 5;}\listid1} {\list\listtemplateid2 {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9679 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f7\fi-283\li283} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9679 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f7\fi-283\li567} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9679 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f7\fi-283\li850} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9679 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f7\fi-283\li1134} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9679 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f7\fi-283\li1417} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9679 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f7\fi-283\li1701} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9679 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f7\fi-283\li1984} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9679 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f7\fi-283\li2268} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9679 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f7\fi-283\li2551} {\*\soutlvl{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9679 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f7\fi-283\li2835}}{\listname RTF_Num 3;}\listid2} {\list\listtemplateid3 {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'00.;}{\levelnumbers\'01;}\fi-283\li283} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'01.;}{\levelnumbers\'01;}\fi-283\li567} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'02.;}{\levelnumbers\'01;}\fi-283\li850} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'03.;}{\levelnumbers\'01;}\fi-283\li1134} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'04.;}{\levelnumbers\'01;}\fi-283\li1417} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'05.;}{\levelnumbers\'01;}\fi-283\li1701} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'06.;}{\levelnumbers\'01;}\fi-283\li1984} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'07.;}{\levelnumbers\'01;}\fi-283\li2268} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'08.;}{\levelnumbers\'01;}\fi-283\li2551} {\*\soutlvl{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'09.;}{\levelnumbers\'01;}\fi-283\li2835}}{\listname RTF_Num 4;}\listid3} {\list\listtemplateid4\listsimple {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u61623 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f8\fi-360\li720} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u111 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f6\fi-360\li1440} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u61607 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f9\fi-360\li2160} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u61623 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f8\fi-360\li2880} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u111 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f6\fi-360\li3600} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u61607 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f9\fi-360\li4320} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u61623 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f8\fi-360\li5040} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u111 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f6\fi-360\li5760} {\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u61607 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f9\fi-360\li6480} {\*\soutlvl{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u61623 ?;}{\levelnumbers;}\f7\fs18\f7\fs18\f7\fs18\f8\fi-360\li7200}}{\listname WW8Num1;}\listid4} {\list\listtemplateid5 {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'00.;}{\levelnumbers\'01;}\fi-283\li283} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'01.;}{\levelnumbers\'01;}\fi-283\li567} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'02.;}{\levelnumbers\'01;}\fi-283\li850} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'03.;}{\levelnumbers\'01;}\fi-283\li1134} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'04.;}{\levelnumbers\'01;}\fi-283\li1417} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'05.;}{\levelnumbers\'01;}\fi-283\li1701} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'06.;}{\levelnumbers\'01;}\fi-283\li1984} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'07.;}{\levelnumbers\'01;}\fi-283\li2268} {\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'08.;}{\levelnumbers\'01;}\fi-283\li2551} {\*\soutlvl{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'09.;}{\levelnumbers\'01;}\fi-283\li2835}}{\listname RTF_Num 2;}\listid5} }{\listoverridetable{\listoverride\listid1\listoverridecount0\ls0}{\listoverride\listid2\listoverridecount0\ls1}{\listoverride\listid3\listoverridecount0\ls2}{\listoverride\listid4\listoverridecount0\ls3}{\listoverride\listid5\listoverridecount0\ls4}} {\info{\comment StarWriter}{\vern6450}}\deftab720 {\*\pgdsctbl {\pgdsc0\pgdscuse195\pgwsxn12240\pghsxn15840\marglsxn1800\margrsxn1800\margtsxn1440\margbsxn1440\pgdscnxt0 Default;}} {\*\pgdscno0}\paperh15840\paperw12240\margl1800\margr1800\margt1440\margb1440\sectd\sbknone\pgwsxn12240\pghsxn15840\marglsxn1800\margrsxn1800\margtsxn1440\margbsxn1440\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc \pard\plain \sb240\sa60\keepn\f2\fs32\b\f12\fs32\b\f2\fs32\b\qc\aspalpha \ltrpar\s7\qc\aspalpha\sb240\sa60\keepn\rtlch\af2\afs32\lang255\ab\ltrch\dbch\af12\afs32\langfe255\ab\loch\f2\fs32\lang1033\b {\loch\f2\fs32\lang1033\i0\b AppArmor regression test suite} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \sb240\sa60\keepn\f2\fs28\i\b\f12\fs28\i\b\f2\fs28\i\b\aspalpha \ltrpar\s8\aspalpha\sb240\sa60\keepn\ql\rtlch\af2\afs28\lang255\ai\ab\ltrch\dbch\af12\afs28\langfe255\ai\ab\loch\f2\fs28\lang1033\i\b {\loch\f2\fs28\lang1033\i\b Overview} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 The AppArmor regression test suite is designed to be an easily extensible family of tests where the individual tests are highly decomposed. Tests exist to check for regressions in the key areas of AppArmor functionality.} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 At the time of writing there are 26 tests that comprise the regression test suite. A dedicated shell script implements each test. Some of these tests are very simple verifying just a few basic operations ('open' for example) whilst others are highly compl ex involving many subtests and techniques such as iteration over features and comparison of behavior ('capabilities' and 'exec_qual' for example). Regardless of the varying complexity each of these examples (shell scripts) utilize a common format and inf rastructure support.} \par \pard\plain \sb240\sa60\keepn\f2\fs28\i\b\f12\fs28\i\b\f2\fs28\i\b\aspalpha \ltrpar\s8\aspalpha\sb240\sa60\keepn\ql\rtlch\af2\afs28\lang255\ai\ab\ltrch\dbch\af12\afs28\langfe255\ai\ab\loch\f2\fs28\lang1033\i\b {\loch\f2\fs28\lang1033\i\b Running the Test Suite} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 The test suite is designed to be built and run from the main makefile using the command \lquote make tests\rquote . The test suite may be built without running it using the command \lquote make. Individual tests may be sun by the command \lquote sh .sh\rquote . The set of tes tnames is defined in the makefile as variable TESTS.} \par \pard\plain \sb240\sa60\keepn\f2\fs28\i\b\f12\fs28\i\b\f2\fs28\i\b\aspalpha \ltrpar\s8\aspalpha\sb240\sa60\keepn\ql\rtlch\af2\afs28\lang255\ai\ab\ltrch\dbch\af12\afs28\langfe255\ai\ab\loch\f2\fs28\lang1033\i\b {\loch\f2\fs28\lang1033\i\b Parsing the Results} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 As detailed above, each test comprising the test suite is controlled by a dedicated shell script (normally called .sh). This test will cycle through its subtests. Most shell scripts produce no output to the tty for a successful run (although t he controlling makefile outputs \lquote running ' before running each shell script) although some tests (capabilities for example) output the name of each subtest as it is run.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 In the case of an error, the test framework will output a diagnostic to the tty. Diagnostics are divided into two main areas, functional errors and unexpected errors. Functional errors occur are when the test executed but it did not function as expected . For functional failure the test suite will output \lquote Error:\rdblquote followed by a description of the error. Unexpected errors are when either a test did executed in a way whereby the framework could not determine it\rquote s behavior or the framework itself experienced an error.} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 Examples:} \par \pard\plain {\listtext\pard\plain \li720\ri0\lin720\rin0\fi-360\f2\fs20\f12\fs20\f2\fs20\f7\fs18\f7\fs18\f7\fs18 \u61623 ?}\ilvl0 \ltrpar\s9\ls3\li720\ri0\lin720\rin0\fi-360\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 Functional error, test was expected to fail due to receipt of a signal but it passed:} \par \pard\plain \ltrpar\s9\li1440\ri0\lin1440\rin0\fi0\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 Error: changehat_twice passed. Test 'CHANGEHAT (subprofile->subprofile w/ bad magic)' was expected to 'signal9'} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain {\listtext\pard\plain \li720\ri0\lin720\rin0\fi-360\f2\fs20\f12\fs20\f2\fs20\f7\fs18\f7\fs18\f7\fs18 \u61623 ?}\ilvl0 \ltrpar\s9\ls3\li720\ri0\lin720\rin0\fi-360\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 Functional error, test was expected to pass but it failed:} \par \pard\plain \ltrpar\s9\li1440\ri0\lin1440\rin0\fi0\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 Error: open failed. Test 'OPEN RW' was expected to 'pass'.} \par \pard\plain \ltrpar\s9\li1440\ri0\lin1440\rin0\fi0\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 Reason for failure 'FAIL: open /tmp/sdtest.13983-32704-z13990/file failed - Permission denied'} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain {\listtext\pard\plain \li720\ri0\lin720\rin0\fi-360\f2\fs20\f12\fs20\f2\fs20\f7\fs18\f7\fs18\f7\fs18 \u61623 ?}\ilvl0 \ltrpar\s9\ls3\li720\ri0\lin720\rin0\fi-360\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 Unexpected error, unable to execute a test:} \par \pard\plain \ltrpar\s9\li1440\ri0\lin1440\rin0\fi0\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 Fatal Error (open): Unable to run test sub-executable} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain {\listtext\pard\plain \li720\ri0\lin720\rin0\fi-360\f2\fs20\f12\fs20\f2\fs20\f7\fs18\f7\fs18\f7\fs18 \u61623 ?}\ilvl0 \ltrpar\s9\ls3\li720\ri0\lin720\rin0\fi-360\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 Unexpected error< error in shell script (or in framework):} \par \pard\plain \ltrpar\s9\li1440\ri0\lin1440\rin0\fi0\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 Fatal Error (open): Unexpected shell error. } \par \pard\plain \ltrpar\s9\li1440\ri0\lin1440\rin0\fi0\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 Run with -x to debug} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 In dealing with a failure, the first step is to determine reproducibility. As previously mentioned, if you are running all of the tests via \lquote make tests\rquote you can rerun an individual test via \lquote sh .sh\rquote .} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 If the problem is reproducible, running the test with \lquote -r\rquote as the first argument, i.e. \lquote sh .sh -r\rquote will retain the files for the test that failed and the directory path to these files will be output to the tty. In this directory a script called \lquote runtest\rquote will be present which can be used to rerun the first failing component of the test script outside of the test harness where it is easier to determine the problem. } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 Beyond this, knowledge of the actual test may be required to debug the problem further. See \ldblquote Design of Test Suite\rdblquote for technical information on how the tests are implemented.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 In the case of an unexpected error, running with the \lquote -r\rquote option will usually not he helpful as the test script or framework may have a problem. At this point, as indicated by the diagnostic, it is normally necessary to run the script with the -x option ( \lquote sh -x testname.sh\rquote ) and debug either the test script or the base framework. This is not for the uninitiated.} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \sb240\sa60\keepn\f5\fs28\i\b\f12\fs28\i\b\f5\fs28\i\b\aspalpha \ltrpar\s8\aspalpha\sb240\sa60\keepn\ql\rtlch\af5\afs28\lang255\ai\ab\ltrch\dbch\af12\afs28\langfe255\ai\ab\loch\f5\fs28\lang1033\i\b {\loch\f5\fs28\lang1033\i\b Pre-Requisites for running the suite} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 In order to compile the test suite the GNU C compiler (gcc) must be installed on the system plus necessary development tools such as make.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 In order to execute the tests the AppArmor module must be loaded and the AppArmor filesystem (normally /subdomain) must be mounted. In addition the AppArmor parser (normally /sbin/subdomain_parser but an alternate location may be specified in uservars.inc ) must be present. The parser and module must be compatible or errors will result during the loading of profiles during test suite execution.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 You must be root to run the test suite (it is not necessary to be root to build the suite however).} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 Note: Executing /etc/init.d/subdomain start as root will ensure that these requirements are met on a system with the SHASS software correctly installed.} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \sb240\sa60\keepn\f5\fs28\i\b\f12\fs28\i\b\f5\fs28\i\b\aspalpha \ltrpar\s8\aspalpha\sb240\sa60\keepn\ql\rtlch\af5\afs28\lang255\ai\ab\ltrch\dbch\af12\afs28\langfe255\ai\ab\loch\f5\fs28\lang1033\i\b {\loch\f5\fs28\lang1033\i\b Design of Test Suite} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 Each test consists of one controlling shell script and one or more executable files. This is a requirement of the test harness. Practically speaking most of the test functionality is of sufficient complexity that no shell api exists and functionality mus t be implemented in an executable. Currently all executables are written in the C programming language.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 It is important to note that a functioning test is achieved by the correct operation of both the shell script and the executable. The framework requires certain behavior but outside of this the test author is free to design the test as they wish but the sh ell script and executables must agree on their conventions in order to correctly implement the test.} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 The file \lquote prologue.inc\rquote implements most of the underlying shell framework. It must be loaded into the shell script before any other test functionality is performed. You will see it loaded via the shell \lquote .\rquote function at the start of each test shell script.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 By default, prologue.inc assumes the test binary is the same name as the shell script, with \lquote .sh\rquote removed. For test scripts with only one executable this makes things simple. You may want to have a single shell script run multiple executables (syscall.sh for example). In this case, the \lquote settest\rquote function is used to select a new binary executable for this test.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 The \lquote genprofile\rquote function generates a profile based on passed arguments. The function automatically adds the necessary shared libraries and output files necessary to support the execution, it is not necessary to specify these manually. Therefore a call t o genprofile without arguments will build a profile allowing the executable to run but without any additional access (which assuming the test application attempts to access files will most likely cause AppArmor to report a REJECTION). Specifying additional arguments to genprofile in the form of : will allow additional access.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 Genprofile also allows capabilities to be specified, it uses the same syntax as specifying file permissions except the filename component is always the literal \lquote capability\rquote . For example, specifying \lquote capability:admin\rquote as an argument to genprofile will grant the profile CAP_SYS_ADMIN.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 By default genprofile assumes it is creating a profile for the test binary corresponding to the current testname (shell script name minus trailing .sh or the name specified in a subsequent \lquote settest\rquote command). Normally this is sufficient but certain tests require more complex profiles. Genprofile now supports two keywords \lquote subhat\rquote and \lquote image\rdblquote that allow complex profiles to be built which include subhats (necessary if the parent calls changehat) and support for more than one executable (useful if the primar y test wants to pass control to a different executable which also must be controlled by a profile). The \lquote \emdash \lquote separator is used to separate each portion of the argument stream. The separator may be used multiple times on the same argument line but after ea ch use the only token that may follow it is \lquote subhat=\rquote or \lquote image=\rquote .} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 The following is a simple example showing the use of the \lquote subhat\rquote keyword:} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 settest changehat_write} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 genprofile -- subhat=hat1 /tmp/file:rw -- subhat=hat2 /tmp/file2:rw} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 runchecktest "changehat (hat1 w access sub file1)" pass hat1 /tmp/file} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 runchecktest "changehat (hat1 w access sub file2)" fail hat1 /tmp/file2} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 runchecktest "changehat (hat2 w access sub file1)" pass hat2 /tmp/file} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 runchecktest "changehat (hat2 w access sub file2)" fail hat2 /tmp/file2} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 This sequence of actions generates a profile for the executable changehat_write. You will note that \lquote \emdash \lquote is the first argument after genprofile. This terminates the generation of files for the parent profile. This means that the only file entries for the p arent hat will be those shared libraries and other support files automatically generated for changehat_write to begin execution (these files are determined by the function resolve_libs).} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 It also creates two subhats for the profile called \lquote hat1\rquote and \lquote hat2\rquote . Hat1 has rw access to file\rquote /tmp/file\rquote and hat2 has rw access to file \lquote /tmp/file2\rquote .} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 The changehat_write test attempts to change to the specified hat (passed to changehat_write as argv[1]), open, read and write to the file passed as argv[2].} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 It should be obvious from the profile and from the 4 calls to runchecktest (see below for a description of this function) that hat1 only has the necessary access to /tmp/file and hat2 only to /tmp/file2.} \par \pard\plain \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\aspalpha\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 The following is an example showing the use of the \lquote image\rquote keyword:} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 settest fork_child} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 genprofile $bin/fork_child2:px -- image=$bin/fork_child2 /tmp/file1:rw} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 runchecktest "subexecutable w access" fail $bin/fork_child3 /tmp/file1} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 runchecktest "subexecutable w access" pass $bin/fork_child2 /tmp/file1} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 runchecktest "subexecutable w access" fail $bin/fork_child2 /tmp/file2} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\aspalpha\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 This generates two profiles, which are loaded in one operation. First is a profile for \lquote fork_child\rquote (implicitly $bin/fork_child) which in addition to containing access to the necessary library/support files also has \lquote px\rquote (meaning the profile must exist) a ccess to fork_child2. A profile for the executable fork_child2 is also created again with access to the necessary libraries and support files and also with rw access to /tmp/file1. In this example, the executable fork_child forks and execs the file speci fied as its argv[1] passing to this image argv[2] as the child\rquote s argv[1]. The child attempts to open, read and write it\rquote s argv[1]. Clearly the first test fails because the profile for fork_child does not grant execute access to fork_child3. The third te st also fails because although fork_test2 was successfully executed, it\rquote s profile does not allow access to /tmp/file2.} \par \pard\plain \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\aspalpha\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 Executing a test is achieved by calling the \lquote runchecktest\rquote function which will run either the executable matching the name of the shell script, or specified by settest. The first argument is a brief description of what the executable does in this mode, wh ich is displayed in the event of an error. The second argument is either \ldblquote pass\rdblquote or \ldblquote fail\rdblquote indicating whether the test is expected to pass or fail. The executable is expected to output \ldblquote PASS\rdblquote for success and \ldblquote FAIL: \rdblquote in the event of a failu re. If the executable outputs something other than this, the controlling shell script will interpret this as a test failure and output \ldblquote unable to run test sub executable\rdblquote and terminate. Remaining arguments to runchecktest are passed to the executable as argv[1] .. argv[n].} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 The runchecktest command executes and checks the test serially. If a test requires to be run in the background, so that the shell may do subsequent operations, such as sending it a signal before checking it\rquote s output, this is accomplished by separately cal ling \lquote runtestbg\rquote and \lquote checktestbg\rquote instead of calling \lquote runchecktest\rquote .} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 Profile loading, replacing and unloading is automatically handled by the shell script (via prologue.inc). Also, cleanup (tempfile removal and profile unloading) on exit is automatic.} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \sb240\sa60\keepn\f5\fs28\i\b\f12\fs28\i\b\f5\fs28\i\b\aspalpha \ltrpar\s8\aspalpha\sb240\sa60\keepn\ql\rtlch\af5\afs28\lang255\ai\ab\ltrch\dbch\af12\afs28\langfe255\ai\ab\loch\f5\fs28\lang1033\i\b {\loch\f5\fs28\lang1033\i\b Implementing a new test case} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 As an example, the text shell script for exec (exec.sh) is 24 lines and} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 may be used as a template for creating new simple tests (changehat.sh is} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 a good template for subprofile tests and rw.sh is a template for tests} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 requiring signal passing)} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 #! /bin/bash} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 pwd=`dirname $0`} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 pwd=`cd $pwd ; pwd`} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 bin must be set prior to including prologue.inc. This is the only requirement placed on the shell script author by prologue.inc} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 bin=$pwd} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 prologie.inc must be included before running any tests} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 . $bin/prologue.inc} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 variable definitions used by this script} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 file=/bin/true} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 okperm=ix} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 badperm=r} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 # PASS TEST} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 generate a profile allowing ix access to /bin/true} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 genprofile $file:$okperm} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 run this test (exec) passing /bin/true as argv[1]} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 check it's output, it is expected to pass} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 runchecktest "EXEC with x" pass $file} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 # NOLINK PERMTEST} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 generate a new profile allowing only r access to /bin/true} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 subdomain_parser will automatically be invoked in -r mode} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 genprofile $file:$badperm} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 run this test (exec) passing /bin/true as argv[1]} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 check it's output, it is expected to FAIL} \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 runchecktest "EXEC no x" fail $file} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs20\lang255\ai\ltrch\dbch\af12\afs20\langfe255\ai\loch\f2\fs20\lang1033\i {\loch\f2\fs20\lang1033\i\b0 That\rquote s it. Exit status $rc is automatically returned by epilogue.inc} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 The above shows the controlling shell script but this is only \'bd of the test. The other half is the source code for the \lquote exec\rquote executable. The following is the sample code:} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab #include } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab #include } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab #include } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab #include } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab #include } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab #include } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab int main(int argc, char *argv[])} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \{} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab pid_t pid;} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab extern char **environ;} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab /* basic check of arguments} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab * runchecktest will pass the image to exec as argv[1]} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab */} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab if (argc < 2)\{} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab fprintf(stderr, "usage: %s program [args] \\n", argv[0]);} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab return 1;} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \}} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab pid=fork();} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab if (pid)\{ /* parent */} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab int status;} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab while (wait(&status) != pid);} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab \tab /* runchecktest requires output of} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab \tab * PASS} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab \tab * -or-} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab \tab * FAILED - reason} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab \tab */} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab if (WIFEXITED(status))\{} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab printf("PASS\\n");} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \}else\{} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab \tab \tab /* most likely because child sent us a sigkill} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab \tab \tab * because the exec was denied by AppArmor} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab \tab \tab */} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab fprintf(stderr, "FAILED, child did not exit"} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \tab \tab \tab \tab \tab "normally\\n");} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \}} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \}else\{} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab /* child */} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab (void)execve(argv[1], &argv[1], environ);} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab } \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab /* exec failed, kill outselves to flag parent */} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab (void)kill(getpid(), SIGKILL);} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \}} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab return 0;} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 {\loch\f10\fs20\lang1033\i0\b0 \tab \}} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f10\fs20\lang1033 \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af12\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 As you can see from these two examples, the test framework has certain requirements that the executable must adhere to (outputting PASS/FAIL and argument passing conventions are examples) but there is a fair degree of latitude in how to implement the test to achieve the goals required (for example, the decision to fork a subprocess inside the executable, other tests pass signals back and forth between the shell script and the executable). What is important to understand is that although you may pick a diff erent implementation strategy for different tests, for a given test, the functionality is achieved by both the shell script and the executable and they must operate correctly together.} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \sb240\sa60\keepn\f5\fs28\i\b\f11\fs28\i\b\f5\fs28\i\b\aspalpha \ltrpar\s8\aspalpha\sb240\sa60\keepn\ql\rtlch\af5\afs28\lang255\ai\ab\ltrch\dbch\af11\afs28\langfe255\ai\ab\loch\f5\fs28\lang1033\i\b {\loch\f5\fs28\lang1033\i\b Test Coverage} \par \pard\plain \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\aspalpha\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 1.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 capabilities } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 The capabilities test is an attempt to determine that for a variety of syscalls, the expected capability (especially since Immunix intercepts capability processing for confined processes) and no others allows successful access. For every syscall in the t est, we iterate over each capability individually (plus no capabilities) in order to verify that only the expected capability grants access to the privileged operation. The same is repeated for capabilities within hats. } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 The goal is to eventually extend this test verifying additional syscalls and also to perhaps do combinations of capabilities rather than just each individually.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 2.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 changehat } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 verifies basic file access permission checks for a parent profile and one subprofile/hat} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 3.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 changehat_fork } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 As 'changehat' but access checks for hats are verified across a fork} \par \pard\plain \ltrpar\s2\li283\ri0\lin283\rin0\fi0\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 4.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 changehat_misc } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Variety of tests verifying entry to subprofiles and return back to parent. AppArmor has rigid requirements around the correct use of the magic# token passed to changehat.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 5.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 chdir } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Verify change directory functions correctly for a confined process. Subdomain should allow 'x' access on a directory without it being explicitly listed in tasks profile.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 6.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 exec } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Currently this test verifies inherit (ix) functionality. Ensure that 'ix' is required in order to exec an executable. Support for 'px' and 'ux' needs to be added (see Future Work)} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 7.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 exec_qual } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 See 'matrix.doc' in the SubDomain/Documentation directory. This test currently verifies the enforce mode handling of exec between the various confinement conditions for execer and execee. } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 It needs to be extended to include the complain mode verification.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 8.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 fork } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Verifies that profiles are duplicated correctly for fork (the subtask receives a copy of it's parents profile). The test attempts to access the files passed as arguments for both a parent and a child. The test is repeated for permissive and restrictive profiles.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 9.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 link } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Link requires 'l' permission and that permissions on the src and target must match. This test verifies matching, non-matching and missing link permissions in a profile.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 10.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 mmap } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 This test verifies that mmap based access control is also subject to the AppArmor profiles access specification. The test needs some attention/rethought, It is unclear what it's purpose really is. Also why does it fail when the profile is replaced with just read permission as no mapped write is reattempted. Also a test should be added which causes the initial mmap write to fail (due to lack of write permission). } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 11.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 mount } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 This test verifies that the mount syscall is indeed restricted for confined processes.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 12.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 named_pipe } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 This test verifies that subdomain file access checks function correctly for named piped (nodes in the filesystem created with mknod). The test creates a parent/child process relationship which attempt to rendevous via the named pipe. The tests are attem pted for unconfined and confined processes and also for subhats.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 13.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 open } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Verify that the open syscall is correctly managed for confined profiles. A test should be added verifying for non-confined.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 14.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 owlsm } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 AppArmor implements a portion of the OWLSM functionality related to hard and symbolic links.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Creating a hard link (as a non root user or more accurately, without CAP_FUSER) to a file owned by another user is disallowed. Following a symbolic link in a directory with the sticky bit set is not allowed if the link is owned by a different user than th e directory. Note that these restrictions are for all processes, not just confined ones.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 15.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 pipe } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 This test is structured similarly to named_pipe except it uses the pipe(2) call to create a communication channel between parent and child rather than a node in the filesystem. AppArmor does not mediate pipe io for either confined or non confined process es. This test verifies that io functions as expected for both an unconfined process and a confined process with an empty profile.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 16.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 ptrace} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Read permission is required for a confined process to be able to be traced using ptrace. This test verifies this. Currently is it not functioning correctly. It stopped functioning correctly somewhere between 2.4.18 and 2.4.20.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 17.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 regex } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 This test verifies that tail globbing and regex globbing (perl regex engine) are functioning correctly for confined processes. Single character, multi character and character class regexes are verified.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 18.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 rename } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 The rename system call changes the name of a file in the filesystem. The test verifies that this operation (which involves AppArmor write and link permission checks) functions correctly for a confined process.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 19.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 readdir } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 AppArmor requires 'r' permission on a directory in order for a confined task to be able to read the directory contents. This test verifies this.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 20.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 rw } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 This test verifies read/write operation. AppArmor caches a successful open but checks (on read/write) to see if a confined processes profile has been replaced asynchronously. If it has, access is reevaluated. The test waits for a signal at which point it reattempts to write, read and verify data. The controlling script performs a profile replacement before sending the signal for the test to reattempt the io.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 21.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 swap } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Confined processes are prohibited from executing certain system calls entirely, including swapon(2) swapoff (2). This test verifies that unconfined processes can call these syscalls but confined processes cannot.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 22.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 setattr } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Write permission is required in a confined processes profile in order to change the mode (chmod, chgrp, chown) of a file. This test verifies these system calls for unconfined and confined processes.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 23.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 symlink } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 As the 'link' test but for symbolic rather than hard links.} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 24.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 syscall } \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 Confined processes are prohibited from executing certain system calls entirely. This test checks a variety of such syscalls including ptrace, mknod, sysctl (write), sethostname, setdomainname, ioperm, iopl and reboot} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\sa120\f11\f13 25.}\ilvl0 \ltrpar\s2\ls4\li283\ri0\lin283\rin0\fi-283\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 unlink} \par \pard\plain \ltrpar\s2\sa120\ql\rtlch\af13\afs24\lang255\ltrch\dbch\af11\afs24\langfe255\loch\fs24\lang1033 {\loch\f1\fs24\lang1033\i0\b0 In order to unlink a file, a confined process must have 'l' permission in it's profile for the relevant file. This test verifies this.} \par \pard\plain \sb240\sa60\keepn\f5\fs28\i\b\f11\fs28\i\b\f5\fs28\i\b\aspalpha \ltrpar\s8\aspalpha\sb240\sa60\keepn\ql\rtlch\af5\afs28\lang255\ai\ab\ltrch\dbch\af11\afs28\langfe255\ai\ab\loch\f5\fs28\lang1033\i\b {\loch\f5\fs28\lang1033\i\b Future work/changes} \par \pard\plain \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\aspalpha\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\f2\f2\f2\aspalpha 1.}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls2\aspalpha\li283\ri0\lin283\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 Add metadata (comments) to each test which can be used to automatically generate test coverage data (as the above section risks getting out of date quickly).} \par \pard\plain \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\aspalpha\li283\ri0\lin283\rin0\fi0\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\f2\f2\f2\aspalpha 2.}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls2\aspalpha\li283\ri0\lin283\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 Test cases for the following need to be added:} \par \pard\plain {\listtext\pard\plain \li1003\ri0\lin1003\rin0\fi-283\f2\f2\f2\aspalpha\f7\fs18\f7\fs18\f7\fs18 \u9679 ?}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls1\aspalpha\li1003\ri0\lin1003\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 exec (unconstrained/ux and profile/px)} \par \pard\plain {\listtext\pard\plain \li1003\ri0\lin1003\rin0\fi-283\f2\f2\f2\aspalpha\f7\fs18\f7\fs18\f7\fs18 \u9679 ?}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls1\aspalpha\li1003\ri0\lin1003\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 add fork tests (for clone, vfork etc)} \par \pard\plain {\listtext\pard\plain \li1003\ri0\lin1003\rin0\fi-283\f2\f2\f2\aspalpha\f7\fs18\f7\fs18\f7\fs18 \u9679 ?}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls1\aspalpha\li1003\ri0\lin1003\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 add read/write tests (pread/pwrite/readv/writev)} \par \pard\plain {\listtext\pard\plain \li1003\ri0\lin1003\rin0\fi-283\f2\f2\f2\aspalpha\f7\fs18\f7\fs18\f7\fs18 \u9679 ?}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls1\aspalpha\li1003\ri0\lin1003\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 complain mode verification for exec (exec_qual). The enforce portion of the matrix has been completed.} \par \pard\plain {\listtext\pard\plain \li1003\ri0\lin1003\rin0\fi-283\f2\f2\f2\aspalpha\f7\fs18\f7\fs18\f7\fs18 \u9679 ?}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls1\aspalpha\li1003\ri0\lin1003\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 netdomain (complete tcp tests. Create udp tests including sendmsg). Lots to do.} \par \pard\plain {\listtext\pard\plain \li1003\ri0\lin1003\rin0\fi-283\f2\f2\f2\aspalpha\f7\fs18\f7\fs18\f7\fs18 \u9679 ?}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls1\aspalpha\li1003\ri0\lin1003\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 /proc/pid/attr tests. Verify current restrictions on who can changehat, who can setprofile etc.} \par \pard\plain {\listtext\pard\plain \li1003\ri0\lin1003\rin0\fi-283\f2\f2\f2\aspalpha\f7\fs18\f7\fs18\f7\fs18 \u9679 ?}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls1\aspalpha\li1003\ri0\lin1003\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 file descriptor passing via Unix domain sockets (normal and inside hat)} \par \pard\plain {\listtext\pard\plain \li1003\ri0\lin1003\rin0\fi-283\f2\f2\f2\aspalpha\f7\fs18\f7\fs18\f7\fs18 \u9679 ?}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls1\aspalpha\li1003\ri0\lin1003\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 reorganise structure of various changehat tests. } \par \pard\plain {\listtext\pard\plain \li1003\ri0\lin1003\rin0\fi-283\f2\f2\f2\aspalpha\f7\fs18\f7\fs18\f7\fs18 \u9679 ?}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls1\aspalpha\li1003\ri0\lin1003\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 Add more globbing (perl regex) tests} \par \pard\plain \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\aspalpha\li720\ri0\lin720\rin0\fi0\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\f2\f2\f2\aspalpha\f8\f8\f8 3.}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls0\aspalpha\li283\ri0\lin283\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 Resolve mediation of mkdir/rmdir} \par \pard\plain {\listtext\pard\plain \li283\ri0\lin283\rin0\fi-283\f2\f2\f2\aspalpha\f8\f8\f8 4.}\ilvl0 \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\ls0\aspalpha\li283\ri0\lin283\rin0\fi-283\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 {\loch\f2\fs24\lang1033\i0\b0 Resolve issues with ptrace failing (see README)} \par \pard\plain \sb240\sa60\keepn\f5\fs28\i\b\f12\fs28\i\b\f5\fs28\i\b\aspalpha \ltrpar\s8\aspalpha\sb240\sa60\keepn\ql\rtlch\af5\afs28\lang255\ai\ab\ltrch\dbch\af12\afs28\langfe255\ai\ab\loch\f5\fs28\lang1033\i\b {\loch\f5\fs28\lang1033\i\b Additional Documentation} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 {\loch\f2\fs20\lang1033\i0\b0 For additional information see the file 'README' in the test suite package.} \par \pard\plain \ltrpar\s9\ql\rtlch\af2\afs20\lang255\ltrch\dbch\af12\afs20\langfe255\loch\f2\fs20\lang1033 \par \pard\plain \ltrpar\s1{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\aspalpha\ql\rtlch\af2\afs24\lang255\ltrch\dbch\af2\afs24\langfe255\loch\f2\fs24\lang1033 \par }apparmor-2.8.95~2430/tests/regression/apparmor/syscall_ptrace.c0000644000175000017500000000240211503736226024357 0ustar sarnoldsarnold/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include "changehat.h" #define FALSE 0 #define TRUE !FALSE int main(int argc, char *argv[]) { pid_t pid; int retval = 0; if (argc != 2){ fprintf(stderr, "usage: %s\n", argv[0]); return 1; } pid=fork(); if (pid){ /* parent */ int status; while (wait(&status) != pid); retval = WEXITSTATUS(status); }else{ /* change profile so that ptrace can fail */ if (change_hat(argv[1], SD_ID_MAGIC + 1) == -1 && errno != EPERM) { /* confined process failed to change_hat */ fprintf(stderr, "FAIL: changehat %s failed - %s\n", argv[1], strerror(errno)); return errno; } if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1){ fprintf(stderr, "FAIL: ptrace failed - %s\n", strerror(errno)); retval = errno; }else{ printf("PASS\n"); } } return retval; } apparmor-2.8.95~2430/tests/regression/apparmor/i18n.sh0000755000175000017500000000414211503736226022324 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME i18n #=DESCRIPTION # This script tests some of the basic handling of i18n characters in the # kernel and (eventually) the parser. We try to open a file with weird bytes # in the name. #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc okperm=rw badperm1=r badperm2=w globfile=${tmpdir}/file_*_post settest open # skip NULL (\x0) and / (\x2F) because they can't be part of filenames # skip DEL (\x7f) for now until Ican get it properly passed through for i in $(seq 1 46) $(seq 48 126) $(seq 128 255); do #for i in $(seq 127 127 ); do symbol=$(printf "\\$(printf "%03o" $i)") # Sigh, in the case of \012, bash would strip it out during # variable assignment. -ENOCLUE why it doesn't work for \177 case "$i" in 10) file=$tmpdir/file_$'\012'_post ;; 127) file=$tmpdir/file_$'\177'_post ;; *) file=$tmpdir/file_${symbol}_post ;; esac parser_file=$tmpdir/file_\\$(printf "%03o" $i)_post #echo "$i '$symbol' $file $parser_file" #echo "$file" | od -x touch "$file" chmod 600 "$file" # PASS TEST genprofile "${globfile}:$okperm" runchecktest "i18n ($i) OPEN (glob) \"${file}\" RW" pass "$file" # FAIL TEST #genprofile "${globfile}:$badperm2" #runchecktest "i18n ($i) OPEN (glob) \"${file}\" W" fail "$file" # skip the ':', since it is a delimiter for mkprofile # We'll test it below in the \octal test. if [ "${symbol}" != ":" ] ; then # PASS TEST genprofile -E "${file}:$okperm" runchecktest "i18n ($i) OPEN \"${file}\" RW" pass "$file" # FAIL TEST #genprofile -E "${file}:$badperm2" #runchecktest "i18n ($i) OPEN \"${file}\" W" fail "$file" fi # PASS TEST genprofile -E ${parser_file}:$okperm runchecktest "i18n ($i) OPEN (octal) \"${file}\" RW" pass "$file" # FAIL TEST genprofile -E "${parser_file}:$badperm2" runchecktest "i18n ($i) OPEN (octal) \"${file}\" W" fail "$file" done apparmor-2.8.95~2430/tests/regression/apparmor/clone.sh0000644000175000017500000000142711503736226022645 0ustar sarnoldsarnold#! /bin/bash # 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. #=NAME clone #=DESCRIPTION # Verifies that clone is allowed under AppArmor, but that CLONE_NEWNS is # restriced. #=END pwd=`dirname $0` pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc # TEST1 unconfined runchecktest "CLONE/unconfined" pass # TEST2. confined genprofile runchecktest "CLONE/confined" pass # TEST3. confined + CLONE_NEWNS genprofile runchecktest "CLONE/confined/NEWNS" fail --newns # TEST4. confined + CLONE_NEWNS + cap_sys_admin genprofile cap:sys_admin runchecktest "CLONE/confined/NEWNS" pass --newns apparmor-2.8.95~2430/tests/regression/apparmor/dbus_message.c0000644000175000017500000002036012204737773024022 0ustar sarnoldsarnold/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* dbus_message.c Utility program to send messages from the command line * * Copyright (C) 2003 Philip Blundell * Copyright (C) 2013 Canonical, Ltd. * * Originally dbus-send.c from the dbus package. It has been heavily modified * to work within the regression test framework. * * 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; either version 2 of the License, or * (at your option) any later version. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "dbus_common.h" DBusConnection *connection; DBusError error; DBusBusType type = DBUS_BUS_SESSION; const char *type_str = NULL; const char *name = NULL; const char *interface = NULL; const char *member = NULL; const char *path = NULL; int message_type = DBUS_MESSAGE_TYPE_SIGNAL; const char *address = NULL; int session_or_system = FALSE; int log_fd = -1; static void usage(int ecode) { char *prefix = ecode ? "FAIL: " : ""; fprintf(stderr, "%6sUsage: dbus_message [ADDRESS] [--name=NAME] [--type=TYPE] [contents ...]\n" " ADDRESS\t\t--system, --session (default), or --address=ADDR\n" " NAME\t\tthe message destination\n" " TYPE\t\tsignal (default) or method_call\n" " path\t\tpath to object (such as /org/freedesktop/DBus)\n" " interface\t\tinterface to use (such as org.freedesktop.DBus)\n" " member\t\tname of the method or signal (such as ListNames)\n", prefix); exit(ecode); } static int do_message(int argc, char *argv[]) { DBusMessage *message; DBusMessageIter iter; int i = 0; if (message_type == DBUS_MESSAGE_TYPE_METHOD_CALL) { message = dbus_message_new_method_call(NULL, path, interface, member); dbus_message_set_auto_start(message, TRUE); } else if (message_type == DBUS_MESSAGE_TYPE_SIGNAL) { message = dbus_message_new_signal(path, interface, member); } else { fprintf(stderr, "FAIL: Internal error, unknown message type\n"); return 1; } if (message == NULL) { fprintf(stderr, "FAIL: Couldn't allocate D-Bus message\n"); return 1; } if (name && !dbus_message_set_destination(message, name)) { fprintf(stderr, "FAIL: Not enough memory\n"); return 1; } dbus_message_iter_init_append(message, &iter); while (i < argc) { char *arg; char *c; int type; int secondary_type; int container_type; DBusMessageIter *target_iter; DBusMessageIter container_iter; type = DBUS_TYPE_INVALID; arg = argv[i++]; c = strchr(arg, ':'); if (c == NULL) { fprintf(stderr, "FAIL: %s: Data item \"%s\" is badly formed\n", argv[0], arg); return 1; } *(c++) = 0; container_type = DBUS_TYPE_INVALID; if (strcmp(arg, "variant") == 0) container_type = DBUS_TYPE_VARIANT; else if (strcmp(arg, "array") == 0) container_type = DBUS_TYPE_ARRAY; else if (strcmp(arg, "dict") == 0) container_type = DBUS_TYPE_DICT_ENTRY; if (container_type != DBUS_TYPE_INVALID) { arg = c; c = strchr(arg, ':'); if (c == NULL) { fprintf(stderr, "FAIL: %s: Data item \"%s\" is badly formed\n", argv[0], arg); return 1; } *(c++) = 0; } if (arg[0] == 0) type = DBUS_TYPE_STRING; else type = type_from_name(arg); if (container_type == DBUS_TYPE_DICT_ENTRY) { char sig[5]; arg = c; c = strchr(c, ':'); if (c == NULL) { fprintf(stderr, "FAIL: %s: Data item \"%s\" is badly formed\n", argv[0], arg); return 1; } *(c++) = 0; secondary_type = type_from_name(arg); sig[0] = DBUS_DICT_ENTRY_BEGIN_CHAR; sig[1] = type; sig[2] = secondary_type; sig[3] = DBUS_DICT_ENTRY_END_CHAR; sig[4] = '\0'; dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, sig, &container_iter); target_iter = &container_iter; } else if (container_type != DBUS_TYPE_INVALID) { char sig[2]; sig[0] = type; sig[1] = '\0'; dbus_message_iter_open_container(&iter, container_type, sig, &container_iter); target_iter = &container_iter; } else target_iter = &iter; if (container_type == DBUS_TYPE_ARRAY) { append_array(target_iter, type, c); } else if (container_type == DBUS_TYPE_DICT_ENTRY) { append_dict(target_iter, type, secondary_type, c); } else append_arg(target_iter, type, c); if (container_type != DBUS_TYPE_INVALID) { dbus_message_iter_close_container(&iter, &container_iter); } } if (message_type == DBUS_MESSAGE_TYPE_METHOD_CALL) { DBusMessage *reply; log_message(log_fd, "sent ", message); dbus_error_init(&error); reply = dbus_connection_send_with_reply_and_block(connection, message, -1, &error); if (dbus_error_is_set(&error)) { fprintf(stderr, "FAIL: %s: %s\n", error.name, error.message); return 1; } if (reply) { dbus_message_unref(reply); } } else { log_message(log_fd, "sent ", message); dbus_connection_send(connection, message, NULL); dbus_connection_flush(connection); } dbus_message_unref(message); return 0; } int main(int argc, char *argv[]) { int i, rc; if (argc < 3) usage(1); for (i = 1; i < argc && interface == NULL; i++) { char *arg = argv[i]; if (strcmp(arg, "--system") == 0) { type = DBUS_BUS_SYSTEM; session_or_system = TRUE; } else if (strcmp(arg, "--session") == 0) { type = DBUS_BUS_SESSION; session_or_system = TRUE; } else if (strstr(arg, "--address") == arg) { address = strchr(arg, '='); if (address == NULL) { fprintf(stderr, "FAIL: \"--address=\" requires an ADDRESS\n"); usage(1); } else { address = address + 1; } } else if (strstr(arg, "--name=") == arg) name = strchr(arg, '=') + 1; else if (strstr(arg, "--type=") == arg) type_str = strchr(arg, '=') + 1; else if (strstr(arg, "--log=") == arg) { char *path = strchr(arg, '=') + 1; log_fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (log_fd < 0) { fprintf(stderr, "FAIL: Couldn't open log file \"%s\": %m\n", path); exit(1); } } else if (!strcmp(arg, "--help")) usage(0); else if (arg[0] == '-') usage(1); else if (path == NULL) path = arg; else /* interface == NULL guaranteed by the 'while' loop */ interface = arg; } if (interface == NULL) usage(1); else { char *last_dot = strrchr(interface, '.'); if (last_dot == NULL) { fprintf(stderr, "FAIL: Must use org.mydomain.Interface.Member notation, no dot in \"%s\"\n", interface); exit(1); } *last_dot = '\0'; member = last_dot + 1; } if (session_or_system && address != NULL) { fprintf(stderr, "FAIL: \"--address\" may not be used with \"--system\" or \"--session\"\n"); usage(1); } if (type_str != NULL) { message_type = dbus_message_type_from_string(type_str); if (!(message_type == DBUS_MESSAGE_TYPE_METHOD_CALL || message_type == DBUS_MESSAGE_TYPE_SIGNAL)) { fprintf(stderr, "FAIL: Message type \"%s\" is not supported\n", type_str); exit(1); } } dbus_error_init(&error); if (address != NULL) connection = dbus_connection_open(address, &error); else connection = dbus_bus_get(type, &error); if (connection == NULL) { fprintf(stderr, "FAIL: Failed to open connection to \"%s\" message bus: %s\n", (address != NULL) ? address : ((type == DBUS_BUS_SYSTEM) ? "system" : "session"), error.message); dbus_error_free(&error); exit(1); } else if (address != NULL) dbus_bus_register(connection, &error); rc = do_message(argc - i, argv + i); dbus_connection_unref(connection); if (rc == 0) printf("PASS\n"); exit(rc); } apparmor-2.8.95~2430/tests/regression/apparmor/owlsm.sh0000644000175000017500000000454311503736226022710 0ustar sarnoldsarnold#!/bin/bash # 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. #=NAME owlsm #=DESCRIPTION # AppArmor implements a portion of the OWLSM functionality related to hard and # symbolic links. Creating a hard link (as a non root user or more accurately, # without CAP_FUSER) to a file owned by another user is disallowed. Following # a symbolic link in a directory with the sticky bit set is not allowed if the # link is owned by a different user than the directory. Note that these # restrictions are for all processes, not just confined ones. #=END # Test whether or not the owlsm stuff is functional # don't need to generate subdomain profiles for this. echo "owlsm mediation in AppArmor has been removed"; exit 1 pwd=$(dirname $0) pwd=$(cd ${pwd} ; /bin/pwd) bin=${pwd} . ${bin}/prologue.inc target=file1 source=file2 OWLSM_TOGGLE_FILE=/subdomain/control/owlsm # Save the current state of the owlsm module, and disable it if [ -f "${OWLSM_TOGGLE_FILE}" ] ; then OWLSM_SAVED=$(cat ${OWLSM_TOGGLE_FILE}) OWLSM_TOGGLE=true fi dir=$(mktemp -d ${tmpdir}/tmp.XXXXXXXXXXXXXXX) chmod 1775 ${dir} touch ${dir}/${target} # test making a hardlink (as non-root) to a file owned by someone else. settest dropprivs_wrapper if [ -n "${OWLSM_TOGGLE}" ] ; then echo 0 > ${OWLSM_TOGGLE_FILE} runchecktest "[owlsm-disabled] hardlink to unowned file" pass link ${dir}/${target} ${dir}/${source} echo 1 > ${OWLSM_TOGGLE_FILE} rm -f ${dir}/${source} fi runchecktest "[owlsm] hardlink to unowned file" fail link ${dir}/${target} ${dir}/${source} rm -f ${dir}/${source} # # test not following a symlink owned by someone else in a +t directory # settest open ln -s ${target} ${dir}/${source} chown --no-dereference nobody ${dir}/${source} if [ -n "${OWLSM_TOGGLE}" ] ; then echo 0 > ${OWLSM_TOGGLE_FILE} runchecktest "[owlsm-disabled] following non-owned symlink" pass ${dir}/${source} echo 1 > ${OWLSM_TOGGLE_FILE} fi runchecktest "[owlsm] following non-owned symlink" fail ${dir}/${source} # cleanup -- be paranoid in case there was an error in the mktemp call. rm -f ${dir}/${target} ${dir}/${source} rmdir ${dir} if [ -n "${OWLSM_TOGGLE}" ] ; then echo ${OWLSM_SAVED} > ${OWLSM_TOGGLE_FILE} fi apparmor-2.8.95~2430/tests/regression/apparmor/exec.c0000644000175000017500000000162710433400436022273 0ustar sarnoldsarnold#include /* * 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. */ #include #include #include #include #include int main(int argc, char *argv[]) { pid_t pid; extern char **environ; if (argc < 2){ fprintf(stderr, "usage: %s program [args] \n", argv[0]); return 1; } pid=fork(); if (pid){ /* parent */ int status; while (wait(&status) != pid); if (WIFEXITED(status)){ printf("PASS\n"); }else{ fprintf(stderr, "FAILED, child did not exit normally\n"); } }else{ /* child */ (void)execve(argv[1], &argv[1], environ); /* exec failed, kill outselves to flag parent */ (void)kill(getpid(), SIGKILL); } return 0; } apparmor-2.8.95~2430/tests/regression/apparmor/scripts/0000755000175000017500000000000012311706714022671 5ustar sarnoldsarnoldapparmor-2.8.95~2430/tests/regression/apparmor/scripts/pull_metadata.pl0000755000175000017500000000164110417022466026047 0ustar sarnoldsarnold#!/usr/bin/perl # # Pulls metadata out of the .sh files and writes it to a file # $i = 1; open (WRITEFILE, ">metadata.txt"); opendir (TESTDIR, ".") or die "Could not open directory"; while (defined ($file = readdir(TESTDIR))) { if ($file =~ /\.sh$/) { push @file_array, $file; } } closedir(TESTDIR); @file_array = sort(@file_array); $in_description = 0; foreach $file (@file_array) { open (SCRIPT, $file); print "Handling: $file\n"; while (