Last active
May 10, 2017 12:21
-
-
Save chihchun/2dcc82d9a9f7ab18bf4354bc112976bf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 707167d861d0856c855f2639a12280057959bbf4 Mon Sep 17 00:00:00 2001 | |
From: Andy Whitcroft <apw@canonical.com> | |
Date: Thu, 27 Nov 2008 19:12:07 +0000 | |
Subject: [PATCH 01/76] UBUNTU: SAUCE: (no-up) version: Implement | |
version_signature proc file. | |
Signed-off-by: Andy Whitcroft <apw@canonical.com> | |
Acked-by: Tim Gardener <tim.gardner@canonical.com> | |
--- | |
fs/proc/Makefile | 1 + | |
fs/proc/version_signature.c | 31 +++++++++++++++++++++++++++++++ | |
init/Kconfig | 9 +++++++++ | |
init/version.c | 6 +++++- | |
4 files changed, 46 insertions(+), 1 deletion(-) | |
create mode 100644 fs/proc/version_signature.c | |
diff --git a/fs/proc/Makefile b/fs/proc/Makefile | |
index 12c6922..c3299ee 100644 | |
--- a/fs/proc/Makefile | |
+++ b/fs/proc/Makefile | |
@@ -31,3 +31,4 @@ proc-$(CONFIG_PROC_KCORE) += kcore.o | |
proc-$(CONFIG_PROC_VMCORE) += vmcore.o | |
proc-$(CONFIG_PRINTK) += kmsg.o | |
proc-$(CONFIG_PROC_PAGE_MONITOR) += page.o | |
+proc-y += version_signature.o | |
diff --git a/fs/proc/version_signature.c b/fs/proc/version_signature.c | |
new file mode 100644 | |
index 0000000..859fb60 | |
--- /dev/null | |
+++ b/fs/proc/version_signature.c | |
@@ -0,0 +1,31 @@ | |
+#include <linux/fs.h> | |
+#include <linux/init.h> | |
+#include <linux/kernel.h> | |
+#include <linux/proc_fs.h> | |
+#include <linux/seq_file.h> | |
+#include <linux/utsname.h> | |
+ | |
+static int version_signature_proc_show(struct seq_file *m, void *v) | |
+{ | |
+ seq_printf(m, "%s\n", CONFIG_VERSION_SIGNATURE); | |
+ return 0; | |
+} | |
+ | |
+static int version_signature_proc_open(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, version_signature_proc_show, NULL); | |
+} | |
+ | |
+static const struct file_operations version_signature_proc_fops = { | |
+ .open = version_signature_proc_open, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+}; | |
+ | |
+static int __init proc_version_signature_init(void) | |
+{ | |
+ proc_create("version_signature", 0, NULL, &version_signature_proc_fops); | |
+ return 0; | |
+} | |
+module_init(proc_version_signature_init); | |
diff --git a/init/Kconfig b/init/Kconfig | |
index 34407f1..3cae67b 100644 | |
--- a/init/Kconfig | |
+++ b/init/Kconfig | |
@@ -220,6 +220,15 @@ config DEFAULT_HOSTNAME | |
but you may wish to use a different default here to make a minimal | |
system more usable with less configuration. | |
+config VERSION_SIGNATURE | |
+ string "Arbitrary version signature" | |
+ help | |
+ This string will be created in a file, /proc/version_signature. It | |
+ is useful in determining arbitrary data about your kernel. For instance, | |
+ if you have several kernels of the same version, but need to keep track | |
+ of a revision of the same kernel, but not affect it's ability to load | |
+ compatible modules, this is the easiest way to do that. | |
+ | |
config SWAP | |
bool "Support for paging of anonymous memory (swap)" | |
depends on MMU && BLOCK | |
diff --git a/init/version.c b/init/version.c | |
index fe41a63..1b13bce 100644 | |
--- a/init/version.c | |
+++ b/init/version.c | |
@@ -45,7 +45,11 @@ struct uts_namespace init_uts_ns = { | |
/* FIXED STRINGS! Don't touch! */ | |
const char linux_banner[] = | |
"Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@" | |
- LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n"; | |
+ LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION | |
+#ifdef CONFIG_VERSION_SIGNATURE | |
+ " (" CONFIG_VERSION_SIGNATURE ")" | |
+#endif | |
+ "\n"; | |
const char linux_proc_banner[] = | |
"%s version %s" | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 07125cc725d1c68a4a2fa835bed6e4eb62af305e Mon Sep 17 00:00:00 2001 | |
From: Tim Gardner <tim.gardner@canonical.com> | |
Date: Wed, 28 Nov 2012 12:09:30 -0700 | |
Subject: [PATCH 02/76] UBUNTU: SAUCE: (no-up) Revert "VFS: don't do protected | |
{sym,hard}links by default" | |
This reverts commit 561ec64ae67ef25cac8d72bb9c4bfc955edfd415. | |
BugLink: http://bugs.launchpad.net/bugs/1084192 | |
Reverting this in the kernel as opposed to adding a sysctl | |
to the procps package guarentees that this regression will be | |
propagated to the Raring LTS kernel. | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
fs/namei.c | 4 ++-- | |
1 file changed, 2 insertions(+), 2 deletions(-) | |
diff --git a/fs/namei.c b/fs/namei.c | |
index 5b4eed2..befad2d 100644 | |
--- a/fs/namei.c | |
+++ b/fs/namei.c | |
@@ -888,8 +888,8 @@ static inline void put_link(struct nameidata *nd) | |
path_put(&last->link); | |
} | |
-int sysctl_protected_symlinks __read_mostly = 0; | |
-int sysctl_protected_hardlinks __read_mostly = 0; | |
+int sysctl_protected_symlinks __read_mostly = 1; | |
+int sysctl_protected_hardlinks __read_mostly = 1; | |
/** | |
* may_follow_link - Check symlink following for unsafe situations | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From cb968915a87ea3988ff77076ca609ed490fb419c Mon Sep 17 00:00:00 2001 | |
From: Scott James Remnant <scott@ubuntu.com> | |
Date: Tue, 27 Oct 2009 10:05:32 +0000 | |
Subject: [PATCH 03/76] UBUNTU: SAUCE: (no-up) trace: add trace events for | |
open(), exec() and uselib() (for v3.7+) | |
BugLink: http://bugs.launchpad.net/bugs/462111 | |
This patch uses TRACE_EVENT to add tracepoints for the open(), | |
exec() and uselib() syscalls so that ureadahead can cheaply trace | |
the boot sequence to determine what to read to speed up the next. | |
It's not upstream because it will need to be rebased onto the syscall | |
trace events whenever that gets merged, and is a stop-gap. | |
[apw@canonical.com: updated for v3.7 and later.] | |
[apw@canonical.com: updated for v3.19 and later.] | |
BugLink: http://bugs.launchpad.net/bugs/1085766 | |
Signed-off-by: Scott James Remnant <scott@ubuntu.com> | |
Acked-by: Stefan Bader <stefan.bader@canonical.com> | |
Acked-by: Andy Whitcroft <andy.whitcroft@canonical.com> | |
Signed-off-by: Stefan Bader <stefan.bader@canonical.com> | |
Conflicts: | |
fs/open.c | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
fs/exec.c | 4 ++++ | |
fs/open.c | 4 ++++ | |
include/trace/events/fs.h | 53 +++++++++++++++++++++++++++++++++++++++++++++++ | |
3 files changed, 61 insertions(+) | |
create mode 100644 include/trace/events/fs.h | |
diff --git a/fs/exec.c b/fs/exec.c | |
index 67e8657..6e034ff 100644 | |
--- a/fs/exec.c | |
+++ b/fs/exec.c | |
@@ -58,6 +58,8 @@ | |
#include <linux/compat.h> | |
#include <linux/vmalloc.h> | |
+#include <trace/events/fs.h> | |
+ | |
#include <asm/uaccess.h> | |
#include <asm/mmu_context.h> | |
#include <asm/tlb.h> | |
@@ -838,6 +840,8 @@ static struct file *do_open_execat(int fd, struct filename *name, int flags) | |
if (name->name[0] != '\0') | |
fsnotify_open(file); | |
+ trace_open_exec(name->name); | |
+ | |
out: | |
return file; | |
diff --git a/fs/open.c b/fs/open.c | |
index d3ed817..f0a2e89 100644 | |
--- a/fs/open.c | |
+++ b/fs/open.c | |
@@ -34,6 +34,9 @@ | |
#include "internal.h" | |
+#define CREATE_TRACE_POINTS | |
+#include <trace/events/fs.h> | |
+ | |
int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs, | |
struct file *filp) | |
{ | |
@@ -1057,6 +1060,7 @@ long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode) | |
} else { | |
fsnotify_open(f); | |
fd_install(fd, f); | |
+ trace_do_sys_open(tmp->name, flags, mode); | |
} | |
} | |
putname(tmp); | |
diff --git a/include/trace/events/fs.h b/include/trace/events/fs.h | |
new file mode 100644 | |
index 0000000..fb634b7 | |
--- /dev/null | |
+++ b/include/trace/events/fs.h | |
@@ -0,0 +1,53 @@ | |
+#undef TRACE_SYSTEM | |
+#define TRACE_SYSTEM fs | |
+ | |
+#if !defined(_TRACE_FS_H) || defined(TRACE_HEADER_MULTI_READ) | |
+#define _TRACE_FS_H | |
+ | |
+#include <linux/fs.h> | |
+#include <linux/tracepoint.h> | |
+ | |
+TRACE_EVENT(do_sys_open, | |
+ | |
+ TP_PROTO(const char *filename, int flags, int mode), | |
+ | |
+ TP_ARGS(filename, flags, mode), | |
+ | |
+ TP_STRUCT__entry( | |
+ __string( filename, filename ) | |
+ __field( int, flags ) | |
+ __field( int, mode ) | |
+ ), | |
+ | |
+ TP_fast_assign( | |
+ __assign_str(filename, filename); | |
+ __entry->flags = flags; | |
+ __entry->mode = mode; | |
+ ), | |
+ | |
+ TP_printk("\"%s\" %x %o", | |
+ __get_str(filename), __entry->flags, __entry->mode) | |
+); | |
+ | |
+TRACE_EVENT(open_exec, | |
+ | |
+ TP_PROTO(const char *filename), | |
+ | |
+ TP_ARGS(filename), | |
+ | |
+ TP_STRUCT__entry( | |
+ __string( filename, filename ) | |
+ ), | |
+ | |
+ TP_fast_assign( | |
+ __assign_str(filename, filename); | |
+ ), | |
+ | |
+ TP_printk("\"%s\"", | |
+ __get_str(filename)) | |
+); | |
+ | |
+#endif /* _TRACE_FS_H */ | |
+ | |
+/* This part must be outside protection */ | |
+#include <trace/define_trace.h> | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 592ca4cd2afc16a3a3b8d0445d508ef9e16425b3 Mon Sep 17 00:00:00 2001 | |
From: Andy Whitcroft <apw@canonical.com> | |
Date: Wed, 16 Apr 2014 19:40:57 +0100 | |
Subject: [PATCH 04/76] UBUNTU: SAUCE: vt -- maintain bootloader screen mode | |
and content until vt switch | |
Introduce a new VT mode KD_TRANSPARENT which endevours to leave the current | |
content of the framebuffer untouched. This allows the bootloader to insert | |
a graphical splash and have the kernel maintain it until the OS splash | |
can take over. When we finally switch away (either through programs like | |
plymouth or manually) the content is lost and the VT reverts to text mode. | |
BugLink: http://bugs.launchpad.net/bugs/1308685 | |
Signed-off-by: Andy Whitcroft <apw@canonical.com> | |
--- | |
drivers/tty/vt/vt.c | 37 ++++++++++++++++++++++++++++++++----- | |
drivers/tty/vt/vt_ioctl.c | 10 +++++----- | |
include/linux/vt_kern.h | 3 ++- | |
include/uapi/linux/kd.h | 2 ++ | |
4 files changed, 41 insertions(+), 11 deletions(-) | |
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c | |
index 8c3bf3d..00416fc 100644 | |
--- a/drivers/tty/vt/vt.c | |
+++ b/drivers/tty/vt/vt.c | |
@@ -102,6 +102,7 @@ | |
#include <linux/uaccess.h> | |
#include <linux/kdb.h> | |
#include <linux/ctype.h> | |
+#include <linux/screen_info.h> | |
#define MAX_NR_CON_DRIVER 16 | |
@@ -146,7 +147,7 @@ struct con_driver { | |
static int con_open(struct tty_struct *, struct file *); | |
static void vc_init(struct vc_data *vc, unsigned int rows, | |
- unsigned int cols, int do_clear); | |
+ unsigned int cols, int do_clear, int mode); | |
static void gotoxy(struct vc_data *vc, int new_x, int new_y); | |
static void save_cur(struct vc_data *vc); | |
static void reset_terminal(struct vc_data *vc, int do_clear); | |
@@ -170,6 +171,9 @@ static void vc_init(struct vc_data *vc, unsigned int rows, | |
static int cur_default = CUR_DEFAULT; | |
module_param(cur_default, int, S_IRUGO | S_IWUSR); | |
+int vt_handoff = 0; | |
+module_param_named(handoff, vt_handoff, int, S_IRUGO | S_IWUSR); | |
+ | |
/* | |
* ignore_poke: don't unblank the screen when things are typed. This is | |
* mainly for the privacy of braille terminal users. | |
@@ -678,6 +682,13 @@ void redraw_screen(struct vc_data *vc, int is_switch) | |
} | |
if (tty0dev) | |
sysfs_notify(&tty0dev->kobj, NULL, "active"); | |
+ /* | |
+ * If we are switching away from a transparent VT the contents | |
+ * will be lost, convert it into a blank text console then | |
+ * it will be repainted blank if we ever switch back. | |
+ */ | |
+ if (old_vc->vc_mode == KD_TRANSPARENT) | |
+ old_vc->vc_mode = KD_TEXT; | |
} else { | |
hide_cursor(vc); | |
redraw = 1; | |
@@ -794,7 +805,7 @@ int vc_allocate(unsigned int currcons) /* return 0 on success */ | |
if (global_cursor_default == -1) | |
global_cursor_default = 1; | |
- vc_init(vc, vc->vc_rows, vc->vc_cols, 1); | |
+ vc_init(vc, vc->vc_rows, vc->vc_cols, 1, KD_TEXT); | |
vcs_make_sysfs(currcons); | |
atomic_notifier_call_chain(&vt_notifier_list, VT_ALLOCATE, ¶m); | |
@@ -2936,7 +2947,7 @@ static void con_shutdown(struct tty_struct *tty) | |
module_param_named(underline, default_underline_color, int, S_IRUGO | S_IWUSR); | |
static void vc_init(struct vc_data *vc, unsigned int rows, | |
- unsigned int cols, int do_clear) | |
+ unsigned int cols, int do_clear, int mode) | |
{ | |
int j, k ; | |
@@ -2947,7 +2958,7 @@ static void vc_init(struct vc_data *vc, unsigned int rows, | |
set_origin(vc); | |
vc->vc_pos = vc->vc_origin; | |
- reset_vc(vc); | |
+ reset_vc(vc, mode); | |
for (j=k=0; j<16; j++) { | |
vc->vc_palette[k++] = default_red[j] ; | |
vc->vc_palette[k++] = default_grn[j] ; | |
@@ -3004,16 +3015,32 @@ static int __init con_init(void) | |
mod_timer(&console_timer, jiffies + (blankinterval * HZ)); | |
} | |
+ if (vt_handoff > 0 && vt_handoff <= MAX_NR_CONSOLES) { | |
+ currcons = vt_handoff - 1; | |
+ vc_cons[currcons].d = vc = kzalloc(sizeof(struct vc_data), GFP_NOWAIT); | |
+ INIT_WORK(&vc_cons[currcons].SAK_work, vc_SAK); | |
+ tty_port_init(&vc->port); | |
+ visual_init(vc, currcons, 1); | |
+ vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT); | |
+ vc_init(vc, vc->vc_rows, vc->vc_cols, 0, KD_TRANSPARENT); | |
+ } | |
for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) { | |
+ if (currcons == vt_handoff - 1) | |
+ continue; | |
vc_cons[currcons].d = vc = kzalloc(sizeof(struct vc_data), GFP_NOWAIT); | |
INIT_WORK(&vc_cons[currcons].SAK_work, vc_SAK); | |
tty_port_init(&vc->port); | |
visual_init(vc, currcons, 1); | |
vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT); | |
vc_init(vc, vc->vc_rows, vc->vc_cols, | |
- currcons || !vc->vc_sw->con_save_screen); | |
+ currcons || !vc->vc_sw->con_save_screen, KD_TEXT); | |
} | |
currcons = fg_console = 0; | |
+ if (vt_handoff > 0) { | |
+ printk(KERN_INFO "vt handoff: transparent VT on vt#%d\n", | |
+ vt_handoff); | |
+ currcons = fg_console = vt_handoff - 1; | |
+ } | |
master_display_fg = vc = vc_cons[currcons].d; | |
set_origin(vc); | |
save_screen(vc); | |
diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c | |
index f62c598..73613dc 100644 | |
--- a/drivers/tty/vt/vt_ioctl.c | |
+++ b/drivers/tty/vt/vt_ioctl.c | |
@@ -1040,9 +1040,9 @@ int vt_ioctl(struct tty_struct *tty, | |
return ret; | |
} | |
-void reset_vc(struct vc_data *vc) | |
+void reset_vc(struct vc_data *vc, int mode) | |
{ | |
- vc->vc_mode = KD_TEXT; | |
+ vc->vc_mode = mode; | |
vt_reset_unicode(vc->vc_num); | |
vc->vt_mode.mode = VT_AUTO; | |
vc->vt_mode.waitv = 0; | |
@@ -1074,7 +1074,7 @@ void vc_SAK(struct work_struct *work) | |
*/ | |
if (tty) | |
__do_SAK(tty); | |
- reset_vc(vc); | |
+ reset_vc(vc, KD_TEXT); | |
} | |
console_unlock(); | |
} | |
@@ -1331,7 +1331,7 @@ static void complete_change_console(struct vc_data *vc) | |
* this outside of VT_PROCESS but there is no single process | |
* to account for and tracking tty count may be undesirable. | |
*/ | |
- reset_vc(vc); | |
+ reset_vc(vc, KD_TEXT); | |
if (old_vc_mode != vc->vc_mode) { | |
if (vc->vc_mode == KD_TEXT) | |
@@ -1403,7 +1403,7 @@ void change_console(struct vc_data *new_vc) | |
* this outside of VT_PROCESS but there is no single process | |
* to account for and tracking tty count may be undesirable. | |
*/ | |
- reset_vc(vc); | |
+ reset_vc(vc, KD_TEXT); | |
/* | |
* Fall through to normal (VT_AUTO) handling of the switch... | |
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h | |
index 6abd24f..a1f63dc 100644 | |
--- a/include/linux/vt_kern.h | |
+++ b/include/linux/vt_kern.h | |
@@ -129,7 +129,8 @@ int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc) | |
void vt_event_post(unsigned int event, unsigned int old, unsigned int new); | |
int vt_waitactive(int n); | |
void change_console(struct vc_data *new_vc); | |
-void reset_vc(struct vc_data *vc); | |
+void reset_vc(struct vc_data *vc, int mode); | |
+ | |
extern int do_unbind_con_driver(const struct consw *csw, int first, int last, | |
int deflt); | |
int vty_init(const struct file_operations *console_fops); | |
diff --git a/include/uapi/linux/kd.h b/include/uapi/linux/kd.h | |
index 87b7cc4..6dc8d3f 100644 | |
--- a/include/uapi/linux/kd.h | |
+++ b/include/uapi/linux/kd.h | |
@@ -45,6 +45,8 @@ struct consolefontdesc { | |
#define KD_GRAPHICS 0x01 | |
#define KD_TEXT0 0x02 /* obsolete */ | |
#define KD_TEXT1 0x03 /* obsolete */ | |
+#define KD_TRANSPARENT 0x04 | |
+ | |
#define KDGETMODE 0x4B3B /* get current mode */ | |
#define KDMAPDISP 0x4B3C /* map display into address space */ | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 92d8cddcd3486ba105d9f99e6495071a7e82a1b6 Mon Sep 17 00:00:00 2001 | |
From: Tim Gardner <tim.gardner@canonical.com> | |
Date: Mon, 20 Jul 2015 08:58:20 -0600 | |
Subject: [PATCH 05/76] UBUNTU: SAUCE: Fix FTBS in proc_version_signature | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
fs/proc/version_signature.c | 3 ++- | |
1 file changed, 2 insertions(+), 1 deletion(-) | |
diff --git a/fs/proc/version_signature.c b/fs/proc/version_signature.c | |
index 859fb60..bfd43d2 100644 | |
--- a/fs/proc/version_signature.c | |
+++ b/fs/proc/version_signature.c | |
@@ -1,6 +1,7 @@ | |
+#include <linux/kernel.h> | |
+#include <linux/module.h> | |
#include <linux/fs.h> | |
#include <linux/init.h> | |
-#include <linux/kernel.h> | |
#include <linux/proc_fs.h> | |
#include <linux/seq_file.h> | |
#include <linux/utsname.h> | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 4635b99a099321e20d6eb71fb54703682c882c49 Mon Sep 17 00:00:00 2001 | |
From: Jay Vosburgh <jay.vosburgh@canonical.com> | |
Date: Wed, 1 Apr 2015 16:11:09 -0700 | |
Subject: [PATCH 06/76] UBUNTU: SAUCE: fan: tunnel multiple mapping mode (v3) | |
Switch to a single tunnel for all mappings, this removes the limitations | |
on how many mappings each tunnel can handle, and therefore how many Fan | |
slices each local address may hold. | |
NOTE: This introduces a new kernel netlink interface which needs updated | |
iproute2 support. | |
BugLink: http://bugs.launchpad.net/bugs/1470091 | |
Signed-off-by: Jay Vosburgh <jay.vosburgh@canonical.com> | |
Signed-off-by: Andy Whitcroft <apw@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
Conflicts: | |
include/net/ip_tunnels.h | |
--- | |
include/net/ip_tunnels.h | 15 ++++ | |
include/uapi/linux/if_tunnel.h | 20 +++++ | |
net/ipv4/ip_tunnel.c | 7 +- | |
net/ipv4/ipip.c | 183 ++++++++++++++++++++++++++++++++++++++++- | |
4 files changed, 221 insertions(+), 4 deletions(-) | |
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h | |
index 59557c0..47848db 100644 | |
--- a/include/net/ip_tunnels.h | |
+++ b/include/net/ip_tunnels.h | |
@@ -98,6 +98,19 @@ struct ip_tunnel_prl_entry { | |
}; | |
struct metadata_dst; | |
+/* A fan overlay /8 (250.0.0.0/8, for example) maps to exactly one /16 | |
+ * underlay (10.88.0.0/16, for example). Multiple local addresses within | |
+ * the /16 may be used, but a particular overlay may not span | |
+ * multiple underlay subnets. | |
+ * | |
+ * We store one underlay, indexed by the overlay's high order octet. | |
+ */ | |
+#define FAN_OVERLAY_CNT 256 | |
+ | |
+struct ip_tunnel_fan { | |
+/* u32 __rcu *map;*/ | |
+ u32 map[FAN_OVERLAY_CNT]; | |
+}; | |
struct ip_tunnel { | |
struct ip_tunnel __rcu *next; | |
@@ -129,6 +142,7 @@ struct ip_tunnel { | |
#endif | |
struct ip_tunnel_prl_entry __rcu *prl; /* potential router list */ | |
unsigned int prl_count; /* # of entries in PRL */ | |
+ struct ip_tunnel_fan fan; | |
int ip_tnl_net_id; | |
struct gro_cells gro_cells; | |
bool collect_md; | |
@@ -151,6 +165,7 @@ struct ip_tunnel { | |
#define TUNNEL_NOCACHE __cpu_to_be16(0x2000) | |
#define TUNNEL_OPTIONS_PRESENT (TUNNEL_GENEVE_OPT | TUNNEL_VXLAN_OPT) | |
+#define TUNNEL_FAN __cpu_to_be16(0x4000) | |
struct tnl_ptk_info { | |
__be16 flags; | |
diff --git a/include/uapi/linux/if_tunnel.h b/include/uapi/linux/if_tunnel.h | |
index 92f3c86..423e4fa 100644 | |
--- a/include/uapi/linux/if_tunnel.h | |
+++ b/include/uapi/linux/if_tunnel.h | |
@@ -75,6 +75,10 @@ enum { | |
IFLA_IPTUN_ENCAP_SPORT, | |
IFLA_IPTUN_ENCAP_DPORT, | |
IFLA_IPTUN_COLLECT_METADATA, | |
+ | |
+ __IFLA_IPTUN_VENDOR_BREAK, /* Ensure new entries do not hit the below. */ | |
+ IFLA_IPTUN_FAN_MAP = 33, | |
+ | |
__IFLA_IPTUN_MAX, | |
}; | |
#define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1) | |
@@ -151,4 +155,20 @@ enum { | |
}; | |
#define IFLA_VTI_MAX (__IFLA_VTI_MAX - 1) | |
+ | |
+enum { | |
+ IFLA_FAN_UNSPEC, | |
+ IFLA_FAN_MAPPING, | |
+ __IFLA_FAN_MAX, | |
+}; | |
+ | |
+#define IFLA_FAN_MAX (__IFLA_FAN_MAX - 1) | |
+ | |
+struct ip_tunnel_fan_map { | |
+ __be32 underlay; | |
+ __be32 overlay; | |
+ __u16 underlay_prefix; | |
+ __u16 overlay_prefix; | |
+}; | |
+ | |
#endif /* _UAPI_IF_TUNNEL_H_ */ | |
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c | |
index 5719d6b..11f55a1 100644 | |
--- a/net/ipv4/ip_tunnel.c | |
+++ b/net/ipv4/ip_tunnel.c | |
@@ -1100,6 +1100,11 @@ int ip_tunnel_newlink(struct net_device *dev, struct nlattr *tb[], | |
} | |
EXPORT_SYMBOL_GPL(ip_tunnel_newlink); | |
+static int ip_tunnel_is_fan(struct ip_tunnel *tunnel) | |
+{ | |
+ return tunnel->parms.i_flags & TUNNEL_FAN; | |
+} | |
+ | |
int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[], | |
struct ip_tunnel_parm *p) | |
{ | |
@@ -1109,7 +1114,7 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[], | |
struct ip_tunnel_net *itn = net_generic(net, tunnel->ip_tnl_net_id); | |
if (dev == itn->fb_tunnel_dev) | |
- return -EINVAL; | |
+ return ip_tunnel_is_fan(tunnel) ? 0 : -EINVAL; | |
t = ip_tunnel_find(itn, p, dev->type); | |
diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c | |
index c939258..591caad 100644 | |
--- a/net/ipv4/ipip.c | |
+++ b/net/ipv4/ipip.c | |
@@ -106,6 +106,7 @@ | |
#include <linux/init.h> | |
#include <linux/netfilter_ipv4.h> | |
#include <linux/if_ether.h> | |
+#include <linux/inetdevice.h> | |
#include <net/sock.h> | |
#include <net/ip.h> | |
@@ -245,6 +246,40 @@ static int mplsip_rcv(struct sk_buff *skb) | |
} | |
#endif | |
+static int ipip_tunnel_is_fan(struct ip_tunnel *tunnel) | |
+{ | |
+ return tunnel->parms.i_flags & TUNNEL_FAN; | |
+} | |
+ | |
+/* | |
+ * Determine fan tunnel endpoint to send packet to, based on the inner IP | |
+ * address. For an overlay (inner) address Y.A.B.C, the transformation is | |
+ * F.G.A.B, where "F" and "G" are the first two octets of the underlay | |
+ * network (the network portion of a /16), "A" and "B" are the low order | |
+ * two octets of the underlay network host (the host portion of a /16), | |
+ * and "Y" is a configured first octet of the overlay network. | |
+ * | |
+ * E.g., underlay host 10.88.3.4 with an overlay of 99 would host overlay | |
+ * subnet 99.3.4.0/24. An overlay network datagram from 99.3.4.5 to | |
+ * 99.6.7.8, would be directed to underlay host 10.88.6.7, which hosts | |
+ * overlay network 99.6.7.0/24. | |
+ */ | |
+static int ipip_build_fan_iphdr(struct ip_tunnel *tunnel, struct sk_buff *skb, struct iphdr *iph) | |
+{ | |
+ unsigned int overlay; | |
+ u32 daddr, underlay; | |
+ | |
+ daddr = ntohl(ip_hdr(skb)->daddr); | |
+ overlay = daddr >> 24; | |
+ underlay = tunnel->fan.map[overlay]; | |
+ if (!underlay) | |
+ return -EINVAL; | |
+ | |
+ *iph = tunnel->parms.iph; | |
+ iph->daddr = htonl(underlay | ((daddr >> 8) & 0x0000ffff)); | |
+ return 0; | |
+} | |
+ | |
/* | |
* This function assumes it is being called from dev_queue_xmit() | |
* and that skb is filled properly by that function. | |
@@ -255,6 +290,7 @@ static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb, | |
struct ip_tunnel *tunnel = netdev_priv(dev); | |
const struct iphdr *tiph = &tunnel->parms.iph; | |
u8 ipproto; | |
+ struct iphdr fiph; | |
switch (skb->protocol) { | |
case htons(ETH_P_IP): | |
@@ -275,6 +311,14 @@ static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb, | |
if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP4)) | |
goto tx_error; | |
+ if (ipip_tunnel_is_fan(tunnel)) { | |
+ if (ipip_build_fan_iphdr(tunnel, skb, &fiph)) | |
+ goto tx_error; | |
+ tiph = &fiph; | |
+ } else { | |
+ tiph = &tunnel->parms.iph; | |
+ } | |
+ | |
skb_set_inner_ipproto(skb, ipproto); | |
if (tunnel->collect_md) | |
@@ -464,21 +508,89 @@ static bool ipip_netlink_encap_parms(struct nlattr *data[], | |
return ret; | |
} | |
+static void ipip_fan_free_map(struct ip_tunnel *t) | |
+{ | |
+ memset(&t->fan.map, 0, sizeof(t->fan.map)); | |
+} | |
+ | |
+static int ipip_fan_set_map(struct ip_tunnel *t, struct ip_tunnel_fan_map *map) | |
+{ | |
+ u32 overlay, overlay_mask, underlay, underlay_mask; | |
+ | |
+ if ((map->underlay_prefix && map->underlay_prefix != 16) || | |
+ (map->overlay_prefix && map->overlay_prefix != 8)) | |
+ return -EINVAL; | |
+ | |
+ overlay = ntohl(map->overlay); | |
+ overlay_mask = ntohl(inet_make_mask(map->overlay_prefix)); | |
+ | |
+ underlay = ntohl(map->underlay); | |
+ underlay_mask = ntohl(inet_make_mask(map->underlay_prefix)); | |
+ | |
+ if ((overlay & ~overlay_mask) || (underlay & ~underlay_mask)) | |
+ return -EINVAL; | |
+ | |
+ if (!(overlay & overlay_mask) && (underlay & underlay_mask)) | |
+ return -EINVAL; | |
+ | |
+ t->parms.i_flags |= TUNNEL_FAN; | |
+ | |
+ /* Special case: overlay 0 and underlay 0 clears all mappings */ | |
+ if (!overlay && !underlay) { | |
+ ipip_fan_free_map(t); | |
+ return 0; | |
+ } | |
+ | |
+ overlay >>= (32 - map->overlay_prefix); | |
+ t->fan.map[overlay] = underlay; | |
+ | |
+ return 0; | |
+} | |
+ | |
+ | |
+static int ipip_netlink_fan(struct nlattr *data[], struct ip_tunnel *t, | |
+ struct ip_tunnel_parm *parms) | |
+{ | |
+ struct ip_tunnel_fan_map *map; | |
+ struct nlattr *attr; | |
+ int rem, rv; | |
+ | |
+ if (!data[IFLA_IPTUN_FAN_MAP]) | |
+ return 0; | |
+ | |
+ if (parms->iph.daddr) | |
+ return -EINVAL; | |
+ | |
+ nla_for_each_nested(attr, data[IFLA_IPTUN_FAN_MAP], rem) { | |
+ map = nla_data(attr); | |
+ rv = ipip_fan_set_map(t, map); | |
+ if (rv) | |
+ return rv; | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
static int ipip_newlink(struct net *src_net, struct net_device *dev, | |
struct nlattr *tb[], struct nlattr *data[]) | |
{ | |
struct ip_tunnel *t = netdev_priv(dev); | |
struct ip_tunnel_parm p; | |
struct ip_tunnel_encap ipencap; | |
+ struct ip_tunnel *t = netdev_priv(dev); | |
+ int err; | |
if (ipip_netlink_encap_parms(data, &ipencap)) { | |
- int err = ip_tunnel_encap_setup(t, &ipencap); | |
+ err = ip_tunnel_encap_setup(t, &ipencap); | |
if (err < 0) | |
return err; | |
} | |
ipip_netlink_parms(data, &p, &t->collect_md); | |
+ err = ipip_netlink_fan(data, t, &p); | |
+ if (err < 0) | |
+ return err; | |
return ip_tunnel_newlink(dev, tb, &p); | |
} | |
@@ -488,10 +600,11 @@ static int ipip_changelink(struct net_device *dev, struct nlattr *tb[], | |
struct ip_tunnel_parm p; | |
struct ip_tunnel_encap ipencap; | |
bool collect_md; | |
+ struct ip_tunnel *t = netdev_priv(dev); | |
+ int err; | |
if (ipip_netlink_encap_parms(data, &ipencap)) { | |
- struct ip_tunnel *t = netdev_priv(dev); | |
- int err = ip_tunnel_encap_setup(t, &ipencap); | |
+ err = ip_tunnel_encap_setup(t, &ipencap); | |
if (err < 0) | |
return err; | |
@@ -500,6 +613,9 @@ static int ipip_changelink(struct net_device *dev, struct nlattr *tb[], | |
ipip_netlink_parms(data, &p, &collect_md); | |
if (collect_md) | |
return -EINVAL; | |
+ err = ipip_netlink_fan(data, t, &p); | |
+ if (err < 0) | |
+ return err; | |
if (((dev->flags & IFF_POINTOPOINT) && !p.iph.daddr) || | |
(!(dev->flags & IFF_POINTOPOINT) && p.iph.daddr)) | |
@@ -535,6 +651,8 @@ static size_t ipip_get_size(const struct net_device *dev) | |
nla_total_size(2) + | |
/* IFLA_IPTUN_COLLECT_METADATA */ | |
nla_total_size(0) + | |
+ /* IFLA_IPTUN_FAN_MAP */ | |
+ nla_total_size(sizeof(struct ip_tunnel_fan_map)) * 256 + | |
0; | |
} | |
@@ -566,6 +684,29 @@ static int ipip_fill_info(struct sk_buff *skb, const struct net_device *dev) | |
if (tunnel->collect_md) | |
if (nla_put_flag(skb, IFLA_IPTUN_COLLECT_METADATA)) | |
goto nla_put_failure; | |
+ if (tunnel->parms.i_flags & TUNNEL_FAN) { | |
+ struct nlattr *fan_nest; | |
+ int i; | |
+ | |
+ fan_nest = nla_nest_start(skb, IFLA_IPTUN_FAN_MAP); | |
+ if (!fan_nest) | |
+ goto nla_put_failure; | |
+ for (i = 0; i < 256; i++) { | |
+ if (tunnel->fan.map[i]) { | |
+ struct ip_tunnel_fan_map map; | |
+ | |
+ map.underlay = htonl(tunnel->fan.map[i]); | |
+ map.underlay_prefix = 16; | |
+ map.overlay = htonl(i << 24); | |
+ map.overlay_prefix = 8; | |
+ if (nla_put(skb, IFLA_FAN_MAPPING, | |
+ sizeof(map), &map)) | |
+ goto nla_put_failure; | |
+ } | |
+ } | |
+ nla_nest_end(skb, fan_nest); | |
+ } | |
+ | |
return 0; | |
nla_put_failure: | |
@@ -585,6 +726,9 @@ static int ipip_fill_info(struct sk_buff *skb, const struct net_device *dev) | |
[IFLA_IPTUN_ENCAP_SPORT] = { .type = NLA_U16 }, | |
[IFLA_IPTUN_ENCAP_DPORT] = { .type = NLA_U16 }, | |
[IFLA_IPTUN_COLLECT_METADATA] = { .type = NLA_FLAG }, | |
+ | |
+ [__IFLA_IPTUN_VENDOR_BREAK ... IFLA_IPTUN_MAX] = { .type = NLA_BINARY }, | |
+ [IFLA_IPTUN_FAN_MAP] = { .type = NLA_NESTED }, | |
}; | |
static struct rtnl_link_ops ipip_link_ops __read_mostly = { | |
@@ -634,6 +778,23 @@ static void __net_exit ipip_exit_net(struct net *net) | |
.size = sizeof(struct ip_tunnel_net), | |
}; | |
+#ifdef CONFIG_SYSCTL | |
+static struct ctl_table_header *ipip_fan_header; | |
+static unsigned int ipip_fan_version = 3; | |
+ | |
+static struct ctl_table ipip_fan_sysctls[] = { | |
+ { | |
+ .procname = "version", | |
+ .data = &ipip_fan_version, | |
+ .maxlen = sizeof(ipip_fan_version), | |
+ .mode = 0444, | |
+ .proc_handler = proc_dointvec, | |
+ }, | |
+ {}, | |
+}; | |
+ | |
+#endif /* CONFIG_SYSCTL */ | |
+ | |
static int __init ipip_init(void) | |
{ | |
int err; | |
@@ -659,9 +820,22 @@ static int __init ipip_init(void) | |
if (err < 0) | |
goto rtnl_link_failed; | |
+#ifdef CONFIG_SYSCTL | |
+ ipip_fan_header = register_net_sysctl(&init_net, "net/fan", | |
+ ipip_fan_sysctls); | |
+ if (!ipip_fan_header) { | |
+ err = -ENOMEM; | |
+ goto sysctl_failed; | |
+ } | |
+#endif /* CONFIG_SYSCTL */ | |
+ | |
out: | |
return err; | |
+#ifdef CONFIG_SYSCTL | |
+sysctl_failed: | |
+ rtnl_link_unregister(&ipip_link_ops); | |
+#endif /* CONFIG_SYSCTL */ | |
rtnl_link_failed: | |
#if IS_ENABLED(CONFIG_MPLS) | |
xfrm4_tunnel_deregister(&mplsip_handler, AF_INET); | |
@@ -676,6 +850,9 @@ static int __init ipip_init(void) | |
static void __exit ipip_fini(void) | |
{ | |
+#ifdef CONFIG_SYSCTL | |
+ unregister_net_sysctl_table(ipip_fan_header); | |
+#endif /* CONFIG_SYSCTL */ | |
rtnl_link_unregister(&ipip_link_ops); | |
if (xfrm4_tunnel_deregister(&ipip_handler, AF_INET)) | |
pr_info("%s: can't deregister tunnel\n", __func__); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From af91a947d47afd2531a8e43f636ba4a4ac9993e2 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Thu, 28 Jul 2016 08:43:18 -0500 | |
Subject: [PATCH 07/76] UBUNTU: SAUCE: xenbus: Use proc_create_mount_point() to | |
create /proc/xen | |
BugLink: http://bugs.launchpad.net/bugs/1607374 | |
Mounting proc in user namespace containers fails if the xenbus | |
filesystem is mounted on /proc/xen because this directory fails | |
the "permanently empty" test. proc_create_mount_point() exists | |
specifically to create such mountpoints in proc but is currently | |
proc-internal. Export this interface to modules, then use it in | |
xenbus when creating /proc/xen. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
Acked-by: Tim Gardner <tim.gardner@canonical.com> | |
Signed-off-by: Kamal Mostafa <kamal@canonical.com> | |
--- | |
drivers/xen/xenbus/xenbus_probe.c | 2 +- | |
fs/proc/generic.c | 1 + | |
fs/proc/internal.h | 1 - | |
include/linux/proc_fs.h | 2 ++ | |
4 files changed, 4 insertions(+), 2 deletions(-) | |
diff --git a/drivers/xen/xenbus/xenbus_probe.c b/drivers/xen/xenbus/xenbus_probe.c | |
index 33a31cf..b5c1dec 100644 | |
--- a/drivers/xen/xenbus/xenbus_probe.c | |
+++ b/drivers/xen/xenbus/xenbus_probe.c | |
@@ -826,7 +826,7 @@ static int __init xenbus_init(void) | |
* Create xenfs mountpoint in /proc for compatibility with | |
* utilities that expect to find "xenbus" under "/proc/xen". | |
*/ | |
- proc_mkdir("xen", NULL); | |
+ proc_create_mount_point("xen"); | |
#endif | |
out_error: | |
diff --git a/fs/proc/generic.c b/fs/proc/generic.c | |
index 5f2dc20..7eb3cef 100644 | |
--- a/fs/proc/generic.c | |
+++ b/fs/proc/generic.c | |
@@ -479,6 +479,7 @@ struct proc_dir_entry *proc_create_mount_point(const char *name) | |
} | |
return ent; | |
} | |
+EXPORT_SYMBOL(proc_create_mount_point); | |
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode, | |
struct proc_dir_entry *parent, | |
diff --git a/fs/proc/internal.h b/fs/proc/internal.h | |
index 5378441..7de6795 100644 | |
--- a/fs/proc/internal.h | |
+++ b/fs/proc/internal.h | |
@@ -195,7 +195,6 @@ static inline bool is_empty_pde(const struct proc_dir_entry *pde) | |
{ | |
return S_ISDIR(pde->mode) && !pde->proc_iops; | |
} | |
-struct proc_dir_entry *proc_create_mount_point(const char *name); | |
/* | |
* inode.c | |
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h | |
index b97bf2e..8bd2f72 100644 | |
--- a/include/linux/proc_fs.h | |
+++ b/include/linux/proc_fs.h | |
@@ -21,6 +21,7 @@ extern struct proc_dir_entry *proc_mkdir_data(const char *, umode_t, | |
struct proc_dir_entry *, void *); | |
extern struct proc_dir_entry *proc_mkdir_mode(const char *, umode_t, | |
struct proc_dir_entry *); | |
+struct proc_dir_entry *proc_create_mount_point(const char *name); | |
extern struct proc_dir_entry *proc_create_data(const char *, umode_t, | |
struct proc_dir_entry *, | |
@@ -56,6 +57,7 @@ static inline struct proc_dir_entry *proc_symlink(const char *name, | |
struct proc_dir_entry *parent,const char *dest) { return NULL;} | |
static inline struct proc_dir_entry *proc_mkdir(const char *name, | |
struct proc_dir_entry *parent) {return NULL;} | |
+static inline struct proc_dir_entry *proc_create_mount_point(const char *name) { return NULL; } | |
static inline struct proc_dir_entry *proc_mkdir_data(const char *name, | |
umode_t mode, struct proc_dir_entry *parent, void *data) { return NULL; } | |
static inline struct proc_dir_entry *proc_mkdir_mode(const char *name, | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 409e7ea79281a0abf89c70f8bcdae852354d7913 Mon Sep 17 00:00:00 2001 | |
From: Ben Hutchings <ben@decadent.org.uk> | |
Date: Tue, 16 Aug 2016 10:27:00 -0600 | |
Subject: [PATCH 08/76] UBUNTU: SAUCE: security,perf: Allow further restriction | |
of perf_event_open | |
https://lkml.org/lkml/2016/1/11/587 | |
The GRKERNSEC_PERF_HARDEN feature extracted from grsecurity. Adds the | |
option to disable perf_event_open() entirely for unprivileged users. | |
This standalone version doesn't include making the variable read-only | |
(or renaming it). | |
When kernel.perf_event_open is set to 3 (or greater), disallow all | |
access to performance events by users without CAP_SYS_ADMIN. | |
Add a Kconfig symbol CONFIG_SECURITY_PERF_EVENTS_RESTRICT that | |
makes this value the default. | |
This is based on a similar feature in grsecurity | |
(CONFIG_GRKERNSEC_PERF_HARDEN). This version doesn't include making | |
the variable read-only. It also allows enabling further restriction | |
at run-time regardless of whether the default is changed. | |
Signed-off-by: Ben Hutchings <ben@decadent.org.uk> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
include/linux/perf_event.h | 5 +++++ | |
kernel/events/core.c | 10 +++++++++- | |
security/Kconfig | 9 +++++++++ | |
3 files changed, 23 insertions(+), 1 deletion(-) | |
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h | |
index 4741ecd..531b8b1 100644 | |
--- a/include/linux/perf_event.h | |
+++ b/include/linux/perf_event.h | |
@@ -1168,6 +1168,11 @@ extern int perf_cpu_time_max_percent_handler(struct ctl_table *table, int write, | |
int perf_event_max_stack_handler(struct ctl_table *table, int write, | |
void __user *buffer, size_t *lenp, loff_t *ppos); | |
+static inline bool perf_paranoid_any(void) | |
+{ | |
+ return sysctl_perf_event_paranoid > 2; | |
+} | |
+ | |
static inline bool perf_paranoid_tracepoint_raw(void) | |
{ | |
return sysctl_perf_event_paranoid > -1; | |
diff --git a/kernel/events/core.c b/kernel/events/core.c | |
index 4b33231..af97936 100644 | |
--- a/kernel/events/core.c | |
+++ b/kernel/events/core.c | |
@@ -389,8 +389,13 @@ enum event_type_t { | |
* 0 - disallow raw tracepoint access for unpriv | |
* 1 - disallow cpu events for unpriv | |
* 2 - disallow kernel profiling for unpriv | |
+ * 3 - disallow all unpriv perf event use | |
*/ | |
-int sysctl_perf_event_paranoid __read_mostly = 2; | |
+#ifdef CONFIG_SECURITY_PERF_EVENTS_RESTRICT | |
+int sysctl_perf_event_paranoid __read_mostly = 3; | |
+#else | |
+int sysctl_perf_event_paranoid __read_mostly = 1; | |
+#endif | |
/* Minimum for 512 kiB + 1 user control page */ | |
int sysctl_perf_event_mlock __read_mostly = 512 + (PAGE_SIZE / 1024); /* 'free' kiB per user */ | |
@@ -9592,6 +9597,9 @@ static int perf_event_set_clock(struct perf_event *event, clockid_t clk_id) | |
if (flags & ~PERF_FLAG_ALL) | |
return -EINVAL; | |
+ if (perf_paranoid_any() && !capable(CAP_SYS_ADMIN)) | |
+ return -EACCES; | |
+ | |
err = perf_copy_attr(attr_uptr, &attr); | |
if (err) | |
return err; | |
diff --git a/security/Kconfig b/security/Kconfig | |
index 118f454..59aea7d 100644 | |
--- a/security/Kconfig | |
+++ b/security/Kconfig | |
@@ -18,6 +18,15 @@ config SECURITY_DMESG_RESTRICT | |
If you are unsure how to answer this question, answer N. | |
+config SECURITY_PERF_EVENTS_RESTRICT | |
+ bool "Restrict unprivileged use of performance events" | |
+ depends on PERF_EVENTS | |
+ help | |
+ If you say Y here, the kernel.perf_event_paranoid sysctl | |
+ will be set to 3 by default, and no unprivileged use of the | |
+ perf_event_open syscall will be permitted unless it is | |
+ changed. | |
+ | |
config SECURITY | |
bool "Enable different security models" | |
depends on SYSFS | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From c3df5001847cbbb21cf9ce0d8f246ec6e2660890 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Mon, 13 Jun 2016 17:05:18 +0300 | |
Subject: [PATCH 09/76] UBUNTU: SAUCE: (no-up) apparmor: rebase of | |
apparmor3.5-beta1 snapshot for 4.8 | |
BugLink: http://bugs.launchpad.net/bugs/1379535 | |
This is a sync and squash of the apparmor 3.5-beta1 snapshot. The | |
set of patches in this squash are available in | |
git://kernel.ubuntu.com/jj/ubuntu-xenial.git | |
using the the tag | |
apparmor-3.5-beta1-presuash-snapshot | |
This fixes multiple bugs and adds the policy namespace stacking features. | |
BugLink: http://bugs.launchpad.net/bugs/1379535 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/.gitignore | 1 + | |
security/apparmor/Kconfig | 59 +- | |
security/apparmor/Makefile | 44 +- | |
security/apparmor/af_unix.c | 643 +++++++++ | |
security/apparmor/apparmorfs.c | 511 +++++-- | |
security/apparmor/audit.c | 120 +- | |
security/apparmor/capability.c | 56 +- | |
security/apparmor/context.c | 152 +-- | |
security/apparmor/domain.c | 1358 ++++++++++++------- | |
security/apparmor/file.c | 595 ++++++--- | |
security/apparmor/include/af_unix.h | 114 ++ | |
security/apparmor/include/apparmor.h | 91 +- | |
security/apparmor/include/apparmorfs.h | 9 +- | |
security/apparmor/include/audit.h | 180 ++- | |
security/apparmor/include/capability.h | 6 +- | |
security/apparmor/include/context.h | 216 +-- | |
security/apparmor/include/domain.h | 9 +- | |
security/apparmor/include/file.h | 120 +- | |
security/apparmor/include/ipc.h | 22 +- | |
security/apparmor/include/label.h | 502 +++++++ | |
security/apparmor/include/lib.h | 317 +++++ | |
security/apparmor/include/match.h | 20 + | |
security/apparmor/include/mount.h | 54 + | |
security/apparmor/include/net.h | 124 ++ | |
security/apparmor/include/path.h | 63 +- | |
security/apparmor/include/perms.h | 167 +++ | |
security/apparmor/include/policy.h | 269 +--- | |
security/apparmor/include/policy_ns.h | 127 ++ | |
security/apparmor/include/policy_unpack.h | 1 + | |
security/apparmor/include/procattr.h | 3 +- | |
security/apparmor/include/resource.h | 4 +- | |
security/apparmor/include/sig_names.h | 95 ++ | |
security/apparmor/ipc.c | 234 +++- | |
security/apparmor/label.c | 2077 +++++++++++++++++++++++++++++ | |
security/apparmor/lib.c | 473 ++++++- | |
security/apparmor/lsm.c | 1000 +++++++++++--- | |
security/apparmor/match.c | 29 +- | |
security/apparmor/mount.c | 731 ++++++++++ | |
security/apparmor/net.c | 357 +++++ | |
security/apparmor/nulldfa.in | 1 + | |
security/apparmor/path.c | 126 +- | |
security/apparmor/policy.c | 950 +++++-------- | |
security/apparmor/policy_ns.c | 323 +++++ | |
security/apparmor/policy_unpack.c | 227 +++- | |
security/apparmor/procattr.c | 94 +- | |
security/apparmor/resource.c | 114 +- | |
46 files changed, 10233 insertions(+), 2555 deletions(-) | |
create mode 100644 security/apparmor/af_unix.c | |
create mode 100644 security/apparmor/include/af_unix.h | |
create mode 100644 security/apparmor/include/label.h | |
create mode 100644 security/apparmor/include/lib.h | |
create mode 100644 security/apparmor/include/mount.h | |
create mode 100644 security/apparmor/include/net.h | |
create mode 100644 security/apparmor/include/perms.h | |
create mode 100644 security/apparmor/include/policy_ns.h | |
create mode 100644 security/apparmor/include/sig_names.h | |
create mode 100644 security/apparmor/label.c | |
create mode 100644 security/apparmor/mount.c | |
create mode 100644 security/apparmor/net.c | |
create mode 100644 security/apparmor/nulldfa.in | |
create mode 100644 security/apparmor/policy_ns.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/Kconfig b/security/apparmor/Kconfig | |
index be5e941..ae38e60 100644 | |
--- a/security/apparmor/Kconfig | |
+++ b/security/apparmor/Kconfig | |
@@ -30,6 +30,41 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE | |
If you are unsure how to answer this question, answer 1. | |
+config SECURITY_APPARMOR_STATS | |
+ bool "enable debug statistics" | |
+ depends on SECURITY_APPARMOR | |
+ select APPARMOR_LABEL_STATS | |
+ default n | |
+ help | |
+ This enables keeping statistics on various internal structures | |
+ and functions in apparmor. | |
+ | |
+ If you are unsure how to answer this question, answer N. | |
+ | |
+config SECURITY_APPARMOR_UNCONFINED_INIT | |
+ bool "Set init to unconfined on boot" | |
+ depends on SECURITY_APPARMOR | |
+ default y | |
+ help | |
+ This option determines policy behavior during early boot by | |
+ placing the init process in the unconfined state, or the | |
+ 'default' profile. | |
+ | |
+ This option determines policy behavior during early boot by | |
+ placing the init process in the unconfined state, or the | |
+ 'default' profile. | |
+ | |
+ 'Y' means init and its children are not confined, unless the | |
+ init process is re-execed after a policy load; loaded policy | |
+ will only apply to processes started after the load. | |
+ | |
+ 'N' means init and its children are confined in a profile | |
+ named 'default', which can be replaced later and thus | |
+ provide for confinement for processes started early at boot, | |
+ though not confined during early boot. | |
+ | |
+ If you are unsure how to answer this question, answer Y. | |
+ | |
config SECURITY_APPARMOR_HASH | |
bool "Enable introspection of sha1 hashes for loaded profiles" | |
depends on SECURITY_APPARMOR | |
@@ -42,15 +77,15 @@ config SECURITY_APPARMOR_HASH | |
is available to userspace via the apparmor filesystem. | |
config SECURITY_APPARMOR_HASH_DEFAULT | |
- bool "Enable policy hash introspection by default" | |
- depends on SECURITY_APPARMOR_HASH | |
- default y | |
- | |
- help | |
- This option selects whether sha1 hashing of loaded policy | |
- is enabled by default. The generation of sha1 hashes for | |
- loaded policy provide system administrators a quick way | |
- to verify that policy in the kernel matches what is expected, | |
- however it can slow down policy load on some devices. In | |
- these cases policy hashing can be disabled by default and | |
- enabled only if needed. | |
+ bool "Enable policy hash introspection by default" | |
+ depends on SECURITY_APPARMOR_HASH | |
+ default y | |
+ | |
+ help | |
+ This option selects whether sha1 hashing of loaded policy | |
+ is enabled by default. The generation of sha1 hashes for | |
+ loaded policy provide system administrators a quick way | |
+ to verify that policy in the kernel matches what is expected, | |
+ however it can slow down policy load on some devices. In | |
+ these cases policy hashing can be disabled by default and | |
+ enabled only if needed. | |
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile | |
index d693df8..3a2d395 100644 | |
--- a/security/apparmor/Makefile | |
+++ b/security/apparmor/Makefile | |
@@ -4,11 +4,45 @@ 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 label.o mount.o net.o af_unix.o \ | |
+ policy_ns.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 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 "/AF_ROUTE/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 -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "/AF_ROUTE/d" -e \ | |
+ '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 capability names | |
# Transforms lines from | |
@@ -61,6 +95,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 +103,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/af_unix.c b/security/apparmor/af_unix.c | |
new file mode 100644 | |
index 0000000..8178498 | |
--- /dev/null | |
+++ b/security/apparmor/af_unix.c | |
@@ -0,0 +1,643 @@ | |
+/* | |
+ * AppArmor security module | |
+ * | |
+ * This file contains AppArmor af_unix fine grained mediation | |
+ * | |
+ * Copyright 2014 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 <net/tcp_states.h> | |
+ | |
+#include "include/af_unix.h" | |
+#include "include/apparmor.h" | |
+#include "include/context.h" | |
+#include "include/file.h" | |
+#include "include/label.h" | |
+#include "include/path.h" | |
+#include "include/policy.h" | |
+ | |
+static inline struct sock *aa_sock(struct unix_sock *u) | |
+{ | |
+ return &u->sk; | |
+} | |
+ | |
+static inline int unix_fs_perm(const char *op, u32 mask, struct aa_label *label, | |
+ struct unix_sock *u, int flags) | |
+{ | |
+ AA_BUG(!label); | |
+ AA_BUG(!u); | |
+ AA_BUG(!UNIX_FS(aa_sock(u))); | |
+ | |
+ if (unconfined(label) || !LABEL_MEDIATES(label, AA_CLASS_FILE)) | |
+ return 0; | |
+ | |
+ mask &= NET_FS_PERMS; | |
+ if (!u->path.dentry) { | |
+ struct path_cond cond = { }; | |
+ struct aa_perms perms = { }; | |
+ struct aa_profile *profile; | |
+ | |
+ /* socket path has been cleared because it is being shutdown | |
+ * can only fall back to original sun_path request | |
+ */ | |
+ struct aa_sk_ctx *ctx = SK_CTX(&u->sk); | |
+ if (ctx->path.dentry) | |
+ return aa_path_perm(op, label, &ctx->path, flags, mask, | |
+ &cond); | |
+ return fn_for_each_confined(label, profile, | |
+ ((flags | profile->path_flags) & PATH_MEDIATE_DELETED) ? | |
+ __aa_path_perm(op, profile, | |
+ u->addr->name->sun_path, mask, | |
+ &cond, flags, &perms) : | |
+ aa_audit_file(profile, &nullperms, op, mask, | |
+ u->addr->name->sun_path, NULL, | |
+ NULL, cond.uid, | |
+ "Failed name lookup - " | |
+ "deleted entry", -EACCES)); | |
+ } else { | |
+ /* the sunpath may not be valid for this ns so use the path */ | |
+ struct path_cond cond = { u->path.dentry->d_inode->i_uid, | |
+ u->path.dentry->d_inode->i_mode | |
+ }; | |
+ | |
+ return aa_path_perm(op, label, &u->path, flags, mask, &cond); | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* passing in state returned by PROFILE_MEDIATES_AF */ | |
+static unsigned int match_to_prot(struct aa_profile *profile, | |
+ unsigned int state, int type, int protocol, | |
+ const char **info) | |
+{ | |
+ u16 buffer[2]; | |
+ buffer[0] = cpu_to_be16(type); | |
+ buffer[1] = cpu_to_be16(protocol); | |
+ state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer, | |
+ 4); | |
+ if (!state) | |
+ *info = "failed type and protocol match"; | |
+ return state; | |
+} | |
+ | |
+static unsigned int match_addr(struct aa_profile *profile, unsigned int state, | |
+ struct sockaddr_un *addr, int addrlen) | |
+{ | |
+ if (addr) | |
+ /* include leading \0 */ | |
+ state = aa_dfa_match_len(profile->policy.dfa, state, | |
+ addr->sun_path, | |
+ unix_addr_len(addrlen)); | |
+ else | |
+ /* anonymous end point */ | |
+ state = aa_dfa_match_len(profile->policy.dfa, state, "\x01", | |
+ 1); | |
+ /* todo change to out of band */ | |
+ state = aa_dfa_null_transition(profile->policy.dfa, state); | |
+ return state; | |
+} | |
+ | |
+static unsigned int match_to_local(struct aa_profile *profile, | |
+ unsigned int state, int type, int protocol, | |
+ struct sockaddr_un *addr, int addrlen, | |
+ const char **info) | |
+{ | |
+ state = match_to_prot(profile, state, type, protocol, info); | |
+ if (state) { | |
+ state = match_addr(profile, state, addr, addrlen); | |
+ if (state) { | |
+ /* todo: local label matching */ | |
+ state = aa_dfa_null_transition(profile->policy.dfa, | |
+ state); | |
+ if (!state) | |
+ *info = "failed local label match"; | |
+ } else | |
+ *info = "failed local address match"; | |
+ } | |
+ | |
+ return state; | |
+} | |
+ | |
+static unsigned int match_to_sk(struct aa_profile *profile, | |
+ unsigned int state, struct unix_sock *u, | |
+ const char **info) | |
+{ | |
+ struct sockaddr_un *addr = NULL; | |
+ int addrlen = 0; | |
+ | |
+ if (u->addr) { | |
+ addr = u->addr->name; | |
+ addrlen = u->addr->len; | |
+ } | |
+ | |
+ return match_to_local(profile, state, u->sk.sk_type, u->sk.sk_protocol, | |
+ addr, addrlen, info); | |
+} | |
+ | |
+#define CMD_ADDR 1 | |
+#define CMD_LISTEN 2 | |
+#define CMD_OPT 4 | |
+ | |
+static inline unsigned int match_to_cmd(struct aa_profile *profile, | |
+ unsigned int state, struct unix_sock *u, | |
+ char cmd, const char **info) | |
+{ | |
+ state = match_to_sk(profile, state, u, info); | |
+ if (state) { | |
+ state = aa_dfa_match_len(profile->policy.dfa, state, &cmd, 1); | |
+ if (!state) | |
+ *info = "failed cmd selection match"; | |
+ } | |
+ | |
+ return state; | |
+} | |
+ | |
+static inline unsigned int match_to_peer(struct aa_profile *profile, | |
+ unsigned int state, | |
+ struct unix_sock *u, | |
+ struct sockaddr_un *peer_addr, | |
+ int peer_addrlen, | |
+ const char **info) | |
+{ | |
+ state = match_to_cmd(profile, state, u, CMD_ADDR, info); | |
+ if (state) { | |
+ state = match_addr(profile, state, peer_addr, peer_addrlen); | |
+ if (!state) | |
+ *info = "failed peer address match"; | |
+ } | |
+ return state; | |
+} | |
+ | |
+static int do_perms(struct aa_profile *profile, unsigned int state, u32 request, | |
+ struct common_audit_data *sa) | |
+{ | |
+ struct aa_perms perms; | |
+ | |
+ AA_BUG(!profile); | |
+ | |
+ aa_compute_perms(profile->policy.dfa, state, &perms); | |
+ aa_apply_modes_to_perms(profile, &perms); | |
+ return aa_check_perms(profile, &perms, request, sa, | |
+ audit_net_cb); | |
+} | |
+ | |
+static int match_label(struct aa_profile *profile, struct aa_profile *peer, | |
+ unsigned int state, u32 request, | |
+ struct common_audit_data *sa) | |
+{ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!peer); | |
+ | |
+ aad(sa)->peer = &peer->label; | |
+ | |
+ if (state) { | |
+ state = aa_dfa_match(profile->policy.dfa, state, aa_peer_name(peer)); | |
+ if (!state) | |
+ aad(sa)->info = "failed peer label match"; | |
+ } | |
+ return do_perms(profile, state, request, sa); | |
+} | |
+ | |
+ | |
+/* unix sock creation comes before we know if the socket will be an fs | |
+ * socket | |
+ * v6 - semantics are handled by mapping in profile load | |
+ * v7 - semantics require sock create for tasks creating an fs socket. | |
+ */ | |
+static int profile_create_perm(struct aa_profile *profile, int family, | |
+ int type, int protocol) | |
+{ | |
+ unsigned int state; | |
+ DEFINE_AUDIT_NET(sa, OP_CREATE, NULL, family, type, protocol); | |
+ | |
+ AA_BUG(!profile); | |
+ AA_BUG(profile_unconfined(profile)); | |
+ | |
+ if ((state = PROFILE_MEDIATES_AF(profile, AF_UNIX))) { | |
+ state = match_to_prot(profile, state, type, protocol, | |
+ &aad(&sa)->info); | |
+ return do_perms(profile, state, AA_MAY_CREATE, &sa); | |
+ } | |
+ | |
+ return aa_profile_af_perm(profile, &sa, AA_MAY_CREATE, family, type); | |
+} | |
+ | |
+int aa_unix_create_perm(struct aa_label *label, int family, int type, | |
+ int protocol) | |
+{ | |
+ struct aa_profile *profile; | |
+ | |
+ if (unconfined(label)) | |
+ return 0; | |
+ | |
+ return fn_for_each_confined(label, profile, | |
+ profile_create_perm(profile, family, type, protocol)); | |
+} | |
+ | |
+ | |
+static inline int profile_sk_perm(struct aa_profile *profile, const char *op, | |
+ u32 request, struct sock *sk) | |
+{ | |
+ unsigned int state; | |
+ DEFINE_AUDIT_SK(sa, op, sk); | |
+ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!sk); | |
+ AA_BUG(UNIX_FS(sk)); | |
+ AA_BUG(profile_unconfined(profile)); | |
+ | |
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX); | |
+ if (state) { | |
+ state = match_to_sk(profile, state, unix_sk(sk), | |
+ &aad(&sa)->info); | |
+ return do_perms(profile, state, request, &sa); | |
+ } | |
+ | |
+ return aa_profile_af_sk_perm(profile, &sa, request, sk); | |
+} | |
+ | |
+int aa_unix_label_sk_perm(struct aa_label *label, const char *op, u32 request, | |
+ struct sock *sk) | |
+{ | |
+ struct aa_profile *profile; | |
+ | |
+ return fn_for_each_confined(label, profile, | |
+ profile_sk_perm(profile, op, request, sk)); | |
+} | |
+ | |
+static int unix_label_sock_perm(struct aa_label *label, const char *op, u32 request, | |
+ struct socket *sock) | |
+{ | |
+ if (unconfined(label)) | |
+ return 0; | |
+ if (UNIX_FS(sock->sk)) | |
+ return unix_fs_perm(op, request, label, unix_sk(sock->sk), 0); | |
+ | |
+ return aa_unix_label_sk_perm(label, op, request, sock->sk); | |
+} | |
+ | |
+/* revaliation, get/set attr */ | |
+int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock) | |
+{ | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ int error = unix_label_sock_perm(label, op, request, sock); | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, | |
+ struct sockaddr *addr, int addrlen) | |
+{ | |
+ unsigned int state; | |
+ DEFINE_AUDIT_SK(sa, OP_BIND, sk); | |
+ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!sk); | |
+ AA_BUG(addr->sa_family != AF_UNIX); | |
+ AA_BUG(profile_unconfined(profile)); | |
+ AA_BUG(unix_addr_fs(addr, addrlen)); | |
+ | |
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX); | |
+ if (state) { | |
+ /* bind for abstract socket */ | |
+ aad(&sa)->net.addr = unix_addr(addr); | |
+ aad(&sa)->net.addrlen = addrlen; | |
+ | |
+ state = match_to_local(profile, state, | |
+ sk->sk_type, sk->sk_protocol, | |
+ unix_addr(addr), addrlen, | |
+ &aad(&sa)->info); | |
+ return do_perms(profile, state, AA_MAY_BIND, &sa); | |
+ } | |
+ | |
+ return aa_profile_af_sk_perm(profile, &sa, AA_MAY_BIND, sk); | |
+} | |
+ | |
+int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address, | |
+ int addrlen) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ int error = 0; | |
+ | |
+ /* fs bind is handled by mknod */ | |
+ if (!(unconfined(label) || unix_addr_fs(address, addrlen))) | |
+ error = fn_for_each_confined(label, profile, | |
+ profile_bind_perm(profile, sock->sk, address, | |
+ addrlen)); | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address, | |
+ int addrlen) | |
+{ | |
+ /* unix connections are covered by the | |
+ * - unix_stream_connect (stream) and unix_may_send hooks (dgram) | |
+ * - fs connect is handled by open | |
+ */ | |
+ return 0; | |
+} | |
+ | |
+static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, | |
+ int backlog) | |
+{ | |
+ unsigned int state; | |
+ DEFINE_AUDIT_SK(sa, OP_LISTEN, sk); | |
+ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!sk); | |
+ AA_BUG(UNIX_FS(sk)); | |
+ AA_BUG(profile_unconfined(profile)); | |
+ | |
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX); | |
+ if (state) { | |
+ u16 b = cpu_to_be16(backlog); | |
+ | |
+ state = match_to_cmd(profile, state, unix_sk(sk), CMD_LISTEN, | |
+ &aad(&sa)->info); | |
+ if (state) { | |
+ state = aa_dfa_match_len(profile->policy.dfa, state, | |
+ (char *) &b, 2); | |
+ if (!state) | |
+ aad(&sa)->info = "failed listen backlog match"; | |
+ } | |
+ return do_perms(profile, state, AA_MAY_LISTEN, &sa); | |
+ } | |
+ | |
+ return aa_profile_af_sk_perm(profile, &sa, AA_MAY_LISTEN, sk); | |
+} | |
+ | |
+int aa_unix_listen_perm(struct socket *sock, int backlog) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ int error = 0; | |
+ | |
+ if (!(unconfined(label) || UNIX_FS(sock->sk))) | |
+ error = fn_for_each_confined(label, profile, | |
+ profile_listen_perm(profile, sock->sk, | |
+ backlog)); | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+ | |
+static inline int profile_accept_perm(struct aa_profile *profile, | |
+ struct sock *sk, | |
+ struct sock *newsk) | |
+{ | |
+ unsigned int state; | |
+ DEFINE_AUDIT_SK(sa, OP_ACCEPT, sk); | |
+ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!sk); | |
+ AA_BUG(UNIX_FS(sk)); | |
+ AA_BUG(profile_unconfined(profile)); | |
+ | |
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX); | |
+ if (state) { | |
+ state = match_to_sk(profile, state, unix_sk(sk), | |
+ &aad(&sa)->info); | |
+ return do_perms(profile, state, AA_MAY_ACCEPT, &sa); | |
+ } | |
+ | |
+ return aa_profile_af_sk_perm(profile, &sa, AA_MAY_ACCEPT, sk); | |
+} | |
+ | |
+/* ability of sock to connect, not peer address binding */ | |
+int aa_unix_accept_perm(struct socket *sock, struct socket *newsock) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ int error = 0; | |
+ | |
+ if (!(unconfined(label) || UNIX_FS(sock->sk))) | |
+ error = fn_for_each_confined(label, profile, | |
+ profile_accept_perm(profile, sock->sk, | |
+ newsock->sk)); | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+ | |
+/* dgram handled by unix_may_sendmsg, right to send on stream done at connect | |
+ * could do per msg unix_stream here | |
+ */ | |
+/* sendmsg, recvmsg */ | |
+int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock, | |
+ struct msghdr *msg, int size) | |
+{ | |
+ return 0; | |
+} | |
+ | |
+ | |
+static int profile_opt_perm(struct aa_profile *profile, const char *op, u32 request, | |
+ struct sock *sk, int level, int optname) | |
+{ | |
+ unsigned int state; | |
+ DEFINE_AUDIT_SK(sa, op, sk); | |
+ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!sk); | |
+ AA_BUG(UNIX_FS(sk)); | |
+ AA_BUG(profile_unconfined(profile)); | |
+ | |
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX); | |
+ if (state) { | |
+ u16 b = cpu_to_be16(optname); | |
+ | |
+ state = match_to_cmd(profile, state, unix_sk(sk), CMD_OPT, | |
+ &aad(&sa)->info); | |
+ if (state) { | |
+ state = aa_dfa_match_len(profile->policy.dfa, state, | |
+ (char *) &b, 2); | |
+ if (!state) | |
+ aad(&sa)->info = "failed sockopt match"; | |
+ } | |
+ return do_perms(profile, state, request, &sa); | |
+ } | |
+ | |
+ return aa_profile_af_sk_perm(profile, &sa, request, sk); | |
+} | |
+ | |
+int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level, | |
+ int optname) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ int error = 0; | |
+ | |
+ if (!(unconfined(label) || UNIX_FS(sock->sk))) | |
+ error = fn_for_each_confined(label, profile, | |
+ profile_opt_perm(profile, op, request, | |
+ sock->sk, level, optname)); | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+/* null peer_label is allowed, in which case the peer_sk label is used */ | |
+static int profile_peer_perm(struct aa_profile *profile, const char *op, u32 request, | |
+ struct sock *sk, struct sock *peer_sk, | |
+ struct aa_label *peer_label, | |
+ struct common_audit_data *sa) | |
+{ | |
+ unsigned int state; | |
+ | |
+ AA_BUG(!profile); | |
+ AA_BUG(profile_unconfined(profile)); | |
+ AA_BUG(!sk); | |
+ AA_BUG(!peer_sk); | |
+ AA_BUG(UNIX_FS(peer_sk)); | |
+ | |
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX); | |
+ if (state) { | |
+ struct aa_sk_ctx *peer_ctx = SK_CTX(peer_sk); | |
+ struct aa_profile *peerp; | |
+ struct sockaddr_un *addr = NULL; | |
+ int len = 0; | |
+ if (unix_sk(peer_sk)->addr) { | |
+ addr = unix_sk(peer_sk)->addr->name; | |
+ len = unix_sk(peer_sk)->addr->len; | |
+ } | |
+ state = match_to_peer(profile, state, unix_sk(sk), | |
+ addr, len, &aad(sa)->info); | |
+ if (!peer_label) | |
+ peer_label = peer_ctx->label; | |
+ return fn_for_each(peer_label, peerp, | |
+ match_label(profile, peerp, state, request, | |
+ sa)); | |
+ } | |
+ | |
+ return aa_profile_af_sk_perm(profile, sa, request, sk); | |
+} | |
+ | |
+/** | |
+ * | |
+ * Requires: lock held on both @sk and @peer_sk | |
+ */ | |
+int aa_unix_peer_perm(struct aa_label *label, const char *op, u32 request, | |
+ struct sock *sk, struct sock *peer_sk, | |
+ struct aa_label *peer_label) | |
+{ | |
+ struct unix_sock *peeru = unix_sk(peer_sk); | |
+ struct unix_sock *u = unix_sk(sk); | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!sk); | |
+ AA_BUG(!peer_sk); | |
+ | |
+ if (UNIX_FS(aa_sock(peeru))) | |
+ return unix_fs_perm(op, request, label, peeru, 0); | |
+ else if (UNIX_FS(aa_sock(u))) | |
+ return unix_fs_perm(op, request, label, u, 0); | |
+ else { | |
+ struct aa_profile *profile; | |
+ DEFINE_AUDIT_SK(sa, op, sk); | |
+ aad(&sa)->net.peer_sk = peer_sk; | |
+ | |
+ /* TODO: ns!!! */ | |
+ if (!net_eq(sock_net(sk), sock_net(peer_sk))) { | |
+ ; | |
+ } | |
+ | |
+ if (unconfined(label)) | |
+ return 0; | |
+ | |
+ return fn_for_each_confined(label, profile, | |
+ profile_peer_perm(profile, op, request, sk, | |
+ peer_sk, peer_label, &sa)); | |
+ } | |
+} | |
+ | |
+ | |
+/* from net/unix/af_unix.c */ | |
+static void unix_state_double_lock(struct sock *sk1, struct sock *sk2) | |
+{ | |
+ if (unlikely(sk1 == sk2) || !sk2) { | |
+ unix_state_lock(sk1); | |
+ return; | |
+ } | |
+ if (sk1 < sk2) { | |
+ unix_state_lock(sk1); | |
+ unix_state_lock_nested(sk2); | |
+ } else { | |
+ unix_state_lock(sk2); | |
+ unix_state_lock_nested(sk1); | |
+ } | |
+} | |
+ | |
+static void unix_state_double_unlock(struct sock *sk1, struct sock *sk2) | |
+{ | |
+ if (unlikely(sk1 == sk2) || !sk2) { | |
+ unix_state_unlock(sk1); | |
+ return; | |
+ } | |
+ unix_state_unlock(sk1); | |
+ unix_state_unlock(sk2); | |
+} | |
+ | |
+int aa_unix_file_perm(struct aa_label *label, const char *op, u32 request, | |
+ struct socket *sock) | |
+{ | |
+ struct sock *peer_sk = NULL; | |
+ u32 sk_req = request & ~NET_PEER_MASK; | |
+ int error = 0; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!sock); | |
+ AA_BUG(!sock->sk); | |
+ AA_BUG(sock->sk->sk_family != AF_UNIX); | |
+ | |
+ /* TODO: update sock label with new task label */ | |
+ unix_state_lock(sock->sk); | |
+ peer_sk = unix_peer(sock->sk); | |
+ if (peer_sk) | |
+ sock_hold(peer_sk); | |
+ if (!unix_connected(sock) && sk_req) { | |
+ error = unix_label_sock_perm(label, op, sk_req, sock); | |
+ if (!error) { | |
+ // update label | |
+ } | |
+ } | |
+ unix_state_unlock(sock->sk); | |
+ if (!peer_sk) | |
+ return error; | |
+ | |
+ unix_state_double_lock(sock->sk, peer_sk); | |
+ if (UNIX_FS(sock->sk)) { | |
+ error = unix_fs_perm(op, request, label, unix_sk(sock->sk), | |
+ PATH_SOCK_COND); | |
+ } else if (UNIX_FS(peer_sk)) { | |
+ error = unix_fs_perm(op, request, label, unix_sk(peer_sk), | |
+ PATH_SOCK_COND); | |
+ } else { | |
+ struct aa_sk_ctx *pctx = SK_CTX(peer_sk); | |
+ if (sk_req) | |
+ error = aa_unix_label_sk_perm(label, op, sk_req, | |
+ sock->sk); | |
+ last_error(error, | |
+ xcheck(aa_unix_peer_perm(label, op, | |
+ MAY_READ | MAY_WRITE, | |
+ sock->sk, peer_sk, NULL), | |
+ aa_unix_peer_perm(pctx->label, op, | |
+ MAY_READ | MAY_WRITE, | |
+ peer_sk, sock->sk, label))); | |
+ } | |
+ | |
+ unix_state_double_unlock(sock->sk, peer_sk); | |
+ sock_put(peer_sk); | |
+ | |
+ return error; | |
+} | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index 5923d56..9c2b4e2 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -18,17 +18,23 @@ | |
#include <linux/module.h> | |
#include <linux/seq_file.h> | |
#include <linux/uaccess.h> | |
+#include <linux/mount.h> | |
#include <linux/namei.h> | |
#include <linux/capability.h> | |
#include <linux/rcupdate.h> | |
+#include <uapi/linux/major.h> | |
#include "include/apparmor.h" | |
#include "include/apparmorfs.h" | |
#include "include/audit.h" | |
#include "include/context.h" | |
#include "include/crypto.h" | |
+#include "include/ipc.h" | |
+#include "include/policy_ns.h" | |
+#include "include/label.h" | |
#include "include/policy.h" | |
#include "include/resource.h" | |
+#include "include/lib.h" | |
/** | |
* aa_mangle_name - mangle a profile name to std profile layout form | |
@@ -37,7 +43,7 @@ | |
* | |
* Returns: length of mangled name | |
*/ | |
-static int mangle_name(char *name, char *target) | |
+static int mangle_name(const char *name, char *target) | |
{ | |
char *t = target; | |
@@ -71,7 +77,6 @@ static int mangle_name(char *name, char *target) | |
/** | |
* aa_simple_write_to_buffer - common routine for getting policy from user | |
- * @op: operation doing the user buffer copy | |
* @userbuf: user buffer to copy data from (NOT NULL) | |
* @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size) | |
* @copy_size: size of data to copy from user buffer | |
@@ -80,7 +85,7 @@ static int mangle_name(char *name, char *target) | |
* Returns: kernel buffer containing copy of user buffer data or an | |
* ERR_PTR on failure. | |
*/ | |
-static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, | |
+static char *aa_simple_write_to_buffer(const char __user *userbuf, | |
size_t alloc_size, size_t copy_size, | |
loff_t *pos) | |
{ | |
@@ -92,13 +97,6 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, | |
/* only writes from pos 0, that is complete writes */ | |
return ERR_PTR(-ESPIPE); | |
- /* | |
- * Don't allow profile load/replace/remove from profiles that don't | |
- * have CAP_MAC_ADMIN | |
- */ | |
- if (!aa_may_manage_policy(op)) | |
- return ERR_PTR(-EACCES); | |
- | |
/* freed by caller to simple_write_to_buffer */ | |
data = kvmalloc(alloc_size); | |
if (data == NULL) | |
@@ -112,25 +110,40 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, | |
return data; | |
} | |
- | |
-/* .load file hook fn to load policy */ | |
-static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, | |
- loff_t *pos) | |
+static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, | |
+ loff_t *pos) | |
{ | |
- char *data; | |
+ struct aa_label *label; | |
ssize_t error; | |
+ char *data; | |
- data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos); | |
+ label = aa_begin_current_label(DO_UPDATE); | |
+ /* high level check about policy management - fine grained in | |
+ * below after unpack | |
+ */ | |
+ error = aa_may_manage_policy(label, mask); | |
+ if (error) | |
+ return error; | |
+ | |
+ data = aa_simple_write_to_buffer(buf, size, size, pos); | |
error = PTR_ERR(data); | |
if (!IS_ERR(data)) { | |
- error = aa_replace_profiles(data, size, PROF_ADD); | |
+ error = aa_replace_profiles(label, mask, data, size); | |
kvfree(data); | |
} | |
+ aa_end_current_label(label); | |
return error; | |
} | |
+/* .load file hook fn to load policy */ | |
+static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, | |
+ loff_t *pos) | |
+{ | |
+ return policy_update(AA_MAY_LOAD_POLICY, buf, size, pos); | |
+} | |
+ | |
static const struct file_operations aa_fs_profile_load = { | |
.write = profile_load, | |
.llseek = default_llseek, | |
@@ -140,17 +153,8 @@ static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, | |
static ssize_t profile_replace(struct file *f, const char __user *buf, | |
size_t size, loff_t *pos) | |
{ | |
- char *data; | |
- ssize_t error; | |
- | |
- data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos); | |
- error = PTR_ERR(data); | |
- if (!IS_ERR(data)) { | |
- error = aa_replace_profiles(data, size, PROF_REPLACE); | |
- kvfree(data); | |
- } | |
- | |
- return error; | |
+ return policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY, | |
+ buf, size, pos); | |
} | |
static const struct file_operations aa_fs_profile_replace = { | |
@@ -162,21 +166,31 @@ static ssize_t profile_replace(struct file *f, const char __user *buf, | |
static ssize_t profile_remove(struct file *f, const char __user *buf, | |
size_t size, loff_t *pos) | |
{ | |
- char *data; | |
+ struct aa_label *label; | |
ssize_t error; | |
+ char *data; | |
+ | |
+ label = aa_begin_current_label(DO_UPDATE); | |
+ /* high level check about policy management - fine grained in | |
+ * below after unpack | |
+ */ | |
+ error = aa_may_manage_policy(label, AA_MAY_REMOVE_POLICY); | |
+ if (error) | |
+ return error; | |
/* | |
* aa_remove_profile needs a null terminated string so 1 extra | |
* byte is allocated and the copied data is null terminated. | |
*/ | |
- data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos); | |
+ data = aa_simple_write_to_buffer(buf, size + 1, size, pos); | |
error = PTR_ERR(data); | |
if (!IS_ERR(data)) { | |
data[size] = 0; | |
- error = aa_remove_profiles(data, size); | |
+ error = aa_remove_profiles(label, data, size); | |
kvfree(data); | |
} | |
+ aa_end_current_label(label); | |
return error; | |
} | |
@@ -186,6 +200,176 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, | |
.llseek = default_llseek, | |
}; | |
+ | |
+static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, | |
+ const char *match_str, size_t match_len) | |
+{ | |
+ struct aa_perms tmp; | |
+ struct aa_dfa *dfa; | |
+ unsigned int state = 0; | |
+ | |
+ if (profile_unconfined(profile)) | |
+ return; | |
+ if (profile->file.dfa && *match_str == AA_CLASS_FILE) { | |
+ dfa = profile->file.dfa; | |
+ state = aa_dfa_match_len(dfa, profile->file.start, | |
+ match_str + 1, match_len - 1); | |
+ tmp = nullperms; | |
+ if (state) { | |
+ struct path_cond cond = { }; | |
+ tmp = aa_compute_fperms(dfa, state, &cond); | |
+ } | |
+ } else if (profile->policy.dfa) { | |
+ if (!PROFILE_MEDIATES_SAFE(profile, *match_str)) | |
+ return; /* no change to current perms */ | |
+ dfa = profile->policy.dfa; | |
+ state = aa_dfa_match_len(dfa, profile->policy.start[0], | |
+ match_str, match_len); | |
+ if (state) | |
+ aa_compute_perms(dfa, state, &tmp); | |
+ else | |
+ tmp = nullperms; | |
+ } | |
+ aa_apply_modes_to_perms(profile, &tmp); | |
+ aa_perms_accum_raw(perms, &tmp); | |
+} | |
+ | |
+/** | |
+ * query_label - queries a label and writes permissions to buf | |
+ * @buf: the resulting permissions string is stored here (NOT NULL) | |
+ * @buf_len: size of buf | |
+ * @query: binary query string to match against the dfa | |
+ * @query_len: size of query | |
+ * | |
+ * The buffers pointed to by buf and query may overlap. The query buffer is | |
+ * parsed before buf is written to. | |
+ * | |
+ * The query should look like "LABEL_NAME\0DFA_STRING" where LABEL_NAME is | |
+ * the name of the label, in the current namespace, that is to be queried and | |
+ * DFA_STRING is a binary string to match against the label(s)'s DFA. | |
+ * | |
+ * LABEL_NAME must be NUL terminated. DFA_STRING may contain NUL characters | |
+ * but must *not* be NUL terminated. | |
+ * | |
+ * Returns: number of characters written to buf or -errno on failure | |
+ */ | |
+static ssize_t query_label(char *buf, size_t buf_len, | |
+ char *query, size_t query_len, bool ns_only) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct aa_label *label, *curr; | |
+ char *label_name, *match_str; | |
+ size_t label_name_len, match_len; | |
+ struct aa_perms perms; | |
+ struct label_it i; | |
+ | |
+ if (!query_len) | |
+ return -EINVAL; | |
+ | |
+ label_name = query; | |
+ label_name_len = strnlen(query, query_len); | |
+ if (!label_name_len || label_name_len == query_len) | |
+ return -EINVAL; | |
+ | |
+ /** | |
+ * The extra byte is to account for the null byte between the | |
+ * profile name and dfa string. profile_name_len is greater | |
+ * than zero and less than query_len, so a byte can be safely | |
+ * added or subtracted. | |
+ */ | |
+ match_str = label_name + label_name_len + 1; | |
+ match_len = query_len - label_name_len - 1; | |
+ | |
+ curr = aa_begin_current_label(DO_UPDATE); | |
+ label = aa_label_parse(curr, label_name, GFP_KERNEL, false, false); | |
+ aa_end_current_label(curr); | |
+ if (IS_ERR(label)) | |
+ return PTR_ERR(label); | |
+ | |
+ perms = allperms; | |
+ if (ns_only) { | |
+ label_for_each_in_ns(i, labels_ns(label), label, profile) { | |
+ profile_query_cb(profile, &perms, match_str, match_len); | |
+ } | |
+ } else { | |
+ label_for_each(i, label, profile) { | |
+ profile_query_cb(profile, &perms, match_str, match_len); | |
+ } | |
+ } | |
+ aa_put_label(label); | |
+ | |
+ return scnprintf(buf, buf_len, | |
+ "allow 0x%08x\ndeny 0x%08x\naudit 0x%08x\nquiet 0x%08x\n", | |
+ perms.allow, perms.deny, perms.audit, perms.quiet); | |
+} | |
+ | |
+#define QUERY_CMD_LABEL "label\0" | |
+#define QUERY_CMD_LABEL_LEN 6 | |
+#define QUERY_CMD_PROFILE "profile\0" | |
+#define QUERY_CMD_PROFILE_LEN 8 | |
+#define QUERY_CMD_LABELALL "labelall\0" | |
+#define QUERY_CMD_LABELALL_LEN 9 | |
+ | |
+/** | |
+ * aa_write_access - generic permissions query | |
+ * @file: pointer to open apparmorfs/access file | |
+ * @ubuf: user buffer containing the complete query string (NOT NULL) | |
+ * @count: size of ubuf | |
+ * @ppos: position in the file (MUST BE ZERO) | |
+ * | |
+ * Allows for one permission query per open(), write(), and read() sequence. | |
+ * The only query currently supported is a label-based query. For this query | |
+ * ubuf must begin with "label\0", followed by the profile query specific | |
+ * format described in the query_label() function documentation. | |
+ * | |
+ * Returns: number of bytes written or -errno on failure | |
+ */ | |
+static ssize_t aa_write_access(struct file *file, const char __user *ubuf, | |
+ size_t count, loff_t *ppos) | |
+{ | |
+ char *buf; | |
+ ssize_t len; | |
+ | |
+ if (*ppos) | |
+ return -ESPIPE; | |
+ | |
+ buf = simple_transaction_get(file, ubuf, count); | |
+ if (IS_ERR(buf)) | |
+ return PTR_ERR(buf); | |
+ | |
+ if (count > QUERY_CMD_PROFILE_LEN && | |
+ !memcmp(buf, QUERY_CMD_PROFILE, QUERY_CMD_PROFILE_LEN)) { | |
+ len = query_label(buf, SIMPLE_TRANSACTION_LIMIT, | |
+ buf + QUERY_CMD_PROFILE_LEN, | |
+ count - QUERY_CMD_PROFILE_LEN, true); | |
+ } else if (count > QUERY_CMD_LABEL_LEN && | |
+ !memcmp(buf, QUERY_CMD_LABEL, QUERY_CMD_LABEL_LEN)) { | |
+ len = query_label(buf, SIMPLE_TRANSACTION_LIMIT, | |
+ buf + QUERY_CMD_LABEL_LEN, | |
+ count - QUERY_CMD_LABEL_LEN, true); | |
+ } else if (count > QUERY_CMD_LABELALL_LEN && | |
+ !memcmp(buf, QUERY_CMD_LABELALL, QUERY_CMD_LABELALL_LEN)) { | |
+ len = query_label(buf, SIMPLE_TRANSACTION_LIMIT, | |
+ buf + QUERY_CMD_LABELALL_LEN, | |
+ count - QUERY_CMD_LABELALL_LEN, false); | |
+ } else | |
+ len = -EINVAL; | |
+ | |
+ if (len < 0) | |
+ return len; | |
+ | |
+ simple_transaction_set(file, len); | |
+ | |
+ return count; | |
+} | |
+ | |
+static const struct file_operations aa_fs_access = { | |
+ .write = aa_write_access, | |
+ .read = simple_transaction_read, | |
+ .release = simple_transaction_release, | |
+ .llseek = generic_file_llseek, | |
+}; | |
+ | |
static int aa_fs_seq_show(struct seq_file *seq, void *v) | |
{ | |
struct aa_fs_entry *fs_file = seq->private; | |
@@ -227,12 +411,12 @@ static int aa_fs_seq_open(struct inode *inode, struct file *file) | |
static int aa_fs_seq_profile_open(struct inode *inode, struct file *file, | |
int (*show)(struct seq_file *, void *)) | |
{ | |
- struct aa_replacedby *r = aa_get_replacedby(inode->i_private); | |
- int error = single_open(file, show, r); | |
+ struct aa_proxy *proxy = aa_get_proxy(inode->i_private); | |
+ int error = single_open(file, show, proxy); | |
if (error) { | |
file->private_data = NULL; | |
- aa_put_replacedby(r); | |
+ aa_put_proxy(proxy); | |
} | |
return error; | |
@@ -242,16 +426,17 @@ static int aa_fs_seq_profile_release(struct inode *inode, struct file *file) | |
{ | |
struct seq_file *seq = (struct seq_file *) file->private_data; | |
if (seq) | |
- aa_put_replacedby(seq->private); | |
+ aa_put_proxy(seq->private); | |
return single_release(inode, file); | |
} | |
static int aa_fs_seq_profname_show(struct seq_file *seq, void *v) | |
{ | |
- struct aa_replacedby *r = seq->private; | |
- struct aa_profile *profile = aa_get_profile_rcu(&r->profile); | |
+ struct aa_proxy *proxy = seq->private; | |
+ struct aa_label *label = aa_get_label_rcu(&proxy->label); | |
+ struct aa_profile *profile = labels_profile(label); | |
seq_printf(seq, "%s\n", profile->base.name); | |
- aa_put_profile(profile); | |
+ aa_put_label(label); | |
return 0; | |
} | |
@@ -271,10 +456,11 @@ static int aa_fs_seq_profname_open(struct inode *inode, struct file *file) | |
static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v) | |
{ | |
- struct aa_replacedby *r = seq->private; | |
- struct aa_profile *profile = aa_get_profile_rcu(&r->profile); | |
+ struct aa_proxy *proxy = seq->private; | |
+ struct aa_label *label = aa_get_label_rcu(&proxy->label); | |
+ struct aa_profile *profile = labels_profile(label); | |
seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]); | |
- aa_put_profile(profile); | |
+ aa_put_label(label); | |
return 0; | |
} | |
@@ -294,15 +480,16 @@ static int aa_fs_seq_profmode_open(struct inode *inode, struct file *file) | |
static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v) | |
{ | |
- struct aa_replacedby *r = seq->private; | |
- struct aa_profile *profile = aa_get_profile_rcu(&r->profile); | |
+ struct aa_proxy *proxy = seq->private; | |
+ struct aa_label *label = aa_get_label_rcu(&proxy->label); | |
+ struct aa_profile *profile = labels_profile(label); | |
if (profile->attach) | |
seq_printf(seq, "%s\n", profile->attach); | |
else if (profile->xmatch) | |
seq_puts(seq, "<unknown>\n"); | |
else | |
seq_printf(seq, "%s\n", profile->base.name); | |
- aa_put_profile(profile); | |
+ aa_put_label(label); | |
return 0; | |
} | |
@@ -322,8 +509,9 @@ static int aa_fs_seq_profattach_open(struct inode *inode, struct file *file) | |
static int aa_fs_seq_hash_show(struct seq_file *seq, void *v) | |
{ | |
- struct aa_replacedby *r = seq->private; | |
- struct aa_profile *profile = aa_get_profile_rcu(&r->profile); | |
+ struct aa_proxy *proxy = seq->private; | |
+ struct aa_label *label = aa_get_label_rcu(&proxy->label); | |
+ struct aa_profile *profile = labels_profile(label); | |
unsigned int i, size = aa_hash_size(); | |
if (profile->hash) { | |
@@ -331,7 +519,7 @@ static int aa_fs_seq_hash_show(struct seq_file *seq, void *v) | |
seq_printf(seq, "%.2x", profile->hash[i]); | |
seq_puts(seq, "\n"); | |
} | |
- aa_put_profile(profile); | |
+ aa_put_label(label); | |
return 0; | |
} | |
@@ -350,6 +538,11 @@ static int aa_fs_seq_hash_open(struct inode *inode, struct file *file) | |
}; | |
/** fns to setup dynamic per profile/namespace files **/ | |
+ | |
+/** | |
+ * | |
+ * Requires: @profile->ns->lock held | |
+ */ | |
void __aa_fs_profile_rmdir(struct aa_profile *profile) | |
{ | |
struct aa_profile *child; | |
@@ -357,27 +550,36 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile) | |
if (!profile) | |
return; | |
+ AA_BUG(!mutex_is_locked(&profiles_ns(profile)->lock)); | |
list_for_each_entry(child, &profile->base.profiles, base.list) | |
__aa_fs_profile_rmdir(child); | |
for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) { | |
- struct aa_replacedby *r; | |
+ struct aa_proxy *proxy; | |
if (!profile->dents[i]) | |
continue; | |
- r = d_inode(profile->dents[i])->i_private; | |
+ proxy = d_inode(profile->dents[i])->i_private; | |
securityfs_remove(profile->dents[i]); | |
- aa_put_replacedby(r); | |
+ aa_put_proxy(proxy); | |
profile->dents[i] = NULL; | |
} | |
} | |
+/** | |
+ * | |
+ * Requires: @old->ns->lock held | |
+ */ | |
void __aa_fs_profile_migrate_dents(struct aa_profile *old, | |
struct aa_profile *new) | |
{ | |
int i; | |
+ AA_BUG(!old); | |
+ AA_BUG(!new); | |
+ AA_BUG(!mutex_is_locked(&profiles_ns(old)->lock)); | |
+ | |
for (i = 0; i < AAFS_PROF_SIZEOF; i++) { | |
new->dents[i] = old->dents[i]; | |
if (new->dents[i]) | |
@@ -390,23 +592,29 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name, | |
struct aa_profile *profile, | |
const struct file_operations *fops) | |
{ | |
- struct aa_replacedby *r = aa_get_replacedby(profile->replacedby); | |
+ struct aa_proxy *proxy = aa_get_proxy(profile->label.proxy); | |
struct dentry *dent; | |
- dent = securityfs_create_file(name, S_IFREG | 0444, dir, r, fops); | |
+ dent = securityfs_create_file(name, S_IFREG | 0444, dir, proxy, fops); | |
if (IS_ERR(dent)) | |
- aa_put_replacedby(r); | |
+ aa_put_proxy(proxy); | |
return dent; | |
} | |
-/* requires lock be held */ | |
+/** | |
+ * | |
+ * Requires: @profile->ns->lock held | |
+ */ | |
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) | |
{ | |
struct aa_profile *child; | |
struct dentry *dent = NULL, *dir; | |
int error; | |
+ AA_BUG(!profile); | |
+ AA_BUG(!mutex_is_locked(&profiles_ns(profile)->lock)); | |
+ | |
if (!parent) { | |
struct aa_profile *p; | |
p = aa_deref_parent(profile); | |
@@ -477,21 +685,26 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) | |
return error; | |
} | |
-void __aa_fs_namespace_rmdir(struct aa_namespace *ns) | |
+/** | |
+ * | |
+ * Requires: @ns->lock held | |
+ */ | |
+void __aa_fs_ns_rmdir(struct aa_ns *ns) | |
{ | |
- struct aa_namespace *sub; | |
+ struct aa_ns *sub; | |
struct aa_profile *child; | |
int i; | |
if (!ns) | |
return; | |
+ AA_BUG(!mutex_is_locked(&ns->lock)); | |
list_for_each_entry(child, &ns->base.profiles, base.list) | |
__aa_fs_profile_rmdir(child); | |
list_for_each_entry(sub, &ns->sub_ns, base.list) { | |
mutex_lock(&sub->lock); | |
- __aa_fs_namespace_rmdir(sub); | |
+ __aa_fs_ns_rmdir(sub); | |
mutex_unlock(&sub->lock); | |
} | |
@@ -501,14 +714,21 @@ void __aa_fs_namespace_rmdir(struct aa_namespace *ns) | |
} | |
} | |
-int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, | |
- const char *name) | |
+/** | |
+ * | |
+ * Requires: @ns->lock held | |
+ */ | |
+int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) | |
{ | |
- struct aa_namespace *sub; | |
+ struct aa_ns *sub; | |
struct aa_profile *child; | |
struct dentry *dent, *dir; | |
int error; | |
+ AA_BUG(!ns); | |
+ AA_BUG(!parent); | |
+ AA_BUG(!mutex_is_locked(&ns->lock)); | |
+ | |
if (!name) | |
name = ns->base.name; | |
@@ -535,7 +755,7 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, | |
list_for_each_entry(sub, &ns->sub_ns, base.list) { | |
mutex_lock(&sub->lock); | |
- error = __aa_fs_namespace_mkdir(sub, ns_subns_dir(ns), NULL); | |
+ error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL); | |
mutex_unlock(&sub->lock); | |
if (error) | |
goto fail2; | |
@@ -547,7 +767,7 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, | |
error = PTR_ERR(dent); | |
fail2: | |
- __aa_fs_namespace_rmdir(ns); | |
+ __aa_fs_ns_rmdir(ns); | |
return error; | |
} | |
@@ -556,7 +776,7 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, | |
#define list_entry_is_head(pos, head, member) (&pos->member == (head)) | |
/** | |
- * __next_namespace - find the next namespace to list | |
+ * __next_ns - find the next namespace to list | |
* @root: root namespace to stop search at (NOT NULL) | |
* @ns: current ns position (NOT NULL) | |
* | |
@@ -567,10 +787,13 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, | |
* Requires: ns->parent->lock to be held | |
* NOTE: will not unlock root->lock | |
*/ | |
-static struct aa_namespace *__next_namespace(struct aa_namespace *root, | |
- struct aa_namespace *ns) | |
+static struct aa_ns *__next_ns(struct aa_ns *root, struct aa_ns *ns) | |
{ | |
- struct aa_namespace *parent, *next; | |
+ struct aa_ns *parent, *next; | |
+ | |
+ AA_BUG(!root); | |
+ AA_BUG(!ns); | |
+ AA_BUG(ns != root && !mutex_is_locked(&ns->parent->lock)); | |
/* is next namespace a child */ | |
if (!list_empty(&ns->sub_ns)) { | |
@@ -598,15 +821,17 @@ static struct aa_namespace *__next_namespace(struct aa_namespace *root, | |
/** | |
* __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) | |
+ * @ns: namespace to start in (MAY BE NULL) | |
* | |
* Returns: unrefcounted profile or NULL if no profile | |
- * Requires: profile->ns.lock to be held | |
+ * Requires: ns.lock to be held | |
*/ | |
-static struct aa_profile *__first_profile(struct aa_namespace *root, | |
- struct aa_namespace *ns) | |
+static struct aa_profile *__first_profile(struct aa_ns *root, struct aa_ns *ns) | |
{ | |
- for (; ns; ns = __next_namespace(root, ns)) { | |
+ AA_BUG(!root); | |
+ AA_BUG(ns && !mutex_is_locked(&ns->lock)); | |
+ | |
+ for (; ns; ns = __next_ns(root, ns)) { | |
if (!list_empty(&ns->base.profiles)) | |
return list_first_entry(&ns->base.profiles, | |
struct aa_profile, base.list); | |
@@ -626,7 +851,9 @@ static struct aa_profile *__first_profile(struct aa_namespace *root, | |
static struct aa_profile *__next_profile(struct aa_profile *p) | |
{ | |
struct aa_profile *parent; | |
- struct aa_namespace *ns = p->ns; | |
+ struct aa_ns *ns = p->ns; | |
+ | |
+ AA_BUG(!mutex_is_locked(&profiles_ns(p)->lock)); | |
/* is next profile a child */ | |
if (!list_empty(&p->base.profiles)) | |
@@ -660,7 +887,7 @@ static struct aa_profile *__next_profile(struct aa_profile *p) | |
* | |
* Returns: next profile or NULL if there isn't one | |
*/ | |
-static struct aa_profile *next_profile(struct aa_namespace *root, | |
+static struct aa_profile *next_profile(struct aa_ns *root, | |
struct aa_profile *profile) | |
{ | |
struct aa_profile *next = __next_profile(profile); | |
@@ -668,7 +895,7 @@ static struct aa_profile *next_profile(struct aa_namespace *root, | |
return next; | |
/* finished all profiles in namespace move to next namespace */ | |
- return __first_profile(root, __next_namespace(root, profile->ns)); | |
+ return __first_profile(root, __next_ns(root, profile->ns)); | |
} | |
/** | |
@@ -683,10 +910,9 @@ static struct aa_profile *next_profile(struct aa_namespace *root, | |
static void *p_start(struct seq_file *f, loff_t *pos) | |
{ | |
struct aa_profile *profile = NULL; | |
- struct aa_namespace *root = aa_current_profile()->ns; | |
+ struct aa_ns *root = aa_get_current_ns(); | |
loff_t l = *pos; | |
- f->private = aa_get_namespace(root); | |
- | |
+ f->private = root; | |
/* find the first profile */ | |
mutex_lock(&root->lock); | |
@@ -712,7 +938,7 @@ static void *p_start(struct seq_file *f, loff_t *pos) | |
static void *p_next(struct seq_file *f, void *p, loff_t *pos) | |
{ | |
struct aa_profile *profile = p; | |
- struct aa_namespace *ns = f->private; | |
+ struct aa_ns *ns = f->private; | |
(*pos)++; | |
return next_profile(ns, profile); | |
@@ -728,14 +954,14 @@ static void *p_next(struct seq_file *f, void *p, loff_t *pos) | |
static void p_stop(struct seq_file *f, void *p) | |
{ | |
struct aa_profile *profile = p; | |
- struct aa_namespace *root = f->private, *ns; | |
+ struct aa_ns *root = f->private, *ns; | |
if (profile) { | |
for (ns = profile->ns; ns && ns != root; ns = ns->parent) | |
mutex_unlock(&ns->lock); | |
} | |
mutex_unlock(&root->lock); | |
- aa_put_namespace(root); | |
+ aa_put_ns(root); | |
} | |
/** | |
@@ -748,12 +974,11 @@ static void p_stop(struct seq_file *f, void *p) | |
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; | |
+ struct aa_ns *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, | |
- aa_profile_mode_names[profile->mode]); | |
+ aa_label_seq_xprint(f, root, &profile->label, | |
+ FLAG_SHOW_MODE | FLAG_VIEW_SUBNS, GFP_KERNEL); | |
+ seq_printf(f, "\n"); | |
return 0; | |
} | |
@@ -767,6 +992,9 @@ static int seq_show_profile(struct seq_file *f, void *p) | |
static int profiles_open(struct inode *inode, struct file *file) | |
{ | |
+ if (!policy_admin_capable()) | |
+ return -EACCES; | |
+ | |
return seq_open(file, &aa_fs_profiles_op); | |
} | |
@@ -790,34 +1018,76 @@ static int profiles_release(struct inode *inode, struct file *file) | |
{ } | |
}; | |
+static struct aa_fs_entry aa_fs_entry_ptrace[] = { | |
+ AA_FS_FILE_STRING("mask", "read trace"), | |
+ { } | |
+}; | |
+ | |
+static struct aa_fs_entry aa_fs_entry_signal[] = { | |
+ AA_FS_FILE_STRING("mask", AA_FS_SIG_MASK), | |
+ { } | |
+}; | |
+ | |
static struct aa_fs_entry aa_fs_entry_domain[] = { | |
AA_FS_FILE_BOOLEAN("change_hat", 1), | |
AA_FS_FILE_BOOLEAN("change_hatv", 1), | |
AA_FS_FILE_BOOLEAN("change_onexec", 1), | |
AA_FS_FILE_BOOLEAN("change_profile", 1), | |
+ AA_FS_FILE_BOOLEAN("stack", 1), | |
+ { } | |
+}; | |
+ | |
+static struct aa_fs_entry aa_fs_entry_versions[] = { | |
+ AA_FS_FILE_BOOLEAN("v5", 1), | |
+ AA_FS_FILE_BOOLEAN("v6", 1), | |
+ AA_FS_FILE_BOOLEAN("v7", 1), | |
{ } | |
}; | |
static struct aa_fs_entry aa_fs_entry_policy[] = { | |
- AA_FS_FILE_BOOLEAN("set_load", 1), | |
- {} | |
+ AA_FS_DIR("versions", aa_fs_entry_versions), | |
+ 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_ns[] = { | |
+ AA_FS_FILE_BOOLEAN("profile", 1), | |
+ AA_FS_FILE_BOOLEAN("pivot_root", 1), | |
+ { } | |
+}; | |
+ | |
+static struct aa_fs_entry aa_fs_entry_dbus[] = { | |
+ AA_FS_FILE_STRING("mask", "acquire send receive"), | |
+ { } | |
}; | |
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_DIR("mount", aa_fs_entry_mount), | |
+ AA_FS_DIR("namespaces", aa_fs_entry_ns), | |
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), | |
+ AA_FS_DIR("ptrace", aa_fs_entry_ptrace), | |
+ AA_FS_DIR("signal", aa_fs_entry_signal), | |
+ AA_FS_DIR("dbus", aa_fs_entry_dbus), | |
{ } | |
}; | |
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_FILE_FOPS(".load", 0666, &aa_fs_profile_load), | |
+ AA_FS_FILE_FOPS(".replace", 0666, &aa_fs_profile_replace), | |
+ AA_FS_FILE_FOPS(".remove", 0666, &aa_fs_profile_remove), | |
+ AA_FS_FILE_FOPS(".access", 0666, &aa_fs_access), | |
+ AA_FS_FILE_FOPS("profiles", 0444, &aa_fs_profiles_fops), | |
AA_FS_DIR("features", aa_fs_entry_features), | |
{ } | |
}; | |
@@ -926,6 +1196,51 @@ void __init aa_destroy_aafs(void) | |
aafs_remove_dir(&aa_fs_entry); | |
} | |
+ | |
+#define NULL_FILE_NAME ".null" | |
+struct path aa_null; | |
+ | |
+static int aa_mk_null_file(struct dentry *parent) | |
+{ | |
+ struct vfsmount *mount = NULL; | |
+ struct dentry *dentry; | |
+ struct inode *inode; | |
+ int count = 0; | |
+ int error = simple_pin_fs(parent->d_sb->s_type, &mount, &count); | |
+ if (error) | |
+ return error; | |
+ | |
+ inode_lock(d_inode(parent)); | |
+ dentry = lookup_one_len(NULL_FILE_NAME, parent, strlen(NULL_FILE_NAME)); | |
+ if (IS_ERR(dentry)) { | |
+ error = PTR_ERR(dentry); | |
+ goto out; | |
+ } | |
+ inode = new_inode(parent->d_inode->i_sb); | |
+ if (!inode) { | |
+ error = -ENOMEM; | |
+ goto out1; | |
+ } | |
+ | |
+ inode->i_ino = get_next_ino(); | |
+ inode->i_mode = S_IFCHR | S_IRUGO | S_IWUGO; | |
+ inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; | |
+ init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO, | |
+ MKDEV(MEM_MAJOR, 3)); | |
+ d_instantiate(dentry, inode); | |
+ aa_null.dentry = dget(dentry); | |
+ aa_null.mnt = mntget(mount); | |
+ | |
+ error = 0; | |
+ | |
+out1: | |
+ dput(dentry); | |
+out: | |
+ inode_unlock(d_inode(parent)); | |
+ simple_release_fs(&mount, &count); | |
+ return error; | |
+} | |
+ | |
/** | |
* aa_create_aafs - create the apparmor security filesystem | |
* | |
@@ -950,12 +1265,20 @@ static int __init aa_create_aafs(void) | |
if (error) | |
goto error; | |
- error = __aa_fs_namespace_mkdir(root_ns, aa_fs_entry.dentry, | |
- "policy"); | |
+ mutex_lock(&root_ns->lock); | |
+ error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy"); | |
+ mutex_unlock(&root_ns->lock); | |
+ | |
+ if (error) | |
+ goto error; | |
+ | |
+ error = aa_mk_null_file(aa_fs_entry.dentry); | |
if (error) | |
goto error; | |
- /* TODO: add support for apparmorfs_null and apparmorfs_mnt */ | |
+ if (!aa_g_unconfined_init) { | |
+ /* TODO: add default profile to apparmorfs */ | |
+ } | |
/* Report that AppArmor fs is enabled */ | |
aa_info_message("AppArmor Filesystem Enabled"); | |
diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c | |
index 3a7f1da..ec2daa2 100644 | |
--- a/security/apparmor/audit.c | |
+++ b/security/apparmor/audit.c | |
@@ -18,60 +18,8 @@ | |
#include "include/apparmor.h" | |
#include "include/audit.h" | |
#include "include/policy.h" | |
+#include "include/policy_ns.h" | |
-const char *const op_table[] = { | |
- "null", | |
- | |
- "sysctl", | |
- "capable", | |
- | |
- "unlink", | |
- "mkdir", | |
- "rmdir", | |
- "mknod", | |
- "truncate", | |
- "link", | |
- "symlink", | |
- "rename_src", | |
- "rename_dest", | |
- "chmod", | |
- "chown", | |
- "getattr", | |
- "open", | |
- | |
- "file_perm", | |
- "file_lock", | |
- "file_mmap", | |
- "file_mprotect", | |
- | |
- "create", | |
- "post_create", | |
- "bind", | |
- "connect", | |
- "listen", | |
- "accept", | |
- "sendmsg", | |
- "recvmsg", | |
- "getsockname", | |
- "getpeername", | |
- "getsockopt", | |
- "setsockopt", | |
- "socket_shutdown", | |
- | |
- "ptrace", | |
- | |
- "exec", | |
- "change_hat", | |
- "change_profile", | |
- "change_onexec", | |
- | |
- "setprocattr", | |
- "setrlimit", | |
- | |
- "profile_replace", | |
- "profile_load", | |
- "profile_remove" | |
-}; | |
const char *const audit_mode_names[] = { | |
"normal", | |
@@ -114,34 +62,42 @@ static void audit_pre(struct audit_buffer *ab, void *ca) | |
if (aa_g_audit_header) { | |
audit_log_format(ab, "apparmor="); | |
- audit_log_string(ab, aa_audit_type[sa->aad->type]); | |
+ audit_log_string(ab, aa_audit_type[aad(sa)->type]); | |
} | |
- if (sa->aad->op) { | |
+ if (aad(sa)->op) { | |
audit_log_format(ab, " operation="); | |
- audit_log_string(ab, op_table[sa->aad->op]); | |
+ audit_log_string(ab, aad(sa)->op); | |
} | |
- if (sa->aad->info) { | |
+ if (aad(sa)->info) { | |
audit_log_format(ab, " info="); | |
- audit_log_string(ab, sa->aad->info); | |
- if (sa->aad->error) | |
- audit_log_format(ab, " error=%d", sa->aad->error); | |
+ audit_log_string(ab, aad(sa)->info); | |
+ if (aad(sa)->error) | |
+ audit_log_format(ab, " error=%d", aad(sa)->error); | |
} | |
- if (sa->aad->profile) { | |
- struct aa_profile *profile = sa->aad->profile; | |
- if (profile->ns != root_ns) { | |
- audit_log_format(ab, " namespace="); | |
- audit_log_untrustedstring(ab, profile->ns->base.hname); | |
+ if (aad(sa)->label) { | |
+ struct aa_label *label = aad(sa)->label; | |
+ if (label_isprofile(label)) { | |
+ struct aa_profile *profile = labels_profile(label); | |
+ if (profile->ns != root_ns) { | |
+ audit_log_format(ab, " namespace="); | |
+ audit_log_untrustedstring(ab, | |
+ profile->ns->base.hname); | |
+ } | |
+ audit_log_format(ab, " profile="); | |
+ audit_log_untrustedstring(ab, profile->base.hname); | |
+ } else { | |
+ audit_log_format(ab, " label="); | |
+ aa_label_xaudit(ab, root_ns, label, FLAG_VIEW_SUBNS, | |
+ GFP_ATOMIC); | |
} | |
- audit_log_format(ab, " profile="); | |
- audit_log_untrustedstring(ab, profile->base.hname); | |
} | |
- if (sa->aad->name) { | |
+ if (aad(sa)->name) { | |
audit_log_format(ab, " name="); | |
- audit_log_untrustedstring(ab, sa->aad->name); | |
+ audit_log_untrustedstring(ab, aad(sa)->name); | |
} | |
} | |
@@ -153,7 +109,12 @@ static void audit_pre(struct audit_buffer *ab, void *ca) | |
void aa_audit_msg(int type, struct common_audit_data *sa, | |
void (*cb) (struct audit_buffer *, void *)) | |
{ | |
- sa->aad->type = type; | |
+ /* TODO: redirect messages for profile to the correct ns | |
+ * rejects from subns should goto the audit associated | |
+ * with it, and audits from parent ns should got ns | |
+ * associated with it | |
+ */ | |
+ aad(sa)->type = type; | |
common_lsm_audit(sa, audit_pre, cb); | |
} | |
@@ -161,7 +122,6 @@ void aa_audit_msg(int type, struct common_audit_data *sa, | |
* aa_audit - Log a profile based audit event to the audit subsystem | |
* @type: audit type for the message | |
* @profile: profile to check against (NOT NULL) | |
- * @gfp: allocation flags to use | |
* @sa: audit event (NOT NULL) | |
* @cb: optional callback fn for type specific fields (MAYBE NULL) | |
* | |
@@ -169,14 +129,13 @@ void aa_audit_msg(int type, struct common_audit_data *sa, | |
* | |
* Returns: error on failure | |
*/ | |
-int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, | |
- struct common_audit_data *sa, | |
+int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa, | |
void (*cb) (struct audit_buffer *, void *)) | |
{ | |
BUG_ON(!profile); | |
if (type == AUDIT_APPARMOR_AUTO) { | |
- if (likely(!sa->aad->error)) { | |
+ if (likely(!aad(sa)->error)) { | |
if (AUDIT_MODE(profile) != AUDIT_ALL) | |
return 0; | |
type = AUDIT_APPARMOR_AUDIT; | |
@@ -188,23 +147,22 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, | |
if (AUDIT_MODE(profile) == AUDIT_QUIET || | |
(type == AUDIT_APPARMOR_DENIED && | |
AUDIT_MODE(profile) == AUDIT_QUIET)) | |
- return sa->aad->error; | |
+ return aad(sa)->error; | |
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED) | |
type = AUDIT_APPARMOR_KILL; | |
- if (!unconfined(profile)) | |
- sa->aad->profile = profile; | |
+ aad(sa)->label = &profile->label; | |
aa_audit_msg(type, sa, cb); | |
- if (sa->aad->type == AUDIT_APPARMOR_KILL) | |
+ if (aad(sa)->type == AUDIT_APPARMOR_KILL) | |
(void)send_sig_info(SIGKILL, NULL, | |
sa->type == LSM_AUDIT_DATA_TASK && sa->u.tsk ? | |
sa->u.tsk : current); | |
- if (sa->aad->type == AUDIT_APPARMOR_ALLOWED) | |
- return complain_error(sa->aad->error); | |
+ if (aad(sa)->type == AUDIT_APPARMOR_ALLOWED) | |
+ return complain_error(aad(sa)->error); | |
- return sa->aad->error; | |
+ return aad(sa)->error; | |
} | |
diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c | |
index 1101c6f..3c3b69b 100644 | |
--- a/security/apparmor/capability.c | |
+++ b/security/apparmor/capability.c | |
@@ -53,6 +53,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) | |
/** | |
* audit_caps - audit a capability | |
+ * @sa: audit data | |
* @profile: profile being tested for confinement (NOT NULL) | |
* @cap: capability tested | |
* @error: error code returned by test | |
@@ -62,17 +63,12 @@ static void audit_cb(struct audit_buffer *ab, void *va) | |
* | |
* Returns: 0 or sa->error on success, error code on failure | |
*/ | |
-static int audit_caps(struct aa_profile *profile, int cap, int error) | |
+static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile, | |
+ int cap, int error) | |
{ | |
struct audit_cache *ent; | |
int type = AUDIT_APPARMOR_AUTO; | |
- struct common_audit_data sa; | |
- struct apparmor_audit_data aad = {0,}; | |
- sa.type = LSM_AUDIT_DATA_CAP; | |
- sa.aad = &aad; | |
- sa.u.cap = cap; | |
- sa.aad->op = OP_CAPABLE; | |
- sa.aad->error = error; | |
+ aad(sa)->error = error; | |
if (likely(!error)) { | |
/* test if auditing is being forced */ | |
@@ -104,24 +100,44 @@ static int audit_caps(struct aa_profile *profile, int cap, int error) | |
} | |
put_cpu_var(audit_cache); | |
- return aa_audit(type, profile, GFP_ATOMIC, &sa, audit_cb); | |
+ return aa_audit(type, profile, sa, audit_cb); | |
} | |
/** | |
* profile_capable - test if profile allows use of capability @cap | |
* @profile: profile being enforced (NOT NULL, NOT unconfined) | |
* @cap: capability to test if allowed | |
+ * @audit: whether an audit record should be generated | |
+ * @sa: audit data (MAY BE NULL indicating no auditing) | |
* | |
* Returns: 0 if allowed else -EPERM | |
*/ | |
-static int profile_capable(struct aa_profile *profile, int cap) | |
+static int profile_capable(struct aa_profile *profile, int cap, int audit, | |
+ struct common_audit_data *sa) | |
{ | |
- return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM; | |
+ int error; | |
+ | |
+ if (cap_raised(profile->caps.allow, cap) && | |
+ !cap_raised(profile->caps.denied, cap)) | |
+ error = 0; | |
+ else | |
+ error = -EPERM; | |
+ | |
+ if (audit == SECURITY_CAP_NOAUDIT) { | |
+ if (!COMPLAIN_MODE(profile)) | |
+ return error; | |
+ /* audit the cap request in complain mode but note that it | |
+ * should be optional. | |
+ */ | |
+ aad(sa)->info = "optional: no audit"; | |
+ } | |
+ | |
+ return audit_caps(sa, profile, cap, error); | |
} | |
/** | |
* aa_capable - test permission to use capability | |
- * @profile: profile being tested against (NOT NULL) | |
+ * @label: label being tested for capability (NOT NULL) | |
* @cap: capability to be tested | |
* @audit: whether an audit record should be generated | |
* | |
@@ -129,15 +145,15 @@ static int profile_capable(struct aa_profile *profile, int cap) | |
* | |
* Returns: 0 on success, or else an error code. | |
*/ | |
-int aa_capable(struct aa_profile *profile, int cap, int audit) | |
+int aa_capable(struct aa_label *label, int cap, int audit) | |
{ | |
- int error = profile_capable(profile, cap); | |
+ struct aa_profile *profile; | |
+ int error = 0; | |
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE); | |
+ sa.u.cap = cap; | |
- if (!audit) { | |
- if (COMPLAIN_MODE(profile)) | |
- return complain_error(error); | |
- return error; | |
- } | |
+ error = fn_for_each_confined(label, profile, | |
+ profile_capable(profile, cap, audit, &sa)); | |
- return audit_caps(profile, cap, error); | |
+ return error; | |
} | |
diff --git a/security/apparmor/context.c b/security/apparmor/context.c | |
index 3064c6c..825b115 100644 | |
--- a/security/apparmor/context.c | |
+++ b/security/apparmor/context.c | |
@@ -13,11 +13,11 @@ | |
* License. | |
* | |
* | |
- * AppArmor sets confinement on every task, via the the aa_task_cxt and | |
- * the aa_task_cxt.profile, both of which are required and are not allowed | |
- * to be NULL. The aa_task_cxt is not reference counted and is unique | |
- * to each cred (which is reference count). The profile pointed to by | |
- * the task_cxt is reference counted. | |
+ * AppArmor sets confinement on every task, via the the aa_task_ctx and | |
+ * the aa_task_ctx.label, both of which are required and are not allowed | |
+ * to be NULL. The aa_task_ctx is not reference counted and is unique | |
+ * to each cred (which is reference count). The label pointed to by | |
+ * the task_ctx is reference counted. | |
* | |
* TODO | |
* If a task uses change_hat it currently does not return to the old | |
@@ -30,28 +30,28 @@ | |
#include "include/policy.h" | |
/** | |
- * aa_alloc_task_context - allocate a new task_cxt | |
+ * aa_alloc_task_context - allocate a new task_ctx | |
* @flags: gfp flags for allocation | |
* | |
* Returns: allocated buffer or NULL on failure | |
*/ | |
-struct aa_task_cxt *aa_alloc_task_context(gfp_t flags) | |
+struct aa_task_ctx *aa_alloc_task_context(gfp_t flags) | |
{ | |
- return kzalloc(sizeof(struct aa_task_cxt), flags); | |
+ return kzalloc(sizeof(struct aa_task_ctx), flags); | |
} | |
/** | |
- * aa_free_task_context - free a task_cxt | |
- * @cxt: task_cxt to free (MAYBE NULL) | |
+ * aa_free_task_context - free a task_ctx | |
+ * @ctx: task_ctx to free (MAYBE NULL) | |
*/ | |
-void aa_free_task_context(struct aa_task_cxt *cxt) | |
+void aa_free_task_context(struct aa_task_ctx *ctx) | |
{ | |
- if (cxt) { | |
- aa_put_profile(cxt->profile); | |
- aa_put_profile(cxt->previous); | |
- aa_put_profile(cxt->onexec); | |
+ if (ctx) { | |
+ aa_put_label(ctx->label); | |
+ aa_put_label(ctx->previous); | |
+ aa_put_label(ctx->onexec); | |
- kzfree(cxt); | |
+ kzfree(ctx); | |
} | |
} | |
@@ -60,64 +60,63 @@ void aa_free_task_context(struct aa_task_cxt *cxt) | |
* @new: a blank task context (NOT NULL) | |
* @old: the task context to copy (NOT NULL) | |
*/ | |
-void aa_dup_task_context(struct aa_task_cxt *new, const struct aa_task_cxt *old) | |
+void aa_dup_task_context(struct aa_task_ctx *new, const struct aa_task_ctx *old) | |
{ | |
*new = *old; | |
- aa_get_profile(new->profile); | |
- aa_get_profile(new->previous); | |
- aa_get_profile(new->onexec); | |
+ aa_get_label(new->label); | |
+ aa_get_label(new->previous); | |
+ aa_get_label(new->onexec); | |
} | |
/** | |
- * aa_get_task_profile - Get another task's profile | |
+ * aa_get_task_label - Get another task's label | |
* @task: task to query (NOT NULL) | |
* | |
- * Returns: counted reference to @task's profile | |
+ * Returns: counted reference to @task's label | |
*/ | |
-struct aa_profile *aa_get_task_profile(struct task_struct *task) | |
+struct aa_label *aa_get_task_label(struct task_struct *task) | |
{ | |
- struct aa_profile *p; | |
+ struct aa_label *p; | |
rcu_read_lock(); | |
- p = aa_get_profile(__aa_task_profile(task)); | |
+ p = aa_get_newest_label(__aa_task_raw_label(task)); | |
rcu_read_unlock(); | |
return p; | |
} | |
/** | |
- * aa_replace_current_profile - replace the current tasks profiles | |
- * @profile: new profile (NOT NULL) | |
+ * aa_replace_current_label - replace the current tasks label | |
+ * @label: new label (NOT NULL) | |
* | |
* Returns: 0 or error on failure | |
*/ | |
-int aa_replace_current_profile(struct aa_profile *profile) | |
+int aa_replace_current_label(struct aa_label *label) | |
{ | |
- struct aa_task_cxt *cxt = current_cxt(); | |
+ struct aa_task_ctx *ctx = current_ctx(); | |
struct cred *new; | |
- BUG_ON(!profile); | |
+ BUG_ON(!label); | |
- if (cxt->profile == profile) | |
+ if (ctx->label == label) | |
return 0; | |
+ if (current_cred() != current_real_cred()) | |
+ return -EBUSY; | |
+ | |
new = prepare_creds(); | |
if (!new) | |
return -ENOMEM; | |
- cxt = cred_cxt(new); | |
- if (unconfined(profile) || (cxt->profile->ns != profile->ns)) | |
- /* if switching to unconfined or a different profile namespace | |
+ ctx = cred_ctx(new); | |
+ if (unconfined(label) || (labels_ns(ctx->label) != labels_ns(label))) | |
+ /* if switching to unconfined or a different label namespace | |
* clear out context state | |
*/ | |
- aa_clear_task_cxt_trans(cxt); | |
+ aa_clear_task_ctx_trans(ctx); | |
- /* be careful switching cxt->profile, when racing replacement it | |
- * is possible that cxt->profile->replacedby->profile is the reference | |
- * keeping @profile valid, so make sure to get its reference before | |
- * dropping the reference on cxt->profile */ | |
- aa_get_profile(profile); | |
- aa_put_profile(cxt->profile); | |
- cxt->profile = profile; | |
+ aa_get_label(label); | |
+ aa_put_label(ctx->label); | |
+ ctx->label = label; | |
commit_creds(new); | |
return 0; | |
@@ -125,21 +124,22 @@ int aa_replace_current_profile(struct aa_profile *profile) | |
/** | |
* aa_set_current_onexec - set the tasks change_profile to happen onexec | |
- * @profile: system profile to set at exec (MAYBE NULL to clear value) | |
- * | |
+ * @label: system label to set at exec (MAYBE NULL to clear value) | |
+ * @stack: whether stacking should be done | |
* Returns: 0 or error on failure | |
*/ | |
-int aa_set_current_onexec(struct aa_profile *profile) | |
+int aa_set_current_onexec(struct aa_label *label, bool stack) | |
{ | |
- struct aa_task_cxt *cxt; | |
+ struct aa_task_ctx *ctx; | |
struct cred *new = prepare_creds(); | |
if (!new) | |
return -ENOMEM; | |
- cxt = cred_cxt(new); | |
- aa_get_profile(profile); | |
- aa_put_profile(cxt->onexec); | |
- cxt->onexec = profile; | |
+ ctx = cred_ctx(new); | |
+ aa_get_label(label); | |
+ aa_clear_task_ctx_trans(ctx); | |
+ ctx->onexec = label; | |
+ ctx->token = stack; | |
commit_creds(new); | |
return 0; | |
@@ -147,7 +147,7 @@ int aa_set_current_onexec(struct aa_profile *profile) | |
/** | |
* aa_set_current_hat - set the current tasks hat | |
- * @profile: profile to set as the current hat (NOT NULL) | |
+ * @label: label to set as the current hat (NOT NULL) | |
* @token: token value that must be specified to change from the hat | |
* | |
* Do switch of tasks hat. If the task is currently in a hat | |
@@ -155,67 +155,67 @@ int aa_set_current_onexec(struct aa_profile *profile) | |
* | |
* Returns: 0 or error on failure | |
*/ | |
-int aa_set_current_hat(struct aa_profile *profile, u64 token) | |
+int aa_set_current_hat(struct aa_label *label, u64 token) | |
{ | |
- struct aa_task_cxt *cxt; | |
+ struct aa_task_ctx *ctx; | |
struct cred *new = prepare_creds(); | |
if (!new) | |
return -ENOMEM; | |
- BUG_ON(!profile); | |
+ BUG_ON(!label); | |
- cxt = cred_cxt(new); | |
- if (!cxt->previous) { | |
+ ctx = cred_ctx(new); | |
+ if (!ctx->previous) { | |
/* transfer refcount */ | |
- cxt->previous = cxt->profile; | |
- cxt->token = token; | |
- } else if (cxt->token == token) { | |
- aa_put_profile(cxt->profile); | |
+ ctx->previous = ctx->label; | |
+ ctx->token = token; | |
+ } else if (ctx->token == token) { | |
+ aa_put_label(ctx->label); | |
} else { | |
- /* previous_profile && cxt->token != token */ | |
+ /* previous_profile && ctx->token != token */ | |
abort_creds(new); | |
return -EACCES; | |
} | |
- cxt->profile = aa_get_newest_profile(profile); | |
+ ctx->label = aa_get_newest_label(label); | |
/* clear exec on switching context */ | |
- aa_put_profile(cxt->onexec); | |
- cxt->onexec = NULL; | |
+ aa_put_label(ctx->onexec); | |
+ ctx->onexec = NULL; | |
commit_creds(new); | |
return 0; | |
} | |
/** | |
- * aa_restore_previous_profile - exit from hat context restoring the profile | |
+ * aa_restore_previous_label - exit from hat context restoring previous label | |
* @token: the token that must be matched to exit hat context | |
* | |
- * Attempt to return out of a hat to the previous profile. The token | |
+ * Attempt to return out of a hat to the previous label. The token | |
* must match the stored token value. | |
* | |
* Returns: 0 or error of failure | |
*/ | |
-int aa_restore_previous_profile(u64 token) | |
+int aa_restore_previous_label(u64 token) | |
{ | |
- struct aa_task_cxt *cxt; | |
+ struct aa_task_ctx *ctx; | |
struct cred *new = prepare_creds(); | |
if (!new) | |
return -ENOMEM; | |
- cxt = cred_cxt(new); | |
- if (cxt->token != token) { | |
+ ctx = cred_ctx(new); | |
+ if (ctx->token != token) { | |
abort_creds(new); | |
return -EACCES; | |
} | |
- /* ignore restores when there is no saved profile */ | |
- if (!cxt->previous) { | |
+ /* ignore restores when there is no saved label */ | |
+ if (!ctx->previous) { | |
abort_creds(new); | |
return 0; | |
} | |
- aa_put_profile(cxt->profile); | |
- cxt->profile = aa_get_newest_profile(cxt->previous); | |
- BUG_ON(!cxt->profile); | |
+ aa_put_label(ctx->label); | |
+ ctx->label = aa_get_newest_label(ctx->previous); | |
+ BUG_ON(!ctx->label); | |
/* clear exec && prev information when restoring to previous context */ | |
- aa_clear_task_cxt_trans(cxt); | |
+ aa_clear_task_ctx_trans(ctx); | |
commit_creds(new); | |
return 0; | |
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c | |
index a4d90aa..765c9c8 100644 | |
--- a/security/apparmor/domain.c | |
+++ b/security/apparmor/domain.c | |
@@ -50,76 +50,259 @@ void aa_free_domain_entries(struct aa_domain *domain) | |
/** | |
* may_change_ptraced_domain - check if can change profile on ptraced task | |
- * @to_profile: profile to change to (NOT NULL) | |
+ * @to_label: profile to change to (NOT NULL) | |
+ * @info: message if there is an error | |
* | |
* Check if current is ptraced and if so if the tracing task is allowed | |
* to trace the new domain | |
* | |
* Returns: %0 or error if change not allowed | |
*/ | |
-static int may_change_ptraced_domain(struct aa_profile *to_profile) | |
+static int may_change_ptraced_domain(struct aa_label *to_label, | |
+ const char **info) | |
{ | |
struct task_struct *tracer; | |
- struct aa_profile *tracerp = NULL; | |
+ struct aa_label *tracerl = NULL; | |
int error = 0; | |
rcu_read_lock(); | |
tracer = ptrace_parent(current); | |
if (tracer) | |
/* released below */ | |
- tracerp = aa_get_task_profile(tracer); | |
+ tracerl = aa_get_task_label(tracer); | |
/* not ptraced */ | |
- if (!tracer || unconfined(tracerp)) | |
+ if (!tracer || unconfined(tracerl)) | |
goto out; | |
- error = aa_may_ptrace(tracerp, to_profile, PTRACE_MODE_ATTACH); | |
+ error = aa_may_ptrace(tracerl, to_label, PTRACE_MODE_ATTACH); | |
out: | |
rcu_read_unlock(); | |
- aa_put_profile(tracerp); | |
+ aa_put_label(tracerl); | |
+ if (error) | |
+ *info = "ptrace prevents transition"; | |
return error; | |
} | |
+/**** TODO: dedup to aa_label_match - needs perm and dfa, merging | |
+ * specifically this is an exact copy of aa_label_match except | |
+ * aa_compute_perms is replaced with aa_compute_fperms | |
+ * and policy.dfa with file.dfa | |
+ ****/ | |
+/* match a profile and its associated ns component if needed | |
+ * Assumes visibility test has already been done. | |
+ * If a subns profile is not to be matched should be prescreened with | |
+ * visibility test. | |
+ */ | |
+/* match a profile and its associated ns component if needed | |
+ * Assumes visibility test has already been done. | |
+ * If a subns profile is not to be matched should be prescreened with | |
+ * visibility test. | |
+ */ | |
+static inline unsigned int match_component(struct aa_profile *profile, | |
+ struct aa_profile *tp, | |
+ bool stack, unsigned int state) | |
+{ | |
+ const char *ns_name; | |
+ | |
+ if (stack) | |
+ state = aa_dfa_match(profile->file.dfa, state, "&"); | |
+ if (profile->ns == tp->ns) | |
+ return aa_dfa_match(profile->file.dfa, state, tp->base.hname); | |
+ | |
+ /* try matching with namespace name and then profile */ | |
+ ns_name = aa_ns_name(profile->ns, tp->ns, true); | |
+ state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); | |
+ state = aa_dfa_match(profile->file.dfa, state, ns_name); | |
+ state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); | |
+ return aa_dfa_match(profile->file.dfa, state, tp->base.hname); | |
+} | |
+ | |
+/** | |
+ * label_component_match - find perms for full compound label | |
+ * @profile: profile to find perms for | |
+ * @label: label to check access permissions for | |
+ * @stack: whether this is a stacking request | |
+ * @start: state to start match in | |
+ * @subns: whether to do permission checks on components in a subns | |
+ * @request: permissions to request | |
+ * @perms: perms struct to set | |
+ * | |
+ * Returns: 0 on success else ERROR | |
+ * | |
+ * For the label A//&B//&C this does the perm match for A//&B//&C | |
+ * @perms should be preinitialized with allperms OR a previous permission | |
+ * check to be stacked. | |
+ */ | |
+static int label_compound_match(struct aa_profile *profile, | |
+ struct aa_label *label, bool stack, | |
+ unsigned int state, bool subns, u32 request, | |
+ struct aa_perms *perms) | |
+{ | |
+ struct aa_profile *tp; | |
+ struct label_it i; | |
+ struct path_cond cond = { }; | |
+ | |
+ /* find first subcomponent that is visible */ | |
+ label_for_each(i, label, tp) { | |
+ if (!aa_ns_visible(profile->ns, tp->ns, subns)) | |
+ continue; | |
+ state = match_component(profile, tp, stack, state); | |
+ if (!state) | |
+ goto fail; | |
+ goto next; | |
+ } | |
+ | |
+ /* no component visible */ | |
+ *perms = allperms; | |
+ return 0; | |
+ | |
+next: | |
+ label_for_each_cont(i, label, tp) { | |
+ if (!aa_ns_visible(profile->ns, tp->ns, subns)) | |
+ continue; | |
+ state = aa_dfa_match(profile->file.dfa, state, "//&"); | |
+ state = match_component(profile, tp, false, state); | |
+ if (!state) | |
+ goto fail; | |
+ } | |
+ *perms = aa_compute_fperms(profile->file.dfa, state, &cond); | |
+ aa_apply_modes_to_perms(profile, perms); | |
+ if ((perms->allow & request) != request) | |
+ return -EACCES; | |
+ | |
+ return 0; | |
+ | |
+fail: | |
+ *perms = nullperms; | |
+ return -EACCES; | |
+} | |
+ | |
+/** | |
+ * label_component_match - find perms for all subcomponents of a label | |
+ * @profile: profile to find perms for | |
+ * @label: label to check access permissions for | |
+ * @stack: whether this is a stacking request | |
+ * @start: state to start match in | |
+ * @subns: whether to do permission checks on components in a subns | |
+ * @request: permissions to request | |
+ * @perms: an initialized perms struct to add accumulation to | |
+ * | |
+ * Returns: 0 on success else ERROR | |
+ * | |
+ * For the label A//&B//&C this does the perm match for each of A and B and C | |
+ * @perms should be preinitialized with allperms OR a previous permission | |
+ * check to be stacked. | |
+ */ | |
+static int label_components_match(struct aa_profile *profile, | |
+ struct aa_label *label, bool stack, | |
+ unsigned int start, bool subns, u32 request, | |
+ struct aa_perms *perms) | |
+{ | |
+ struct aa_profile *tp; | |
+ struct label_it i; | |
+ struct aa_perms tmp; | |
+ struct path_cond cond = { }; | |
+ unsigned int state = 0; | |
+ | |
+ /* find first subcomponent to test */ | |
+ label_for_each(i, label, tp) { | |
+ if (!aa_ns_visible(profile->ns, tp->ns, subns)) | |
+ continue; | |
+ state = match_component(profile, tp, stack, start); | |
+ if (!state) | |
+ goto fail; | |
+ goto next; | |
+ } | |
+ | |
+ /* no subcomponents visible - no change in perms */ | |
+ return 0; | |
+ | |
+next: | |
+ tmp = aa_compute_fperms(profile->file.dfa, state, &cond); | |
+ aa_apply_modes_to_perms(profile, &tmp); | |
+ aa_perms_accum(perms, &tmp); | |
+ label_for_each_cont(i, label, tp) { | |
+ if (!aa_ns_visible(profile->ns, tp->ns, subns)) | |
+ continue; | |
+ state = match_component(profile, tp, stack, start); | |
+ if (!state) | |
+ goto fail; | |
+ tmp = aa_compute_fperms(profile->file.dfa, state, &cond); | |
+ aa_apply_modes_to_perms(profile, &tmp); | |
+ aa_perms_accum(perms, &tmp); | |
+ } | |
+ | |
+ if ((perms->allow & request) != request) | |
+ return -EACCES; | |
+ | |
+ return 0; | |
+ | |
+fail: | |
+ *perms = nullperms; | |
+ return -EACCES; | |
+} | |
+ | |
+/** | |
+ * aa_label_match - do a multi-component label match | |
+ * @profile: profile to match against (NOT NULL) | |
+ * @label: label to match (NOT NULL) | |
+ * @stack: whether this is a stacking request | |
+ * @state: state to start in | |
+ * @subns: whether to match subns components | |
+ * @request: permission request | |
+ * @perms: Returns computed perms (NOT NULL) | |
+ * | |
+ * Returns: the state the match finished in, may be the none matching state | |
+ */ | |
+static int label_match(struct aa_profile *profile, struct aa_label *label, | |
+ bool stack, unsigned int state, bool subns, u32 request, | |
+ struct aa_perms *perms) | |
+{ | |
+ int error; | |
+ | |
+ *perms = nullperms; | |
+ error = label_compound_match(profile, label, stack, state, subns, | |
+ request, perms); | |
+ if (!error) | |
+ return error; | |
+ | |
+ *perms = allperms; | |
+ return label_components_match(profile, label, stack, state, subns, | |
+ request, perms); | |
+} | |
+ | |
+/******* end TODO: dedup *****/ | |
+ | |
/** | |
* change_profile_perms - find permissions for change_profile | |
* @profile: the current profile (NOT NULL) | |
- * @ns: the namespace being switched to (NOT NULL) | |
- * @name: the name of the profile to change to (NOT NULL) | |
+ * @target: label to transition to (NOT NULL) | |
+ * @stack: whether this is a stacking request | |
* @request: requested perms | |
* @start: state to start matching in | |
* | |
+ * | |
* Returns: permission set | |
+ * | |
+ * currently only matches full label A//&B//&C or individual components A, B, C | |
+ * not arbitrary combinations. Eg. A//&B, C | |
*/ | |
-static struct file_perms change_profile_perms(struct aa_profile *profile, | |
- struct aa_namespace *ns, | |
- const char *name, u32 request, | |
- unsigned int start) | |
+static int change_profile_perms(struct aa_profile *profile, | |
+ struct aa_label *target, bool stack, | |
+ u32 request, unsigned int start, | |
+ struct aa_perms *perms) | |
{ | |
- struct file_perms perms; | |
- struct path_cond cond = { }; | |
- unsigned int state; | |
- | |
- if (unconfined(profile)) { | |
- perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; | |
- perms.audit = perms.quiet = perms.kill = 0; | |
- return perms; | |
- } else if (!profile->file.dfa) { | |
- return nullperms; | |
- } else if ((ns == profile->ns)) { | |
- /* try matching against rules with out namespace prepended */ | |
- aa_str_perms(profile->file.dfa, start, name, &cond, &perms); | |
- if (COMBINED_PERM_MASK(perms) & request) | |
- return perms; | |
+ if (profile_unconfined(profile)) { | |
+ perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; | |
+ perms->audit = perms->quiet = perms->kill = 0; | |
+ return 0; | |
} | |
- /* try matching with namespace name and then profile */ | |
- state = aa_dfa_match(profile->file.dfa, start, ns->base.name); | |
- state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); | |
- aa_str_perms(profile->file.dfa, state, name, &cond, &perms); | |
- | |
- return perms; | |
+ /* TODO: add profile in ns screening */ | |
+ return label_match(profile, target, stack, start, true, request, perms); | |
} | |
/** | |
@@ -143,7 +326,7 @@ static struct aa_profile *__attach_match(const char *name, | |
struct aa_profile *profile, *candidate = NULL; | |
list_for_each_entry_rcu(profile, head, base.list) { | |
- if (profile->flags & PFLAG_NULL) | |
+ if (profile->label.flags & FLAG_NULL) | |
continue; | |
if (profile->xmatch && profile->xmatch_len > len) { | |
unsigned int state = aa_dfa_match(profile->xmatch, | |
@@ -168,10 +351,10 @@ static struct aa_profile *__attach_match(const char *name, | |
* @list: list to search (NOT NULL) | |
* @name: the executable name to match against (NOT NULL) | |
* | |
- * Returns: profile or NULL if no match found | |
+ * Returns: label or NULL if no match found | |
*/ | |
-static struct aa_profile *find_attach(struct aa_namespace *ns, | |
- struct list_head *list, const char *name) | |
+static struct aa_label *find_attach(struct aa_ns *ns, struct list_head *list, | |
+ const char *name) | |
{ | |
struct aa_profile *profile; | |
@@ -179,49 +362,7 @@ static struct aa_profile *find_attach(struct aa_namespace *ns, | |
profile = aa_get_profile(__attach_match(name, list)); | |
rcu_read_unlock(); | |
- return profile; | |
-} | |
- | |
-/** | |
- * separate_fqname - separate the namespace and profile names | |
- * @fqname: the fqname name to split (NOT NULL) | |
- * @ns_name: the namespace name if it exists (NOT NULL) | |
- * | |
- * This is the xtable equivalent routine of aa_split_fqname. It finds the | |
- * split in an xtable fqname which contains an embedded \0 instead of a : | |
- * if a namespace is specified. This is done so the xtable is constant and | |
- * isn't re-split on every lookup. | |
- * | |
- * Either the profile or namespace name may be optional but if the namespace | |
- * is specified the profile name termination must be present. This results | |
- * in the following possible encodings: | |
- * profile_name\0 | |
- * :ns_name\0profile_name\0 | |
- * :ns_name\0\0 | |
- * | |
- * NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table | |
- * | |
- * Returns: profile name if it is specified else NULL | |
- */ | |
-static const char *separate_fqname(const char *fqname, const char **ns_name) | |
-{ | |
- const char *name; | |
- | |
- if (fqname[0] == ':') { | |
- /* In this case there is guaranteed to be two \0 terminators | |
- * in the string. They are verified at load time by | |
- * by unpack_trans_table | |
- */ | |
- *ns_name = fqname + 1; /* skip : */ | |
- name = *ns_name + strlen(*ns_name) + 1; | |
- if (!*name) | |
- name = NULL; | |
- } else { | |
- *ns_name = NULL; | |
- name = fqname; | |
- } | |
- | |
- return name; | |
+ return profile ? &profile->label : NULL; | |
} | |
static const char *next_name(int xtype, const char *name) | |
@@ -233,99 +374,291 @@ static const char *next_name(int xtype, const char *name) | |
* x_table_lookup - lookup an x transition name via transition table | |
* @profile: current profile (NOT NULL) | |
* @xindex: index into x transition table | |
+ * @name: returns: name tested to find label (NOT NULL) | |
* | |
- * Returns: refcounted profile, or NULL on failure (MAYBE NULL) | |
+ * Returns: refcounted label, or NULL on failure (MAYBE NULL) | |
*/ | |
-static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) | |
+struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, | |
+ const char **name) | |
{ | |
- struct aa_profile *new_profile = NULL; | |
- struct aa_namespace *ns = profile->ns; | |
+ struct aa_label *label = NULL; | |
u32 xtype = xindex & AA_X_TYPE_MASK; | |
int index = xindex & AA_X_INDEX_MASK; | |
- const char *name; | |
- /* index is guaranteed to be in range, validated at load time */ | |
- for (name = profile->file.trans.table[index]; !new_profile && name; | |
- name = next_name(xtype, name)) { | |
- struct aa_namespace *new_ns; | |
- const char *xname = NULL; | |
+ AA_BUG(!name); | |
- new_ns = NULL; | |
+ /* index is guaranteed to be in range, validated at load time */ | |
+ /* TODO: move lookup parsing to unpack time so this is a straight | |
+ * index into the resultant label | |
+ */ | |
+ for (*name = profile->file.trans.table[index]; !label && *name; | |
+ *name = next_name(xtype, *name)) { | |
if (xindex & AA_X_CHILD) { | |
+ struct aa_profile *new_profile; | |
/* release by caller */ | |
- new_profile = aa_find_child(profile, name); | |
- continue; | |
- } else if (*name == ':') { | |
- /* switching namespace */ | |
- const char *ns_name; | |
- xname = name = separate_fqname(name, &ns_name); | |
- if (!xname) | |
- /* no name so use profile name */ | |
- xname = profile->base.hname; | |
- if (*ns_name == '@') { | |
- /* TODO: variable support */ | |
- ; | |
- } | |
- /* released below */ | |
- new_ns = aa_find_namespace(ns, ns_name); | |
- if (!new_ns) | |
- continue; | |
- } else if (*name == '@') { | |
- /* TODO: variable support */ | |
+ new_profile = aa_find_child(profile, *name); | |
+ if (new_profile) | |
+ label = &new_profile->label; | |
continue; | |
- } else { | |
- /* basic namespace lookup */ | |
- xname = name; | |
} | |
- | |
- /* released by caller */ | |
- new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname); | |
- aa_put_namespace(new_ns); | |
+ label = aa_label_parse(&profile->label, *name, GFP_ATOMIC, | |
+ true, false); | |
+ if (IS_ERR(label)) | |
+ label = NULL; | |
} | |
/* released by caller */ | |
- return new_profile; | |
+ return label; | |
} | |
/** | |
- * x_to_profile - get target profile for a given xindex | |
+ * x_to_label - get target label for a given xindex | |
* @profile: current profile (NOT NULL) | |
* @name: name to lookup (NOT NULL) | |
* @xindex: index into x transition table | |
+ * @lookupname: returns: name used in lookup if one was specified (NOT NULL) | |
* | |
- * find profile for a transition index | |
+ * find label for a transition index | |
* | |
- * Returns: refcounted profile or NULL if not found available | |
+ * Returns: refcounted label or NULL if not found available | |
*/ | |
-static struct aa_profile *x_to_profile(struct aa_profile *profile, | |
- const char *name, u32 xindex) | |
+static struct aa_label *x_to_label(struct aa_profile *profile, | |
+ const char *name, u32 xindex, | |
+ const char **lookupname, | |
+ const char **info) | |
{ | |
- struct aa_profile *new_profile = NULL; | |
- struct aa_namespace *ns = profile->ns; | |
+ struct aa_label *new = NULL; | |
+ struct aa_ns *ns = profile->ns; | |
u32 xtype = xindex & AA_X_TYPE_MASK; | |
+ const char *stack = NULL; | |
switch (xtype) { | |
case AA_X_NONE: | |
/* fail exec unless ix || ux fallback - handled by caller */ | |
- return NULL; | |
+ *lookupname = NULL; | |
+ break; | |
+ case AA_X_TABLE: | |
+ /* TODO: fix when perm mapping done at unload */ | |
+ stack = profile->file.trans.table[xindex & AA_X_INDEX_MASK]; | |
+ if (*stack != '&') { | |
+ /* released by caller */ | |
+ new = x_table_lookup(profile, xindex, lookupname); | |
+ stack = NULL; | |
+ break; | |
+ } | |
+ /* fall through to X_NAME */ | |
case AA_X_NAME: | |
if (xindex & AA_X_CHILD) | |
/* released by caller */ | |
- new_profile = find_attach(ns, &profile->base.profiles, | |
- name); | |
+ new = find_attach(ns, &profile->base.profiles, | |
+ name); | |
else | |
/* released by caller */ | |
- new_profile = find_attach(ns, &ns->base.profiles, | |
- name); | |
- break; | |
- case AA_X_TABLE: | |
- /* released by caller */ | |
- new_profile = x_table_lookup(profile, xindex); | |
+ new = find_attach(ns, &ns->base.profiles, | |
+ name); | |
+ *lookupname = name; | |
break; | |
} | |
+ if (!new) { | |
+ if (xindex & AA_X_INHERIT) { | |
+ /* (p|c|n)ix - don't change profile but do | |
+ * use the newest version | |
+ */ | |
+ *info = "ix fallback"; | |
+ /* no profile && no error */ | |
+ new = aa_get_newest_label(&profile->label); | |
+ } else if (xindex & AA_X_UNCONFINED) { | |
+ new = aa_get_newest_label(ns_unconfined(profile->ns)); | |
+ *info = "ux fallback"; | |
+ } | |
+ } | |
+ | |
+ if (new && stack) { | |
+ /* base the stack on post domain transition */ | |
+ struct aa_label *base = new; | |
+ new = aa_label_parse(base, stack, GFP_ATOMIC, true, false); | |
+ if (IS_ERR(new)) | |
+ new = NULL; | |
+ aa_put_label(base); | |
+ } | |
+ | |
/* released by caller */ | |
- return new_profile; | |
+ return new; | |
+} | |
+ | |
+static struct aa_label *profile_transition(struct aa_profile *profile, | |
+ const char *name, | |
+ struct path_cond *cond, | |
+ bool *secure_exec) | |
+{ | |
+ struct aa_label *new = NULL; | |
+ const char *info = NULL; | |
+ unsigned int state = profile->file.start; | |
+ struct aa_perms perms = {}; | |
+ const char *target = NULL; | |
+ int error = 0; | |
+ | |
+ if (profile_unconfined(profile)) { | |
+ new = find_attach(profile->ns, &profile->ns->base.profiles, | |
+ name); | |
+ if (new) { | |
+ AA_DEBUG("unconfined attached to new label"); | |
+ | |
+ return new; | |
+ } | |
+ AA_DEBUG("unconfined exec no attachment"); | |
+ | |
+ return aa_get_newest_label(&profile->label); | |
+ } | |
+ | |
+ /* find exec permissions for name */ | |
+ state = aa_str_perms(profile->file.dfa, state, name, cond, &perms); | |
+ if (perms.allow & MAY_EXEC) { | |
+ /* exec permission determine how to transition */ | |
+ new = x_to_label(profile, name, perms.xindex, &target, &info); | |
+ if (new == &profile->label && info) { | |
+ /* hack ix fallback - improve how this is detected */ | |
+ goto audit; | |
+ } else if (!new) { | |
+ error = -EACCES; | |
+ info = "profile transition not found"; | |
+ /* remove MAY_EXEC to audit as failure */ | |
+ perms.allow &= ~MAY_EXEC; | |
+ } | |
+ } else if (COMPLAIN_MODE(profile)) { | |
+ /* no exec permission - learning mode */ | |
+ struct aa_profile *new_profile = aa_null_profile(profile, false, | |
+ name, GFP_ATOMIC); | |
+ if (!new_profile) { | |
+ error = -ENOMEM; | |
+ info = "could not create null profile"; | |
+ } else { | |
+ error = -EACCES; | |
+ new = &new_profile->label; | |
+ } | |
+ perms.xindex |= AA_X_UNSAFE; | |
+ } else | |
+ /* fail exec */ | |
+ error = -EACCES; | |
+ | |
+ if (!new) | |
+ goto audit; | |
+ | |
+ if (!(perms.xindex & AA_X_UNSAFE)) { | |
+ if (DEBUG_ON) { | |
+ dbg_printk("apparmor: scrubbing environment variables " | |
+ "for %s profile=", name); | |
+ aa_label_printk(new, GFP_ATOMIC); | |
+ dbg_printk("\n"); | |
+ } | |
+ *secure_exec = true; | |
+ } | |
+ | |
+audit: | |
+ aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, target, new, | |
+ cond->uid, info, error); | |
+ if (!new) | |
+ return ERR_PTR(error); | |
+ | |
+ return new; | |
+} | |
+ | |
+static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, | |
+ bool stack, const char *xname, struct path_cond *cond, | |
+ bool *secure_exec) | |
+{ | |
+ unsigned int state = profile->file.start; | |
+ struct aa_perms perms = {}; | |
+ const char *info = "change_profile onexec"; | |
+ int error = -EACCES; | |
+ | |
+ if (profile_unconfined(profile)) { | |
+ /* change_profile on exec already granted */ | |
+ /* | |
+ * NOTE: Domain transitions from unconfined are allowed | |
+ * even when no_new_privs is set because this aways results | |
+ * in a further reduction of permissions. | |
+ */ | |
+ return 0; | |
+ } | |
+ | |
+ /* find exec permissions for name */ | |
+ state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms); | |
+ if (!(perms.allow & AA_MAY_ONEXEC)) { | |
+ info = "no change_onexec valid for executable"; | |
+ goto audit; | |
+ } | |
+ /* test if this exec can be paired with change_profile onexec. | |
+ * onexec permission is linked to exec with a standard pairing | |
+ * exec\0change_profile | |
+ */ | |
+ state = aa_dfa_null_transition(profile->file.dfa, state); | |
+ error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC, | |
+ state, &perms); | |
+ if (error) | |
+ goto audit; | |
+ | |
+ if (!(perms.xindex & AA_X_UNSAFE)) { | |
+ if (DEBUG_ON) { | |
+ dbg_printk("appaarmor: scrubbing environment " | |
+ "variables for %s label=", xname); | |
+ aa_label_printk(onexec, GFP_ATOMIC); | |
+ dbg_printk("\n"); | |
+ } | |
+ *secure_exec = true; | |
+ } | |
+ | |
+audit: | |
+ return aa_audit_file(profile, &perms, OP_EXEC, AA_MAY_ONEXEC, xname, | |
+ NULL, onexec, cond->uid, info, error); | |
+} | |
+ | |
+/* ensure none ns domain transitions are correctly applied with onexec */ | |
+ | |
+static struct aa_label *handle_onexec(struct aa_label *label, | |
+ struct aa_label *onexec, bool stack, | |
+ const char *xname, | |
+ struct path_cond *cond, | |
+ bool *unsafe) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct aa_label *new; | |
+ int error; | |
+ | |
+ if (!stack) { | |
+ error = fn_for_each_in_ns(label, profile, | |
+ profile_onexec(profile, onexec, stack, | |
+ xname, cond, unsafe)); | |
+ if (error) | |
+ return ERR_PTR(error); | |
+ new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, | |
+ aa_get_newest_label(onexec), | |
+ profile_transition(profile, xname, | |
+ cond, unsafe)); | |
+ } else { | |
+ /* TODO: determine how much we want to losen this */ | |
+ error = fn_for_each_in_ns(label, profile, | |
+ profile_onexec(profile, onexec, stack, xname, | |
+ cond, unsafe)); | |
+ if (error) | |
+ return ERR_PTR(error); | |
+ new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, | |
+ aa_label_merge(label, onexec, | |
+ GFP_ATOMIC), | |
+ profile_transition(profile, xname, | |
+ cond, unsafe)); | |
+ } | |
+ | |
+ if (new) | |
+ return new; | |
+ | |
+ error = fn_for_each_in_ns(label, profile, | |
+ aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC, | |
+ AA_MAY_ONEXEC, xname, NULL, onexec, | |
+ GLOBAL_ROOT_UID, | |
+ "failed to build target label", -ENOMEM)); | |
+ return ERR_PTR(error); | |
} | |
/** | |
@@ -333,136 +666,76 @@ static struct aa_profile *x_to_profile(struct aa_profile *profile, | |
* @bprm: binprm for the exec (NOT NULL) | |
* | |
* Returns: %0 or error on failure | |
+ * | |
+ * TODO: once the other paths are done see if we can't refactor into a fn | |
*/ | |
int apparmor_bprm_set_creds(struct linux_binprm *bprm) | |
{ | |
- struct aa_task_cxt *cxt; | |
- struct aa_profile *profile, *new_profile = NULL; | |
- struct aa_namespace *ns; | |
+ struct aa_task_ctx *ctx; | |
+ struct aa_label *label, *new = NULL; | |
+ struct aa_profile *profile; | |
char *buffer = NULL; | |
- unsigned int state; | |
- struct file_perms perms = {}; | |
+ const char *xname = NULL; | |
+ const char *info = NULL; | |
+ int error = 0; | |
+ bool unsafe = false; | |
struct path_cond cond = { | |
file_inode(bprm->file)->i_uid, | |
file_inode(bprm->file)->i_mode | |
}; | |
- const char *name = NULL, *info = NULL; | |
- int error = 0; | |
if (bprm->cred_prepared) | |
return 0; | |
- cxt = cred_cxt(bprm->cred); | |
- BUG_ON(!cxt); | |
+ ctx = cred_ctx(bprm->cred); | |
+ AA_BUG(!ctx); | |
- profile = aa_get_newest_profile(cxt->profile); | |
- /* | |
- * get the namespace from the replacement profile as replacement | |
- * can change the namespace | |
- */ | |
- ns = profile->ns; | |
- state = profile->file.start; | |
+ label = aa_get_newest_label(ctx->label); | |
+ profile = labels_profile(label); | |
- /* buffer freed below, name is pointer into buffer */ | |
- error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer, | |
- &name, &info); | |
+ /* buffer freed below, xname is pointer into buffer */ | |
+ get_buffers(buffer); | |
+ error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, | |
+ &xname, &info, profile->disconnected); | |
if (error) { | |
- if (unconfined(profile) || | |
- (profile->flags & PFLAG_IX_ON_NAME_ERROR)) | |
+ if (profile_unconfined(profile) || | |
+ (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) | |
error = 0; | |
- name = bprm->filename; | |
+ xname = bprm->filename; | |
goto audit; | |
} | |
- /* Test for onexec first as onexec directives override other | |
- * x transitions. | |
- */ | |
- if (unconfined(profile)) { | |
- /* unconfined task */ | |
- if (cxt->onexec) | |
- /* change_profile on exec already been granted */ | |
- new_profile = aa_get_profile(cxt->onexec); | |
- else | |
- new_profile = find_attach(ns, &ns->base.profiles, name); | |
- if (!new_profile) | |
- goto cleanup; | |
- /* | |
- * NOTE: Domain transitions from unconfined are allowed | |
- * even when no_new_privs is set because this aways results | |
- * in a further reduction of permissions. | |
- */ | |
- goto apply; | |
- } | |
- | |
- /* find exec permissions for name */ | |
- state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms); | |
- if (cxt->onexec) { | |
- struct file_perms cp; | |
- info = "change_profile onexec"; | |
- new_profile = aa_get_newest_profile(cxt->onexec); | |
- if (!(perms.allow & AA_MAY_ONEXEC)) | |
- goto audit; | |
- | |
- /* test if this exec can be paired with change_profile onexec. | |
- * onexec permission is linked to exec with a standard pairing | |
- * exec\0change_profile | |
- */ | |
- state = aa_dfa_null_transition(profile->file.dfa, state); | |
- cp = change_profile_perms(profile, cxt->onexec->ns, | |
- cxt->onexec->base.name, | |
- AA_MAY_ONEXEC, state); | |
- | |
- if (!(cp.allow & AA_MAY_ONEXEC)) | |
- goto audit; | |
- goto apply; | |
+ /* Test for onexec first as onexec override other x transitions. */ | |
+ if (ctx->onexec) | |
+ new = handle_onexec(label, ctx->onexec, ctx->token, xname, | |
+ &cond, &unsafe); | |
+ else | |
+ new = fn_label_build(label, profile, GFP_ATOMIC, | |
+ profile_transition(profile, xname, &cond, | |
+ &unsafe)); | |
+ | |
+ AA_BUG(!new); | |
+ if (IS_ERR(new)) { | |
+ error = PTR_ERR(new); | |
+ goto done; | |
+ } else if (!new) { | |
+ error = -ENOMEM; | |
+ goto done; | |
} | |
- if (perms.allow & MAY_EXEC) { | |
- /* exec permission determine how to transition */ | |
- new_profile = x_to_profile(profile, name, perms.xindex); | |
- if (!new_profile) { | |
- if (perms.xindex & AA_X_INHERIT) { | |
- /* (p|c|n)ix - don't change profile but do | |
- * use the newest version, which was picked | |
- * up above when getting profile | |
- */ | |
- info = "ix fallback"; | |
- new_profile = aa_get_profile(profile); | |
- goto x_clear; | |
- } else if (perms.xindex & AA_X_UNCONFINED) { | |
- new_profile = aa_get_newest_profile(ns->unconfined); | |
- info = "ux fallback"; | |
- } else { | |
- error = -EACCES; | |
- info = "profile not found"; | |
- /* remove MAY_EXEC to audit as failure */ | |
- perms.allow &= ~MAY_EXEC; | |
- } | |
- } | |
- } else if (COMPLAIN_MODE(profile)) { | |
- /* no exec permission - are we in learning mode */ | |
- new_profile = aa_new_null_profile(profile, 0); | |
- if (!new_profile) { | |
- error = -ENOMEM; | |
- info = "could not create null profile"; | |
- } else | |
- error = -EACCES; | |
- perms.xindex |= AA_X_UNSAFE; | |
- } else | |
- /* fail exec */ | |
- error = -EACCES; | |
- | |
- /* | |
- * Policy has specified a domain transition, if no_new_privs then | |
- * fail the exec. | |
+ /* Policy has specified a domain transitions. if no_new_privs and | |
+ * confined and not transitioning to the current domain fail. | |
+ * | |
+ * NOTE: Domain transitions from unconfined and to stritly stacked | |
+ * subsets are allowed even when no_new_privs is set because this | |
+ * aways results in a further reduction of permissions. | |
*/ | |
- if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) { | |
+ if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS && | |
+ !unconfined(label) && !aa_label_is_subset(new, label)) { | |
error = -EPERM; | |
- goto cleanup; | |
- } | |
- | |
- if (!new_profile) | |
+ info = "no new privs"; | |
goto audit; | |
+ } | |
if (bprm->unsafe & LSM_UNSAFE_SHARE) { | |
/* FIXME: currently don't mediate shared state */ | |
@@ -470,53 +743,53 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) | |
} | |
if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) { | |
- error = may_change_ptraced_domain(new_profile); | |
+ /* TODO: test needs to be profile of label to new */ | |
+ error = may_change_ptraced_domain(new, &info); | |
if (error) | |
goto audit; | |
} | |
- /* Determine if secure exec is needed. | |
- * Can be at this point for the following reasons: | |
- * 1. unconfined switching to confined | |
- * 2. confined switching to different confinement | |
- * 3. confined switching to unconfined | |
- * | |
- * Cases 2 and 3 are marked as requiring secure exec | |
- * (unless policy specified "unsafe exec") | |
- * | |
- * bprm->unsafe is used to cache the AA_X_UNSAFE permission | |
- * to avoid having to recompute in secureexec | |
- */ | |
- if (!(perms.xindex & AA_X_UNSAFE)) { | |
- AA_DEBUG("scrubbing environment variables for %s profile=%s\n", | |
- name, new_profile->base.hname); | |
+ if (unsafe) { | |
+ if (DEBUG_ON) { | |
+ dbg_printk("scrubbing environment variables for %s " | |
+ "label=", xname); | |
+ aa_label_printk(new, GFP_ATOMIC); | |
+ dbg_printk("\n"); | |
+ } | |
bprm->unsafe |= AA_SECURE_X_NEEDED; | |
} | |
-apply: | |
- /* when transitioning profiles clear unsafe personality bits */ | |
- bprm->per_clear |= PER_CLEAR_ON_SETID; | |
- | |
-x_clear: | |
- aa_put_profile(cxt->profile); | |
- /* transfer new profile reference will be released when cxt is freed */ | |
- cxt->profile = new_profile; | |
- new_profile = NULL; | |
- /* clear out all temporary/transitional state from the context */ | |
- aa_clear_task_cxt_trans(cxt); | |
+ if (label != new) { | |
+ /* when transitioning clear unsafe personality bits */ | |
+ if (DEBUG_ON) { | |
+ dbg_printk("apparmor: clearing unsafe personality " | |
+ "bits. %s label=", xname); | |
+ aa_label_printk(new, GFP_ATOMIC); | |
+ dbg_printk("\n"); | |
+ } | |
+ bprm->per_clear |= PER_CLEAR_ON_SETID; | |
+ } | |
+ aa_put_label(ctx->label); | |
+ /* transfer reference, released when ctx is freed */ | |
+ ctx->label = new; | |
-audit: | |
- error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC, | |
- name, | |
- new_profile ? new_profile->base.hname : NULL, | |
- cond.uid, info, error); | |
+done: | |
+ /* clear out temporary/transitional state from the context */ | |
+ aa_clear_task_ctx_trans(ctx); | |
-cleanup: | |
- aa_put_profile(new_profile); | |
- aa_put_profile(profile); | |
- kfree(buffer); | |
+ aa_put_label(label); | |
+ put_buffers(buffer); | |
return error; | |
+ | |
+audit: | |
+ error = fn_for_each(label, profile, | |
+ aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC, | |
+ xname, NULL, new, | |
+ file_inode(bprm->file)->i_uid, info, | |
+ error)); | |
+ aa_put_label(new); | |
+ goto done; | |
} | |
/** | |
@@ -536,53 +809,146 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm) | |
return 0; | |
} | |
-/** | |
- * apparmor_bprm_committing_creds - do task cleanup on committing new creds | |
- * @bprm: binprm for the exec (NOT NULL) | |
+/* | |
+ * Functions for self directed profile change | |
*/ | |
-void apparmor_bprm_committing_creds(struct linux_binprm *bprm) | |
-{ | |
- struct aa_profile *profile = __aa_current_profile(); | |
- struct aa_task_cxt *new_cxt = cred_cxt(bprm->cred); | |
- | |
- /* bail out if unconfined or not changing profile */ | |
- if ((new_cxt->profile == profile) || | |
- (unconfined(new_cxt->profile))) | |
- return; | |
- | |
- current->pdeath_signal = 0; | |
- /* reset soft limits and set hard limits for the new profile */ | |
- __aa_transition_rlimits(profile, new_cxt->profile); | |
-} | |
-/** | |
- * apparmor_bprm_commited_cred - do cleanup after new creds committed | |
- * @bprm: binprm for the exec (NOT NULL) | |
+/* helper fn for change_hat | |
+ * | |
+ * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL | |
*/ | |
-void apparmor_bprm_committed_creds(struct linux_binprm *bprm) | |
+static struct aa_label *build_change_hat(struct aa_profile *profile, | |
+ const char *name, bool sibling) | |
{ | |
- /* TODO: cleanup signals - ipc mediation */ | |
- return; | |
-} | |
+ struct aa_profile *root, *hat = NULL; | |
+ const char *info = NULL; | |
+ int error = 0; | |
-/* | |
- * Functions for self directed profile change | |
- */ | |
+ if (sibling && PROFILE_IS_HAT(profile)) { | |
+ root = aa_get_profile_rcu(&profile->parent); | |
+ } else if (!sibling && !PROFILE_IS_HAT(profile)) { | |
+ root = aa_get_profile(profile); | |
+ } else { | |
+ info = "conflicting target types"; | |
+ error = -EPERM; | |
+ goto audit; | |
+ } | |
-/** | |
- * new_compound_name - create an hname with @n2 appended to @n1 | |
- * @n1: base of hname (NOT NULL) | |
- * @n2: name to append (NOT NULL) | |
+ hat = aa_find_child(root, name); | |
+ if (!hat) { | |
+ error = -ENOENT; | |
+ if (COMPLAIN_MODE(profile)) { | |
+ hat = aa_null_profile(profile, true, name, GFP_KERNEL); | |
+ if (!hat) { | |
+ info = "failed null profile create"; | |
+ error = -ENOMEM; | |
+ } | |
+ } | |
+ } | |
+ aa_put_profile(root); | |
+ | |
+audit: | |
+ aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT, | |
+ name, hat ? hat->base.hname : NULL, hat ? &hat->label : NULL, GLOBAL_ROOT_UID, | |
+ NULL, error); | |
+ if (!hat || (error && error != -ENOENT)) | |
+ return ERR_PTR(error); | |
+ /* if hat && error - complain mode, already audited and we adjust for | |
+ * complain mode allow by returning hat->label | |
+ */ | |
+ return &hat->label; | |
+} | |
+ | |
+/* helper fn for changing into a hat | |
* | |
- * Returns: new name or NULL on error | |
+ * Returns: label for hat transition or ERR_PTR. Does not return NULL | |
*/ | |
-static char *new_compound_name(const char *n1, const char *n2) | |
+static struct aa_label *change_hat(struct aa_label *label, const char *hats[], | |
+ int count, bool permtest) | |
{ | |
- char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL); | |
- if (name) | |
- sprintf(name, "%s//%s", n1, n2); | |
- return name; | |
+ struct aa_profile *profile, *root, *hat = NULL; | |
+ struct aa_label *new; | |
+ struct label_it it; | |
+ bool sibling = false; | |
+ const char *name, *info = NULL; | |
+ int i, error; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!hats); | |
+ AA_BUG(count < 1); | |
+ | |
+ if (PROFILE_IS_HAT(labels_profile(label))) | |
+ sibling = true; | |
+ | |
+ /*find first matching hat */ | |
+ for (i = 0; i < count && !hat; i++) { | |
+ name = hats[i]; | |
+ label_for_each_in_ns(it, labels_ns(label), label, profile) { | |
+ if (sibling && PROFILE_IS_HAT(profile)) { | |
+ root = aa_get_profile_rcu(&profile->parent); | |
+ } else if (!sibling && !PROFILE_IS_HAT(profile)) { | |
+ root = aa_get_profile(profile); | |
+ } else { /* conflicting change type */ | |
+ info = "conflicting targets types"; | |
+ error = -EPERM; | |
+ goto fail; | |
+ } | |
+ hat = aa_find_child(root, name); | |
+ aa_put_profile(root); | |
+ if (!hat) { | |
+ if (!COMPLAIN_MODE(profile)) | |
+ goto outer_continue; | |
+ /* complain mode succeed as if hat */ | |
+ } else if (!PROFILE_IS_HAT(hat)) { | |
+ info = "target not hat"; | |
+ error = -EPERM; | |
+ aa_put_profile(hat); | |
+ goto fail; | |
+ } | |
+ aa_put_profile(hat); | |
+ } | |
+ /* found a hat for all profiles in ns */ | |
+ goto build; | |
+ outer_continue: ; | |
+ } | |
+ /* no hats that match, find appropriate error | |
+ * | |
+ * In complain mode audit of the failure is based off of the first | |
+ * hat supplied. This is done due how userspace interacts with | |
+ * change_hat. | |
+ */ | |
+ name = NULL; | |
+ label_for_each_in_ns(it, labels_ns(label), label, profile) { | |
+ if (!list_empty(&profile->base.profiles)) { | |
+ info = "hat not found"; | |
+ error = -ENOENT; | |
+ goto fail; | |
+ } | |
+ } | |
+ info = "no hats defined"; | |
+ error = -ECHILD; | |
+ | |
+fail: | |
+ fn_for_each_in_ns(label, profile, | |
+ /* no target as it has failed to be found or built */ | |
+ /* TODO: get rid of GLOBAL_ROOT_UID */ | |
+ aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, | |
+ AA_MAY_CHANGEHAT, name, NULL, NULL, | |
+ GLOBAL_ROOT_UID, info, error)); | |
+ return (ERR_PTR(error)); | |
+ | |
+build: | |
+ new = fn_label_build_in_ns(label, profile, GFP_KERNEL, | |
+ build_change_hat(profile, name, sibling), | |
+ aa_get_label(&profile->label)); | |
+ if (!new) { | |
+ info = "label build failed"; | |
+ error = -ENOMEM; | |
+ goto fail; | |
+ } /* else if (IS_ERR) build_change_hat has logged error so return new */ | |
+ | |
+ return new; | |
} | |
/** | |
@@ -592,22 +958,24 @@ static char *new_compound_name(const char *n1, const char *n2) | |
* @token: magic value to validate the hat change | |
* @permtest: true if this is just a permission test | |
* | |
+ * Returns %0 on success, error otherwise. | |
+ * | |
* Change to the first profile specified in @hats that exists, and store | |
* the @hat_magic in the current task context. If the count == 0 and the | |
* @token matches that stored in the current task context, return to the | |
* top level profile. | |
* | |
- * Returns %0 on success, error otherwise. | |
+ * change_hat only applies to profiles in the current ns, and each profile | |
+ * in the ns must make the same transition otherwise change_hat will fail. | |
*/ | |
int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) | |
{ | |
const struct cred *cred; | |
- struct aa_task_cxt *cxt; | |
- struct aa_profile *profile, *previous_profile, *hat = NULL; | |
- char *name = NULL; | |
- int i; | |
- struct file_perms perms = {}; | |
- const char *target = NULL, *info = NULL; | |
+ struct aa_task_ctx *ctx; | |
+ struct aa_label *label, *previous, *new = NULL, *target = NULL; | |
+ struct aa_profile *profile; | |
+ struct aa_perms perms = {}; | |
+ const char *info = NULL; | |
int error = 0; | |
/* | |
@@ -615,123 +983,102 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) | |
* There is no exception for unconfined as change_hat is not | |
* available. | |
*/ | |
- if (task_no_new_privs(current)) | |
+ if (task_no_new_privs(current)) { | |
+ /* not an apparmor denial per se, so don't log it */ | |
+ AA_DEBUG("no_new_privs - chanage_hat denied"); | |
return -EPERM; | |
+ } | |
/* released below */ | |
cred = get_current_cred(); | |
- cxt = cred_cxt(cred); | |
- profile = aa_get_newest_profile(aa_cred_profile(cred)); | |
- previous_profile = aa_get_newest_profile(cxt->previous); | |
+ ctx = cred_ctx(cred); | |
+ label = aa_get_newest_cred_label(cred); | |
+ previous = aa_get_newest_label(ctx->previous); | |
- if (unconfined(profile)) { | |
- info = "unconfined"; | |
+ if (unconfined(label)) { | |
+ info = "unconfined can not change_hat"; | |
error = -EPERM; | |
- goto audit; | |
+ goto fail; | |
} | |
if (count) { | |
- /* attempting to change into a new hat or switch to a sibling */ | |
- struct aa_profile *root; | |
- if (PROFILE_IS_HAT(profile)) | |
- root = aa_get_profile_rcu(&profile->parent); | |
- else | |
- root = aa_get_profile(profile); | |
- | |
- /* find first matching hat */ | |
- for (i = 0; i < count && !hat; i++) | |
- /* released below */ | |
- hat = aa_find_child(root, hats[i]); | |
- if (!hat) { | |
- if (!COMPLAIN_MODE(root) || permtest) { | |
- if (list_empty(&root->base.profiles)) | |
- error = -ECHILD; | |
- else | |
- error = -ENOENT; | |
- aa_put_profile(root); | |
- goto out; | |
- } | |
- | |
- /* | |
- * In complain mode and failed to match any hats. | |
- * Audit the failure is based off of the first hat | |
- * supplied. This is done due how userspace | |
- * interacts with change_hat. | |
- * | |
- * TODO: Add logging of all failed hats | |
- */ | |
- | |
- /* freed below */ | |
- name = new_compound_name(root->base.hname, hats[0]); | |
- aa_put_profile(root); | |
- target = name; | |
- /* released below */ | |
- hat = aa_new_null_profile(profile, 1); | |
- if (!hat) { | |
- info = "failed null profile create"; | |
- error = -ENOMEM; | |
- goto audit; | |
- } | |
- } else { | |
- aa_put_profile(root); | |
- target = hat->base.hname; | |
- if (!PROFILE_IS_HAT(hat)) { | |
- info = "target not hat"; | |
- error = -EPERM; | |
- goto audit; | |
- } | |
+ new = change_hat(label, hats, count, permtest); | |
+ AA_BUG(!new); | |
+ if (IS_ERR(new)) { | |
+ error = PTR_ERR(new); | |
+ new = NULL; | |
+ /* already audited */ | |
+ goto out; | |
} | |
- error = may_change_ptraced_domain(hat); | |
+ error = may_change_ptraced_domain(new, &info); | |
+ if (error) | |
+ goto fail; | |
+ | |
+ if (permtest) | |
+ goto out; | |
+ | |
+ target = new; | |
+ error = aa_set_current_hat(new, token); | |
+ if (error == -EACCES) | |
+ /* kill task in case of brute force attacks */ | |
+ goto kill; | |
+ } else if (previous && !permtest) { | |
+ /* Return to saved label. Kill task if restore fails | |
+ * to avoid brute force attacks | |
+ */ | |
+ target = previous; | |
+ error = aa_restore_previous_label(token); | |
if (error) { | |
- info = "ptraced"; | |
- error = -EPERM; | |
- goto audit; | |
- } | |
- | |
- if (!permtest) { | |
- error = aa_set_current_hat(hat, token); | |
if (error == -EACCES) | |
- /* kill task in case of brute force attacks */ | |
- perms.kill = AA_MAY_CHANGEHAT; | |
- else if (name && !error) | |
- /* reset error for learning of new hats */ | |
- error = -ENOENT; | |
+ goto kill; | |
+ goto fail; | |
} | |
- } else if (previous_profile) { | |
- /* Return to saved profile. Kill task if restore fails | |
- * to avoid brute force attacks | |
- */ | |
- target = previous_profile->base.hname; | |
- error = aa_restore_previous_profile(token); | |
- perms.kill = AA_MAY_CHANGEHAT; | |
- } else | |
- /* ignore restores when there is no saved profile */ | |
- goto out; | |
- | |
-audit: | |
- if (!permtest) | |
- error = aa_audit_file(profile, &perms, GFP_KERNEL, | |
- OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL, | |
- target, GLOBAL_ROOT_UID, info, error); | |
+ } /* else ignore permtest && restores when there is no saved profile */ | |
out: | |
- aa_put_profile(hat); | |
- kfree(name); | |
- aa_put_profile(profile); | |
- aa_put_profile(previous_profile); | |
+ aa_put_label(new); | |
+ aa_put_label(previous); | |
+ aa_put_label(label); | |
put_cred(cred); | |
return error; | |
+ | |
+kill: | |
+ info = "failed token match"; | |
+ perms.kill = AA_MAY_CHANGEHAT; | |
+ | |
+fail: | |
+ fn_for_each_in_ns(label, profile, | |
+ aa_audit_file(profile, &perms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT, | |
+ NULL, NULL, target, GLOBAL_ROOT_UID, info, error)); | |
+ | |
+ goto out; | |
+} | |
+ | |
+ | |
+static int change_profile_perms_wrapper(const char *op, const char *name, | |
+ struct aa_profile *profile, | |
+ struct aa_label *target, bool stack, | |
+ u32 request, struct aa_perms *perms) | |
+{ | |
+ int error = change_profile_perms(profile, target, | |
+ stack, request, | |
+ profile->file.start, perms); | |
+ if (error) | |
+ error = aa_audit_file(profile, perms, op, request, name, | |
+ NULL, target, GLOBAL_ROOT_UID, NULL, | |
+ error); | |
+ | |
+ return error; | |
} | |
/** | |
* aa_change_profile - perform a one-way profile transition | |
- * @ns_name: name of the profile namespace to change to (MAYBE NULL) | |
- * @hname: name of profile to change to (MAYBE NULL) | |
+ * @fqname: name of profile may include namespace (NOT NULL) | |
* @onexec: whether this transition is to take place immediately or at exec | |
* @permtest: true if this is just a permission test | |
- * | |
+ * @stack: true if this call is to stack on top of current domain | |
* Change to new profile @name. Unlike with hats, there is no way | |
* to change back. If @name isn't specified the current profile name is | |
* used. | |
@@ -740,111 +1087,144 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) | |
* | |
* Returns %0 on success, error otherwise. | |
*/ | |
-int aa_change_profile(const char *ns_name, const char *hname, bool onexec, | |
- bool permtest) | |
+int aa_change_profile(const char *fqname, bool onexec, | |
+ bool permtest, bool stack) | |
{ | |
- const struct cred *cred; | |
- struct aa_profile *profile, *target = NULL; | |
- struct aa_namespace *ns = NULL; | |
- struct file_perms perms = {}; | |
- const char *name = NULL, *info = NULL; | |
- int op, error = 0; | |
+ struct aa_label *label, *new = NULL, *target = NULL; | |
+ struct aa_profile *profile; | |
+ struct aa_perms perms = {}; | |
+ const char *info = NULL; | |
+ const char *auditname = fqname; /* retain leading & if stack */ | |
+ int error = 0; | |
+ char *op; | |
u32 request; | |
- if (!hname && !ns_name) | |
+ if (!fqname || !*fqname) { | |
+ AA_DEBUG("no profile name"); | |
return -EINVAL; | |
+ } | |
if (onexec) { | |
request = AA_MAY_ONEXEC; | |
- op = OP_CHANGE_ONEXEC; | |
+ if (stack) | |
+ op = OP_STACK_ONEXEC; | |
+ else | |
+ op = OP_CHANGE_ONEXEC; | |
} else { | |
request = AA_MAY_CHANGE_PROFILE; | |
- op = OP_CHANGE_PROFILE; | |
+ if (stack) | |
+ op = OP_STACK; | |
+ else | |
+ op = OP_CHANGE_PROFILE; | |
} | |
- cred = get_current_cred(); | |
- profile = aa_cred_profile(cred); | |
+ label = aa_get_current_label(); | |
- /* | |
- * Fail explicitly requested domain transitions if no_new_privs | |
- * and not unconfined. | |
- * Domain transitions from unconfined are allowed even when | |
- * no_new_privs is set because this aways results in a reduction | |
- * of permissions. | |
- */ | |
- if (task_no_new_privs(current) && !unconfined(profile)) { | |
- put_cred(cred); | |
- return -EPERM; | |
+ if (*fqname == '&') { | |
+ stack = true; | |
+ /* don't have label_parse() do stacking */ | |
+ fqname++; | |
} | |
- | |
- if (ns_name) { | |
+ target = aa_label_parse(label, fqname, GFP_KERNEL, true, false); | |
+ if (IS_ERR(target)) { | |
+ struct aa_profile *tprofile; | |
+ | |
+ info = "label not found"; | |
+ error = PTR_ERR(target); | |
+ target = NULL; | |
+ /* TODO: fixme using labels_profile is not right - do profile | |
+ per complain profile ??? */ | |
+ if (permtest || !COMPLAIN_MODE(labels_profile(label))) | |
+ goto audit; | |
/* released below */ | |
- ns = aa_find_namespace(profile->ns, ns_name); | |
- if (!ns) { | |
- /* we don't create new namespace in complain mode */ | |
- name = ns_name; | |
- info = "namespace not found"; | |
- error = -ENOENT; | |
+ tprofile = aa_null_profile(labels_profile(label), false, fqname, GFP_KERNEL); | |
+ if (!tprofile) { | |
+ info = "failed null profile create"; | |
+ error = -ENOMEM; | |
goto audit; | |
} | |
- } else | |
- /* released below */ | |
- ns = aa_get_namespace(profile->ns); | |
- | |
- /* if the name was not specified, use the name of the current profile */ | |
- if (!hname) { | |
- if (unconfined(profile)) | |
- hname = ns->unconfined->base.hname; | |
- else | |
- hname = profile->base.hname; | |
+ target = &tprofile->label; | |
+ goto check; | |
} | |
- perms = change_profile_perms(profile, ns, hname, request, | |
- profile->file.start); | |
- if (!(perms.allow & request)) { | |
- error = -EACCES; | |
+ /* | |
+ * Fail explicitly requested domain transitions when no_new_privs | |
+ * and not unconfined OR the transition results in a stack on | |
+ * the current label. | |
+ * Stacking domain transitions and transitions from unconfined are | |
+ * allowed even when no_new_privs is set because this aways results | |
+ * in a reduction of permissions. | |
+ */ | |
+ if (task_no_new_privs(current) && !stack && !unconfined(label) && | |
+ !aa_label_is_subset(target, label)) { | |
+ info = "no new privs"; | |
+ error = -EPERM; | |
goto audit; | |
} | |
- /* released below */ | |
- target = aa_lookup_profile(ns, hname); | |
- if (!target) { | |
- info = "profile not found"; | |
- error = -ENOENT; | |
- if (permtest || !COMPLAIN_MODE(profile)) | |
- goto audit; | |
- /* released below */ | |
- target = aa_new_null_profile(profile, 0); | |
- if (!target) { | |
- info = "failed null profile create"; | |
- error = -ENOMEM; | |
- goto audit; | |
- } | |
- } | |
+ /* self directed transitions only apply to current policy ns */ | |
+ /* TODO: currently requiring perms for stacking and straight change | |
+ * stacking doesn't strictly need this. Determine how much | |
+ * we want to loosen this restriction for stacking | |
+ */ | |
+ /* if (!stack) { */ | |
+ error = fn_for_each_in_ns(label, profile, | |
+ change_profile_perms_wrapper(op, auditname, | |
+ profile, target, stack, | |
+ request, &perms)); | |
+ if (error) | |
+ /* auditing done in change_profile_perms_wrapper */ | |
+ goto out; | |
+ | |
+ /* } */ | |
+check: | |
/* check if tracing task is allowed to trace target domain */ | |
- error = may_change_ptraced_domain(target); | |
- if (error) { | |
- info = "ptrace prevents transition"; | |
+ error = may_change_ptraced_domain(target, &info); | |
+ if (error && !fn_for_each_in_ns(label, profile, | |
+ COMPLAIN_MODE(profile))) | |
goto audit; | |
- } | |
- if (permtest) | |
+ /* TODO: add permission check to allow this | |
+ if (onexec && !current_is_single_threaded()) { | |
+ info = "not a single threaded task"; | |
+ error = -EACCES; | |
goto audit; | |
+ } | |
+ */ | |
+ if (permtest) | |
+ goto out; | |
- if (onexec) | |
- error = aa_set_current_onexec(target); | |
- else | |
- error = aa_replace_current_profile(target); | |
+ if (!onexec) { | |
+ /* only transition profiles in the current ns */ | |
+ if (stack) | |
+ new = aa_label_merge(label, target, GFP_KERNEL); | |
+ else | |
+ new = fn_label_build_in_ns(label, profile, GFP_KERNEL, | |
+ aa_get_label(target), | |
+ aa_get_label(&profile->label)); | |
+ if (IS_ERR_OR_NULL(new)) { | |
+ info = "failed to build target label"; | |
+ error = PTR_ERR(new); | |
+ new = NULL; | |
+ perms.allow = 0; | |
+ goto audit; | |
+ } | |
+ error = aa_replace_current_label(new); | |
+ } else | |
+ /* full transition will be built in exec path */ | |
+ error = aa_set_current_onexec(target, stack); | |
audit: | |
- if (!permtest) | |
- error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, | |
- name, hname, GLOBAL_ROOT_UID, info, error); | |
+ error = fn_for_each_in_ns(label, profile, | |
+ aa_audit_file(profile, &perms, op, request, auditname, | |
+ NULL, new ? new : target, | |
+ GLOBAL_ROOT_UID, info, error)); | |
- aa_put_namespace(ns); | |
- aa_put_profile(target); | |
- put_cred(cred); | |
+out: | |
+ aa_put_label(new); | |
+ aa_put_label(target); | |
+ aa_put_label(label); | |
return error; | |
} | |
diff --git a/security/apparmor/file.c b/security/apparmor/file.c | |
index 4d2af4b..d2473b6 100644 | |
--- a/security/apparmor/file.c | |
+++ b/security/apparmor/file.c | |
@@ -12,15 +12,30 @@ | |
* License. | |
*/ | |
+#include <linux/tty.h> | |
+#include <linux/fdtable.h> | |
+#include <linux/file.h> | |
+ | |
+#include "include/af_unix.h" | |
#include "include/apparmor.h" | |
#include "include/audit.h" | |
+#include "include/context.h" | |
#include "include/file.h" | |
#include "include/match.h" | |
#include "include/path.h" | |
#include "include/policy.h" | |
+#include "include/label.h" | |
-struct file_perms nullperms; | |
+static u32 map_mask_to_chr_mask(u32 mask) | |
+{ | |
+ u32 m = mask & PERMS_CHRS_MASK; | |
+ if (mask & AA_MAY_GETATTR) | |
+ m |= MAY_READ; | |
+ if (mask & (AA_MAY_SETATTR | AA_MAY_CHMOD | AA_MAY_CHOWN)) | |
+ m |= MAY_WRITE; | |
+ return m; | |
+} | |
/** | |
* audit_file_mask - convert mask to permission string | |
@@ -31,29 +46,7 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask) | |
{ | |
char str[10]; | |
- char *m = str; | |
- | |
- if (mask & AA_EXEC_MMAP) | |
- *m++ = 'm'; | |
- if (mask & (MAY_READ | AA_MAY_META_READ)) | |
- *m++ = 'r'; | |
- if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD | | |
- AA_MAY_CHOWN)) | |
- *m++ = 'w'; | |
- else if (mask & MAY_APPEND) | |
- *m++ = 'a'; | |
- if (mask & AA_MAY_CREATE) | |
- *m++ = 'c'; | |
- if (mask & AA_MAY_DELETE) | |
- *m++ = 'd'; | |
- if (mask & AA_MAY_LINK) | |
- *m++ = 'l'; | |
- if (mask & AA_MAY_LOCK) | |
- *m++ = 'k'; | |
- if (mask & MAY_EXEC) | |
- *m++ = 'x'; | |
- *m = '\0'; | |
- | |
+ aa_perm_mask_to_str(str, aa_file_perm_chrs, map_mask_to_chr_mask(mask)); | |
audit_log_string(ab, str); | |
} | |
@@ -67,24 +60,28 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) | |
struct common_audit_data *sa = va; | |
kuid_t fsuid = current_fsuid(); | |
- if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) { | |
+ if (aad(sa)->request & AA_AUDIT_FILE_MASK) { | |
audit_log_format(ab, " requested_mask="); | |
- audit_file_mask(ab, sa->aad->fs.request); | |
+ audit_file_mask(ab, aad(sa)->request); | |
} | |
- if (sa->aad->fs.denied & AA_AUDIT_FILE_MASK) { | |
+ if (aad(sa)->denied & AA_AUDIT_FILE_MASK) { | |
audit_log_format(ab, " denied_mask="); | |
- audit_file_mask(ab, sa->aad->fs.denied); | |
+ audit_file_mask(ab, aad(sa)->denied); | |
} | |
- if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) { | |
+ if (aad(sa)->request & AA_AUDIT_FILE_MASK) { | |
audit_log_format(ab, " fsuid=%d", | |
from_kuid(&init_user_ns, fsuid)); | |
audit_log_format(ab, " ouid=%d", | |
- from_kuid(&init_user_ns, sa->aad->fs.ouid)); | |
+ from_kuid(&init_user_ns, aad(sa)->fs.ouid)); | |
} | |
- if (sa->aad->fs.target) { | |
+ if (aad(sa)->peer) { | |
+ audit_log_format(ab, " target="); | |
+ aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, | |
+ FLAG_VIEW_SUBNS, GFP_ATOMIC); | |
+ } else if (aad(sa)->fs.target) { | |
audit_log_format(ab, " target="); | |
- audit_log_untrustedstring(ab, sa->aad->fs.target); | |
+ audit_log_untrustedstring(ab, aad(sa)->fs.target); | |
} | |
} | |
@@ -92,66 +89,102 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) | |
* aa_audit_file - handle the auditing of file operations | |
* @profile: the profile being enforced (NOT NULL) | |
* @perms: the permissions computed for the request (NOT NULL) | |
- * @gfp: allocation flags | |
* @op: operation being mediated | |
* @request: permissions requested | |
* @name: name of object being mediated (MAYBE NULL) | |
* @target: name of target (MAYBE NULL) | |
+ * @tlabel: target label (MAY BE NULL) | |
* @ouid: object uid | |
* @info: extra information message (MAYBE NULL) | |
* @error: 0 if operation allowed else failure error code | |
* | |
* Returns: %0 or error on failure | |
*/ | |
-int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, | |
- gfp_t gfp, int op, u32 request, const char *name, | |
- const char *target, kuid_t ouid, const char *info, int error) | |
+int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, | |
+ const char *op, u32 request, const char *name, | |
+ const char *target, struct aa_label *tlabel, | |
+ kuid_t ouid, const char *info, int error) | |
{ | |
int type = AUDIT_APPARMOR_AUTO; | |
- struct common_audit_data sa; | |
- struct apparmor_audit_data aad = {0,}; | |
- sa.type = LSM_AUDIT_DATA_TASK; | |
+ | |
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, op); | |
sa.u.tsk = NULL; | |
- sa.aad = &aad; | |
- aad.op = op, | |
- aad.fs.request = request; | |
- aad.name = name; | |
- aad.fs.target = target; | |
- aad.fs.ouid = ouid; | |
- aad.info = info; | |
- aad.error = error; | |
- | |
- if (likely(!sa.aad->error)) { | |
+ aad(&sa)->request = request; | |
+ aad(&sa)->name = name; | |
+ aad(&sa)->fs.target = target; | |
+ aad(&sa)->peer = tlabel; | |
+ aad(&sa)->fs.ouid = ouid; | |
+ aad(&sa)->info = info; | |
+ aad(&sa)->error = error; | |
+ sa.u.tsk = NULL; | |
+ | |
+ if (likely(!aad(&sa)->error)) { | |
u32 mask = perms->audit; | |
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) | |
mask = 0xffff; | |
/* mask off perms that are not being force audited */ | |
- sa.aad->fs.request &= mask; | |
+ aad(&sa)->request &= mask; | |
- if (likely(!sa.aad->fs.request)) | |
+ if (likely(!aad(&sa)->request)) | |
return 0; | |
type = AUDIT_APPARMOR_AUDIT; | |
} else { | |
/* only report permissions that were denied */ | |
- sa.aad->fs.request = sa.aad->fs.request & ~perms->allow; | |
+ aad(&sa)->request = aad(&sa)->request & ~perms->allow; | |
+ AA_BUG(!aad(&sa)->request); | |
- if (sa.aad->fs.request & perms->kill) | |
+ if (aad(&sa)->request & perms->kill) | |
type = AUDIT_APPARMOR_KILL; | |
/* quiet known rejects, assumes quiet and kill do not overlap */ | |
- if ((sa.aad->fs.request & perms->quiet) && | |
+ if ((aad(&sa)->request & perms->quiet) && | |
AUDIT_MODE(profile) != AUDIT_NOQUIET && | |
AUDIT_MODE(profile) != AUDIT_ALL) | |
- sa.aad->fs.request &= ~perms->quiet; | |
+ aad(&sa)->request &= ~perms->quiet; | |
+ | |
+ if (!aad(&sa)->request) | |
+ return aad(&sa)->error; | |
+ } | |
+ | |
+ aad(&sa)->denied = aad(&sa)->request & ~perms->allow; | |
+ return aa_audit(type, profile, &sa, file_audit_cb); | |
+} | |
+ | |
+/** | |
+ * is_deleted - test if a file has been completely unlinked | |
+ * @dentry: dentry of file to test for deletion (NOT NULL) | |
+ * | |
+ * Returns: %1 if deleted else %0 | |
+ */ | |
+static inline bool is_deleted(struct dentry *dentry) | |
+{ | |
+ if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0) | |
+ return 1; | |
+ return 0; | |
+} | |
- if (!sa.aad->fs.request) | |
- return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; | |
+static int path_name(const char *op, struct aa_label *label, | |
+ const struct path *path, int flags, char *buffer, | |
+ const char**name, struct path_cond *cond, u32 request, | |
+ bool delegate_deleted) | |
+{ | |
+ struct aa_profile *profile; | |
+ const char *info = NULL; | |
+ int error = aa_path_name(path, flags, buffer, name, &info, | |
+ labels_profile(label)->disconnected); | |
+ if (error) { | |
+ if (error == -ENOENT && is_deleted(path->dentry) && | |
+ delegate_deleted) | |
+ return 0; | |
+ fn_for_each_confined(label, profile, | |
+ aa_audit_file(profile, &nullperms, op, request, *name, | |
+ NULL, NULL, cond->uid, info, error)); | |
+ return error; | |
} | |
- sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow; | |
- return aa_audit(type, profile, gfp, &sa, file_audit_cb); | |
+ return 0; | |
} | |
/** | |
@@ -164,10 +197,11 @@ static u32 map_old_perms(u32 old) | |
{ | |
u32 new = old & 0xf; | |
if (old & MAY_READ) | |
- new |= AA_MAY_META_READ; | |
+ new |= AA_MAY_GETATTR | AA_MAY_OPEN; | |
if (old & MAY_WRITE) | |
- new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE | | |
- AA_MAY_CHMOD | AA_MAY_CHOWN; | |
+ new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE | | |
+ AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN | | |
+ AA_MAY_DELETE; | |
if (old & 0x10) | |
new |= AA_MAY_LINK; | |
/* the old mapping lock and link_subset flags where overlaid | |
@@ -182,7 +216,7 @@ static u32 map_old_perms(u32 old) | |
} | |
/** | |
- * compute_perms - convert dfa compressed perms to internal perms | |
+ * aa_compute_fperms - convert dfa compressed perms to internal perms | |
* @dfa: dfa to compute perms for (NOT NULL) | |
* @state: state in dfa | |
* @cond: conditions to consider (NOT NULL) | |
@@ -192,17 +226,21 @@ static u32 map_old_perms(u32 old) | |
* | |
* Returns: computed permission set | |
*/ | |
-static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, | |
- struct path_cond *cond) | |
+struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, | |
+ struct path_cond *cond) | |
{ | |
- struct file_perms perms; | |
+ struct aa_perms perms; | |
/* FIXME: change over to new dfa format | |
* currently file perms are encoded in the dfa, new format | |
* splits the permissions from the dfa. This mapping can be | |
* done at profile load | |
*/ | |
- perms.kill = 0; | |
+ perms.deny = 0; | |
+ perms.kill = perms.stop = 0; | |
+ perms.complain = perms.cond = 0; | |
+ perms.hide = 0; | |
+ perms.prompt = 0; | |
if (uid_eq(current_fsuid(), cond->uid)) { | |
perms.allow = map_old_perms(dfa_user_allow(dfa, state)); | |
@@ -215,7 +253,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, | |
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); | |
perms.xindex = dfa_other_xindex(dfa, state); | |
} | |
- perms.allow |= AA_MAY_META_READ; | |
+ perms.allow |= AA_MAY_GETATTR; | |
/* change_profile wasn't determined by ownership in old mapping */ | |
if (ACCEPT_TABLE(dfa)[state] & 0x80000000) | |
@@ -238,37 +276,34 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, | |
*/ | |
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, | |
const char *name, struct path_cond *cond, | |
- struct file_perms *perms) | |
+ struct aa_perms *perms) | |
{ | |
unsigned int state; | |
- if (!dfa) { | |
- *perms = nullperms; | |
- return DFA_NOMATCH; | |
- } | |
- | |
state = aa_dfa_match(dfa, start, name); | |
- *perms = compute_perms(dfa, state, cond); | |
+ *perms = aa_compute_fperms(dfa, state, cond); | |
return state; | |
} | |
-/** | |
- * is_deleted - test if a file has been completely unlinked | |
- * @dentry: dentry of file to test for deletion (NOT NULL) | |
- * | |
- * Returns: %1 if deleted else %0 | |
- */ | |
-static inline bool is_deleted(struct dentry *dentry) | |
+int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, | |
+ u32 request, struct path_cond *cond, int flags, | |
+ struct aa_perms *perms) | |
{ | |
- if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0) | |
- return 1; | |
- return 0; | |
+ int e = 0; | |
+ if (profile_unconfined(profile) || | |
+ ((flags & PATH_SOCK_COND) && !PROFILE_MEDIATES_AF(profile, AF_UNIX))) | |
+ return 0; | |
+ aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms); | |
+ if (request & ~perms->allow) | |
+ e = -EACCES; | |
+ return aa_audit_file(profile, perms, op, request, name, NULL, NULL, | |
+ cond->uid, NULL, e); | |
} | |
/** | |
* aa_path_perm - do permissions check & audit for @path | |
* @op: operation being checked | |
- * @profile: profile being enforced (NOT NULL) | |
+ * @label: profile being enforced (NOT NULL) | |
* @path: path to check permissions of (NOT NULL) | |
* @flags: any additional path flags beyond what the profile specifies | |
* @request: requested permissions | |
@@ -276,35 +311,29 @@ static inline bool is_deleted(struct dentry *dentry) | |
* | |
* Returns: %0 else error if access denied or other error | |
*/ | |
-int aa_path_perm(int op, struct aa_profile *profile, const struct path *path, | |
- int flags, u32 request, struct path_cond *cond) | |
+int aa_path_perm(const char *op, struct aa_label *label, | |
+ const struct path *path, int flags, u32 request, | |
+ struct path_cond *cond) | |
{ | |
+ struct aa_perms perms = {}; | |
char *buffer = NULL; | |
- struct file_perms perms = {}; | |
- const char *name, *info = NULL; | |
+ const char *name; | |
+ struct aa_profile *profile; | |
int error; | |
- flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); | |
- error = aa_path_name(path, flags, &buffer, &name, &info); | |
- if (error) { | |
- if (error == -ENOENT && is_deleted(path->dentry)) { | |
- /* Access to open files that are deleted are | |
- * give a pass (implicit delegation) | |
- */ | |
- error = 0; | |
- info = NULL; | |
- perms.allow = request; | |
- } | |
- } else { | |
- aa_str_perms(profile->file.dfa, profile->file.start, name, cond, | |
- &perms); | |
- if (request & ~perms.allow) | |
- error = -EACCES; | |
- } | |
- error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, name, | |
- NULL, cond->uid, info, error); | |
- kfree(buffer); | |
+ /* TODO: fix path lookup flags */ | |
+ flags |= labels_profile(label)->path_flags | | |
+ (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); | |
+ get_buffers(buffer); | |
+ | |
+ error = path_name(op, label, path, flags, buffer, &name, cond, | |
+ request, true); | |
+ if (!error) | |
+ error = fn_for_each_confined(label, profile, | |
+ __aa_path_perm(op, profile, name, request, cond, | |
+ flags, &perms)); | |
+ put_buffers(buffer); | |
return error; | |
} | |
@@ -328,65 +357,25 @@ static inline bool xindex_is_subset(u32 link, u32 target) | |
return 1; | |
} | |
-/** | |
- * aa_path_link - Handle hard link permission check | |
- * @profile: the profile being enforced (NOT NULL) | |
- * @old_dentry: the target dentry (NOT NULL) | |
- * @new_dir: directory the new link will be created in (NOT NULL) | |
- * @new_dentry: the link being created (NOT NULL) | |
- * | |
- * Handle the permission test for a link & target pair. Permission | |
- * is encoded as a pair where the link permission is determined | |
- * first, and if allowed, the target is tested. The target test | |
- * is done from the point of the link match (not start of DFA) | |
- * making the target permission dependent on the link permission match. | |
- * | |
- * The subset test if required forces that permissions granted | |
- * on link are a subset of the permission granted to target. | |
- * | |
- * Returns: %0 if allowed else error | |
- */ | |
-int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, | |
- const struct path *new_dir, struct dentry *new_dentry) | |
+static int profile_path_link(struct aa_profile *profile, const char *lname, | |
+ const char *tname, struct path_cond *cond) | |
{ | |
- struct path link = { new_dir->mnt, new_dentry }; | |
- struct path target = { new_dir->mnt, old_dentry }; | |
- struct path_cond cond = { | |
- d_backing_inode(old_dentry)->i_uid, | |
- d_backing_inode(old_dentry)->i_mode | |
- }; | |
- char *buffer = NULL, *buffer2 = NULL; | |
- const char *lname, *tname = NULL, *info = NULL; | |
- struct file_perms lperms, perms; | |
+ struct aa_perms lperms, perms; | |
+ const char *info = NULL; | |
u32 request = AA_MAY_LINK; | |
unsigned int state; | |
- int error; | |
- | |
- lperms = nullperms; | |
- | |
- /* buffer freed below, lname is pointer in buffer */ | |
- error = aa_path_name(&link, profile->path_flags, &buffer, &lname, | |
- &info); | |
- if (error) | |
- goto audit; | |
- | |
- /* buffer2 freed below, tname is pointer in buffer2 */ | |
- error = aa_path_name(&target, profile->path_flags, &buffer2, &tname, | |
- &info); | |
- if (error) | |
- goto audit; | |
+ int e = -EACCES; | |
- error = -EACCES; | |
/* aa_str_perms - handles the case of the dfa being NULL */ | |
state = aa_str_perms(profile->file.dfa, profile->file.start, lname, | |
- &cond, &lperms); | |
+ cond, &lperms); | |
if (!(lperms.allow & AA_MAY_LINK)) | |
goto audit; | |
/* test to see if target can be paired with link */ | |
state = aa_dfa_null_transition(profile->file.dfa, state); | |
- aa_str_perms(profile->file.dfa, state, tname, &cond, &perms); | |
+ aa_str_perms(profile->file.dfa, state, tname, cond, &perms); | |
/* force audit/quiet masks for link are stored in the second entry | |
* in the link pair. | |
@@ -397,6 +386,7 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, | |
if (!(perms.allow & AA_MAY_LINK)) { | |
info = "target restricted"; | |
+ lperms = perms; | |
goto audit; | |
} | |
@@ -404,10 +394,10 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, | |
if (!(perms.allow & AA_LINK_SUBSET)) | |
goto done_tests; | |
- /* Do link perm subset test requiring allowed permission on link are a | |
- * subset of the allowed permissions on target. | |
+ /* Do link perm subset test requiring allowed permission on link are | |
+ * a subset of the allowed permissions on target. | |
*/ | |
- aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond, | |
+ aa_str_perms(profile->file.dfa, profile->file.start, tname, cond, | |
&perms); | |
/* AA_MAY_LINK is not considered in the subset test */ | |
@@ -426,13 +416,175 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, | |
} | |
done_tests: | |
- error = 0; | |
+ e = 0; | |
audit: | |
- error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request, | |
- lname, tname, cond.uid, info, error); | |
- kfree(buffer); | |
- kfree(buffer2); | |
+ return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname, | |
+ NULL, cond->uid, info, e); | |
+} | |
+ | |
+/** | |
+ * aa_path_link - Handle hard link permission check | |
+ * @label: the label being enforced (NOT NULL) | |
+ * @old_dentry: the target dentry (NOT NULL) | |
+ * @new_dir: directory the new link will be created in (NOT NULL) | |
+ * @new_dentry: the link being created (NOT NULL) | |
+ * | |
+ * Handle the permission test for a link & target pair. Permission | |
+ * is encoded as a pair where the link permission is determined | |
+ * first, and if allowed, the target is tested. The target test | |
+ * is done from the point of the link match (not start of DFA) | |
+ * making the target permission dependent on the link permission match. | |
+ * | |
+ * The subset test if required forces that permissions granted | |
+ * on link are a subset of the permission granted to target. | |
+ * | |
+ * Returns: %0 if allowed else error | |
+ */ | |
+int aa_path_link(struct aa_label *label, struct dentry *old_dentry, | |
+ const struct path *new_dir, struct dentry *new_dentry) | |
+{ | |
+ struct path link = { new_dir->mnt, new_dentry }; | |
+ struct path target = { new_dir->mnt, old_dentry }; | |
+ struct path_cond cond = { | |
+ d_backing_inode(old_dentry)->i_uid, | |
+ d_backing_inode(old_dentry)->i_mode | |
+ }; | |
+ char *buffer = NULL, *buffer2 = NULL; | |
+ const char *lname, *tname = NULL; | |
+ struct aa_profile *profile; | |
+ int error; | |
+ | |
+ /* TODO: fix path lookup flags, auditing of failed path for profile */ | |
+ profile = labels_profile(label); | |
+ /* buffer freed below, lname is pointer in buffer */ | |
+ get_buffers(buffer, buffer2); | |
+ error = path_name(OP_LINK, label, &link, | |
+ labels_profile(label)->path_flags, buffer, | |
+ &lname, &cond, AA_MAY_LINK, false); | |
+ if (error) | |
+ goto out; | |
+ | |
+ /* buffer2 freed below, tname is pointer in buffer2 */ | |
+ error = path_name(OP_LINK, label, &target, | |
+ labels_profile(label)->path_flags, buffer2, &tname, | |
+ &cond, AA_MAY_LINK, false); | |
+ if (error) | |
+ goto out; | |
+ | |
+ error = fn_for_each_confined(label, profile, | |
+ profile_path_link(profile, lname, tname, &cond)); | |
+ | |
+out: | |
+ put_buffers(buffer, buffer2); | |
+ | |
+ return error; | |
+} | |
+ | |
+static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label, | |
+ u32 request) | |
+{ | |
+ struct aa_label *l, *old; | |
+ | |
+ /* update caching of label on file_ctx */ | |
+ spin_lock(&fctx->lock); | |
+ old = rcu_dereference_protected(fctx->label, | |
+ spin_is_locked(&fctx->lock)); | |
+ l = aa_label_merge(old, label, GFP_ATOMIC); | |
+ if (l) { | |
+ if (l != old) { | |
+ rcu_assign_pointer(fctx->label, l); | |
+ aa_put_label(old); | |
+ } else | |
+ aa_put_label(l); | |
+ fctx->allow |= request; | |
+ } | |
+ spin_unlock(&fctx->lock); | |
+} | |
+ | |
+static int __file_path_perm(const char *op, struct aa_label *label, | |
+ struct aa_label *flabel, struct file *file, | |
+ u32 request, u32 denied) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct aa_perms perms = {}; | |
+ struct path_cond cond = { | |
+ .uid = file_inode(file)->i_uid, | |
+ .mode = file_inode(file)->i_mode | |
+ }; | |
+ const char *name; | |
+ char *buffer; | |
+ int flags, error; | |
+ | |
+ /* revalidation due to label out of date. No revocation at this time */ | |
+ if (!denied && aa_label_is_subset(flabel, label)) | |
+ /* TODO: check for revocation on stale profiles */ | |
+ return 0; | |
+ | |
+ /* TODO: fix path lookup flags */ | |
+ flags = PATH_DELEGATE_DELETED | labels_profile(label)->path_flags | | |
+ (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0); | |
+ get_buffers(buffer); | |
+ | |
+ error = path_name(op, label, &file->f_path, flags, buffer, &name, &cond, | |
+ request, true); | |
+ if (error) { | |
+ if (error == 1) | |
+ /* Access to open files that are deleted are | |
+ * given a pass (implicit delegation) | |
+ */ | |
+ /* TODO not needed when full perms cached */ | |
+ error = 0; | |
+ goto out; | |
+ } | |
+ | |
+ /* check every profile in task label not in current cache */ | |
+ error = fn_for_each_not_in_set(flabel, label, profile, | |
+ __aa_path_perm(op, profile, name, request, &cond, 0, | |
+ &perms)); | |
+ if (denied) { | |
+ /* check every profile in file label that was not tested | |
+ * in the initial check above. | |
+ */ | |
+ /* TODO: cache full perms so this only happens because of | |
+ * conditionals */ | |
+ /* TODO: don't audit here */ | |
+ last_error(error, | |
+ fn_for_each_not_in_set(label, flabel, profile, | |
+ __aa_path_perm(op, profile, name, request, | |
+ &cond, 0, &perms))); | |
+ } | |
+ if (!error) | |
+ update_file_ctx(file_ctx(file), label, request); | |
+ | |
+out: | |
+ put_buffers(buffer); | |
+ | |
+ return error; | |
+} | |
+ | |
+static int __file_sock_perm(const char *op, struct aa_label *label, | |
+ struct aa_label *flabel, struct file *file, | |
+ u32 request, u32 denied) | |
+{ | |
+ struct socket *sock = (struct socket *) file->private_data; | |
+ int error; | |
+ | |
+ AA_BUG(!sock); | |
+ | |
+ /* revalidation due to label out of date. No revocation at this time */ | |
+ if (!denied && aa_label_is_subset(flabel, label)) | |
+ return 0; | |
+ | |
+ /* TODO: improve to skip profiles cached in flabel */ | |
+ error = aa_sock_file_perm(label, op, request, sock); | |
+ if (denied) { | |
+ /* TODO: improve to skip profiles checked above */ | |
+ /* check every profile in file label to is cached */ | |
+ last_error(error, aa_sock_file_perm(flabel, op, request, sock)); | |
+ } | |
+ if (!error) | |
+ update_file_ctx(file_ctx(file), label, request); | |
return error; | |
} | |
@@ -440,20 +592,117 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, | |
/** | |
* aa_file_perm - do permission revalidation check & audit for @file | |
* @op: operation being checked | |
- * @profile: profile being enforced (NOT NULL) | |
+ * @label: label being enforced (NOT NULL) | |
* @file: file to revalidate access permissions on (NOT NULL) | |
* @request: requested permissions | |
* | |
* Returns: %0 if access allowed else error | |
*/ | |
-int aa_file_perm(int op, struct aa_profile *profile, struct file *file, | |
+int aa_file_perm(const char *op, struct aa_label *label, struct file *file, | |
u32 request) | |
{ | |
- struct path_cond cond = { | |
- .uid = file_inode(file)->i_uid, | |
- .mode = file_inode(file)->i_mode | |
- }; | |
+ struct aa_file_ctx *fctx; | |
+ struct aa_label *flabel; | |
+ u32 denied; | |
+ int error = 0; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!file); | |
+ | |
+ fctx = file_ctx(file); | |
+ | |
+ rcu_read_lock(); | |
+ flabel = rcu_dereference(fctx->label); | |
+ AA_BUG(!flabel); | |
+ | |
+ /* revalidate access, if task is unconfined, or the cached cred | |
+ * doesn't match or if the request is for more permissions than | |
+ * was granted. | |
+ * | |
+ * Note: the test for !unconfined(flabel) is to handle file | |
+ * delegation from unconfined tasks | |
+ */ | |
+ denied = request & ~fctx->allow; | |
+ if (unconfined(label) || unconfined(flabel) || | |
+ (!denied && aa_label_is_subset(flabel, label))) | |
+ goto done; | |
+ | |
+ /* TODO: label cross check */ | |
+ | |
+ if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry)) { | |
+ error = __file_path_perm(op, label, flabel, file, request, | |
+ denied); | |
+ | |
+ } else if (S_ISSOCK(file_inode(file)->i_mode)) { | |
+ error = __file_sock_perm(op, label, flabel, file, request, | |
+ denied); | |
+ } | |
+done: | |
+ rcu_read_unlock(); | |
+ | |
+ return error; | |
+} | |
- return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED, | |
- request, &cond); | |
+static void revalidate_tty(struct aa_label *label) | |
+{ | |
+ struct tty_struct *tty; | |
+ int drop_tty = 0; | |
+ | |
+ tty = get_current_tty(); | |
+ if (!tty) | |
+ return; | |
+ | |
+ spin_lock(&tty->files_lock); | |
+ if (!list_empty(&tty->tty_files)) { | |
+ struct tty_file_private *file_priv; | |
+ struct file *file; | |
+ /* TODO: Revalidate access to controlling tty. */ | |
+ file_priv = list_first_entry(&tty->tty_files, | |
+ struct tty_file_private, list); | |
+ file = file_priv->file; | |
+ | |
+ if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE)) | |
+ drop_tty = 1; | |
+ } | |
+ spin_unlock(&tty->files_lock); | |
+ tty_kref_put(tty); | |
+ | |
+ if (drop_tty) | |
+ no_tty(); | |
+} | |
+ | |
+static int match_file(const void *p, struct file *file, unsigned fd) | |
+{ | |
+ struct aa_label *label = (struct aa_label *)p; | |
+ if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file))) | |
+ return fd + 1; | |
+ return 0; | |
+} | |
+ | |
+ | |
+/* based on selinux's flush_unauthorized_files */ | |
+void aa_inherit_files(const struct cred *cred, struct files_struct *files) | |
+{ | |
+ struct aa_label *label = aa_get_newest_cred_label(cred); | |
+ struct file *devnull = NULL; | |
+ unsigned n; | |
+ | |
+ revalidate_tty(label); | |
+ | |
+ /* Revalidate access to inherited open files. */ | |
+ n = iterate_fd(files, 0, match_file, label); | |
+ if (!n) /* none found? */ | |
+ goto out; | |
+ | |
+ devnull = dentry_open(&aa_null, O_RDWR, cred); | |
+ if (IS_ERR(devnull)) | |
+ devnull = NULL; | |
+ /* replace all the matching ones with this */ | |
+ do { | |
+ replace_fd(n - 1, devnull, 0); | |
+ } while ((n = iterate_fd(files, n, match_file, label)) != 0); | |
+ if (devnull) | |
+ fput(devnull); | |
+out: | |
+ aa_put_label(label); | |
} | |
diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h | |
new file mode 100644 | |
index 0000000..d1b7f23 | |
--- /dev/null | |
+++ b/security/apparmor/include/af_unix.h | |
@@ -0,0 +1,114 @@ | |
+/* | |
+ * AppArmor security module | |
+ * | |
+ * This file contains AppArmor af_unix fine grained mediation | |
+ * | |
+ * Copyright 2014 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_AF_UNIX_H | |
+ | |
+#include <net/af_unix.h> | |
+ | |
+#include "label.h" | |
+//#include "include/net.h" | |
+ | |
+#define unix_addr_len(L) ((L) - sizeof(sa_family_t)) | |
+#define unix_abstract_name_len(L) (unix_addr_len(L) - 1) | |
+#define unix_abstract_len(U) (unix_abstract_name_len((U)->addr->len)) | |
+#define addr_unix_abstract_name(B) ((B)[0] == 0) | |
+#define addr_unix_anonymous(U) (addr_unix_len(U) <= 0) | |
+#define addr_unix_abstract(U) (!addr_unix_anonymous(U) && addr_unix_abstract_name((U)->addr)) | |
+//#define unix_addr_fs(U) (!unix_addr_anonymous(U) && !unix_addr_abstract_name((U)->addr)) | |
+ | |
+#define unix_addr(A) ((struct sockaddr_un *)(A)) | |
+#define unix_addr_anon(A, L) ((A) && unix_addr_len(L) <= 0) | |
+#define unix_addr_fs(A, L) (!unix_addr_anon(A, L) && !addr_unix_abstract_name(unix_addr(A)->sun_path)) | |
+ | |
+#define UNIX_ANONYMOUS(U) (!unix_sk(U)->addr) | |
+/* from net/unix/af_unix.c */ | |
+#define UNIX_ABSTRACT(U) (!UNIX_ANONYMOUS(U) && \ | |
+ unix_sk(U)->addr->hash < UNIX_HASH_SIZE) | |
+#define UNIX_FS(U) (!UNIX_ANONYMOUS(U) && unix_sk(U)->addr->name->sun_path[0]) | |
+#define unix_peer(sk) (unix_sk(sk)->peer) | |
+#define unix_connected(S) ((S)->state == SS_CONNECTED) | |
+ | |
+static inline void print_unix_addr(struct sockaddr_un *A, int L) | |
+{ | |
+ char *buf = (A) ? (char *) &(A)->sun_path : NULL; | |
+ int len = unix_addr_len(L); | |
+ if (!buf || len <= 0) | |
+ printk(" <anonymous>"); | |
+ else if (buf[0]) | |
+ printk(" %s", buf); | |
+ else | |
+ /* abstract name len includes leading \0 */ | |
+ printk(" %d @%.*s", len - 1, len - 1, buf+1); | |
+}; | |
+ | |
+/* | |
+ printk("%s: %s: f %d, t %d, p %d", __FUNCTION__, \ | |
+ #SK , \ | |
+*/ | |
+#define print_unix_sk(SK) \ | |
+do { \ | |
+ struct unix_sock *u = unix_sk(SK); \ | |
+ printk("%s: f %d, t %d, p %d", #SK , \ | |
+ (SK)->sk_family, (SK)->sk_type, (SK)->sk_protocol); \ | |
+ if (u->addr) \ | |
+ print_unix_addr(u->addr->name, u->addr->len); \ | |
+ else \ | |
+ print_unix_addr(NULL, sizeof(sa_family_t)); \ | |
+ /* printk("\n");*/ \ | |
+} while (0) | |
+ | |
+#define print_sk(SK) \ | |
+do { \ | |
+ if (!(SK)) { \ | |
+ printk("%s: %s is null\n", __FUNCTION__, #SK); \ | |
+ } else if ((SK)->sk_family == PF_UNIX) { \ | |
+ print_unix_sk(SK); \ | |
+ printk("\n"); \ | |
+ } else { \ | |
+ printk("%s: %s: family %d\n", __FUNCTION__, #SK , \ | |
+ (SK)->sk_family); \ | |
+ } \ | |
+} while (0) | |
+ | |
+#define print_sock_addr(U) \ | |
+do { \ | |
+ printk("%s:\n", __FUNCTION__); \ | |
+ printk(" sock %s:", sock_ctx && sock_ctx->label ? aa_label_printk(sock_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(sock); \ | |
+ printk(" other %s:", other_ctx && other_ctx->label ? aa_label_printk(other_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(other); \ | |
+ printk(" new %s", new_ctx && new_ctx->label ? aa_label_printk(new_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(newsk); \ | |
+} while (0) | |
+ | |
+ | |
+ | |
+ | |
+int aa_unix_peer_perm(struct aa_label *label, const char *op, u32 request, | |
+ struct sock *sk, struct sock *peer_sk, | |
+ struct aa_label *peer_label); | |
+int aa_unix_label_sk_perm(struct aa_label *label, const char *op, u32 request, | |
+ struct sock *sk); | |
+int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock); | |
+int aa_unix_create_perm(struct aa_label *label, int family, int type, | |
+ int protocol); | |
+int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address, | |
+ int addrlen); | |
+int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address, | |
+ int addrlen); | |
+int aa_unix_listen_perm(struct socket *sock, int backlog); | |
+int aa_unix_accept_perm(struct socket *sock, struct socket *newsock); | |
+int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock, | |
+ struct msghdr *msg, int size); | |
+int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level, | |
+ int optname); | |
+int aa_unix_file_perm(struct aa_label *label, const char *op, u32 request, | |
+ struct socket *sock); | |
+ | |
+#endif /* __AA_AF_UNIX_H */ | |
diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h | |
index 5d721e9..a498e39 100644 | |
--- a/security/apparmor/include/apparmor.h | |
+++ b/security/apparmor/include/apparmor.h | |
@@ -1,10 +1,10 @@ | |
/* | |
* AppArmor security module | |
* | |
- * This file contains AppArmor basic global and lib definitions | |
+ * This file contains AppArmor basic global | |
* | |
* Copyright (C) 1998-2008 Novell/SUSE | |
- * Copyright 2009-2010 Canonical Ltd. | |
+ * Copyright 2009-2016 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 | |
@@ -15,10 +15,7 @@ | |
#ifndef __APPARMOR_H | |
#define __APPARMOR_H | |
-#include <linux/slab.h> | |
-#include <linux/fs.h> | |
- | |
-#include "match.h" | |
+#include <linux/types.h> | |
/* | |
* Class of mediation types in the AppArmor policy db | |
@@ -30,8 +27,12 @@ | |
#define AA_CLASS_NET 4 | |
#define AA_CLASS_RLIMITS 5 | |
#define AA_CLASS_DOMAIN 6 | |
+#define AA_CLASS_MOUNT 7 | |
+#define AA_CLASS_PTRACE 9 | |
+#define AA_CLASS_SIGNAL 10 | |
+#define AA_CLASS_LABEL 16 | |
-#define AA_CLASS_LAST AA_CLASS_DOMAIN | |
+#define AA_CLASS_LAST AA_CLASS_LABEL | |
/* Control parameters settable through module/boot flags */ | |
extern enum audit_mode aa_g_audit; | |
@@ -42,80 +43,6 @@ | |
extern bool aa_g_logsyscall; | |
extern bool aa_g_paranoid_load; | |
extern unsigned int aa_g_path_max; | |
- | |
-/* | |
- * DEBUG remains global (no per profile flag) since it is mostly used in sysctl | |
- * which is not related to profile accesses. | |
- */ | |
- | |
-#define AA_DEBUG(fmt, args...) \ | |
- do { \ | |
- if (aa_g_debug && printk_ratelimit()) \ | |
- printk(KERN_DEBUG "AppArmor: " fmt, ##args); \ | |
- } while (0) | |
- | |
-#define AA_ERROR(fmt, args...) \ | |
- do { \ | |
- if (printk_ratelimit()) \ | |
- printk(KERN_ERR "AppArmor: " fmt, ##args); \ | |
- } while (0) | |
- | |
-/* Flag indicating whether initialization completed */ | |
-extern int apparmor_initialized __initdata; | |
- | |
-/* fn's in lib */ | |
-char *aa_split_fqname(char *args, char **ns_name); | |
-void aa_info_message(const char *str); | |
-void *__aa_kvmalloc(size_t size, gfp_t flags); | |
- | |
-static inline void *kvmalloc(size_t size) | |
-{ | |
- return __aa_kvmalloc(size, 0); | |
-} | |
- | |
-static inline void *kvzalloc(size_t size) | |
-{ | |
- return __aa_kvmalloc(size, __GFP_ZERO); | |
-} | |
- | |
-/* returns 0 if kref not incremented */ | |
-static inline int kref_get_not0(struct kref *kref) | |
-{ | |
- return atomic_inc_not_zero(&kref->refcount); | |
-} | |
- | |
-/** | |
- * aa_strneq - compare null terminated @str to a non null terminated substring | |
- * @str: a null terminated string | |
- * @sub: a substring, not necessarily null terminated | |
- * @len: length of @sub to compare | |
- * | |
- * The @str string must be full consumed for this to be considered a match | |
- */ | |
-static inline bool aa_strneq(const char *str, const char *sub, int len) | |
-{ | |
- return !strncmp(str, sub, len) && !str[len]; | |
-} | |
- | |
-/** | |
- * aa_dfa_null_transition - step to next state after null character | |
- * @dfa: the dfa to match against | |
- * @start: the state of the dfa to start matching in | |
- * | |
- * aa_dfa_null_transition transitions to the next state after a null | |
- * character which is not used in standard matching and is only | |
- * used to separate pairs. | |
- */ | |
-static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, | |
- unsigned int start) | |
-{ | |
- /* the null transition only needs the string's null terminator byte */ | |
- return aa_dfa_next(dfa, start, 0); | |
-} | |
- | |
-static inline bool mediated_filesystem(struct dentry *dentry) | |
-{ | |
- return !(dentry->d_sb->s_flags & MS_NOUSER); | |
-} | |
+extern bool aa_g_unconfined_init; | |
#endif /* __APPARMOR_H */ | |
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h | |
index 414e568..c6f84dc 100644 | |
--- a/security/apparmor/include/apparmorfs.h | |
+++ b/security/apparmor/include/apparmorfs.h | |
@@ -15,6 +15,8 @@ | |
#ifndef __AA_APPARMORFS_H | |
#define __AA_APPARMORFS_H | |
+extern struct path aa_null; | |
+ | |
enum aa_fs_type { | |
AA_FS_TYPE_BOOLEAN, | |
AA_FS_TYPE_STRING, | |
@@ -62,7 +64,7 @@ struct aa_fs_entry { | |
extern void __init aa_destroy_aafs(void); | |
struct aa_profile; | |
-struct aa_namespace; | |
+struct aa_ns; | |
enum aafs_ns_type { | |
AAFS_NS_DIR, | |
@@ -97,8 +99,7 @@ enum aafs_prof_type { | |
void __aa_fs_profile_migrate_dents(struct aa_profile *old, | |
struct aa_profile *new); | |
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); | |
-void __aa_fs_namespace_rmdir(struct aa_namespace *ns); | |
-int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, | |
- const char *name); | |
+void __aa_fs_ns_rmdir(struct aa_ns *ns); | |
+int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name); | |
#endif /* __AA_APPARMORFS_H */ | |
diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h | |
index ba3dfd1..0360e86 100644 | |
--- a/security/apparmor/include/audit.h | |
+++ b/security/apparmor/include/audit.h | |
@@ -22,8 +22,7 @@ | |
#include <linux/slab.h> | |
#include "file.h" | |
- | |
-struct aa_profile; | |
+#include "label.h" | |
extern const char *const audit_mode_names[]; | |
#define AUDIT_MAX_INDEX 5 | |
@@ -46,97 +45,140 @@ enum audit_type { | |
AUDIT_APPARMOR_AUTO | |
}; | |
-extern const char *const op_table[]; | |
-enum aa_ops { | |
- OP_NULL, | |
- | |
- OP_SYSCTL, | |
- OP_CAPABLE, | |
- | |
- OP_UNLINK, | |
- OP_MKDIR, | |
- OP_RMDIR, | |
- OP_MKNOD, | |
- OP_TRUNC, | |
- OP_LINK, | |
- OP_SYMLINK, | |
- OP_RENAME_SRC, | |
- OP_RENAME_DEST, | |
- OP_CHMOD, | |
- OP_CHOWN, | |
- OP_GETATTR, | |
- OP_OPEN, | |
- | |
- OP_FPERM, | |
- OP_FLOCK, | |
- OP_FMMAP, | |
- OP_FMPROT, | |
- | |
- OP_CREATE, | |
- OP_POST_CREATE, | |
- OP_BIND, | |
- OP_CONNECT, | |
- OP_LISTEN, | |
- OP_ACCEPT, | |
- OP_SENDMSG, | |
- OP_RECVMSG, | |
- OP_GETSOCKNAME, | |
- OP_GETPEERNAME, | |
- OP_GETSOCKOPT, | |
- OP_SETSOCKOPT, | |
- OP_SOCK_SHUTDOWN, | |
- | |
- OP_PTRACE, | |
- | |
- OP_EXEC, | |
- OP_CHANGE_HAT, | |
- OP_CHANGE_PROFILE, | |
- OP_CHANGE_ONEXEC, | |
- | |
- OP_SETPROCATTR, | |
- OP_SETRLIMIT, | |
- | |
- OP_PROF_REPL, | |
- OP_PROF_LOAD, | |
- OP_PROF_RM, | |
-}; | |
+#define OP_NULL NULL | |
+ | |
+#define OP_SYSCTL "sysctl" | |
+#define OP_CAPABLE "capable" | |
+ | |
+#define OP_UNLINK "unlink" | |
+#define OP_MKDIR "mkdir" | |
+#define OP_RMDIR "rmdir" | |
+#define OP_MKNOD "mknod" | |
+#define OP_TRUNC "truncate" | |
+#define OP_LINK "link" | |
+#define OP_SYMLINK "symlink" | |
+#define OP_RENAME_SRC "rename_src" | |
+#define OP_RENAME_DEST "rename_dest" | |
+#define OP_CHMOD "chmod" | |
+#define OP_CHOWN "chown" | |
+#define OP_GETATTR "getattr" | |
+#define OP_OPEN "open" | |
+ | |
+#define OP_FRECEIVE "file_receive" | |
+#define OP_FPERM "file_perm" | |
+#define OP_FLOCK "file_lock" | |
+#define OP_FMMAP "file_mmap" | |
+#define OP_FMPROT "file_mprotect" | |
+#define OP_INHERIT "file_inherit" | |
+ | |
+#define OP_PIVOTROOT "pivotroot" | |
+#define OP_MOUNT "mount" | |
+#define OP_UMOUNT "umount" | |
+ | |
+#define OP_CREATE "create" | |
+#define OP_POST_CREATE "post_create" | |
+#define OP_BIND "bind" | |
+#define OP_CONNECT "connect" | |
+#define OP_LISTEN "listen" | |
+#define OP_ACCEPT "accept" | |
+#define OP_SENDMSG "sendmsg" | |
+#define OP_RECVMSG "recvmsg" | |
+#define OP_GETSOCKNAME "getsockname" | |
+#define OP_GETPEERNAME "getpeername" | |
+#define OP_GETSOCKOPT "getsockopt" | |
+#define OP_SETSOCKOPT "setsockopt" | |
+#define OP_SHUTDOWN "socket_shutdown" | |
+ | |
+#define OP_PTRACE "ptrace" | |
+#define OP_SIGNAL "signal" | |
+ | |
+#define OP_EXEC "exec" | |
+ | |
+#define OP_CHANGE_HAT "change_hat" | |
+#define OP_CHANGE_PROFILE "change_profile" | |
+#define OP_CHANGE_ONEXEC "change_onexec" | |
+#define OP_STACK "stack" | |
+#define OP_STACK_ONEXEC "stack_onexec" | |
+ | |
+#define OP_SETPROCATTR "setprocattr" | |
+#define OP_SETRLIMIT "setrlimit" | |
+ | |
+#define OP_PROF_REPL "profile_replace" | |
+#define OP_PROF_LOAD "profile_load" | |
+#define OP_PROF_RM "profile_remove" | |
struct apparmor_audit_data { | |
int error; | |
- int op; | |
int type; | |
- void *profile; | |
+ const char *op; | |
+ struct aa_label *label; | |
const char *name; | |
const char *info; | |
+ u32 request; | |
+ u32 denied; | |
union { | |
- void *target; | |
+ /* these entries require a custom callback fn */ | |
+ struct { | |
+ struct aa_label *peer; | |
+ union { | |
+ struct { | |
+ kuid_t ouid; | |
+ const char *target; | |
+ } fs; | |
+ struct { | |
+ int type, protocol; | |
+ struct sock *peer_sk; | |
+ void *addr; | |
+ int addrlen; | |
+ } net; | |
+ int signal; | |
+ }; | |
+ }; | |
struct { | |
+ struct aa_profile *profile; | |
+ const char *ns; | |
long pos; | |
- void *target; | |
} iface; | |
struct { | |
int rlim; | |
unsigned long max; | |
} rlim; | |
struct { | |
- const char *target; | |
- u32 request; | |
- u32 denied; | |
- kuid_t ouid; | |
- } fs; | |
+ const char *src_name; | |
+ const char *type; | |
+ const char *trans; | |
+ const char *data; | |
+ unsigned long flags; | |
+ } mnt; | |
}; | |
}; | |
-/* define a short hand for apparmor_audit_data structure */ | |
-#define aad apparmor_audit_data | |
+/* macros for dealing with apparmor_audit_data structure */ | |
+#define aad(SA) (SA)->apparmor_audit_data | |
+#define DEFINE_AUDIT_DATA(NAME, T, X) \ | |
+ /* TODO: cleanup audit init so we don't need _aad = {0,} */ \ | |
+ struct apparmor_audit_data NAME ## _aad = { .op = (X), }; \ | |
+ struct common_audit_data NAME = \ | |
+ { \ | |
+ .type = (T), \ | |
+ .u.tsk = NULL, \ | |
+ }; \ | |
+ NAME.apparmor_audit_data = &(NAME ## _aad) | |
void aa_audit_msg(int type, struct common_audit_data *sa, | |
void (*cb) (struct audit_buffer *, void *)); | |
-int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, | |
- struct common_audit_data *sa, | |
+int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa, | |
void (*cb) (struct audit_buffer *, void *)); | |
+#define aa_audit_error(ERROR, SA, CB) \ | |
+({ \ | |
+ aad((SA))->error = (ERROR); \ | |
+ aa_audit_msg(AUDIT_APPARMOR_ERROR, (SA), (CB)); \ | |
+ aad((SA))->error; \ | |
+}) | |
+ | |
+ | |
static inline int complain_error(int error) | |
{ | |
if (error == -EPERM || error == -EACCES) | |
diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h | |
index fc3fa38..e01d97f 100644 | |
--- a/security/apparmor/include/capability.h | |
+++ b/security/apparmor/include/capability.h | |
@@ -19,11 +19,12 @@ | |
#include "apparmorfs.h" | |
-struct aa_profile; | |
+struct aa_label; | |
/* aa_caps - confinement data for capabilities | |
* @allowed: capabilities mask | |
* @audit: caps that are to be audited | |
+ * @denied: caps that are explicitly denied | |
* @quiet: caps that should not be audited | |
* @kill: caps that when requested will result in the task being killed | |
* @extended: caps that are subject finer grained mediation | |
@@ -31,6 +32,7 @@ | |
struct aa_caps { | |
kernel_cap_t allow; | |
kernel_cap_t audit; | |
+ kernel_cap_t denied; | |
kernel_cap_t quiet; | |
kernel_cap_t kill; | |
kernel_cap_t extended; | |
@@ -38,7 +40,7 @@ struct aa_caps { | |
extern struct aa_fs_entry aa_fs_entry_caps[]; | |
-int aa_capable(struct aa_profile *profile, int cap, int audit); | |
+int aa_capable(struct aa_label *label, int cap, int audit); | |
static inline void aa_free_cap_rules(struct aa_caps *caps) | |
{ | |
diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h | |
index 6bf6579..01b81b7 100644 | |
--- a/security/apparmor/include/context.h | |
+++ b/security/apparmor/include/context.h | |
@@ -19,99 +19,79 @@ | |
#include <linux/slab.h> | |
#include <linux/sched.h> | |
-#include "policy.h" | |
+#include "label.h" | |
+#include "policy_ns.h" | |
-#define cred_cxt(X) (X)->security | |
-#define current_cxt() cred_cxt(current_cred()) | |
- | |
-/* struct aa_file_cxt - the AppArmor context the file was opened in | |
- * @perms: the permission the file was opened with | |
- * | |
- * The file_cxt could currently be directly stored in file->f_security | |
- * as the profile reference is now stored in the f_cred. However the | |
- * cxt struct will expand in the future so we keep the struct. | |
- */ | |
-struct aa_file_cxt { | |
- u16 allow; | |
-}; | |
- | |
-/** | |
- * aa_alloc_file_context - allocate file_cxt | |
- * @gfp: gfp flags for allocation | |
- * | |
- * Returns: file_cxt or NULL on failure | |
- */ | |
-static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp) | |
-{ | |
- return kzalloc(sizeof(struct aa_file_cxt), gfp); | |
-} | |
- | |
-/** | |
- * aa_free_file_context - free a file_cxt | |
- * @cxt: file_cxt to free (MAYBE_NULL) | |
- */ | |
-static inline void aa_free_file_context(struct aa_file_cxt *cxt) | |
-{ | |
- if (cxt) | |
- kzfree(cxt); | |
-} | |
+#define cred_ctx(X) (X)->security | |
+#define current_ctx() cred_ctx(current_cred()) | |
/** | |
- * struct aa_task_cxt - primary label for confined tasks | |
- * @profile: the current profile (NOT NULL) | |
- * @exec: profile to transition to on next exec (MAYBE NULL) | |
- * @previous: profile the task may return to (MAYBE NULL) | |
- * @token: magic value the task must know for returning to @previous_profile | |
+ * struct aa_task_ctx - primary label for confined tasks | |
+ * @label: the current label (NOT NULL) | |
+ * @exec: label to transition to on next exec (MAYBE NULL) | |
+ * @previous: label the task may return to (MAYBE NULL) | |
+ * @token: magic value the task must know for returning to @previous | |
* | |
- * Contains the task's current profile (which could change due to | |
+ * Contains the task's current label (which could change due to | |
* change_hat). Plus the hat_magic needed during change_hat. | |
* | |
* TODO: make so a task can be confined by a stack of contexts | |
*/ | |
-struct aa_task_cxt { | |
- struct aa_profile *profile; | |
- struct aa_profile *onexec; | |
- struct aa_profile *previous; | |
+struct aa_task_ctx { | |
+ struct aa_label *label; | |
+ struct aa_label *onexec; | |
+ struct aa_label *previous; | |
u64 token; | |
}; | |
-struct aa_task_cxt *aa_alloc_task_context(gfp_t flags); | |
-void aa_free_task_context(struct aa_task_cxt *cxt); | |
-void aa_dup_task_context(struct aa_task_cxt *new, | |
- const struct aa_task_cxt *old); | |
-int aa_replace_current_profile(struct aa_profile *profile); | |
-int aa_set_current_onexec(struct aa_profile *profile); | |
-int aa_set_current_hat(struct aa_profile *profile, u64 token); | |
-int aa_restore_previous_profile(u64 cookie); | |
-struct aa_profile *aa_get_task_profile(struct task_struct *task); | |
+struct aa_task_ctx *aa_alloc_task_context(gfp_t flags); | |
+void aa_free_task_context(struct aa_task_ctx *ctx); | |
+void aa_dup_task_context(struct aa_task_ctx *new, | |
+ const struct aa_task_ctx *old); | |
+int aa_replace_current_label(struct aa_label *label); | |
+int aa_set_current_onexec(struct aa_label *label, bool stack); | |
+int aa_set_current_hat(struct aa_label *label, u64 token); | |
+int aa_restore_previous_label(u64 cookie); | |
+struct aa_label *aa_get_task_label(struct task_struct *task); | |
/** | |
- * aa_cred_profile - obtain cred's profiles | |
- * @cred: cred to obtain profiles from (NOT NULL) | |
+ * aa_cred_raw_label - obtain cred's label | |
+ * @cred: cred to obtain label from (NOT NULL) | |
* | |
- * Returns: confining profile | |
+ * Returns: confining label | |
* | |
* does NOT increment reference count | |
*/ | |
-static inline struct aa_profile *aa_cred_profile(const struct cred *cred) | |
+static inline struct aa_label *aa_cred_raw_label(const struct cred *cred) | |
{ | |
- struct aa_task_cxt *cxt = cred_cxt(cred); | |
- BUG_ON(!cxt || !cxt->profile); | |
- return cxt->profile; | |
+ struct aa_task_ctx *ctx = cred_ctx(cred); | |
+ BUG_ON(!ctx || !ctx->label); | |
+ return ctx->label; | |
} | |
/** | |
- * __aa_task_profile - retrieve another task's profile | |
+ * aa_get_newest_cred_label - obtain the newest version of the label on a cred | |
+ * @cred: cred to obtain label from (NOT NULL) | |
+ * | |
+ * Returns: newest version of confining label | |
+ */ | |
+static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred) | |
+{ | |
+ return aa_get_newest_label(aa_cred_raw_label(cred)); | |
+} | |
+ | |
+/** | |
+ * __aa_task_raw_label - retrieve another task's label | |
* @task: task to query (NOT NULL) | |
* | |
- * Returns: @task's profile without incrementing its ref count | |
+ * Returns: @task's label without incrementing its ref count | |
* | |
* If @task != current needs to be called in RCU safe critical section | |
*/ | |
-static inline struct aa_profile *__aa_task_profile(struct task_struct *task) | |
+static inline struct aa_label *__aa_task_raw_label(struct task_struct *task) | |
{ | |
- return aa_cred_profile(__task_cred(task)); | |
+ return aa_cred_raw_label(__task_cred(task)); | |
} | |
/** | |
@@ -122,57 +102,105 @@ static inline struct aa_profile *__aa_task_profile(struct task_struct *task) | |
*/ | |
static inline bool __aa_task_is_confined(struct task_struct *task) | |
{ | |
- return !unconfined(__aa_task_profile(task)); | |
+ return !unconfined(__aa_task_raw_label(task)); | |
} | |
/** | |
- * __aa_current_profile - find the current tasks confining profile | |
+ * aa_current_raw_label - find the current tasks confining label | |
* | |
- * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) | |
+ * Returns: up to date confining label or the ns unconfined label (NOT NULL) | |
* | |
* This fn will not update the tasks cred to the most up to date version | |
- * of the profile so it is safe to call when inside of locks. | |
+ * of the label so it is safe to call when inside of locks. | |
*/ | |
-static inline struct aa_profile *__aa_current_profile(void) | |
+static inline struct aa_label *aa_current_raw_label(void) | |
{ | |
- return aa_cred_profile(current_cred()); | |
+ return aa_cred_raw_label(current_cred()); | |
} | |
/** | |
- * aa_current_profile - find the current tasks confining profile and do updates | |
+ * aa_get_current_label - get the newest version of the current tasks label | |
+ * | |
+ * Returns: newest version of confining label (NOT NULL) | |
* | |
- * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) | |
+ * This fn will not update the tasks cred, so it is safe inside of locks | |
* | |
- * This fn will update the tasks cred structure if the profile has been | |
- * replaced. Not safe to call inside locks | |
+ * The returned reference must be put with aa_put_label() | |
*/ | |
-static inline struct aa_profile *aa_current_profile(void) | |
+static inline struct aa_label *aa_get_current_label(void) | |
{ | |
- const struct aa_task_cxt *cxt = current_cxt(); | |
- struct aa_profile *profile; | |
- BUG_ON(!cxt || !cxt->profile); | |
- | |
- if (PROFILE_INVALID(cxt->profile)) { | |
- profile = aa_get_newest_profile(cxt->profile); | |
- aa_replace_current_profile(profile); | |
- aa_put_profile(profile); | |
- cxt = current_cxt(); | |
+ struct aa_label *l = aa_current_raw_label(); | |
+ | |
+ if (label_is_stale(l)) | |
+ return aa_get_newest_label(l); | |
+ return aa_get_label(l); | |
+} | |
+ | |
+/** | |
+ * aa_end_current_label - put a reference found with aa_begin_current_label | |
+ * @label: label reference to put | |
+ * | |
+ * Should only be used with a reference obtained with aa_begin_current_label | |
+ * and never used in situations where the task cred may be updated | |
+ */ | |
+static inline void aa_end_current_label(struct aa_label *label) | |
+{ | |
+ if (label != aa_current_raw_label()) | |
+ aa_put_label(label); | |
+} | |
+ | |
+/** | |
+ * aa_begin_current_label - find the current tasks confining label and update it | |
+ * @update: whether the current label can be updated | |
+ * | |
+ * Returns: up to date confining label or the ns unconfined label (NOT NULL) | |
+ * | |
+ * If @update is true this fn will update the tasks cred structure if the | |
+ * label has been replaced. Not safe to call inside locks | |
+ * else | |
+ * just return the up to date label | |
+ * | |
+ * The returned reference must be put with aa_end_current_label() | |
+ * This must NOT be used if the task cred could be updated within the | |
+ * critical section between aa_begin_current_label() .. aa_end_current_label() | |
+ */ | |
+static inline struct aa_label *aa_begin_current_label(bool update) | |
+{ | |
+ struct aa_label *label = aa_current_raw_label(); | |
+ | |
+ if (label_is_stale(label)) { | |
+ label = aa_get_newest_label(label); | |
+ if (update && aa_replace_current_label(label) == 0) | |
+ /* task cred will keep the reference */ | |
+ aa_put_label(label); | |
} | |
- return cxt->profile; | |
+ return label; | |
+} | |
+ | |
+#define NO_UPDATE false | |
+#define DO_UPDATE true | |
+ | |
+static inline struct aa_ns *aa_get_current_ns(void) | |
+{ | |
+ struct aa_label *label = aa_begin_current_label(NO_UPDATE); | |
+ struct aa_ns *ns = aa_get_ns(labels_ns(label)); | |
+ aa_end_current_label(label); | |
+ | |
+ return ns; | |
} | |
/** | |
- * aa_clear_task_cxt_trans - clear transition tracking info from the cxt | |
- * @cxt: task context to clear (NOT NULL) | |
+ * aa_clear_task_ctx_trans - clear transition tracking info from the ctx | |
+ * @ctx: task context to clear (NOT NULL) | |
*/ | |
-static inline void aa_clear_task_cxt_trans(struct aa_task_cxt *cxt) | |
+static inline void aa_clear_task_ctx_trans(struct aa_task_ctx *ctx) | |
{ | |
- aa_put_profile(cxt->previous); | |
- aa_put_profile(cxt->onexec); | |
- cxt->previous = NULL; | |
- cxt->onexec = NULL; | |
- cxt->token = 0; | |
+ aa_put_label(ctx->previous); | |
+ aa_put_label(ctx->onexec); | |
+ ctx->previous = NULL; | |
+ ctx->onexec = NULL; | |
+ ctx->token = 0; | |
} | |
#endif /* __AA_CONTEXT_H */ | |
diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h | |
index de04464..89cfa75 100644 | |
--- a/security/apparmor/include/domain.h | |
+++ b/security/apparmor/include/domain.h | |
@@ -15,6 +15,8 @@ | |
#include <linux/binfmts.h> | |
#include <linux/types.h> | |
+#include "include/label.h" | |
+ | |
#ifndef __AA_DOMAIN_H | |
#define __AA_DOMAIN_H | |
@@ -23,6 +25,9 @@ struct aa_domain { | |
char **table; | |
}; | |
+struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, | |
+ const char **name); | |
+ | |
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); | |
@@ -30,7 +35,7 @@ struct aa_domain { | |
void aa_free_domain_entries(struct aa_domain *domain); | |
int aa_change_hat(const char *hats[], int count, u64 token, bool permtest); | |
-int aa_change_profile(const char *ns_name, const char *name, bool onexec, | |
- bool permtest); | |
+int aa_change_profile(const char *fqname, bool onexec, bool permtest, | |
+ bool stack); | |
#endif /* __AA_DOMAIN_H */ | |
diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h | |
index 4803c97..0fb7e00 100644 | |
--- a/security/apparmor/include/file.h | |
+++ b/security/apparmor/include/file.h | |
@@ -15,38 +15,75 @@ | |
#ifndef __AA_FILE_H | |
#define __AA_FILE_H | |
+#include <linux/spinlock.h> | |
+ | |
#include "domain.h" | |
#include "match.h" | |
+#include "label.h" | |
+#include "perms.h" | |
struct aa_profile; | |
struct path; | |
-/* | |
- * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags | |
- * for profile permissions | |
- */ | |
-#define AA_MAY_CREATE 0x0010 | |
-#define AA_MAY_DELETE 0x0020 | |
-#define AA_MAY_META_WRITE 0x0040 | |
-#define AA_MAY_META_READ 0x0080 | |
- | |
-#define AA_MAY_CHMOD 0x0100 | |
-#define AA_MAY_CHOWN 0x0200 | |
-#define AA_MAY_LOCK 0x0400 | |
-#define AA_EXEC_MMAP 0x0800 | |
- | |
-#define AA_MAY_LINK 0x1000 | |
-#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ | |
-#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */ | |
-#define AA_MAY_CHANGE_PROFILE 0x80000000 | |
-#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */ | |
+#define mask_mode_t(X) (X & (MAY_EXEC | MAY_WRITE | MAY_READ | MAY_APPEND)) | |
#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\ | |
AA_MAY_CREATE | AA_MAY_DELETE | \ | |
- AA_MAY_META_READ | AA_MAY_META_WRITE | \ | |
+ AA_MAY_GETATTR | AA_MAY_SETATTR | \ | |
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ | |
AA_EXEC_MMAP | AA_MAY_LINK) | |
+#define file_ctx(X) ((struct aa_file_ctx *)(X)->f_security) | |
+ | |
+/* struct aa_file_ctx - the AppArmor context the file was opened in | |
+ * @lock: lock to update the ctx | |
+ * @label: label currently cached on the ctx | |
+ * @perms: the permission the file was opened with | |
+ */ | |
+struct aa_file_ctx { | |
+ spinlock_t lock; | |
+ struct aa_label __rcu *label; | |
+ u32 allow; | |
+}; | |
+ | |
+/** | |
+ * aa_alloc_file_ctx - allocate file_ctx | |
+ * @label: initial label of task creating the file | |
+ * @gfp: gfp flags for allocation | |
+ * | |
+ * Returns: file_ctx or NULL on failure | |
+ */ | |
+static inline struct aa_file_ctx *aa_alloc_file_ctx(struct aa_label *label, gfp_t gfp) | |
+{ | |
+ struct aa_file_ctx *ctx; | |
+ | |
+ ctx = kzalloc(sizeof(struct aa_file_ctx), gfp); | |
+ if (ctx) { | |
+ spin_lock_init(&ctx->lock); | |
+ rcu_assign_pointer(ctx->label, aa_get_label(label)); | |
+ } | |
+ return ctx; | |
+} | |
+ | |
+/** | |
+ * aa_free_file_ctx - free a file_ctx | |
+ * @ctx: file_ctx to free (MAYBE_NULL) | |
+ */ | |
+static inline void aa_free_file_ctx(struct aa_file_ctx *ctx) | |
+{ | |
+ if (ctx) { | |
+ aa_put_label(rcu_access_pointer(ctx->label)); | |
+ kzfree(ctx); | |
+ } | |
+} | |
+ | |
+static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx) | |
+{ | |
+ return aa_get_label_rcu(&ctx->label); | |
+} | |
+ | |
+#define inode_ctx(X) (X)->i_security | |
+ | |
/* | |
* The xindex is broken into 3 parts | |
* - index - an index into either the exec name table or the variable table | |
@@ -75,25 +112,6 @@ struct path_cond { | |
umode_t mode; | |
}; | |
-/* struct file_perms - file permission | |
- * @allow: mask of permissions that are allowed | |
- * @audit: mask of permissions to force an audit message for | |
- * @quiet: mask of permissions to quiet audit messages for | |
- * @kill: mask of permissions that when matched will kill the task | |
- * @xindex: exec transition index if @allow contains MAY_EXEC | |
- * | |
- * The @audit and @queit mask should be mutually exclusive. | |
- */ | |
-struct file_perms { | |
- u32 allow; | |
- u32 audit; | |
- u32 quiet; | |
- u32 kill; | |
- u16 xindex; | |
-}; | |
- | |
-extern struct file_perms nullperms; | |
- | |
#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill) | |
/* FIXME: split perms from dfa and match this to description | |
@@ -144,9 +162,9 @@ static inline u16 dfa_map_xindex(u16 mask) | |
#define dfa_other_xindex(dfa, state) \ | |
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff) | |
-int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, | |
- gfp_t gfp, int op, u32 request, const char *name, | |
- const char *target, kuid_t ouid, const char *info, int error); | |
+int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, | |
+ const char *op, u32 request, const char *name, const char *target, struct aa_label *tlabel, | |
+ kuid_t ouid, const char *info, int error); | |
/** | |
* struct aa_file_rules - components used for file rule permissions | |
@@ -167,19 +185,27 @@ struct aa_file_rules { | |
/* TODO: add delegate table */ | |
}; | |
+struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, | |
+ struct path_cond *cond); | |
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, | |
const char *name, struct path_cond *cond, | |
- struct file_perms *perms); | |
+ struct aa_perms *perms); | |
-int aa_path_perm(int op, struct aa_profile *profile, const struct path *path, | |
- int flags, u32 request, struct path_cond *cond); | |
+int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, | |
+ u32 request, struct path_cond *cond, int flags, | |
+ struct aa_perms *perms); | |
+int aa_path_perm(const char *op, struct aa_label *label, | |
+ const struct path *path, int flags, u32 request, | |
+ struct path_cond *cond); | |
-int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, | |
+int aa_path_link(struct aa_label *label, struct dentry *old_dentry, | |
const struct path *new_dir, struct dentry *new_dentry); | |
-int aa_file_perm(int op, struct aa_profile *profile, struct file *file, | |
+int aa_file_perm(const char *op, struct aa_label *label, struct file *file, | |
u32 request); | |
+void aa_inherit_files(const struct cred *cred, struct files_struct *files); | |
+ | |
static inline void aa_free_file_rules(struct aa_file_rules *rules) | |
{ | |
aa_put_dfa(rules->dfa); | |
diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h | |
index 288ca76..a786685 100644 | |
--- a/security/apparmor/include/ipc.h | |
+++ b/security/apparmor/include/ipc.h | |
@@ -4,7 +4,7 @@ | |
* This file contains AppArmor ipc mediation function definitions. | |
* | |
* Copyright (C) 1998-2008 Novell/SUSE | |
- * Copyright 2009-2010 Canonical Ltd. | |
+ * Copyright 2009-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 | |
@@ -19,10 +19,22 @@ | |
struct aa_profile; | |
-int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, | |
- unsigned int mode); | |
+#define AA_PTRACE_TRACE MAY_WRITE | |
+#define AA_PTRACE_READ MAY_READ | |
+#define AA_MAY_BE_TRACED AA_MAY_APPEND | |
+#define AA_MAY_BE_READ AA_MAY_CREATE | |
+#define PTRACE_PERM_SHIFT 2 | |
-int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, | |
- unsigned int mode); | |
+#define AA_PTRACE_PERM_MASK (AA_PTRACE_READ | AA_PTRACE_TRACE | \ | |
+ AA_MAY_BE_READ | AA_MAY_BE_TRACED) | |
+#define AA_SIGNAL_PERM_MASK (MAY_READ | MAY_WRITE) | |
+ | |
+#define AA_FS_SIG_MASK "hup int quit ill trap abrt bus fpe kill usr1 " \ | |
+ "segv usr2 pipe alrm term stkflt chld cont stop stp ttin ttou urg " \ | |
+ "xcpu xfsz vtalrm prof winch io pwr sys emt lost" | |
+ | |
+int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee, | |
+ u32 request); | |
+int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig); | |
#endif /* __AA_IPC_H */ | |
diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h | |
new file mode 100644 | |
index 0000000..829d6e4 | |
--- /dev/null | |
+++ b/security/apparmor/include/label.h | |
@@ -0,0 +1,502 @@ | |
+/* | |
+ * AppArmor security module | |
+ * | |
+ * This file contains AppArmor label definitions | |
+ * | |
+ * Copyright 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. | |
+ */ | |
+ | |
+#ifndef __AA_LABEL_H | |
+#define __AA_LABEL_H | |
+ | |
+#include <linux/atomic.h> | |
+#include <linux/audit.h> | |
+#include <linux/rbtree.h> | |
+#include <linux/rcupdate.h> | |
+ | |
+#include "apparmor.h" | |
+#include "lib.h" | |
+ | |
+struct aa_ns; | |
+ | |
+#define LOCAL_VEC_ENTRIES 8 | |
+#define DEFINE_VEC(T, V) \ | |
+ struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES]; \ | |
+ struct aa_ ## T **(V) | |
+ | |
+#define vec_setup(T, V, N, GFP) \ | |
+({ \ | |
+ if ((N) <= LOCAL_VEC_ENTRIES) { \ | |
+ typeof(N) i; \ | |
+ (V) = (_ ## V ## _localtmp); \ | |
+ for (i = 0; i < (N); i++) \ | |
+ (V)[i] = NULL; \ | |
+ } else \ | |
+ (V) = kzalloc(sizeof(struct aa_ ## T *) * (N), (GFP)); \ | |
+ (V) ? 0 : -ENOMEM; \ | |
+}) | |
+ | |
+#define vec_cleanup(T, V, N) \ | |
+do { \ | |
+ int i; \ | |
+ for (i = 0; i < (N); i++) { \ | |
+ if (!IS_ERR_OR_NULL((V)[i])) \ | |
+ aa_put_ ## T ((V)[i]); \ | |
+ } \ | |
+ if ((V) != _ ## V ## _localtmp) \ | |
+ kfree(V); \ | |
+} while (0) | |
+ | |
+#define vec_last(VEC, SIZE) ((VEC)[(SIZE) - 1]) | |
+#define vec_ns(VEC, SIZE) (vec_last((VEC), (SIZE))->ns) | |
+#define vec_labelset(VEC, SIZE) (&vec_ns((VEC), (SIZE))->labels) | |
+#define cleanup_domain_vec(V, L) cleanup_label_vec((V), (L)->size) | |
+ | |
+struct aa_profile; | |
+#define VEC_FLAG_TERMINATE 1 | |
+int aa_vec_unique(struct aa_profile **vec, int n, int flags); | |
+struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len, | |
+ gfp_t gfp); | |
+#define aa_sort_and_merge_vec(N, V) \ | |
+ aa_sort_and_merge_profiles((N), (struct aa_profile **)(V)) | |
+ | |
+struct labelset_stats { | |
+ atomic_t sread; | |
+ atomic_t fread; | |
+ atomic_t msread; | |
+ atomic_t mfread; | |
+ | |
+ atomic_t insert; | |
+ atomic_t existing; | |
+ atomic_t minsert; | |
+ atomic_t mexisting; | |
+ | |
+ atomic_t stale; /* outstanding stale */ | |
+}; | |
+ | |
+struct label_stats { | |
+ struct labelset_stats set_stats; | |
+ | |
+ atomic_t allocated; | |
+ atomic_t failed; | |
+ atomic_t freed; | |
+ | |
+ atomic_t printk_name_alloc; | |
+ atomic_t printk_name_fail; | |
+ atomic_t seq_print_name_alloc; | |
+ atomic_t seq_print_name_fail; | |
+ atomic_t audit_name_alloc; | |
+ atomic_t audit_name_fail; | |
+}; | |
+ | |
+ | |
+#ifdef AA_LABEL_STATS | |
+#define labelstats_inc(X) atomic_inc(stats.(X)) | |
+#define labelstats_dec(X) atomic_dec(stats.(X)) | |
+#define labelsetstats_inc(LS, X) \ | |
+ do { \ | |
+ labelstats_inc(set_stats.##X); \ | |
+ atomic_inc((LS)->stats.(X)); \ | |
+ } while (0) | |
+#define labelsetstats_dec(LS, X) \ | |
+ do { \ | |
+ labelstats_dec(set_stats.##X); \ | |
+ atomic_dec((LS)->stats.(X)); \ | |
+ } while (0) | |
+#else | |
+#define labelstats_inc(X) | |
+#define labelstats_dec(X) | |
+#define labelsetstats_inc(LS, X) | |
+#define labelsetstats_dec(LS, X) | |
+#endif | |
+#define labelstats_init(X) | |
+ | |
+/* struct aa_labelset - set of labels for a namespace | |
+ * | |
+ * Labels are reference counted; aa_labelset does not contribute to label | |
+ * reference counts. Once a label's last refcount is put it is removed from | |
+ * the set. | |
+ */ | |
+struct aa_labelset { | |
+ rwlock_t lock; | |
+ | |
+ struct rb_root root; | |
+ | |
+ /* stats */ | |
+#ifdef APPARMOR_LABEL_STATS | |
+ struct labelset_stats stats; | |
+#endif | |
+ | |
+}; | |
+ | |
+#define __labelset_for_each(LS, N) \ | |
+ for((N) = rb_first(&(LS)->root); (N); (N) = rb_next(N)) | |
+ | |
+void aa_labelset_destroy(struct aa_labelset *ls); | |
+void aa_labelset_init(struct aa_labelset *ls); | |
+ | |
+ | |
+enum label_flags { | |
+ FLAG_HAT = 1, /* profile is a hat */ | |
+ FLAG_UNCONFINED = 2, /* label unconfined only if all | |
+ * constituant profiles unconfined */ | |
+ FLAG_NULL = 4, /* profile is null learning profile */ | |
+ FLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */ | |
+ FLAG_IMMUTIBLE = 0x10, /* don't allow changes/replacement */ | |
+ FLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */ | |
+ FLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */ | |
+ FLAG_NS_COUNT = 0x80, /* carries NS ref count */ | |
+ FLAG_IN_TREE = 0x100, /* label is in tree */ | |
+ FLAG_PROFILE = 0x200, /* label is a profile */ | |
+ FLAG_EXPLICIT = 0x400, /* explict static label */ | |
+ FLAG_STALE = 0x800, /* replaced/removed */ | |
+ FLAG_RENAMED = 0x1000, /* label has renaming in it */ | |
+ FLAG_REVOKED = 0x2000, /* label has revocation in it */ | |
+ | |
+ /* These flags must correspond with PATH_flags */ | |
+ /* TODO: add new path flags */ | |
+}; | |
+ | |
+struct aa_label; | |
+struct aa_proxy { | |
+ struct kref count; | |
+ struct aa_label __rcu *label; | |
+}; | |
+ | |
+struct label_it { | |
+ int i, j; | |
+}; | |
+ | |
+/* struct aa_label - lazy labeling struct | |
+ * @count: ref count of active users | |
+ * @node: rbtree position | |
+ * @rcu: rcu callback struct | |
+ * @proxy: is set to the label that replaced this label | |
+ * @hname: text representation of the label (MAYBE_NULL) | |
+ * @flags: stale and other flags - values may change under label set lock | |
+ * @sid: sid that references this label | |
+ * @size: number of entries in @ent[] | |
+ * @ent: set of profiles for label, actual size determined by @size | |
+ */ | |
+struct aa_label { | |
+ struct kref count; | |
+ struct rb_node node; | |
+ struct rcu_head rcu; | |
+ struct aa_proxy *proxy; | |
+ __counted char *hname; | |
+ long flags; | |
+ u32 sid; | |
+ int size; | |
+ struct aa_profile *vec[]; | |
+}; | |
+ | |
+#define last_error(E, FN) \ | |
+do { \ | |
+ int __subE = (FN); \ | |
+ if (__subE) \ | |
+ (E) = __subE; \ | |
+} while (0) | |
+ | |
+#define label_isprofile(X) ((X)->flags & FLAG_PROFILE) | |
+#define label_unconfined(X) ((X)->flags & FLAG_UNCONFINED) | |
+#define unconfined(X) label_unconfined(X) | |
+#define label_is_stale(X) ((X)->flags & FLAG_STALE) | |
+#define __label_make_stale(X) do { \ | |
+ labelsetstats_inc(labels_set(X), stale); \ | |
+ ((X)->flags |= FLAG_STALE); \ | |
+} while (0) | |
+#define labels_ns(X) (vec_ns(&((X)->vec[0]), (X)->size)) | |
+#define labels_set(X) (&labels_ns(X)->labels) | |
+#define labels_profile(X) ((X)->vec[(X)->size - 1]) | |
+ | |
+ | |
+int aa_label_next_confined(struct aa_label *l, int i); | |
+ | |
+/* for each profile in a label */ | |
+#define label_for_each_init(I) ((I).i = 0); | |
+#define label_for_each_next(I) (++((I).i)) | |
+#define label_for_each_curr(I, L) ({ (L)->vec[(I).i] ; }) | |
+#define label_for_each(I, L, P) \ | |
+ for ((I).i = 0; ((P) = (L)->vec[(I).i]); ++((I).i)) | |
+ | |
+/* assumes break/goto ended label_for_each */ | |
+#define label_for_each_cont(I, L, P) \ | |
+ for (++((I).i); ((P) = (L)->vec[(I).i]); ++((I).i)) | |
+ | |
+#define next_comb(I, L1, L2) \ | |
+do { \ | |
+ (I).j++; \ | |
+ if ((I).j >= (L2)->size) { \ | |
+ (I).i++; \ | |
+ (I).j = 0; \ | |
+ } \ | |
+} while (0) | |
+ | |
+/* TODO: label_for_each_ns_comb */ | |
+ | |
+/* for each combination of P1 in L1, and P2 in L2 */ | |
+#define label_for_each_comb(I, L1, L2, P1, P2) \ | |
+for ((I).i = (I).j = 0; \ | |
+ ((P1) = (L1)->vec[(I).i]) && ((P2) = (L2)->vec[(I).j]); \ | |
+ (I) = next_comb(I, L1, L2)) | |
+ | |
+#define fn_for_each_comb(L1, L2, P1, P2, FN) \ | |
+({ \ | |
+ struct label_it i; \ | |
+ int __E = 0; \ | |
+ label_for_each_comb(i, (L1), (L2), (P1), (P2)) { \ | |
+ last_error(__E, (FN)); \ | |
+ } \ | |
+ __E; \ | |
+}) | |
+ | |
+/* internal cross check */ | |
+//fn_for_each_comb(L1, L2, P1, P2, xcheck(...)); | |
+ | |
+/* external cross check */ | |
+// xcheck(fn_for_each_comb(L1, L2, ...), | |
+// fn_for_each_comb(L2, L1, ...)); | |
+ | |
+/* for each profile that is enforcing confinement in a label */ | |
+#define label_for_each_confined(I, L, P) \ | |
+ for ((I).i = aa_label_next_confined((L), 0); \ | |
+ ((P) = (L)->vec[(I).i]); \ | |
+ (I).i = aa_label_next_confined((L), (I).i + 1)) | |
+ | |
+#define label_for_each_in_merge(I, A, B, P) \ | |
+ for ((I).i = (I).j = 0; \ | |
+ ((P) = aa_label_next_in_merge(&(I), (A), (B))); \ | |
+ ) | |
+ | |
+#define label_for_each_not_in_set(I, SET, SUB, P) \ | |
+ for ((I).i = (I).j = 0; \ | |
+ ((P) = __aa_label_next_not_in_set(&(I), (SET), (SUB))); \ | |
+ ) | |
+ | |
+#define next_in_ns(i, NS, L) \ | |
+({ \ | |
+ typeof(i) ___i = (i); \ | |
+ while ((L)->vec[___i] && (L)->vec[___i]->ns != (NS)) \ | |
+ (___i)++; \ | |
+ (___i); \ | |
+}) | |
+ | |
+#define label_for_each_in_ns(I, NS, L, P) \ | |
+ for ((I).i = next_in_ns(0, (NS), (L)); \ | |
+ ((P) = (L)->vec[(I).i]); \ | |
+ (I).i = next_in_ns((I).i + 1, (NS), (L))) | |
+ | |
+#define fn_for_each_in_ns(L, P, FN) \ | |
+({ \ | |
+ struct label_it __i; \ | |
+ struct aa_ns *__ns = labels_ns(L); \ | |
+ int __E = 0; \ | |
+ label_for_each_in_ns(__i, __ns, (L), (P)) { \ | |
+ last_error(__E, (FN)); \ | |
+ } \ | |
+ __E; \ | |
+}) | |
+ | |
+ | |
+#define fn_for_each_XXX(L, P, FN, ...) \ | |
+({ \ | |
+ struct label_it i; \ | |
+ int __E = 0; \ | |
+ label_for_each ## __VA_ARGS__ (i, (L), (P)) { \ | |
+ last_error(__E, (FN)); \ | |
+ } \ | |
+ __E; \ | |
+}) | |
+ | |
+#define fn_for_each(L, P, FN) fn_for_each_XXX(L, P, FN) | |
+#define fn_for_each_confined(L, P, FN) fn_for_each_XXX(L, P, FN, _confined) | |
+ | |
+#define fn_for_each2_XXX(L1, L2, P, FN, ...) \ | |
+({ \ | |
+ struct label_it i; \ | |
+ int __E = 0; \ | |
+ label_for_each ## __VA_ARGS__(i, (L1), (L2), (P)) { \ | |
+ last_error(__E, (FN)); \ | |
+ } \ | |
+ __E; \ | |
+}) | |
+ | |
+#define fn_for_each_in_merge(L1, L2, P, FN) \ | |
+ fn_for_each2_XXX((L1), (L2), P, FN, _in_merge) | |
+#define fn_for_each_not_in_set(L1, L2, P, FN) \ | |
+ fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set) | |
+ | |
+#define LABEL_MEDIATES(L, C) \ | |
+({ \ | |
+ struct aa_profile *profile; \ | |
+ struct label_it i; \ | |
+ int ret = 0; \ | |
+ label_for_each(i, (L), profile) { \ | |
+ if (PROFILE_MEDIATES(profile, (C))) { \ | |
+ ret = 1; \ | |
+ break; \ | |
+ } \ | |
+ } \ | |
+ ret; \ | |
+}) | |
+ | |
+ | |
+void aa_labelset_destroy(struct aa_labelset *ls); | |
+void aa_labelset_init(struct aa_labelset *ls); | |
+void __aa_labelset_update_subtree(struct aa_ns *ns); | |
+ | |
+void aa_label_free(struct aa_label *label); | |
+void aa_label_kref(struct kref *kref); | |
+bool aa_label_init(struct aa_label *label, int size); | |
+struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp); | |
+ | |
+bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub); | |
+struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, | |
+ struct aa_label *set, | |
+ struct aa_label *sub); | |
+bool aa_label_remove(struct aa_label *label); | |
+struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l); | |
+bool aa_label_replace(struct aa_label *old, struct aa_label *new); | |
+bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old, | |
+ struct aa_label *new); | |
+ | |
+struct aa_label *aa_label_find(struct aa_label *l); | |
+ | |
+struct aa_profile *aa_label_next_in_merge(struct label_it *I, | |
+ struct aa_label *a, | |
+ struct aa_label *b); | |
+struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b); | |
+struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, | |
+ gfp_t gfp); | |
+ | |
+ | |
+bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp); | |
+ | |
+#define FLAGS_NONE 0 | |
+#define FLAG_SHOW_MODE 1 | |
+#define FLAG_VIEW_SUBNS 2 | |
+#define FLAG_HIDDEN_UNCONFINED 4 | |
+int aa_profile_snxprint(char *str, size_t size, struct aa_ns *ns, | |
+ struct aa_profile *profile, int flags); | |
+int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns, | |
+ struct aa_label *label, int flags); | |
+int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label, | |
+ int flags, gfp_t gfp); | |
+int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns, | |
+ struct aa_label *label, int flags, gfp_t gfp); | |
+void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, | |
+ struct aa_label *label, int flags, gfp_t gfp); | |
+void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, | |
+ struct aa_label *label, int flags, gfp_t gfp); | |
+void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, | |
+ gfp_t gfp); | |
+void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp); | |
+void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp); | |
+void aa_label_printk(struct aa_label *label, gfp_t gfp); | |
+ | |
+struct aa_label *aa_label_parse(struct aa_label *base, const char *str, | |
+ gfp_t gfp, bool create, bool force_stack); | |
+ | |
+ | |
+struct aa_perms; | |
+int aa_label_match(struct aa_profile *profile, struct aa_label *label, | |
+ unsigned int state, bool subns, u32 request, | |
+ struct aa_perms *perms); | |
+ | |
+ | |
+static inline struct aa_label *aa_get_label(struct aa_label *l) | |
+{ | |
+ if (l) | |
+ kref_get(&(l->count)); | |
+ | |
+ return l; | |
+} | |
+ | |
+static inline struct aa_label *aa_get_label_not0(struct aa_label *l) | |
+{ | |
+ if (l && kref_get_not0(&l->count)) | |
+ return l; | |
+ | |
+ return NULL; | |
+} | |
+ | |
+/** | |
+ * aa_get_label_rcu - increment refcount on a label that can be replaced | |
+ * @l: pointer to label that can be replaced (NOT NULL) | |
+ * | |
+ * Returns: pointer to a refcounted label. | |
+ * else NULL if no label | |
+ */ | |
+static inline struct aa_label *aa_get_label_rcu(struct aa_label __rcu **l) | |
+{ | |
+ struct aa_label *c; | |
+ | |
+ rcu_read_lock(); | |
+ do { | |
+ c = rcu_dereference(*l); | |
+ } while (c && !kref_get_not0(&c->count)); | |
+ rcu_read_unlock(); | |
+ | |
+ return c; | |
+} | |
+ | |
+/** | |
+ * aa_get_newest_label - find the newest version of @l | |
+ * @l: the label to check for newer versions of | |
+ * | |
+ * Returns: refcounted newest version of @l taking into account | |
+ * replacement, renames and removals | |
+ * return @l. | |
+ */ | |
+static inline struct aa_label *aa_get_newest_label(struct aa_label *l) | |
+{ | |
+ if (!l) | |
+ return NULL; | |
+ | |
+ if (label_is_stale(l)) { | |
+ struct aa_label *tmp; | |
+ AA_BUG(!l->proxy); | |
+ AA_BUG(!l->proxy->label); | |
+ /* BUG: only way this can happen is @l ref count and its | |
+ * replacement count have gone to 0 and are on their way | |
+ * to destruction. ie. we have a refcounting error | |
+ */ | |
+ AA_BUG(!(tmp = aa_get_label_rcu(&l->proxy->label))); | |
+ return tmp; | |
+ } | |
+ | |
+ return aa_get_label(l); | |
+} | |
+ | |
+static inline void aa_put_label(struct aa_label *l) | |
+{ | |
+ if (l) | |
+ kref_put(&l->count, aa_label_kref); | |
+} | |
+ | |
+ | |
+struct aa_proxy *aa_alloc_proxy(struct aa_label *l, gfp_t gfp); | |
+void aa_proxy_kref(struct kref *kref); | |
+ | |
+static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *proxy) | |
+{ | |
+ if (proxy) | |
+ kref_get(&(proxy->count)); | |
+ | |
+ return proxy; | |
+} | |
+ | |
+static inline void aa_put_proxy(struct aa_proxy *proxy) | |
+{ | |
+ if (proxy) | |
+ kref_put(&proxy->count, aa_proxy_kref); | |
+} | |
+ | |
+void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new); | |
+ | |
+#endif /* __AA_LABEL_H */ | |
diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h | |
new file mode 100644 | |
index 0000000..954e22a | |
--- /dev/null | |
+++ b/security/apparmor/include/lib.h | |
@@ -0,0 +1,317 @@ | |
+/* | |
+ * AppArmor security module | |
+ * | |
+ * This file contains AppArmor lib definitions | |
+ * | |
+ * 2016 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_LIB_H | |
+#define __AA_LIB_H | |
+ | |
+#include <linux/slab.h> | |
+#include <linux/fs.h> | |
+ | |
+#include "match.h" | |
+ | |
+/* Provide our own test for whether a write lock is held for asserts | |
+ * this is because on none SMP systems write_can_lock will always | |
+ * resolve to true, which is what you want for code making decisions | |
+ * based on it, but wrong for asserts checking that the lock is held | |
+ */ | |
+#ifdef CONFIG_SMP | |
+#define write_is_locked(X) !write_can_lock(X) | |
+#else | |
+#define write_is_locked(X) (1) | |
+#endif /* CONFIG_SMP */ | |
+ | |
+/* | |
+ * DEBUG remains global (no per profile flag) since it is mostly used in sysctl | |
+ * which is not related to profile accesses. | |
+ */ | |
+ | |
+#define DEBUG_ON (aa_g_debug && printk_ratelimit()) | |
+#define dbg_printk(__fmt, __args...) printk(KERN_DEBUG __fmt, ##__args) | |
+#define AA_DEBUG(fmt, args...) \ | |
+ do { \ | |
+ if (DEBUG_ON) \ | |
+ dbg_printk("AppArmor: " fmt, ##args); \ | |
+ } while (0) | |
+ | |
+#define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __FUNCTION__, #X) | |
+ | |
+#define AA_BUG(X, args...) AA_BUG_FMT((X), "" args ) | |
+#define AA_BUG_FMT(X, fmt, args...) \ | |
+ WARN((X), "AppArmor WARN %s: (" #X "): " fmt, __FUNCTION__ , ##args ) | |
+ | |
+#define AA_ERROR(fmt, args...) \ | |
+ do { \ | |
+ if (printk_ratelimit()) \ | |
+ printk(KERN_ERR "AppArmor: " fmt, ##args); \ | |
+ } while (0) | |
+ | |
+/* Flag indicating whether initialization completed */ | |
+extern int apparmor_initialized __initdata; | |
+ | |
+/* fn's in lib */ | |
+char *aa_split_fqname(char *args, char **ns_name); | |
+const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, | |
+ size_t *ns_len); | |
+void aa_info_message(const char *str); | |
+void *__aa_kvmalloc(size_t size, gfp_t flags); | |
+ | |
+static inline void *kvmalloc(size_t size) | |
+{ | |
+ return __aa_kvmalloc(size, 0); | |
+} | |
+ | |
+static inline void *kvzalloc(size_t size) | |
+{ | |
+ return __aa_kvmalloc(size, __GFP_ZERO); | |
+} | |
+ | |
+/* returns 0 if kref not incremented */ | |
+static inline int kref_get_not0(struct kref *kref) | |
+{ | |
+ return atomic_inc_not_zero(&kref->refcount); | |
+} | |
+ | |
+/** | |
+ * aa_strneq - compare null terminated @str to a non null terminated substring | |
+ * @str: a null terminated string | |
+ * @sub: a substring, not necessarily null terminated | |
+ * @len: length of @sub to compare | |
+ * | |
+ * The @str string must be full consumed for this to be considered a match | |
+ */ | |
+static inline bool aa_strneq(const char *str, const char *sub, int len) | |
+{ | |
+ return !strncmp(str, sub, len) && !str[len]; | |
+} | |
+ | |
+/** | |
+ * aa_dfa_null_transition - step to next state after null character | |
+ * @dfa: the dfa to match against | |
+ * @start: the state of the dfa to start matching in | |
+ * | |
+ * aa_dfa_null_transition transitions to the next state after a null | |
+ * character which is not used in standard matching and is only | |
+ * used to separate pairs. | |
+ */ | |
+static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, | |
+ unsigned int start) | |
+{ | |
+ /* the null transition only needs the string's null terminator byte */ | |
+ return aa_dfa_next(dfa, start, 0); | |
+} | |
+ | |
+static inline bool path_mediated_fs(struct dentry *dentry) | |
+{ | |
+ return !(dentry->d_sb->s_flags & MS_NOUSER); | |
+} | |
+ | |
+ | |
+struct counted_str { | |
+ struct kref count; | |
+ char name[]; | |
+}; | |
+ | |
+#define str_to_counted(str) \ | |
+ ((struct counted_str *)(str - offsetof(struct counted_str,name))) | |
+ | |
+#define __counted /* atm just a notation */ | |
+ | |
+void aa_str_kref(struct kref *kref); | |
+char *aa_str_alloc(int size, gfp_t gfp); | |
+ | |
+ | |
+static inline __counted char *aa_get_str(__counted char *str) | |
+{ | |
+ if (str) | |
+ kref_get(&(str_to_counted(str)->count)); | |
+ | |
+ return str; | |
+} | |
+ | |
+static inline void aa_put_str(__counted char *str) | |
+{ | |
+ if (str) | |
+ kref_put(&str_to_counted(str)->count, aa_str_kref); | |
+} | |
+ | |
+const char *aa_imode_name(umode_t mode); | |
+ | |
+ | |
+/* struct aa_policy - common part of both namespaces and profiles | |
+ * @name: name of the object | |
+ * @hname - The hierarchical name, NOTE: is .name of struct counted_str | |
+ * @list: list policy object is on | |
+ * @profiles: head of the profiles list contained in the object | |
+ */ | |
+struct aa_policy { | |
+ const char *name; | |
+ __counted char *hname; | |
+ struct list_head list; | |
+ struct list_head profiles; | |
+}; | |
+ | |
+#define aa_peer_name(peer) (peer)->base.hname | |
+ | |
+/** | |
+ * basename - find the last component of an hname | |
+ * @name: hname to find the base profile name component of (NOT NULL) | |
+ * | |
+ * Returns: the tail (base profile name) name component of an hname | |
+ */ | |
+static inline const char *basename(const char *hname) | |
+{ | |
+ char *split; | |
+ hname = strim((char *)hname); | |
+ for (split = strstr(hname, "//"); split; split = strstr(hname, "//")) | |
+ hname = split + 2; | |
+ | |
+ return hname; | |
+} | |
+ | |
+/** | |
+ * __policy_find - find a policy by @name on a policy list | |
+ * @head: list to search (NOT NULL) | |
+ * @name: name to search for (NOT NULL) | |
+ * | |
+ * Requires: rcu_read_lock be held | |
+ * | |
+ * Returns: unrefcounted policy that match @name or NULL if not found | |
+ */ | |
+static inline struct aa_policy *__policy_find(struct list_head *head, | |
+ const char *name) | |
+{ | |
+ struct aa_policy *policy; | |
+ | |
+ list_for_each_entry_rcu(policy, head, list) { | |
+ if (!strcmp(policy->name, name)) | |
+ return policy; | |
+ } | |
+ return NULL; | |
+} | |
+ | |
+/** | |
+ * __policy_strn_find - find a policy that's name matches @len chars of @str | |
+ * @head: list to search (NOT NULL) | |
+ * @str: string to search for (NOT NULL) | |
+ * @len: length of match required | |
+ * | |
+ * Requires: rcu_read_lock be held | |
+ * | |
+ * Returns: unrefcounted policy that match @str or NULL if not found | |
+ * | |
+ * if @len == strlen(@strlen) then this is equiv to __policy_find | |
+ * other wise it allows searching for policy by a partial match of name | |
+ */ | |
+static inline struct aa_policy *__policy_strn_find(struct list_head *head, | |
+ const char *str, int len) | |
+{ | |
+ struct aa_policy *policy; | |
+ | |
+ list_for_each_entry_rcu(policy, head, list) { | |
+ if (aa_strneq(policy->name, str, len)) | |
+ return policy; | |
+ } | |
+ | |
+ return NULL; | |
+} | |
+ | |
+bool aa_policy_init(struct aa_policy *policy, const char *prefix, | |
+ const char *name, gfp_t gfp); | |
+void aa_policy_destroy(struct aa_policy *policy); | |
+ | |
+ | |
+/* | |
+ * fn_label_build - abstract out the build of a label transition | |
+ * @L: label the transition is being computed for | |
+ * @P: profile parameter derived from L by this macro, can be passed to FN | |
+ * @GFP: memory allocation type to use | |
+ * @FN: fn to call for each profile transition. @P is set to the profile | |
+ * | |
+ * Returns: new label on success | |
+ * ERR_PTR if build @FN fails | |
+ * NULL if label_build fails due to low memory conditions | |
+ * | |
+ * @FN must return a label or ERR_PTR on failure. NULL is not allowed | |
+ */ | |
+#define fn_label_build(L, P, GFP, FN) \ | |
+({ \ | |
+ __label__ __cleanup, __done; \ | |
+ struct aa_label *__new_; \ | |
+ \ | |
+ if ((L)->size > 1) { \ | |
+ /* TODO: add cache of transitions already done */ \ | |
+ struct label_it __i; \ | |
+ int __j, __k, __count; \ | |
+ DEFINE_VEC(label, __lvec); \ | |
+ DEFINE_VEC(profile, __pvec); \ | |
+ if (vec_setup(label, __lvec, (L)->size, (GFP))) { \ | |
+ __new_ = NULL; \ | |
+ goto __done; \ | |
+ } \ | |
+ __j = 0; \ | |
+ label_for_each(__i, (L), (P)) { \ | |
+ __new_ = (FN); \ | |
+ AA_BUG(!__new_); \ | |
+ if (IS_ERR(__new_)) \ | |
+ goto __cleanup; \ | |
+ __lvec[__j++] = __new_; \ | |
+ } \ | |
+ for (__j = __count = 0; __j < (L)->size; __j++) \ | |
+ __count += __lvec[__j]->size; \ | |
+ if (!vec_setup(profile, __pvec, __count, (GFP))) { \ | |
+ for (__j = __k = 0; __j < (L)->size; __j++) { \ | |
+ label_for_each(__i, __lvec[__j], (P)) \ | |
+ __pvec[__k++] = aa_get_profile(P); \ | |
+ } \ | |
+ __count -= aa_vec_unique(__pvec, __count, 0); \ | |
+ if (__count > 1) { \ | |
+ __new_ = aa_vec_find_or_create_label(__pvec,\ | |
+ __count, (GFP)); \ | |
+ /* only fails if out of Mem */ \ | |
+ if (!__new_) \ | |
+ __new_ = NULL; \ | |
+ } else \ | |
+ __new_ = aa_get_label(&__pvec[0]->label); \ | |
+ vec_cleanup(profile, __pvec, __count); \ | |
+ } else \ | |
+ __new_ = NULL; \ | |
+ __cleanup: \ | |
+ vec_cleanup(label, __lvec, (L)->size); \ | |
+ } else { \ | |
+ (P) = labels_profile(L); \ | |
+ __new_ = (FN); \ | |
+ } \ | |
+__done: \ | |
+ if (!__new_) \ | |
+ AA_DEBUG("label build failed\n"); \ | |
+ (__new_); \ | |
+}) | |
+ | |
+ | |
+#define __fn_build_in_ns(NS, P, NS_FN, OTHER_FN) \ | |
+({ \ | |
+ struct aa_label *__new; \ | |
+ if ((P)->ns != (NS)) \ | |
+ __new = (OTHER_FN); \ | |
+ else \ | |
+ __new = (NS_FN); \ | |
+ (__new); \ | |
+}) | |
+ | |
+#define fn_label_build_in_ns(L, P, GFP, NS_FN, OTHER_FN) \ | |
+({ \ | |
+ fn_label_build((L), (P), (GFP), \ | |
+ __fn_build_in_ns(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \ | |
+}) | |
+ | |
+#endif /* __AA_LIB_H */ | |
diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h | |
index a1c04fe..a85bb3b 100644 | |
--- a/security/apparmor/include/match.h | |
+++ b/security/apparmor/include/match.h | |
@@ -100,6 +100,8 @@ struct aa_dfa { | |
struct table_header *tables[YYTD_ID_TSIZE]; | |
}; | |
+extern struct aa_dfa *nulldfa; | |
+ | |
#define byte_to_byte(X) (X) | |
#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \ | |
@@ -117,6 +119,9 @@ static inline size_t table_size(size_t len, size_t el_size) | |
return ALIGN(sizeof(struct table_header) + len * el_size, 8); | |
} | |
+int aa_setup_dfa_engine(void); | |
+void aa_teardown_dfa_engine(void); | |
+ | |
struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags); | |
unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, | |
const char *str, int len); | |
@@ -128,6 +133,21 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, | |
void aa_dfa_free_kref(struct kref *kref); | |
/** | |
+ * aa_get_dfa - increment refcount on dfa @p | |
+ * @dfa: dfa (MAYBE NULL) | |
+ * | |
+ * Returns: pointer to @dfa if @dfa is NULL will return NULL | |
+ * Requires: @dfa must be held with valid refcount when called | |
+ */ | |
+static inline struct aa_dfa *aa_get_dfa(struct aa_dfa *dfa) | |
+{ | |
+ if (dfa) | |
+ kref_get(&(dfa->count)); | |
+ | |
+ return dfa; | |
+} | |
+ | |
+/** | |
* aa_put_dfa - put a dfa refcount | |
* @dfa: dfa to put refcount (MAYBE NULL) | |
* | |
diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h | |
new file mode 100644 | |
index 0000000..6f61bd4 | |
--- /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 <linux/fs.h> | |
+#include <linux/path.h> | |
+ | |
+#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_MNT_CONT_MATCH 0x40 | |
+ | |
+#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) | |
+ | |
+int aa_remount(struct aa_label *label, const struct path *path, | |
+ unsigned long flags, void *data); | |
+ | |
+int aa_bind_mount(struct aa_label *label, const struct path *path, | |
+ const char *old_name, unsigned long flags); | |
+ | |
+ | |
+int aa_mount_change_type(struct aa_label *label, const struct path *path, | |
+ unsigned long flags); | |
+ | |
+int aa_move_mount(struct aa_label *label, const struct path *path, | |
+ const char *old_name); | |
+ | |
+int aa_new_mount(struct aa_label *label, const char *dev_name, | |
+ const struct path *path, const char *type, unsigned long flags, | |
+ void *data); | |
+ | |
+int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags); | |
+ | |
+int aa_pivotroot(struct aa_label *label, const struct path *old_path, | |
+ const struct path *new_path); | |
+ | |
+#endif /* __AA_MOUNT_H */ | |
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h | |
new file mode 100644 | |
index 0000000..b97e673 | |
--- /dev/null | |
+++ b/security/apparmor/include/net.h | |
@@ -0,0 +1,124 @@ | |
+/* | |
+ * AppArmor security module | |
+ * | |
+ * This file contains AppArmor network mediation definitions. | |
+ * | |
+ * Copyright (C) 1998-2008 Novell/SUSE | |
+ * Copyright 2009-2014 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 <net/sock.h> | |
+#include <linux/path.h> | |
+ | |
+#include "apparmorfs.h" | |
+#include "label.h" | |
+#include "perms.h" | |
+#include "policy.h" | |
+ | |
+#define AA_MAY_SEND AA_MAY_WRITE | |
+#define AA_MAY_RECEIVE AA_MAY_READ | |
+ | |
+#define AA_MAY_SHUTDOWN AA_MAY_DELETE | |
+ | |
+#define AA_MAY_CONNECT AA_MAY_OPEN | |
+#define AA_MAY_ACCEPT 0x00100000 | |
+ | |
+#define AA_MAY_BIND 0x00200000 | |
+#define AA_MAY_LISTEN 0x00400000 | |
+ | |
+#define AA_MAY_SETOPT 0x01000000 | |
+#define AA_MAY_GETOPT 0x02000000 | |
+ | |
+#define NET_PERMS_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CREATE | \ | |
+ AA_MAY_SHUTDOWN | AA_MAY_BIND | AA_MAY_LISTEN | \ | |
+ AA_MAY_CONNECT | AA_MAY_ACCEPT | AA_MAY_SETATTR | \ | |
+ AA_MAY_GETATTR | AA_MAY_SETOPT | AA_MAY_GETOPT) | |
+ | |
+#define NET_FS_PERMS (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CREATE | \ | |
+ AA_MAY_SHUTDOWN | AA_MAY_CONNECT | AA_MAY_RENAME |\ | |
+ AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_CHMOD | \ | |
+ AA_MAY_CHOWN | AA_MAY_CHGRP | AA_MAY_LOCK | \ | |
+ AA_MAY_MPROT) | |
+ | |
+#define NET_PEER_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CONNECT | \ | |
+ AA_MAY_ACCEPT) | |
+struct aa_sk_ctx { | |
+ struct aa_label *label; | |
+ struct aa_label *peer; | |
+ struct path path; | |
+}; | |
+ | |
+#define SK_CTX(X) (X)->sk_security | |
+#define SOCK_ctx(X) SOCK_INODE(X)->i_security | |
+#define DEFINE_AUDIT_NET(NAME, OP, SK, F, T, P) \ | |
+ struct lsm_network_audit NAME ## _net = { .sk = (SK), \ | |
+ .family = (F)}; \ | |
+ DEFINE_AUDIT_DATA(NAME, \ | |
+ ((SK) && (F) != AF_UNIX) ? LSM_AUDIT_DATA_NET : \ | |
+ LSM_AUDIT_DATA_NONE, \ | |
+ OP); \ | |
+ NAME.u.net = &(NAME ## _net); \ | |
+ aad(&NAME)->net.type = (T); \ | |
+ aad(&NAME)->net.protocol = (P) | |
+ | |
+#define DEFINE_AUDIT_SK(NAME, OP, SK) \ | |
+ DEFINE_AUDIT_NET(NAME, OP, SK, (SK)->sk_family, (SK)->sk_type, \ | |
+ (SK)->sk_protocol) | |
+ | |
+/* 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[]; | |
+ | |
+void audit_net_cb(struct audit_buffer *ab, void *va); | |
+int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa, | |
+ u32 request, u16 family, int type); | |
+static inline int aa_profile_af_sk_perm(struct aa_profile *profile, | |
+ struct common_audit_data *sa, | |
+ u32 request, | |
+ struct sock *sk) | |
+{ | |
+ return aa_profile_af_perm(profile, sa, request, sk->sk_family, | |
+ sk->sk_type); | |
+} | |
+ | |
+int aa_sock_perm(const char *op, u32 request, struct socket *sock); | |
+int aa_sock_create_perm(struct aa_label *label, int family, int type, | |
+ int protocol); | |
+int aa_sock_bind_perm(struct socket *sock, struct sockaddr *address, | |
+ int addrlen); | |
+int aa_sock_connect_perm(struct socket *sock, struct sockaddr *address, | |
+ int addrlen); | |
+int aa_sock_listen_perm(struct socket *sock, int backlog); | |
+int aa_sock_accept_perm(struct socket *sock, struct socket *newsock); | |
+int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, | |
+ struct msghdr *msg, int size); | |
+int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, int level, | |
+ int optname); | |
+int aa_sock_file_perm(struct aa_label *label, const char *op, u32 request, | |
+ struct socket *sock); | |
+ | |
+ | |
+static inline void aa_free_net_rules(struct aa_net *new) | |
+{ | |
+ /* NOP */ | |
+} | |
+ | |
+#endif /* __AA_NET_H */ | |
diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h | |
index 73560f2..4f815c6 100644 | |
--- a/security/apparmor/include/path.h | |
+++ b/security/apparmor/include/path.h | |
@@ -18,15 +18,72 @@ | |
enum path_flags { | |
PATH_IS_DIR = 0x1, /* path is a directory */ | |
+ PATH_SOCK_COND = 0x2, | |
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */ | |
PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */ | |
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */ | |
PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */ | |
- PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */ | |
+ PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */ | |
}; | |
-int aa_path_name(const struct path *path, int flags, char **buffer, | |
- const char **name, const char **info); | |
+int aa_path_name(const struct path *path, int flags, char *buffer, | |
+ const char **name, const char **info, const char *disconnect); | |
+ | |
+#define MAX_PATH_BUFFERS 2 | |
+ | |
+/* Per cpu buffers used during mediation */ | |
+/* preallocated buffers to use during path lookups */ | |
+struct aa_buffers { | |
+ char *buf[MAX_PATH_BUFFERS]; | |
+}; | |
+ | |
+#include <linux/percpu.h> | |
+#include <linux/preempt.h> | |
+ | |
+DECLARE_PER_CPU(struct aa_buffers, aa_buffers); | |
+ | |
+#define COUNT_ARGS(X...) COUNT_ARGS_HELPER ( , ##X ,9,8,7,6,5,4,3,2,1,0) | |
+#define COUNT_ARGS_HELPER(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,n,X...) n | |
+#define CONCAT(X, Y) X ## Y | |
+#define CONCAT_AFTER(X, Y) CONCAT(X, Y) | |
+ | |
+#define ASSIGN(FN, X, N) do { (X) = FN(N); } while (0) | |
+#define EVAL1(FN, X) ASSIGN(FN, X, 0) /*X = FN(0)*/ | |
+#define EVAL2(FN, X, Y...) ASSIGN(FN, X, 1); /*X = FN(1);*/ EVAL1(FN, Y) | |
+#define EVAL(FN, X...) CONCAT_AFTER(EVAL, COUNT_ARGS(X))(FN, X) | |
+ | |
+#define for_each_cpu_buffer(I) for ((I) = 0; (I) < MAX_PATH_BUFFERS; (I)++) | |
+ | |
+#ifdef CONFIG_DEBUG_PREEMPT | |
+#define AA_BUG_PREEMPT_ENABLED(X) AA_BUG(preempt_count() <= 0, X) | |
+#else | |
+#define AA_BUG_PREEMPT_ENABLED(X) /* nop */ | |
+#endif | |
+ | |
+#define __get_buffer(N) ({ \ | |
+ struct aa_buffers *__cpu_var; \ | |
+ AA_BUG_PREEMPT_ENABLED("__get_buffer without preempt disabled"); \ | |
+ __cpu_var = this_cpu_ptr(&aa_buffers); \ | |
+ __cpu_var->buf[(N)]; }) | |
+ | |
+#define __get_buffers(X...) \ | |
+do { \ | |
+ EVAL(__get_buffer, X); \ | |
+} while (0) | |
+ | |
+#define __put_buffers(X, Y...) (void)&(X) | |
+ | |
+#define get_buffers(X...) \ | |
+do { \ | |
+ preempt_disable(); \ | |
+ __get_buffers(X); \ | |
+} while (0) | |
+ | |
+#define put_buffers(X, Y...) \ | |
+do { \ | |
+ __put_buffers(X, Y); \ | |
+ preempt_enable(); \ | |
+} while (0) | |
#endif /* __AA_PATH_H */ | |
diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h | |
new file mode 100644 | |
index 0000000..175fe13 | |
--- /dev/null | |
+++ b/security/apparmor/include/perms.h | |
@@ -0,0 +1,167 @@ | |
+/* | |
+ * AppArmor security module | |
+ * | |
+ * This file contains AppArmor basic permission sets definitions. | |
+ * | |
+ * Copyright 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. | |
+ */ | |
+ | |
+#ifndef __AA_PERM_H | |
+#define __AA_PERM_H | |
+ | |
+#include <linux/fs.h> | |
+#include "label.h" | |
+ | |
+#define AA_MAY_EXEC MAY_EXEC | |
+#define AA_MAY_WRITE MAY_WRITE | |
+#define AA_MAY_READ MAY_READ | |
+#define AA_MAY_APPEND MAY_APPEND | |
+ | |
+#define AA_MAY_CREATE 0x0010 | |
+#define AA_MAY_DELETE 0x0020 | |
+#define AA_MAY_OPEN 0x0040 | |
+#define AA_MAY_RENAME 0x0080 /* pair */ | |
+ | |
+#define AA_MAY_SETATTR 0x0100 /* meta write */ | |
+#define AA_MAY_GETATTR 0x0200 /* meta read */ | |
+#define AA_MAY_SETCRED 0x0400 /* security cred/attr */ | |
+#define AA_MAY_GETCRED 0x0800 | |
+ | |
+#define AA_MAY_CHMOD 0x1000 /* pair */ | |
+#define AA_MAY_CHOWN 0x2000 /* pair */ | |
+#define AA_MAY_CHGRP 0x4000 /* pair */ | |
+#define AA_MAY_LOCK 0x8000 /* LINK_SUBSET overlaid */ | |
+ | |
+#define AA_EXEC_MMAP 0x00010000 | |
+#define AA_MAY_MPROT 0x00020000 /* extend conditions */ | |
+#define AA_MAY_LINK 0x00040000 /* pair */ | |
+#define AA_MAY_SNAPSHOT 0x00080000 /* pair */ | |
+ | |
+#define AA_MAY_DELEGATE | |
+#define AA_CONT_MATCH 0x08000000 | |
+ | |
+#define AA_MAY_STACK 0x10000000 | |
+#define AA_MAY_ONEXEC 0x20000000 /* either stack or change_profile */ | |
+#define AA_MAY_CHANGE_PROFILE 0x40000000 | |
+#define AA_MAY_CHANGEHAT 0x80000000 | |
+ | |
+#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ | |
+ | |
+ | |
+#define PERMS_CHRS_MASK (MAY_READ | MAY_WRITE | AA_MAY_CREATE | \ | |
+ AA_MAY_DELETE | AA_MAY_LINK | AA_MAY_LOCK | \ | |
+ AA_MAY_EXEC | AA_EXEC_MMAP | AA_MAY_APPEND) | |
+ | |
+#define PERMS_NAMES_MASK (PERMS_CHRS_MASK | AA_MAY_OPEN | AA_MAY_RENAME | \ | |
+ AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_SETCRED | \ | |
+ AA_MAY_GETCRED | AA_MAY_CHMOD | AA_MAY_CHOWN | \ | |
+ AA_MAY_CHGRP | AA_MAY_MPROT | AA_MAY_SNAPSHOT | \ | |
+ AA_MAY_STACK | AA_MAY_ONEXEC | \ | |
+ AA_MAY_CHANGE_PROFILE | AA_MAY_CHANGEHAT) | |
+ | |
+extern const char aa_file_perm_chrs[]; | |
+extern const char *aa_file_perm_names[]; | |
+ | |
+ | |
+struct aa_perms { | |
+ u32 allow; | |
+ u32 audit; /* set only when allow is set */ | |
+ | |
+ u32 deny; /* explicit deny, or conflict if allow also set */ | |
+ u32 quiet; /* set only when ~allow | deny */ | |
+ u32 kill; /* set only when ~allow | deny */ | |
+ u32 stop; /* set only when ~allow | deny */ | |
+ | |
+ u32 complain; /* accumulates only used when ~allow & ~deny */ | |
+ u32 cond; /* set only when ~allow and ~deny */ | |
+ | |
+ u32 hide; /* set only when ~allow | deny */ | |
+ u32 prompt; /* accumulates only used when ~allow & ~deny */ | |
+ | |
+ /* Reserved: | |
+ * u32 subtree; / * set only when allow is set * / | |
+ */ | |
+ u16 xindex; | |
+}; | |
+ | |
+#define ALL_PERMS_MASK 0xffffffff | |
+extern struct aa_perms nullperms; | |
+extern struct aa_perms allperms; | |
+ | |
+ | |
+#define xcheck(FN1, FN2) \ | |
+({ \ | |
+ int e, error = FN1; \ | |
+ e = FN2; \ | |
+ if (e) \ | |
+ error = e; \ | |
+ error; \ | |
+}) | |
+ | |
+ | |
+/* TODO: update for labels pointing to labels instead of profiles | |
+* Note: this only works for profiles from a single namespace | |
+*/ | |
+ | |
+#define xcheck_profile_label(P, L, FN, args...) \ | |
+({ \ | |
+ struct aa_profile *__p2; \ | |
+ fn_for_each((L), __p2, FN((P), __p2, args)); \ | |
+}) | |
+ | |
+#define xcheck_ns_labels(L1, L2, FN, args...) \ | |
+({ \ | |
+ struct aa_profile *__p1; \ | |
+ fn_for_each((L1), __p1, FN(__p1, (L2), args)); \ | |
+}) | |
+ | |
+/* todo: fix to handle multiple namespaces */ | |
+#define xcheck_labels(L1, L2, FN, args...) \ | |
+ xcheck_ns_labels((L1), (L2), FN, args) | |
+ | |
+/* Do the cross check but applying FN at the profiles level */ | |
+#define xcheck_labels_profiles(L1, L2, FN, args...) \ | |
+ xcheck_ns_labels((L1), (L2), xcheck_profile_label, (FN), args) | |
+ | |
+ | |
+#define FINAL_CHECK true | |
+ | |
+void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask); | |
+void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask); | |
+void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, | |
+ u32 chrsmask, const char **names, u32 namesmask); | |
+void aa_apply_modes_to_perms(struct aa_profile *profile, | |
+ struct aa_perms *perms); | |
+void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, | |
+ struct aa_perms *perms); | |
+void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend); | |
+void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend); | |
+void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, | |
+ int type, u32 request, struct aa_perms *perms); | |
+int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, | |
+ u32 request, int type, u32 *deny, | |
+ struct common_audit_data *sa); | |
+int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, | |
+ u32 request, struct common_audit_data *sa, | |
+ void (*cb) (struct audit_buffer *, void *)); | |
+ | |
+ | |
+static inline int aa_xlabel_perm(struct aa_profile *profile, | |
+ struct aa_profile *target, | |
+ int type, u32 request, u32 reverse, | |
+ u32 * deny, struct common_audit_data *sa) | |
+{ | |
+ /* TODO: ??? 2nd aa_profile_label_perm needs to reverse perms */ | |
+ return xcheck(aa_profile_label_perm(profile, target, request, type, | |
+ deny, sa), | |
+ aa_profile_label_perm(target, profile, request /*??*/, type, | |
+ deny, sa)); | |
+} | |
+ | |
+ | |
+#endif /* __AA_PERM_H */ | |
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h | |
index 52275f0..10b71c0 100644 | |
--- a/security/apparmor/include/policy.h | |
+++ b/security/apparmor/include/policy.h | |
@@ -27,8 +27,13 @@ | |
#include "capability.h" | |
#include "domain.h" | |
#include "file.h" | |
+#include "label.h" | |
+#include "net.h" | |
+#include "perms.h" | |
#include "resource.h" | |
+struct aa_ns; | |
+ | |
extern const char *const aa_profile_mode_names[]; | |
#define APPARMOR_MODE_NAMES_MAX_INDEX 4 | |
@@ -40,9 +45,9 @@ | |
#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL) | |
-#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT) | |
+#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT) | |
-#define PROFILE_INVALID(_profile) ((_profile)->flags & PFLAG_INVALID) | |
+#define profile_is_stale(_profile) (label_is_stale(&(_profile)->label)) | |
#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2) | |
@@ -59,86 +64,6 @@ enum profile_mode { | |
APPARMOR_UNCONFINED, /* profile set to unconfined */ | |
}; | |
-enum profile_flags { | |
- PFLAG_HAT = 1, /* profile is a hat */ | |
- PFLAG_NULL = 4, /* profile is null learning profile */ | |
- PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */ | |
- PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */ | |
- PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */ | |
- PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */ | |
- PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */ | |
- PFLAG_INVALID = 0x200, /* profile replaced/removed */ | |
- PFLAG_NS_COUNT = 0x400, /* carries NS ref count */ | |
- | |
- /* These flags must correspond with PATH_flags */ | |
- PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */ | |
-}; | |
- | |
-struct aa_profile; | |
- | |
-/* struct aa_policy - common part of both namespaces and profiles | |
- * @name: name of the object | |
- * @hname - The hierarchical name | |
- * @list: list policy object is on | |
- * @profiles: head of the profiles list contained in the object | |
- */ | |
-struct aa_policy { | |
- char *name; | |
- char *hname; | |
- struct list_head list; | |
- struct list_head profiles; | |
-}; | |
- | |
-/* struct aa_ns_acct - accounting of profiles in namespace | |
- * @max_size: maximum space allowed for all profiles in namespace | |
- * @max_count: maximum number of profiles that can be in this namespace | |
- * @size: current size of profiles | |
- * @count: current count of profiles (includes null profiles) | |
- */ | |
-struct aa_ns_acct { | |
- int max_size; | |
- int max_count; | |
- int size; | |
- int count; | |
-}; | |
- | |
-/* struct aa_namespace - namespace for a set of profiles | |
- * @base: common policy | |
- * @parent: parent of namespace | |
- * @lock: lock for modifying the object | |
- * @acct: accounting for the namespace | |
- * @unconfined: special unconfined profile for the namespace | |
- * @sub_ns: list of namespaces under the current namespace. | |
- * @uniq_null: uniq value used for null learning profiles | |
- * @uniq_id: a unique id count for the profiles in the namespace | |
- * @dents: dentries for the namespaces file entries in apparmorfs | |
- * | |
- * An aa_namespace defines the set profiles that are searched to determine | |
- * which profile to attach to a task. Profiles can not be shared between | |
- * aa_namespaces and profile names within a namespace are guaranteed to be | |
- * unique. When profiles in separate namespaces have the same name they | |
- * are NOT considered to be equivalent. | |
- * | |
- * Namespaces are hierarchical and only namespaces and profiles below the | |
- * current namespace are visible. | |
- * | |
- * Namespace names must be unique and can not contain the characters :/\0 | |
- * | |
- * FIXME TODO: add vserver support of namespaces (can it all be done in | |
- * userspace?) | |
- */ | |
-struct aa_namespace { | |
- struct aa_policy base; | |
- struct aa_namespace *parent; | |
- struct mutex lock; | |
- struct aa_ns_acct acct; | |
- struct aa_profile *unconfined; | |
- struct list_head sub_ns; | |
- atomic_t uniq_null; | |
- long uniq_id; | |
- | |
- struct dentry *dents[AAFS_NS_SIZEOF]; | |
-}; | |
/* struct aa_policydb - match engine for a policy | |
* dfa: dfa pattern match | |
@@ -151,31 +76,24 @@ struct aa_policydb { | |
}; | |
-struct aa_replacedby { | |
- struct kref count; | |
- struct aa_profile __rcu *profile; | |
-}; | |
- | |
- | |
/* struct aa_profile - basic confinement data | |
* @base - base components of the profile (name, refcount, lists, lock ...) | |
- * @count: reference count of the obj | |
- * @rcu: rcu head used when removing from @list | |
+ * @label - label this profile is an extension of | |
* @parent: parent of profile | |
* @ns: namespace the profile is in | |
- * @replacedby: is set to the profile that replaced this profile | |
* @rename: optional profile name that this profile renamed | |
* @attach: human readable attachment string | |
* @xmatch: optional extended matching for unconfined executables names | |
* @xmatch_len: xmatch prefix len, used to determine xmatch priority | |
* @audit: the auditing mode of the profile | |
* @mode: the enforcement mode of the profile | |
- * @flags: flags controlling profile behavior | |
* @path_flags: flags controlling path generation behavior | |
+ * @disconnected: what to prepend if attach_disconnected is specified | |
* @size: the memory consumed by this profiles rules | |
* @policy: general match rules governing policy | |
* @file: The set of rules governing basic file access and domain transitions | |
* @caps: capabilities for the profile | |
+ * @net: network controls for the profile | |
* @rlimits: rlimits for the profile | |
* | |
* @dents: dentries for the profiles file entries in apparmorfs | |
@@ -186,8 +104,6 @@ struct aa_replacedby { | |
* used to determine profile attachment against unconfined tasks. All other | |
* attachments are determined by profile X transition rules. | |
* | |
- * The @replacedby struct is write protected by the profile lock. | |
- * | |
* Profiles have a hierarchy where hats and children profiles keep | |
* a reference to their parent. | |
* | |
@@ -197,12 +113,9 @@ struct aa_replacedby { | |
*/ | |
struct aa_profile { | |
struct aa_policy base; | |
- struct kref count; | |
- struct rcu_head rcu; | |
struct aa_profile __rcu *parent; | |
- struct aa_namespace *ns; | |
- struct aa_replacedby *replacedby; | |
+ struct aa_ns *ns; | |
const char *rename; | |
const char *attach; | |
@@ -210,57 +123,91 @@ struct aa_profile { | |
int xmatch_len; | |
enum audit_mode audit; | |
long mode; | |
- long flags; | |
u32 path_flags; | |
+ const char *disconnected; | |
int size; | |
struct aa_policydb policy; | |
struct aa_file_rules file; | |
struct aa_caps caps; | |
+ struct aa_net net; | |
struct aa_rlimit rlimits; | |
unsigned char *hash; | |
char *dirname; | |
struct dentry *dents[AAFS_PROF_SIZEOF]; | |
+ struct aa_label label; | |
}; | |
-extern struct aa_namespace *root_ns; | |
extern enum profile_mode aa_g_profile_mode; | |
-void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); | |
+#define AA_MAY_LOAD_POLICY AA_MAY_APPEND | |
+#define AA_MAY_REPLACE_POLICY AA_MAY_WRITE | |
+#define AA_MAY_REMOVE_POLICY AA_MAY_DELETE | |
-bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view); | |
-const char *aa_ns_name(struct aa_namespace *parent, struct aa_namespace *child); | |
-int aa_alloc_root_ns(void); | |
-void aa_free_root_ns(void); | |
-void aa_free_namespace_kref(struct kref *kref); | |
+#define profiles_ns(P) ((P)->ns) | |
+#define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname) | |
+ | |
+void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); | |
-struct aa_namespace *aa_find_namespace(struct aa_namespace *root, | |
- const char *name); | |
+struct aa_label *aa_setup_default_label(void); | |
-void aa_free_replacedby_kref(struct kref *kref); | |
-struct aa_profile *aa_alloc_profile(const char *name); | |
-struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat); | |
+struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy, | |
+ gfp_t gfp); | |
+struct aa_profile *aa_null_profile(struct aa_profile *parent, bool hat, | |
+ const char *base, gfp_t gfp); | |
void aa_free_profile(struct aa_profile *profile); | |
void aa_free_profile_kref(struct kref *kref); | |
struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name); | |
-struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name); | |
-struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name); | |
- | |
-ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace); | |
-ssize_t aa_remove_profiles(char *name, size_t size); | |
+struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname, | |
+ size_t n); | |
+struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *name); | |
+struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, | |
+ const char *fqname, size_t n); | |
+struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name); | |
+ | |
+ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, | |
+ size_t size); | |
+ssize_t aa_remove_profiles(struct aa_label *label, char *name, size_t size); | |
+void __aa_profile_list_release(struct list_head *head); | |
#define PROF_ADD 1 | |
#define PROF_REPLACE 0 | |
-#define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) | |
+#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) | |
+/** | |
+ * aa_get_newest_profile - simple wrapper fn to wrap the label version | |
+ * @p: profile (NOT NULL) | |
+ * | |
+ * Returns refcount to newest version of the profile (maybe @p) | |
+ * | |
+ * Requires: @p must be held with a valid refcount | |
+ */ | |
+static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) | |
+{ | |
+ return labels_profile(aa_get_newest_label(&p->label)); | |
+} | |
-static inline struct aa_profile *aa_deref_parent(struct aa_profile *p) | |
+#define PROFILE_MEDIATES(P, T) ((P)->policy.start[(T)]) | |
+/* safe version of POLICY_MEDIATES for full range input */ | |
+static inline unsigned int PROFILE_MEDIATES_SAFE(struct aa_profile *profile, | |
+ unsigned char class) | |
{ | |
- return rcu_dereference_protected(p->parent, | |
- mutex_is_locked(&p->ns->lock)); | |
+ if (profile->policy.dfa) | |
+ return aa_dfa_match_len(profile->policy.dfa, | |
+ profile->policy.start[0], &class, 1); | |
+ return 0; | |
+} | |
+ | |
+static inline unsigned int PROFILE_MEDIATES_AF(struct aa_profile *profile, | |
+ u16 AF) { | |
+ unsigned int state = PROFILE_MEDIATES(profile, AA_CLASS_NET); | |
+ u16 be_af = cpu_to_be16(AF); | |
+ if (!state) | |
+ return 0; | |
+ return aa_dfa_match_len(profile->policy.dfa, state, (char *) &be_af, 2); | |
} | |
/** | |
@@ -273,7 +220,7 @@ static inline struct aa_profile *aa_deref_parent(struct aa_profile *p) | |
static inline struct aa_profile *aa_get_profile(struct aa_profile *p) | |
{ | |
if (p) | |
- kref_get(&(p->count)); | |
+ kref_get(&(p->label.count)); | |
return p; | |
} | |
@@ -287,7 +234,7 @@ static inline struct aa_profile *aa_get_profile(struct aa_profile *p) | |
*/ | |
static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p) | |
{ | |
- if (p && kref_get_not0(&p->count)) | |
+ if (p && kref_get_not0(&p->label.count)) | |
return p; | |
return NULL; | |
@@ -307,92 +254,20 @@ static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p) | |
rcu_read_lock(); | |
do { | |
c = rcu_dereference(*p); | |
- } while (c && !kref_get_not0(&c->count)); | |
+ } while (c && !kref_get_not0(&c->label.count)); | |
rcu_read_unlock(); | |
return c; | |
} | |
/** | |
- * aa_get_newest_profile - find the newest version of @profile | |
- * @profile: the profile to check for newer versions of | |
- * | |
- * Returns: refcounted newest version of @profile taking into account | |
- * replacement, renames and removals | |
- * return @profile. | |
- */ | |
-static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) | |
-{ | |
- if (!p) | |
- return NULL; | |
- | |
- if (PROFILE_INVALID(p)) | |
- return aa_get_profile_rcu(&p->replacedby->profile); | |
- | |
- return aa_get_profile(p); | |
-} | |
- | |
-/** | |
* aa_put_profile - decrement refcount on profile @p | |
* @p: profile (MAYBE NULL) | |
*/ | |
static inline void aa_put_profile(struct aa_profile *p) | |
{ | |
if (p) | |
- kref_put(&p->count, aa_free_profile_kref); | |
-} | |
- | |
-static inline struct aa_replacedby *aa_get_replacedby(struct aa_replacedby *p) | |
-{ | |
- if (p) | |
- kref_get(&(p->count)); | |
- | |
- return p; | |
-} | |
- | |
-static inline void aa_put_replacedby(struct aa_replacedby *p) | |
-{ | |
- if (p) | |
- kref_put(&p->count, aa_free_replacedby_kref); | |
-} | |
- | |
-/* requires profile list write lock held */ | |
-static inline void __aa_update_replacedby(struct aa_profile *orig, | |
- struct aa_profile *new) | |
-{ | |
- struct aa_profile *tmp; | |
- tmp = rcu_dereference_protected(orig->replacedby->profile, | |
- mutex_is_locked(&orig->ns->lock)); | |
- rcu_assign_pointer(orig->replacedby->profile, aa_get_profile(new)); | |
- orig->flags |= PFLAG_INVALID; | |
- aa_put_profile(tmp); | |
-} | |
- | |
-/** | |
- * aa_get_namespace - increment references count on @ns | |
- * @ns: namespace to increment reference count of (MAYBE NULL) | |
- * | |
- * Returns: pointer to @ns, if @ns is NULL returns NULL | |
- * Requires: @ns must be held with valid refcount when called | |
- */ | |
-static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns) | |
-{ | |
- if (ns) | |
- aa_get_profile(ns->unconfined); | |
- | |
- return ns; | |
-} | |
- | |
-/** | |
- * aa_put_namespace - decrement refcount on @ns | |
- * @ns: namespace to put reference of | |
- * | |
- * Decrement reference count of @ns and if no longer in use free it | |
- */ | |
-static inline void aa_put_namespace(struct aa_namespace *ns) | |
-{ | |
- if (ns) | |
- aa_put_profile(ns->unconfined); | |
+ kref_put(&p->label.count, aa_label_kref); | |
} | |
static inline int AUDIT_MODE(struct aa_profile *profile) | |
@@ -405,6 +280,6 @@ static inline int AUDIT_MODE(struct aa_profile *profile) | |
bool policy_view_capable(void); | |
bool policy_admin_capable(void); | |
-bool aa_may_manage_policy(int op); | |
+int aa_may_manage_policy(struct aa_label *label, u32 mask); | |
#endif /* __AA_POLICY_H */ | |
diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h | |
new file mode 100644 | |
index 0000000..65b1e1e | |
--- /dev/null | |
+++ b/security/apparmor/include/policy_ns.h | |
@@ -0,0 +1,127 @@ | |
+/* | |
+ * AppArmor security module | |
+ * | |
+ * This file contains AppArmor policy definitions. | |
+ * | |
+ * Copyright (C) 1998-2008 Novell/SUSE | |
+ * Copyright 2009-2015 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_NAMESPACE_H | |
+#define __AA_NAMESPACE_H | |
+ | |
+#include <linux/kref.h> | |
+ | |
+#include "apparmor.h" | |
+#include "apparmorfs.h" | |
+#include "label.h" | |
+#include "policy.h" | |
+ | |
+ | |
+/* struct aa_ns_acct - accounting of profiles in namespace | |
+ * @max_size: maximum space allowed for all profiles in namespace | |
+ * @max_count: maximum number of profiles that can be in this namespace | |
+ * @size: current size of profiles | |
+ * @count: current count of profiles (includes null profiles) | |
+ */ | |
+struct aa_ns_acct { | |
+ int max_size; | |
+ int max_count; | |
+ int size; | |
+ int count; | |
+}; | |
+ | |
+/* struct aa_ns - namespace for a set of profiles | |
+ * @base: common policy | |
+ * @parent: parent of namespace | |
+ * @lock: lock for modifying the object | |
+ * @acct: accounting for the namespace | |
+ * @unconfined: special unconfined profile for the namespace | |
+ * @sub_ns: list of namespaces under the current namespace. | |
+ * @uniq_null: uniq value used for null learning profiles | |
+ * @uniq_id: a unique id count for the profiles in the namespace | |
+ * @dents: dentries for the namespaces file entries in apparmorfs | |
+ * | |
+ * An aa_ns defines the set profiles that are searched to determine which | |
+ * profile to attach to a task. Profiles can not be shared between aa_ns | |
+ * and profile names within a namespace are guaranteed to be unique. When | |
+ * profiles in separate namespaces have the same name they are NOT considered | |
+ * to be equivalent. | |
+ * | |
+ * Namespaces are hierarchical and only namespaces and profiles below the | |
+ * current namespace are visible. | |
+ * | |
+ * Namespace names must be unique and can not contain the characters :/\0 | |
+ */ | |
+struct aa_ns { | |
+ struct aa_policy base; | |
+ struct aa_ns *parent; | |
+ struct mutex lock; | |
+ struct aa_ns_acct acct; | |
+ struct aa_profile *unconfined; | |
+ struct list_head sub_ns; | |
+ atomic_t uniq_null; | |
+ long uniq_id; | |
+ int level; | |
+ struct aa_labelset labels; | |
+ | |
+ struct dentry *dents[AAFS_NS_SIZEOF]; | |
+}; | |
+ | |
+extern struct aa_ns *root_ns; | |
+ | |
+extern const char *aa_hidden_ns_name; | |
+ | |
+#define ns_unconfined(NS) (&(NS)->unconfined->label) | |
+ | |
+bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns); | |
+const char *aa_ns_name(struct aa_ns *parent, struct aa_ns *child, bool subns); | |
+void aa_free_ns(struct aa_ns *ns); | |
+int aa_alloc_root_ns(void); | |
+void aa_free_root_ns(void); | |
+void aa_free_ns_kref(struct kref *kref); | |
+ | |
+struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name); | |
+struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n); | |
+struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name); | |
+void __aa_remove_ns(struct aa_ns *ns); | |
+ | |
+static inline struct aa_profile *aa_deref_parent(struct aa_profile *p) | |
+{ | |
+ return rcu_dereference_protected(p->parent, | |
+ mutex_is_locked(&p->ns->lock)); | |
+} | |
+ | |
+/** | |
+ * aa_get_ns - increment references count on @ns | |
+ * @ns: namespace to increment reference count of (MAYBE NULL) | |
+ * | |
+ * Returns: pointer to @ns, if @ns is NULL returns NULL | |
+ * Requires: @ns must be held with valid refcount when called | |
+ */ | |
+static inline struct aa_ns *aa_get_ns(struct aa_ns *ns) | |
+{ | |
+ if (ns) | |
+ aa_get_profile(ns->unconfined); | |
+ | |
+ return ns; | |
+} | |
+ | |
+/** | |
+ * aa_put_ns - decrement refcount on @ns | |
+ * @ns: ns to put reference of | |
+ * | |
+ * Decrement reference count of @ns and if no longer in use free it | |
+ */ | |
+static inline void aa_put_ns(struct aa_ns *ns) | |
+{ | |
+ if (ns) | |
+ aa_put_profile(ns->unconfined); | |
+} | |
+ | |
+#endif /* AA_NAMESPACE_H */ | |
diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h | |
index c214fb8..c2f7a96 100644 | |
--- a/security/apparmor/include/policy_unpack.h | |
+++ b/security/apparmor/include/policy_unpack.h | |
@@ -22,6 +22,7 @@ struct aa_load_ent { | |
struct aa_profile *new; | |
struct aa_profile *old; | |
struct aa_profile *rename; | |
+ const char *ns_name; | |
}; | |
void aa_load_ent_free(struct aa_load_ent *ent); | |
diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h | |
index 6bd5f33..8fb8549 100644 | |
--- a/security/apparmor/include/procattr.h | |
+++ b/security/apparmor/include/procattr.h | |
@@ -18,8 +18,7 @@ | |
#define AA_DO_TEST 1 | |
#define AA_ONEXEC 1 | |
-int aa_getprocattr(struct aa_profile *profile, char **string); | |
+int aa_getprocattr(struct aa_label *label, char **string); | |
int aa_setprocattr_changehat(char *args, size_t size, int test); | |
-int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test); | |
#endif /* __AA_PROCATTR_H */ | |
diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h | |
index d3f4cf0..69d3b8ad 100644 | |
--- a/security/apparmor/include/resource.h | |
+++ b/security/apparmor/include/resource.h | |
@@ -37,10 +37,10 @@ struct aa_rlimit { | |
extern struct aa_fs_entry aa_fs_entry_rlimit[]; | |
int aa_map_resource(int resource); | |
-int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *, | |
+int aa_task_setrlimit(struct aa_label *label, struct task_struct *, | |
unsigned int resource, struct rlimit *new_rlim); | |
-void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new); | |
+void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new); | |
static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims) | |
{ | |
diff --git a/security/apparmor/include/sig_names.h b/security/apparmor/include/sig_names.h | |
new file mode 100644 | |
index 0000000..8a3dfc5 | |
--- /dev/null | |
+++ b/security/apparmor/include/sig_names.h | |
@@ -0,0 +1,95 @@ | |
+#include <linux/signal.h> | |
+ | |
+#define SIGUNKNOWN 0 | |
+#define MAXMAPPED_SIG 35 | |
+/* provide a mapping of arch signal to internal signal # for mediation | |
+ * those that are always an alias SIGCLD for SIGCLHD and SIGPOLL for SIGIO | |
+ * map to the same entry those that may/or may not get a separate entry | |
+ */ | |
+static const int sig_map[MAXMAPPED_SIG] = { | |
+ [0] = MAXMAPPED_SIG, /* existance test */ | |
+ [SIGHUP] = 1, | |
+ [SIGINT] = 2, | |
+ [SIGQUIT] = 3, | |
+ [SIGILL] = 4, | |
+ [SIGTRAP] = 5, /* -, 5, - */ | |
+ [SIGABRT] = 6, /* SIGIOT: -, 6, - */ | |
+ [SIGBUS] = 7, /* 10, 7, 10 */ | |
+ [SIGFPE] = 8, | |
+ [SIGKILL] = 9, | |
+ [SIGUSR1] = 10, /* 30, 10, 16 */ | |
+ [SIGSEGV] = 11, | |
+ [SIGUSR2] = 12, /* 31, 12, 17 */ | |
+ [SIGPIPE] = 13, | |
+ [SIGALRM] = 14, | |
+ [SIGTERM] = 15, | |
+ [SIGSTKFLT] = 16, /* -, 16, - */ | |
+ [SIGCHLD] = 17, /* 20, 17, 18. SIGCHLD -, -, 18 */ | |
+ [SIGCONT] = 18, /* 19, 18, 25 */ | |
+ [SIGSTOP] = 19, /* 17, 19, 23 */ | |
+ [SIGTSTP] = 20, /* 18, 20, 24 */ | |
+ [SIGTTIN] = 21, /* 21, 21, 26 */ | |
+ [SIGTTOU] = 22, /* 22, 22, 27 */ | |
+ [SIGURG] = 23, /* 16, 23, 21 */ | |
+ [SIGXCPU] = 24, /* 24, 24, 30 */ | |
+ [SIGXFSZ] = 25, /* 25, 25, 31 */ | |
+ [SIGVTALRM] = 26, /* 26, 26, 28 */ | |
+ [SIGPROF] = 27, /* 27, 27, 29 */ | |
+ [SIGWINCH] = 28, /* 28, 28, 20 */ | |
+ [SIGIO] = 29, /* SIGPOLL: 23, 29, 22 */ | |
+ [SIGPWR] = 30, /* 29, 30, 19. SIGINFO 29, -, - */ | |
+#ifdef SIGSYS | |
+ [SIGSYS] = 31, /* 12, 31, 12. often SIG LOST/UNUSED */ | |
+#endif | |
+#ifdef SIGEMT | |
+ [SIGEMT] = 32, /* 7, - , 7 */ | |
+#endif | |
+#if defined(SIGLOST) && SIGPWR != SIGLOST /* sparc */ | |
+ [SIGLOST] = 33, /* unused on Linux */ | |
+#endif | |
+#if defined(SIGLOST) && defined(SIGSYS) && SIGLOST != SIGSYS | |
+ [SIGUNUSED] = 34, /* -, 31, - */ | |
+#endif | |
+}; | |
+ | |
+/* this table is ordered post sig_map[sig] mapping */ | |
+static const char *const sig_names[MAXMAPPED_SIG + 1] = { | |
+ "unknown", | |
+ "hup", | |
+ "int", | |
+ "quit", | |
+ "ill", | |
+ "trap", | |
+ "abrt", | |
+ "bus", | |
+ "fpe", | |
+ "kill", | |
+ "usr1", | |
+ "segv", | |
+ "usr2", | |
+ "pipe", | |
+ "alrm", | |
+ "term", | |
+ "stkflt", | |
+ "chld", | |
+ "cont", | |
+ "stop", | |
+ "stp", | |
+ "ttin", | |
+ "ttou", | |
+ "urg", | |
+ "xcpu", | |
+ "xfsz", | |
+ "vtalrm", | |
+ "prof", | |
+ "winch", | |
+ "io", | |
+ "pwr", | |
+ "sys", | |
+ "emt", | |
+ "lost", | |
+ "unused", | |
+ | |
+ "exists", /* always last existance test mapped to MAXMAPPED_SIG */ | |
+}; | |
+ | |
diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c | |
index 777ac1c..d6e373f 100644 | |
--- a/security/apparmor/ipc.c | |
+++ b/security/apparmor/ipc.c | |
@@ -4,7 +4,7 @@ | |
* This file contains AppArmor ipc mediation | |
* | |
* Copyright (C) 1998-2008 Novell/SUSE | |
- * Copyright 2009-2010 Canonical Ltd. | |
+ * Copyright 2009-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 | |
@@ -20,92 +20,200 @@ | |
#include "include/context.h" | |
#include "include/policy.h" | |
#include "include/ipc.h" | |
+#include "include/sig_names.h" | |
+ | |
+/** | |
+ * audit_ptrace_mask - convert mask to permission string | |
+ * @buffer: buffer to write string to (NOT NULL) | |
+ * @mask: permission mask to convert | |
+ */ | |
+static void audit_ptrace_mask(struct audit_buffer *ab, u32 mask) | |
+{ | |
+ switch (mask) { | |
+ case MAY_READ: | |
+ audit_log_string(ab, "read"); | |
+ break; | |
+ case MAY_WRITE: | |
+ audit_log_string(ab, "trace"); | |
+ break; | |
+ case AA_MAY_BE_READ: | |
+ audit_log_string(ab, "readby"); | |
+ break; | |
+ case AA_MAY_BE_TRACED: | |
+ audit_log_string(ab, "tracedby"); | |
+ break; | |
+ } | |
+} | |
/* call back to audit ptrace fields */ | |
-static void audit_cb(struct audit_buffer *ab, void *va) | |
+static void audit_ptrace_cb(struct audit_buffer *ab, void *va) | |
{ | |
struct common_audit_data *sa = va; | |
- audit_log_format(ab, " target="); | |
- audit_log_untrustedstring(ab, sa->aad->target); | |
+ | |
+ if (aad(sa)->request & AA_PTRACE_PERM_MASK) { | |
+ audit_log_format(ab, " requested_mask="); | |
+ audit_ptrace_mask(ab, aad(sa)->request); | |
+ | |
+ if (aad(sa)->denied & AA_PTRACE_PERM_MASK) { | |
+ audit_log_format(ab, " denied_mask="); | |
+ audit_ptrace_mask(ab, aad(sa)->denied); | |
+ } | |
+ } | |
+ audit_log_format(ab, " peer="); | |
+ aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, | |
+ FLAGS_NONE, GFP_ATOMIC); | |
} | |
-/** | |
- * aa_audit_ptrace - do auditing for ptrace | |
- * @profile: profile being enforced (NOT NULL) | |
- * @target: profile being traced (NOT NULL) | |
- * @error: error condition | |
- * | |
- * Returns: %0 or error code | |
- */ | |
-static int aa_audit_ptrace(struct aa_profile *profile, | |
- struct aa_profile *target, int error) | |
+/* TODO: conditionals */ | |
+static int profile_ptrace_perm(struct aa_profile *profile, | |
+ struct aa_profile *peer, u32 request, | |
+ struct common_audit_data *sa) | |
{ | |
- struct common_audit_data sa; | |
- struct apparmor_audit_data aad = {0,}; | |
- sa.type = LSM_AUDIT_DATA_NONE; | |
- sa.aad = &aad; | |
- aad.op = OP_PTRACE; | |
- aad.target = target; | |
- aad.error = error; | |
- | |
- return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa, | |
- audit_cb); | |
+ struct aa_perms perms = { }; | |
+ | |
+ /* need because of peer in cross check */ | |
+ if (profile_unconfined(profile) || | |
+ !PROFILE_MEDIATES(profile, AA_CLASS_PTRACE)) | |
+ return 0; | |
+ | |
+ aad(sa)->peer = &peer->label; | |
+ aa_profile_match_label(profile, &peer->label, AA_CLASS_PTRACE, request, | |
+ &perms); | |
+ aa_apply_modes_to_perms(profile, &perms); | |
+ return aa_check_perms(profile, &perms, request, sa, audit_ptrace_cb); | |
+} | |
+ | |
+static int cross_ptrace_perm(struct aa_profile *tracer, | |
+ struct aa_profile *tracee, u32 request, | |
+ struct common_audit_data *sa) | |
+{ | |
+ if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE)) | |
+ return xcheck(profile_ptrace_perm(tracer, tracee, request, sa), | |
+ profile_ptrace_perm(tracee, tracer, | |
+ request << PTRACE_PERM_SHIFT, | |
+ sa)); | |
+ /* policy uses the old style capability check for ptrace */ | |
+ if (profile_unconfined(tracer) || tracer == tracee) | |
+ return 0; | |
+ | |
+ aad(sa)->label = &tracer->label; | |
+ aad(sa)->peer = &tracee->label; | |
+ aad(sa)->request = 0; | |
+ aad(sa)->error = aa_capable(&tracer->label, CAP_SYS_PTRACE, 1); | |
+ return aa_audit(AUDIT_APPARMOR_AUTO, tracer, sa, audit_ptrace_cb); | |
} | |
/** | |
* aa_may_ptrace - test if tracer task can trace the tracee | |
- * @tracer: profile of the task doing the tracing (NOT NULL) | |
- * @tracee: task to be traced | |
- * @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH | |
+ * @tracer: label of the task doing the tracing (NOT NULL) | |
+ * @tracee: task label to be traced | |
+ * @request: permission request | |
* | |
* Returns: %0 else error code if permission denied or error | |
*/ | |
-int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, | |
- unsigned int mode) | |
+int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee, | |
+ u32 request) | |
{ | |
- /* TODO: currently only based on capability, not extended ptrace | |
- * rules, | |
- * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH | |
- */ | |
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE); | |
- if (unconfined(tracer) || tracer == tracee) | |
- return 0; | |
- /* log this capability request */ | |
- return aa_capable(tracer, CAP_SYS_PTRACE, 1); | |
+ return xcheck_labels_profiles(tracer, tracee, cross_ptrace_perm, | |
+ request, &sa); | |
+} | |
+ | |
+ | |
+static inline int map_signal_num(int sig) | |
+{ | |
+ if (sig > SIGRTMAX) | |
+ return SIGUNKNOWN; | |
+ else if (sig >= SIGRTMIN) | |
+ return sig - SIGRTMIN + 128; /* rt sigs mapped to 128 */ | |
+ else if (sig <= MAXMAPPED_SIG) | |
+ return sig_map[sig]; | |
+ return SIGUNKNOWN; | |
} | |
/** | |
- * aa_ptrace - do ptrace permission check and auditing | |
- * @tracer: task doing the tracing (NOT NULL) | |
- * @tracee: task being traced (NOT NULL) | |
- * @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH | |
- * | |
- * Returns: %0 else error code if permission denied or error | |
+ * audit_file_mask - convert mask to permission string | |
+ * @buffer: buffer to write string to (NOT NULL) | |
+ * @mask: permission mask to convert | |
*/ | |
-int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, | |
- unsigned int mode) | |
+static void audit_signal_mask(struct audit_buffer *ab, u32 mask) | |
+{ | |
+ if (mask & MAY_READ) | |
+ audit_log_string(ab, "receive"); | |
+ if (mask & MAY_WRITE) | |
+ audit_log_string(ab, "send"); | |
+} | |
+ | |
+/** | |
+ * audit_cb - call back for signal specific audit fields | |
+ * @ab: audit_buffer (NOT NULL) | |
+ * @va: audit struct to audit values of (NOT NULL) | |
+ */ | |
+static void audit_signal_cb(struct audit_buffer *ab, void *va) | |
+{ | |
+ struct common_audit_data *sa = va; | |
+ | |
+ if (aad(sa)->request & AA_SIGNAL_PERM_MASK) { | |
+ audit_log_format(ab, " requested_mask="); | |
+ audit_signal_mask(ab, aad(sa)->request); | |
+ if (aad(sa)->denied & AA_SIGNAL_PERM_MASK) { | |
+ audit_log_format(ab, " denied_mask="); | |
+ audit_signal_mask(ab, aad(sa)->denied); | |
+ } | |
+ } | |
+ if (aad(sa)->signal <= MAXMAPPED_SIG) | |
+ audit_log_format(ab, " signal=%s", sig_names[aad(sa)->signal]); | |
+ else | |
+ audit_log_format(ab, " signal=rtmin+%d", | |
+ aad(sa)->signal - 128); | |
+ audit_log_format(ab, " peer="); | |
+ aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, | |
+ FLAGS_NONE, GFP_ATOMIC); | |
+} | |
+ | |
+/* TODO: update to handle compound name&name2, conditionals */ | |
+static void profile_match_signal(struct aa_profile *profile, const char *label, | |
+ int signal, struct aa_perms *perms) | |
{ | |
- /* | |
- * tracer can ptrace tracee when | |
- * - tracer is unconfined || | |
- * - tracer is in complain mode | |
- * - tracer has rules allowing it to trace tracee currently this is: | |
- * - confined by the same profile || | |
- * - tracer profile has CAP_SYS_PTRACE | |
- */ | |
+ unsigned int state; | |
+ /* TODO: secondary cache check <profile, profile, perm> */ | |
+ state = aa_dfa_next(profile->policy.dfa, | |
+ profile->policy.start[AA_CLASS_SIGNAL], | |
+ signal); | |
+ state = aa_dfa_match(profile->policy.dfa, state, label); | |
+ aa_compute_perms(profile->policy.dfa, state, perms); | |
+} | |
- struct aa_profile *tracer_p = aa_get_task_profile(tracer); | |
- int error = 0; | |
+static int profile_signal_perm(struct aa_profile *profile, | |
+ struct aa_profile *peer, u32 request, | |
+ struct common_audit_data *sa) | |
+{ | |
+ struct aa_perms perms; | |
- if (!unconfined(tracer_p)) { | |
- struct aa_profile *tracee_p = aa_get_task_profile(tracee); | |
+ if (profile_unconfined(profile) || | |
+ !PROFILE_MEDIATES(profile, AA_CLASS_SIGNAL)) | |
+ return 0; | |
- error = aa_may_ptrace(tracer_p, tracee_p, mode); | |
- error = aa_audit_ptrace(tracer_p, tracee_p, error); | |
+ aad(sa)->peer = &peer->label; | |
+ profile_match_signal(profile, aa_peer_name(peer), aad(sa)->signal, | |
+ &perms); | |
+ aa_apply_modes_to_perms(profile, &perms); | |
+ return aa_check_perms(profile, &perms, request, sa, audit_signal_cb); | |
+} | |
- aa_put_profile(tracee_p); | |
- } | |
- aa_put_profile(tracer_p); | |
+static int aa_signal_cross_perm(struct aa_profile *sender, | |
+ struct aa_profile *target, | |
+ struct common_audit_data *sa) | |
+{ | |
+ return xcheck(profile_signal_perm(sender, target, MAY_WRITE, sa), | |
+ profile_signal_perm(target, sender, MAY_READ, sa)); | |
+} | |
- return error; | |
+int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig) | |
+{ | |
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SIGNAL); | |
+ aad(&sa)->signal = map_signal_num(sig); | |
+ return xcheck_labels_profiles(sender, target, aa_signal_cross_perm, | |
+ &sa); | |
} | |
diff --git a/security/apparmor/label.c b/security/apparmor/label.c | |
new file mode 100644 | |
index 0000000..21c9d6f | |
--- /dev/null | |
+++ b/security/apparmor/label.c | |
@@ -0,0 +1,2077 @@ | |
+/* | |
+ * AppArmor security module | |
+ * | |
+ * This file contains AppArmor label definitions | |
+ * | |
+ * Copyright 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. | |
+ */ | |
+ | |
+#include <linux/audit.h> | |
+#include <linux/seq_file.h> | |
+#include <linux/sort.h> | |
+ | |
+#include "include/apparmor.h" | |
+#include "include/context.h" | |
+#include "include/label.h" | |
+#include "include/policy.h" | |
+#include "include/sid.h" | |
+ | |
+ | |
+/* | |
+ * the aa_label represents the set of profiles confining an object | |
+ * | |
+ * Labels maintain a reference count to the set of pointers they reference | |
+ * Labels are ref counted by | |
+ * tasks and object via the security field/security context off the field | |
+ * code - will take a ref count on a label if it needs the label | |
+ * beyond what is possible with an rcu_read_lock. | |
+ * profiles - each profile is a label | |
+ * sids - a pinned sid will keep a refcount of the label it is | |
+ * referencing | |
+ * objects - inode, files, sockets, ... | |
+ * | |
+ * Labels are not ref counted by the label set, so they maybe removed and | |
+ * freed when no longer in use. | |
+ * | |
+ */ | |
+ | |
+#define PROXY_POISON 97 | |
+#define LABEL_POISON 100 | |
+ | |
+static void free_proxy(struct aa_proxy *proxy) | |
+{ | |
+ if (proxy) { | |
+ /* p->label will not updated any more as p is dead */ | |
+ aa_put_label(rcu_dereference_protected(proxy->label, true)); | |
+ memset(proxy, 0, sizeof(*proxy)); | |
+ proxy->label = (struct aa_label *) PROXY_POISON; | |
+ kfree(proxy); | |
+ } | |
+} | |
+ | |
+void aa_proxy_kref(struct kref *kref) | |
+{ | |
+ struct aa_proxy *proxy = container_of(kref, struct aa_proxy, count); | |
+ free_proxy(proxy); | |
+} | |
+ | |
+struct aa_proxy *aa_alloc_proxy(struct aa_label *label, gfp_t gfp) | |
+{ | |
+ struct aa_proxy *new; | |
+ | |
+ new = kzalloc(sizeof(struct aa_proxy), gfp); | |
+ if (new) { | |
+ kref_init(&new->count); | |
+ rcu_assign_pointer(new->label, aa_get_label(label)); | |
+ } | |
+ return new; | |
+} | |
+ | |
+/* requires profile list write lock held */ | |
+void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new) | |
+{ | |
+ struct aa_label *tmp; | |
+ | |
+ AA_BUG(!orig); | |
+ AA_BUG(!new); | |
+ AA_BUG(!write_is_locked(&labels_set(orig)->lock)); | |
+ | |
+ tmp = rcu_dereference_protected(orig->proxy->label, | |
+ &labels_ns(orig)->lock); | |
+ rcu_assign_pointer(orig->proxy->label, aa_get_label(new)); | |
+ orig->flags |= FLAG_STALE; | |
+ aa_put_label(tmp); | |
+} | |
+ | |
+static void __proxy_share(struct aa_label *old, struct aa_label *new) | |
+{ | |
+ struct aa_proxy *proxy = new->proxy; | |
+ new->proxy = aa_get_proxy(old->proxy); | |
+ __aa_proxy_redirect(old, new); | |
+ aa_put_proxy(proxy); | |
+} | |
+ | |
+ | |
+/** | |
+ * ns_cmp - compare ns for label set ordering | |
+ * @a: ns to compare (NOT NULL) | |
+ * @b: ns to compare (NOT NULL) | |
+ * | |
+ * Returns: <0 if a < b | |
+ * ==0 if a == b | |
+ * >0 if a > b | |
+ */ | |
+static int ns_cmp(struct aa_ns *a, struct aa_ns *b) | |
+{ | |
+ int res; | |
+ | |
+ AA_BUG(!a); | |
+ AA_BUG(!b); | |
+ AA_BUG(!a->base.name); | |
+ AA_BUG(!b->base.name); | |
+ | |
+ if (a == b) | |
+ return 0; | |
+ | |
+ res = a->level - b->level; | |
+ if (res) | |
+ return res; | |
+ | |
+ return strcmp(a->base.name, b->base.name); | |
+} | |
+ | |
+/** | |
+ * profile_cmp - profile comparision for set ordering | |
+ * @a: profile to compare (NOT NULL) | |
+ * @b: profile to compare (NOT NULL) | |
+ * | |
+ * Returns: <0 if a < b | |
+ * ==0 if a == b | |
+ * >0 if a > b | |
+ */ | |
+static int profile_cmp(struct aa_profile *a, struct aa_profile *b) | |
+{ | |
+ int res; | |
+ | |
+ AA_BUG(!a); | |
+ AA_BUG(!b); | |
+ AA_BUG(!a->ns); | |
+ AA_BUG(!b->ns); | |
+ AA_BUG(!a->base.hname); | |
+ AA_BUG(!b->base.hname); | |
+ | |
+ if (a == b || a->base.hname == b->base.hname) | |
+ return 0; | |
+ res = ns_cmp(a->ns, b->ns); | |
+ if (res) | |
+ return res; | |
+ | |
+ return strcmp(a->base.hname, b->base.hname); | |
+} | |
+ | |
+/** | |
+ * vec_cmp - label comparision for set ordering | |
+ * @a: label to compare (NOT NULL) | |
+ * @vec: vector of profiles to compare (NOT NULL) | |
+ * @n: length of @vec | |
+ * | |
+ * Returns: <0 if a < vec | |
+ * ==0 if a == vec | |
+ * >0 if a > vec | |
+ */ | |
+static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn) | |
+{ | |
+ int i; | |
+ | |
+ AA_BUG(!a); | |
+ AA_BUG(!*a); | |
+ AA_BUG(!b); | |
+ AA_BUG(!*b); | |
+ AA_BUG(an <= 0); | |
+ AA_BUG(bn <= 0); | |
+ | |
+ for (i = 0; i < an && i < bn; i++) { | |
+ int res = profile_cmp(a[i], b[i]); | |
+ if (res != 0) | |
+ return res; | |
+ } | |
+ | |
+ return an - bn; | |
+} | |
+ | |
+static bool vec_is_stale(struct aa_profile **vec, int n) | |
+{ | |
+ int i; | |
+ | |
+ AA_BUG(!vec); | |
+ | |
+ for (i = 0; i < n; i++) { | |
+ if (profile_is_stale(vec[i])) | |
+ return true; | |
+ } | |
+ | |
+ return false; | |
+} | |
+ | |
+static bool vec_unconfined(struct aa_profile **vec, int n) | |
+{ | |
+ int i; | |
+ | |
+ AA_BUG(!vec); | |
+ | |
+ for (i = 0; i < n; i++) { | |
+ if (!profile_unconfined(vec[i])) | |
+ return false; | |
+ } | |
+ | |
+ return true; | |
+} | |
+ | |
+static int sort_cmp(const void *a, const void *b) | |
+{ | |
+ return profile_cmp(*(struct aa_profile **)a, *(struct aa_profile **)b); | |
+} | |
+ | |
+/* assumes vec is sorted | |
+ * Assumes @vec has null terminator at vec[n], and will null terminate | |
+ * vec[n - dups] | |
+*/ | |
+static inline int unique(struct aa_profile **vec, int n) | |
+{ | |
+ int i, pos, dups = 0; | |
+ | |
+ AA_BUG(n < 1); | |
+ AA_BUG(!vec); | |
+ | |
+ pos = 0; | |
+ for (i = 1; 1 < n; i++) { | |
+ int res = profile_cmp(vec[pos], vec[i]); | |
+ AA_BUG(res > 0, "vec not sorted"); | |
+ if (res == 0) { | |
+ /* drop duplicate */ | |
+ aa_put_profile(vec[i]); | |
+ dups++; | |
+ continue; | |
+ } | |
+ pos++; | |
+ if (dups) | |
+ vec[pos] = vec[i]; | |
+ } | |
+ | |
+ AA_BUG(dups < 0); | |
+ | |
+ return dups; | |
+} | |
+ | |
+/** | |
+ * vec_unique - canonical sort and unique a list of profiles | |
+ * @n: number of refcounted profiles in the list (@n > 0) | |
+ * @vec: list of profiles to sort and merge | |
+ * | |
+ * Returns: the number of duplicates eliminated == references put | |
+ * | |
+ * If @flags & VEC_FLAG_TERMINATE @vec has null terminator at vec[n], and will | |
+ * null terminate vec[n - dups] | |
+ */ | |
+int aa_vec_unique(struct aa_profile **vec, int n, int flags) | |
+{ | |
+ int i, dups = 0; | |
+ | |
+ AA_BUG(n < 1); | |
+ AA_BUG(!vec); | |
+ | |
+ /* vecs are usually small and inorder, have a fallback for larger */ | |
+ if (n > 8) { | |
+ sort(vec, n, sizeof(struct aa_profile *), sort_cmp, NULL); | |
+ dups = unique(vec, n); | |
+ goto out; | |
+ } | |
+ | |
+ /* insertion sort + unique in one */ | |
+ for (i = 1; i < n; i++) { | |
+ struct aa_profile *tmp = vec[i]; | |
+ int pos, j; | |
+ | |
+ for (pos = i - 1 - dups; pos >= 0; pos--) { | |
+ int res = profile_cmp(vec[pos], tmp); | |
+ if (res == 0) { | |
+ /* drop duplicate entry */ | |
+ aa_put_profile(tmp); | |
+ dups++; | |
+ goto continue_outer; | |
+ } else if (res < 0) | |
+ break; | |
+ } | |
+ /* pos is at entry < tmp, or index -1. Set to insert pos */ | |
+ pos++; | |
+ | |
+ for (j = i - dups; j > pos; j--) | |
+ vec[j] = vec[j - 1]; | |
+ vec[pos] = tmp; | |
+ continue_outer: ; | |
+ } | |
+ | |
+ AA_BUG(dups < 0); | |
+ | |
+out: | |
+ if (flags & VEC_FLAG_TERMINATE) | |
+ vec[n - dups] = NULL; | |
+ | |
+ return dups; | |
+} | |
+ | |
+ | |
+static void label_destroy(struct aa_label *label) | |
+{ | |
+ AA_BUG(!label); | |
+ | |
+ if (label_is_stale(label)) | |
+ labelsetstats_dec(labels_set(label), stale); | |
+ | |
+ if (!label_isprofile(label)) { | |
+ struct aa_profile *profile; | |
+ struct label_it i; | |
+ | |
+ aa_put_str(label->hname); | |
+ | |
+ label_for_each(i, label, profile) { | |
+ aa_put_profile(profile); | |
+ label->vec[i.i] = (struct aa_profile *) (LABEL_POISON + (long) i.i); | |
+ } | |
+ } | |
+ | |
+ if (rcu_dereference_protected(label->proxy->label, true) == label) | |
+ rcu_assign_pointer(label->proxy->label, NULL); | |
+ | |
+ aa_free_sid(label->sid); | |
+ aa_put_proxy(label->proxy); | |
+ label->proxy = (struct aa_proxy *) PROXY_POISON + 1; | |
+} | |
+ | |
+void aa_label_free(struct aa_label *label) | |
+{ | |
+ if (!label) | |
+ return; | |
+ | |
+ label_destroy(label); | |
+ labelstats_inc(freed); | |
+ kfree(label); | |
+} | |
+ | |
+static void label_free_switch(struct aa_label *label) | |
+{ | |
+ if (label->flags & FLAG_NS_COUNT) | |
+ aa_free_ns(labels_ns(label)); | |
+ else if (label_isprofile(label)) | |
+ aa_free_profile(labels_profile(label)); | |
+ else | |
+ aa_label_free(label); | |
+} | |
+ | |
+static void label_free_rcu(struct rcu_head *head) | |
+{ | |
+ struct aa_label *label = container_of(head, struct aa_label, rcu); | |
+ | |
+ if (label->flags & FLAG_IN_TREE) | |
+ (void) aa_label_remove(label); | |
+ label_free_switch(label); | |
+} | |
+ | |
+void aa_label_kref(struct kref *kref) | |
+{ | |
+ struct aa_label *label = container_of(kref, struct aa_label, count); | |
+ struct aa_ns *ns = labels_ns(label); | |
+ | |
+ if (!ns) { | |
+ /* never live, no rcu callback needed, just using the fn */ | |
+ label_free_switch(label); | |
+ return; | |
+ } | |
+ /* TODO: update labels_profile macro so it works here */ | |
+ AA_BUG(label_isprofile(label) && on_list_rcu(&label->vec[0]->base.profiles)); | |
+ AA_BUG(label_isprofile(label) && on_list_rcu(&label->vec[0]->base.list)); | |
+ | |
+ /* TODO: if compound label and not stale add to reclaim cache */ | |
+ call_rcu(&label->rcu, label_free_rcu); | |
+} | |
+ | |
+bool aa_label_init(struct aa_label *label, int size) | |
+{ | |
+ AA_BUG(!label); | |
+ AA_BUG(size < 1); | |
+ | |
+ label->sid = aa_alloc_sid(); | |
+ if (label->sid == AA_SID_INVALID) | |
+ return false; | |
+ | |
+ label->size = size; /* doesn't include null */ | |
+ label->vec[size] = NULL; /* null terminate */ | |
+ kref_init(&label->count); | |
+ RB_CLEAR_NODE(&label->node); | |
+ | |
+ return true; | |
+} | |
+ | |
+/** | |
+ * aa_label_alloc - allocate a label with a profile vector of @size length | |
+ * @size: size of profile vector in the label | |
+ * @proxy: proxy to use OR null if to allocate a new one | |
+ * @gfp: memory allocation type | |
+ * | |
+ * Returns: new label | |
+ * else NULL if failed | |
+ */ | |
+struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp) | |
+{ | |
+ struct aa_label *new; | |
+ | |
+ AA_BUG(size < 1); | |
+ | |
+ /* + 1 for null terminator entry on vec */ | |
+ new = kzalloc(sizeof(*new) + sizeof(struct aa_profile *) * (size + 1), | |
+ gfp); | |
+ AA_DEBUG("%s (%p)\n", __func__, new); | |
+ if (!new) | |
+ goto fail; | |
+ | |
+ if (!aa_label_init(new, size)) | |
+ goto fail; | |
+ | |
+ if (!proxy) { | |
+ proxy = aa_alloc_proxy(new, gfp); | |
+ if (!proxy) | |
+ goto fail; | |
+ } else | |
+ aa_get_proxy(proxy); | |
+ /* just set new's proxy, don't redirect proxy here if it was passed in*/ | |
+ new->proxy = proxy; | |
+ | |
+ labelstats_inc(allocated); | |
+ | |
+ return new; | |
+ | |
+fail: | |
+ kfree(new); | |
+ labelstats_inc(failed); | |
+ | |
+ return NULL; | |
+} | |
+ | |
+ | |
+/** | |
+ * label_cmp - label comparision for set ordering | |
+ * @a: label to compare (NOT NULL) | |
+ * @b: label to compare (NOT NULL) | |
+ * | |
+ * Returns: <0 if a < b | |
+ * ==0 if a == b | |
+ * >0 if a > b | |
+ */ | |
+static int label_cmp(struct aa_label *a, struct aa_label *b) | |
+{ | |
+ AA_BUG(!b); | |
+ | |
+ if (a == b) | |
+ return 0; | |
+ | |
+ return vec_cmp(a->vec, a->size, b->vec, b->size); | |
+} | |
+ | |
+/* helper fn for label_for_each_confined */ | |
+int aa_label_next_confined(struct aa_label *label, int i) | |
+{ | |
+ AA_BUG(!label); | |
+ AA_BUG(i < 0); | |
+ | |
+ for (; i < label->size; i++) { | |
+ if (!profile_unconfined(label->vec[i])) | |
+ return i; | |
+ } | |
+ | |
+ return i; | |
+} | |
+ | |
+/** | |
+ * aa_label_next_not_in_set - return the next profile of @sub not in @set | |
+ * @I: label iterator | |
+ * @set: label to test against | |
+ * @sub: label to if is subset of @set | |
+ * | |
+ * Returns: profile in @sub that is not in @set, with iterator set pos after | |
+ * else NULL if @sub is a subset of @set | |
+ */ | |
+struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, | |
+ struct aa_label *set, | |
+ struct aa_label *sub) | |
+{ | |
+ AA_BUG(!set); | |
+ AA_BUG(!I); | |
+ AA_BUG(I->i < 0); | |
+ AA_BUG(I->i > set->size); | |
+ AA_BUG(!sub); | |
+ AA_BUG(I->j < 0); | |
+ AA_BUG(I->j > sub->size); | |
+ | |
+ while (I->j < sub->size && I->i < set->size) { | |
+ int res = profile_cmp(sub->vec[I->j], set->vec[I->i]); | |
+ if (res == 0) { | |
+ (I->j)++; | |
+ (I->i)++; | |
+ } else if (res > 0) | |
+ (I->i)++; | |
+ else | |
+ return sub->vec[(I->j)++]; | |
+ } | |
+ | |
+ if (I->j < sub->size) | |
+ return sub->vec[(I->j)++]; | |
+ | |
+ return NULL; | |
+} | |
+ | |
+/** | |
+ * aa_label_is_subset - test if @sub is a subset of @set | |
+ * @set: label to test against | |
+ * @sub: label to test if is subset of @set | |
+ * | |
+ * Returns: true if @sub is subset of @set | |
+ * else false | |
+ */ | |
+bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub) | |
+{ | |
+ struct label_it i = { }; | |
+ | |
+ AA_BUG(!set); | |
+ AA_BUG(!sub); | |
+ | |
+ if (sub == set) | |
+ return true; | |
+ | |
+ return __aa_label_next_not_in_set(&i, set, sub) == NULL; | |
+} | |
+ | |
+ | |
+ | |
+/** | |
+ * __label_remove - remove @label from the label set | |
+ * @l: label to remove | |
+ * @new: label to redirect to | |
+ * | |
+ * Requires: labels_set(@label)->lock write_lock | |
+ * Returns: true if the label was in the tree and removed | |
+ */ | |
+static bool __label_remove(struct aa_label *label, struct aa_label *new) | |
+{ | |
+ struct aa_labelset *ls = labels_set(label); | |
+ AA_BUG(!ls); | |
+ AA_BUG(!label); | |
+ AA_BUG(!write_is_locked(&ls->lock)); | |
+ | |
+ if (new) | |
+ __aa_proxy_redirect(label, new); | |
+ | |
+ if (label_is_stale(label)) | |
+ labelstats_dec(stale_intree); | |
+ else | |
+ __label_make_stale(label); | |
+ | |
+ if (label->flags & FLAG_IN_TREE) { | |
+ labelsetstats_dec(ls, intree); | |
+ rb_erase(&label->node, &ls->root); | |
+ label->flags &= ~FLAG_IN_TREE; | |
+ return true; | |
+ } | |
+ | |
+ return false; | |
+} | |
+ | |
+/** | |
+ * __label_replace - replace @old with @new in label set | |
+ * @old: label to remove from label set | |
+ * @new: label to replace @old with | |
+ * | |
+ * Requires: labels_set(@old)->lock write_lock | |
+ * valid ref count be held on @new | |
+ * Returns: true if @old was in set and replaced by @new | |
+ * | |
+ * Note: current implementation requires label set be order in such a way | |
+ * that @new directly replaces @old position in the set (ie. | |
+ * using pointer comparison of the label address would not work) | |
+ */ | |
+static bool __label_replace(struct aa_label *old, struct aa_label *new) | |
+{ | |
+ struct aa_labelset *ls = labels_set(old); | |
+ AA_BUG(!ls); | |
+ AA_BUG(!old); | |
+ AA_BUG(!new); | |
+ AA_BUG(!write_is_locked(&ls->lock)); | |
+ AA_BUG(new->flags & FLAG_IN_TREE); | |
+ | |
+ if (label_is_stale(old)) | |
+ labelstats_dec(stale_intree); | |
+ else | |
+ __label_make_stale(old); | |
+ | |
+ if (old->flags & FLAG_IN_TREE) { | |
+ rb_replace_node(&old->node, &new->node, &ls->root); | |
+ old->flags &= ~FLAG_IN_TREE; | |
+ new->flags |= FLAG_IN_TREE; | |
+ return true; | |
+ } | |
+ | |
+ return false; | |
+} | |
+ | |
+/** | |
+ * __label_insert - attempt to insert @l into a label set | |
+ * @ls: set of labels to insert @l into (NOT NULL) | |
+ * @label: new label to insert (NOT NULL) | |
+ * @replace: whether insertion should replace existing entry that is not stale | |
+ * | |
+ * Requires: @ls->lock | |
+ * caller to hold a valid ref on l | |
+ * if @replace is true l has a preallocated proxy associated | |
+ * Returns: @l if successful in inserting @l - with additional refcount | |
+ * else ref counted equivalent label that is already in the set, | |
+ the else condition only happens if @replace is false | |
+ */ | |
+static struct aa_label *__label_insert(struct aa_labelset *ls, | |
+ struct aa_label *label, bool replace) | |
+{ | |
+ struct rb_node **new, *parent = NULL; | |
+ | |
+ AA_BUG(!ls); | |
+ AA_BUG(!label); | |
+ AA_BUG(labels_set(label) != ls); | |
+ AA_BUG(!write_is_locked(&ls->lock)); | |
+ AA_BUG(label->flags & FLAG_IN_TREE); | |
+ | |
+ /* Figure out where to put new node */ | |
+ new = &ls->root.rb_node; | |
+ while (*new) { | |
+ struct aa_label *this = rb_entry(*new, struct aa_label, node); | |
+ int result = label_cmp(label, this); | |
+ | |
+ parent = *new; | |
+ if (result == 0) { | |
+ labelsetstats_inc(ls, existing); | |
+ /* !aa_get_label_not0 means queued for destruction, | |
+ * so replace in place, however the label has | |
+ * died before the replacement so do not share | |
+ * the proxy | |
+ */ | |
+ if (!replace && !label_is_stale(this)) { | |
+ if (aa_get_label_not0(this)) | |
+ return this; | |
+ } else | |
+ __proxy_share(this, label); | |
+ AA_BUG(!__label_replace(this, label)); | |
+ return aa_get_label(label); | |
+ } else if (result < 0) | |
+ new = &((*new)->rb_left); | |
+ else /* (result > 0) */ | |
+ new = &((*new)->rb_right); | |
+ } | |
+ | |
+ /* Add new node and rebalance tree. */ | |
+ rb_link_node(&label->node, parent, new); | |
+ rb_insert_color(&label->node, &ls->root); | |
+ label->flags |= FLAG_IN_TREE; | |
+ labelsetstats_inc(ls, insert); | |
+ labelsetstats_inc(ls, intree); | |
+ | |
+ return aa_get_label(label); | |
+} | |
+ | |
+/** | |
+ * __vec_find - find label that matches @vec in label set | |
+ * @vec: vec of profiles to find matching label for (NOT NULL) | |
+ * @n: length of @vec | |
+ * | |
+ * Requires: @vec_labelset(vec) lock held | |
+ * caller to hold a valid ref on l | |
+ * | |
+ * Returns: ref counted @label if matching label is in tree | |
+ * ref counted label that is equiv to @l in tree | |
+ * else NULL if @vec equiv is not in tree | |
+ */ | |
+static struct aa_label *__vec_find(struct aa_profile **vec, int n) | |
+{ | |
+ struct rb_node *node; | |
+ | |
+ AA_BUG(!vec); | |
+ AA_BUG(!*vec); | |
+ AA_BUG(n <= 0); | |
+ | |
+ node = vec_labelset(vec, n)->root.rb_node; | |
+ while (node) { | |
+ struct aa_label *this = rb_entry(node, struct aa_label, node); | |
+ int result = vec_cmp(this->vec, this->size, vec, n); | |
+ | |
+ if (result > 0) | |
+ node = node->rb_left; | |
+ else if (result < 0) | |
+ node = node->rb_right; | |
+ else | |
+ return aa_get_label_not0(this); | |
+ } | |
+ | |
+ return NULL; | |
+} | |
+ | |
+/** | |
+ * __label_find - find label @label in label set | |
+ * @label: label to find (NOT NULL) | |
+ * | |
+ * Requires: labels_set(@label)->lock held | |
+ * caller to hold a valid ref on l | |
+ * | |
+ * Returns: ref counted @label if @label is in tree OR | |
+ * ref counted label that is equiv to @label in tree | |
+ * else NULL if @label or equiv is not in tree | |
+ */ | |
+static struct aa_label *__label_find(struct aa_label *label) | |
+{ | |
+ AA_BUG(!label); | |
+ | |
+ return __vec_find(label->vec, label->size); | |
+} | |
+ | |
+ | |
+/** | |
+ * aa_label_remove - remove a label from the labelset | |
+ * @label: label to remove | |
+ * | |
+ * Returns: true if @label was removed from the tree | |
+ * else @label was not in tree so it could not be removed | |
+ */ | |
+bool aa_label_remove(struct aa_label *label) | |
+{ | |
+ struct aa_labelset *ls = labels_set(label); | |
+ unsigned long flags; | |
+ bool res; | |
+ | |
+ AA_BUG(!ls); | |
+ | |
+ write_lock_irqsave(&ls->lock, flags); | |
+ res = __label_remove(label, ns_unconfined(labels_ns(label))); | |
+ write_unlock_irqrestore(&ls->lock, flags); | |
+ | |
+ return res; | |
+} | |
+ | |
+/** | |
+ * aa_label_replace - replace a label @old with a new version @new | |
+ * @old: label to replace | |
+ * @new: label replacing @old | |
+ * | |
+ * Returns: true if @old was in tree and replaced | |
+ * else @old was not in tree, and @new was not inserted | |
+ */ | |
+bool aa_label_replace(struct aa_label *old, struct aa_label *new) | |
+{ | |
+ unsigned long flags; | |
+ bool res; | |
+ | |
+ if (name_is_shared(old, new) && labels_ns(old) == labels_ns(new)) { | |
+ write_lock_irqsave(&labels_set(old)->lock, flags); | |
+ if (old->proxy != new->proxy) { | |
+ __proxy_share(old, new); | |
+ } else | |
+ __aa_proxy_redirect(old, new); | |
+ res = __label_replace(old, new); | |
+ write_unlock_irqrestore(&labels_set(old)->lock, flags); | |
+ } else { | |
+ struct aa_label *l; | |
+ struct aa_labelset *ls = labels_set(old); | |
+ write_lock_irqsave(&ls->lock, flags); | |
+ res = __label_remove(old, new); | |
+ if (labels_ns(old) != labels_ns(new)) { | |
+ write_unlock_irqrestore(&ls->lock, flags); | |
+ ls = labels_set(new); | |
+ write_lock_irqsave(&ls->lock, flags); | |
+ } | |
+ l = __label_insert(ls, new, true); | |
+ res = (l == new); | |
+ write_unlock_irqrestore(&ls->lock, flags); | |
+ aa_put_label(l); | |
+ } | |
+ | |
+ return res; | |
+} | |
+ | |
+/** | |
+ * vec_find - find label @l in label set | |
+ * @vec: array of profiles to find equiv label for (NOT NULL) | |
+ * @n: length of @vec | |
+ * | |
+ * Returns: refcounted label if @vec equiv is in tree | |
+ * else NULL if @vec equiv is not in tree | |
+ */ | |
+static struct aa_label *vec_find(struct aa_profile **vec, int n) | |
+{ | |
+ struct aa_labelset *ls; | |
+ struct aa_label *label; | |
+ unsigned long flags; | |
+ | |
+ AA_BUG(!vec); | |
+ AA_BUG(!*vec); | |
+ AA_BUG(n <= 0); | |
+ | |
+ ls = vec_labelset(vec, n); | |
+ read_lock_irqsave(&ls->lock, flags); | |
+ label = __vec_find(vec, n); | |
+ labelstats_inc(sread); | |
+ read_unlock_irqrestore(&ls->lock, flags); | |
+ | |
+ return label; | |
+} | |
+ | |
+/* requires sort and merge done first */ | |
+static struct aa_label *vec_create_and_insert_label(struct aa_profile **vec, | |
+ int len, gfp_t gfp) | |
+{ | |
+ struct aa_label *label = NULL; | |
+ struct aa_labelset *ls; | |
+ unsigned long flags; | |
+ struct aa_label *new; | |
+ int i; | |
+ | |
+ AA_BUG(!vec); | |
+ | |
+ if (len == 1) | |
+ return aa_get_label(&vec[0]->label); | |
+ | |
+ ls = labels_set(&vec[len - 1]->label); | |
+ | |
+ /* TODO: enable when read side is lockless | |
+ * check if label exists before taking locks | |
+ */ | |
+ new = aa_label_alloc(len, NULL, gfp); | |
+ if (!new) | |
+ return NULL; | |
+ | |
+ for (i = 0; i < len; i++) { | |
+ new->vec[i] = aa_get_profile(vec[i]); | |
+ } | |
+ write_lock_irqsave(&ls->lock, flags); | |
+ label = __label_insert(ls, new, false); | |
+ write_unlock_irqrestore(&ls->lock, flags); | |
+ aa_put_label(new); | |
+ | |
+ return label; | |
+} | |
+ | |
+struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len, | |
+ gfp_t gfp) | |
+{ | |
+ struct aa_label *label = vec_find(vec, len); | |
+ if (label) | |
+ return label; | |
+ | |
+ return vec_create_and_insert_label(vec, len, gfp); | |
+} | |
+ | |
+/** | |
+ * aa_label_find - find label @label in label set | |
+ * @label: label to find (NOT NULL) | |
+ * | |
+ * Requires: caller to hold a valid ref on l | |
+ * | |
+ * Returns: refcounted @label if @label is in tree | |
+ * refcounted label that is equiv to @label in tree | |
+ * else NULL if @label or equiv is not in tree | |
+ */ | |
+struct aa_label *aa_label_find(struct aa_label *label) | |
+{ | |
+ AA_BUG(!label); | |
+ | |
+ return vec_find(label->vec, label->size); | |
+} | |
+ | |
+ | |
+/** | |
+ * aa_label_insert - insert label @label into @ls or return existing label | |
+ * @ls - labelset to insert @label into | |
+ * @label - label to insert | |
+ * | |
+ * Requires: caller to hold a valid ref on @label | |
+ * | |
+ * Returns: ref counted @label if successful in inserting @label | |
+ * else ref counted equivalent label that is already in the set | |
+ */ | |
+struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *label) | |
+{ | |
+ struct aa_label *l; | |
+ unsigned long flags; | |
+ | |
+ AA_BUG(!ls); | |
+ AA_BUG(!label); | |
+ | |
+ /* check if label exists before taking lock */ | |
+ if (!label_is_stale(label)) { | |
+ read_lock_irqsave(&ls->lock, flags); | |
+ l = __label_find(label); | |
+ read_unlock_irqrestore(&ls->lock, flags); | |
+ labelstats_inc(fread); | |
+ if (l) | |
+ return l; | |
+ } | |
+ | |
+ write_lock_irqsave(&ls->lock, flags); | |
+ l = __label_insert(ls, label, false); | |
+ write_unlock_irqrestore(&ls->lock, flags); | |
+ | |
+ return l; | |
+} | |
+ | |
+ | |
+/** | |
+ * aa_label_next_in_merge - find the next profile when merging @a and @b | |
+ * @I: label iterator | |
+ * @a: label to merge | |
+ * @b: label to merge | |
+ * | |
+ * Returns: next profile | |
+ * else null if no more profiles | |
+ */ | |
+struct aa_profile *aa_label_next_in_merge(struct label_it *I, | |
+ struct aa_label *a, | |
+ struct aa_label *b) | |
+{ | |
+ AA_BUG(!a); | |
+ AA_BUG(!b); | |
+ AA_BUG(!I); | |
+ AA_BUG(I->i < 0); | |
+ AA_BUG(I->i > a->size); | |
+ AA_BUG(I->j < 0); | |
+ AA_BUG(I->j > b->size); | |
+ | |
+ if (I->i < a->size) { | |
+ if (I->j < b->size) { | |
+ int res = profile_cmp(a->vec[I->i], b->vec[I->j]); | |
+ if (res > 0) | |
+ return b->vec[(I->j)++]; | |
+ if (res == 0) | |
+ (I->j)++; | |
+ } | |
+ | |
+ return a->vec[(I->i)++]; | |
+ } | |
+ | |
+ if (I->j < b->size) | |
+ return b->vec[(I->j)++]; | |
+ | |
+ return NULL; | |
+} | |
+ | |
+/** | |
+ * label_merge_cmp - cmp of @a merging with @b against @z for set ordering | |
+ * @a: label to merge then compare (NOT NULL) | |
+ * @b: label to merge then compare (NOT NULL) | |
+ * @z: label to compare merge against (NOT NULL) | |
+ * | |
+ * Assumes: using the most recent versions of @a, @b, and @z | |
+ * | |
+ * Returns: <0 if a < b | |
+ * ==0 if a == b | |
+ * >0 if a > b | |
+ */ | |
+static int label_merge_cmp(struct aa_label *a, struct aa_label *b, | |
+ struct aa_label *z) | |
+{ | |
+ struct aa_profile *p = NULL; | |
+ struct label_it i = { }; | |
+ int k; | |
+ | |
+ AA_BUG(!a); | |
+ AA_BUG(!b); | |
+ AA_BUG(!z); | |
+ | |
+ for (k = 0; | |
+ k < z->size && (p = aa_label_next_in_merge(&i, a, b)); | |
+ k++) { | |
+ int res = profile_cmp(p, z->vec[k]); | |
+ | |
+ if (res != 0) | |
+ return res; | |
+ } | |
+ | |
+ if (p) | |
+ return 1; | |
+ else if (k < z->size) | |
+ return -1; | |
+ return 0; | |
+} | |
+ | |
+#if 0 | |
+/** | |
+ * label_merge_len - find the length of the merge of @a and @b | |
+ * @a: label to merge (NOT NULL) | |
+ * @b: label to merge (NOT NULL) | |
+ * | |
+ * Assumes: using newest versions of labels @a and @b | |
+ * | |
+ * Returns: length of a label vector for merge of @a and @b | |
+ */ | |
+static int label_merge_len(struct aa_label *a, struct aa_label *b) | |
+{ | |
+ int len = a->size + b->size; | |
+ int i, j; | |
+ | |
+ AA_BUG(!a); | |
+ AA_BUG(!b); | |
+ | |
+ /* find entries in common and remove from count */ | |
+ for (i = j = 0; i < a->size && j < b->size; ) { | |
+ int res = profile_cmp(a->vec[i], b->vec[j]); | |
+ if (res == 0) { | |
+ len--; | |
+ i++; | |
+ j++; | |
+ } else if (res < 0) | |
+ i++; | |
+ else | |
+ j++; | |
+ } | |
+ | |
+ return len; | |
+} | |
+#endif | |
+ | |
+/** | |
+ * label_merge_insert - create a new label by merging @a and @b | |
+ * @new: preallocated label to merge into (NOT NULL) | |
+ * @a: label to merge with @b (NOT NULL) | |
+ * @b: label to merge with @a (NOT NULL) | |
+ * | |
+ * Requires: preallocated proxy | |
+ * | |
+ * Returns: ref counted label either @new if merge is unique | |
+ * @a if @b is a subset of @a | |
+ * @b if @a is a subset of @b | |
+ * | |
+ * NOTE: will not use @new if the merge results in @new == @a or @b | |
+ * | |
+ * Must be used within labelset write lock to avoid racing with | |
+ * setting labels stale. | |
+ */ | |
+static struct aa_label *label_merge_insert(struct aa_label *new, | |
+ struct aa_label *a, | |
+ struct aa_label *b) | |
+{ | |
+ struct aa_label *label; | |
+ struct aa_labelset *ls; | |
+ struct aa_profile *next; | |
+ struct label_it i; | |
+ unsigned long flags; | |
+ int k = 0, invcount = 0; | |
+ bool stale = false; | |
+ | |
+ AA_BUG(!a); | |
+ AA_BUG(a->size < 0); | |
+ AA_BUG(!b); | |
+ AA_BUG(b->size < 0); | |
+ AA_BUG(!new); | |
+ AA_BUG(new->size < a->size + b->size); | |
+ | |
+ label_for_each_in_merge(i, a, b, next) { | |
+ if (profile_is_stale(next)) { | |
+ new->vec[k] = aa_get_newest_profile(next); | |
+ if (next->label.proxy != new->vec[k]->label.proxy) | |
+ invcount++; | |
+ k++; | |
+ stale = true; | |
+ } else | |
+ new->vec[k++] = aa_get_profile(next); | |
+ } | |
+ /* set to actual size which is <= allocated len */ | |
+ new->size = k; | |
+ new->vec[k] = NULL; | |
+ | |
+ if (invcount) { | |
+ new->size -= aa_vec_unique(&new->vec[0], new->size, | |
+ VEC_FLAG_TERMINATE); | |
+ } else if (!stale) { | |
+ /* merge could be same as a || b, note: it is not possible | |
+ * for new->size == a->size == b->size unless a == b */ | |
+ if (k == a->size) | |
+ return aa_get_label(a); | |
+ else if (k == b->size) | |
+ return aa_get_label(b); | |
+ } | |
+ if (vec_unconfined(new->vec, new->size)) | |
+ new->flags |= FLAG_UNCONFINED; | |
+ ls = labels_set(new); | |
+ write_lock_irqsave(&ls->lock, flags); | |
+ label = __label_insert(labels_set(new), new, false); | |
+ write_unlock_irqrestore(&ls->lock, flags); | |
+ | |
+ return label; | |
+} | |
+ | |
+/** | |
+ * labelset_of_merge - find into which labelset a merged label should be inserted | |
+ * @a: label to merge and insert | |
+ * @b: label to merge and insert | |
+ * | |
+ * Returns: labelset that the merged label should be inserted into | |
+ */ | |
+static struct aa_labelset *labelset_of_merge(struct aa_label *a, struct aa_label *b) | |
+{ | |
+ struct aa_ns *nsa = labels_ns(a); | |
+ struct aa_ns *nsb = labels_ns(b); | |
+ | |
+ if (ns_cmp(nsa, nsb) <= 0) | |
+ return &nsa->labels; | |
+ return &nsb->labels; | |
+} | |
+ | |
+/** | |
+ * __label_find_merge - find label that is equiv to merge of @a and @b | |
+ * @ls: set of labels to search (NOT NULL) | |
+ * @a: label to merge with @b (NOT NULL) | |
+ * @b: label to merge with @a (NOT NULL) | |
+ * | |
+ * Requires: ls->lock read_lock held | |
+ * | |
+ * Returns: ref counted label that is equiv to merge of @a and @b | |
+ * else NULL if merge of @a and @b is not in set | |
+ */ | |
+static struct aa_label *__label_find_merge(struct aa_labelset *ls, | |
+ struct aa_label *a, | |
+ struct aa_label *b) | |
+{ | |
+ struct rb_node *node; | |
+ | |
+ AA_BUG(!ls); | |
+ AA_BUG(!a); | |
+ AA_BUG(!b); | |
+ | |
+ if (a == b) | |
+ return __label_find(a); | |
+ | |
+ node = ls->root.rb_node; | |
+ while (node) { | |
+ struct aa_label *this = container_of(node, struct aa_label, | |
+ node); | |
+ int result = label_merge_cmp(a, b, this); | |
+ | |
+ if (result < 0) | |
+ node = node->rb_left; | |
+ else if (result > 0) | |
+ node = node->rb_right; | |
+ else | |
+ return aa_get_label_not0(this); | |
+ } | |
+ | |
+ return NULL; | |
+} | |
+ | |
+ | |
+/** | |
+ * aa_label_find_merge - find label that is equiv to merge of @a and @b | |
+ * @a: label to merge with @b (NOT NULL) | |
+ * @b: label to merge with @a (NOT NULL) | |
+ * | |
+ * Requires: labels be fully constructed with a valid ns | |
+ * | |
+ * Returns: ref counted label that is equiv to merge of @a and @b | |
+ * else NULL if merge of @a and @b is not in set | |
+ */ | |
+struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b) | |
+{ | |
+ struct aa_labelset *ls; | |
+ struct aa_label *label, *ar = NULL, *br = NULL; | |
+ unsigned long flags; | |
+ | |
+ AA_BUG(!a); | |
+ AA_BUG(!b); | |
+ | |
+ if (label_is_stale(a)) | |
+ a = ar = aa_get_newest_label(a); | |
+ if (label_is_stale(b)) | |
+ b = br = aa_get_newest_label(b); | |
+ ls = labelset_of_merge(a, b); | |
+ read_lock_irqsave(&ls->lock, flags); | |
+ label = __label_find_merge(ls, a, b); | |
+ read_unlock_irqrestore(&ls->lock, flags); | |
+ aa_put_label(ar); | |
+ aa_put_label(br); | |
+ labelsetstats_inc(ls, msread); | |
+ | |
+ return label; | |
+} | |
+ | |
+/** | |
+ * aa_label_merge - attempt to insert new merged label of @a and @b | |
+ * @ls: set of labels to insert label into (NOT NULL) | |
+ * @a: label to merge with @b (NOT NULL) | |
+ * @b: label to merge with @a (NOT NULL) | |
+ * @gfp: memory allocation type | |
+ * | |
+ * Requires: caller to hold valid refs on @a and @b | |
+ * labels be fully constructed with a valid ns | |
+ * | |
+ * Returns: ref counted new label if successful in inserting merge of a & b | |
+ * else ref counted equivalent label that is already in the set. | |
+ * else NULL if could not create label (-ENOMEM) | |
+ */ | |
+struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, | |
+ gfp_t gfp) | |
+{ | |
+ struct aa_label *label = NULL; | |
+ | |
+ AA_BUG(!a); | |
+ AA_BUG(!b); | |
+ | |
+ if (a == b) | |
+ return aa_get_newest_label(a); | |
+ | |
+ /* TODO: enable when read side is lockless | |
+ * check if label exists before taking locks | |
+ if (!label_is_stale(a) && !label_is_stale(b)) | |
+ label = aa_label_find_merge(a, b); | |
+ */ | |
+ | |
+ if (!label) { | |
+ struct aa_label *new; | |
+ | |
+ a = aa_get_newest_label(a); | |
+ b = aa_get_newest_label(b); | |
+ | |
+ /* could use label_merge_len(a, b), but requires double | |
+ * comparison for small savings | |
+ */ | |
+ new = aa_label_alloc(a->size + b->size, NULL, gfp); | |
+ if (!new) | |
+ goto out; | |
+ | |
+ label = label_merge_insert(new, a, b); | |
+ aa_put_label(new); | |
+ out: | |
+ aa_put_label(a); | |
+ aa_put_label(b); | |
+ } | |
+ | |
+ return label; | |
+} | |
+ | |
+static inline bool label_is_visible(struct aa_profile *profile, | |
+ struct aa_label *label) | |
+{ | |
+ return aa_ns_visible(profile->ns, labels_ns(label), true); | |
+} | |
+ | |
+/* match a profile and its associated ns component if needed | |
+ * Assumes visibility test has already been done. | |
+ * If a subns profile is not to be matched should be prescreened with | |
+ * visibility test. | |
+ */ | |
+static inline unsigned int match_component(struct aa_profile *profile, | |
+ struct aa_profile *tp, | |
+ unsigned int state) | |
+{ | |
+ const char *ns_name; | |
+ | |
+ if (profile->ns == tp->ns) | |
+ return aa_dfa_match(profile->policy.dfa, state, tp->base.hname); | |
+ | |
+ /* try matching with namespace name and then profile */ | |
+ ns_name = aa_ns_name(profile->ns, tp->ns, true); | |
+ state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); | |
+ state = aa_dfa_match(profile->policy.dfa, state, ns_name); | |
+ state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); | |
+ return aa_dfa_match(profile->policy.dfa, state, tp->base.hname); | |
+} | |
+ | |
+/** | |
+ * label_component_match - find perms for full compound label | |
+ * @profile: profile to find perms for | |
+ * @label: label to check access permissions for | |
+ * @start: state to start match in | |
+ * @subns: whether to do permission checks on components in a subns | |
+ * @request: permissions to request | |
+ * @perms: perms struct to set | |
+ * | |
+ * Returns: 0 on success else ERROR | |
+ * | |
+ * For the label A//&B//&C this does the perm match for A//&B//&C | |
+ * @perms should be preinitialized with allperms OR a previous permission | |
+ * check to be stacked. | |
+ */ | |
+static int label_compound_match(struct aa_profile *profile, | |
+ struct aa_label *label, | |
+ unsigned int state, bool subns, u32 request, | |
+ struct aa_perms *perms) | |
+{ | |
+ struct aa_profile *tp; | |
+ struct label_it i; | |
+ | |
+ /* find first subcomponent that is visible */ | |
+ label_for_each(i, label, tp) { | |
+ if (!aa_ns_visible(profile->ns, tp->ns, subns)) | |
+ continue; | |
+ state = match_component(profile, tp, state); | |
+ if (!state) | |
+ goto fail; | |
+ goto next; | |
+ } | |
+ | |
+ /* no component visible */ | |
+ *perms = allperms; | |
+ return 0; | |
+ | |
+next: | |
+ label_for_each_cont(i, label, tp) { | |
+ if (!aa_ns_visible(profile->ns, tp->ns, subns)) | |
+ continue; | |
+ state = aa_dfa_match(profile->policy.dfa, state, "//&"); | |
+ state = match_component(profile, tp, state); | |
+ if (!state) | |
+ goto fail; | |
+ } | |
+ aa_compute_perms(profile->policy.dfa, state, perms); | |
+ aa_apply_modes_to_perms(profile, perms); | |
+ if ((perms->allow & request) != request) | |
+ return -EACCES; | |
+ | |
+ return 0; | |
+ | |
+fail: | |
+ *perms = nullperms; | |
+ return state; | |
+} | |
+ | |
+/** | |
+ * label_component_match - find perms for all subcomponents of a label | |
+ * @profile: profile to find perms for | |
+ * @label: label to check access permissions for | |
+ * @start: state to start match in | |
+ * @subns: whether to do permission checks on components in a subns | |
+ * @request: permissions to request | |
+ * @perms: an initialized perms struct to add accumulation to | |
+ * | |
+ * Returns: 0 on success else ERROR | |
+ * | |
+ * For the label A//&B//&C this does the perm match for each of A and B and C | |
+ * @perms should be preinitialized with allperms OR a previous permission | |
+ * check to be stacked. | |
+ */ | |
+static int label_components_match(struct aa_profile *profile, | |
+ struct aa_label *label, unsigned int start, | |
+ bool subns, u32 request, | |
+ struct aa_perms *perms) | |
+{ | |
+ struct aa_profile *tp; | |
+ struct label_it i; | |
+ struct aa_perms tmp; | |
+ unsigned int state = 0; | |
+ | |
+ /* find first subcomponent to test */ | |
+ label_for_each(i, label, tp) { | |
+ if (!aa_ns_visible(profile->ns, tp->ns, subns)) | |
+ continue; | |
+ state = match_component(profile, tp, start); | |
+ if (!state) | |
+ goto fail; | |
+ goto next; | |
+ } | |
+ | |
+ /* no subcomponents visible - no change in perms */ | |
+ return 0; | |
+ | |
+next: | |
+ aa_compute_perms(profile->policy.dfa, state, &tmp); | |
+ aa_apply_modes_to_perms(profile, &tmp); | |
+ aa_perms_accum(perms, &tmp); | |
+ label_for_each_cont(i, label, tp) { | |
+ if (!aa_ns_visible(profile->ns, tp->ns, subns)) | |
+ continue; | |
+ state = match_component(profile, tp, start); | |
+ if (!state) | |
+ goto fail; | |
+ aa_compute_perms(profile->policy.dfa, state, &tmp); | |
+ aa_apply_modes_to_perms(profile, &tmp); | |
+ aa_perms_accum(perms, &tmp); | |
+ } | |
+ | |
+ if ((perms->allow & request) != request) | |
+ return -EACCES; | |
+ | |
+ return 0; | |
+ | |
+fail: | |
+ *perms = nullperms; | |
+ return -EACCES; | |
+} | |
+ | |
+/** | |
+ * aa_label_match - do a multi-component label match | |
+ * @profile: profile to match against (NOT NULL) | |
+ * @label: label to match (NOT NULL) | |
+ * @state: state to start in | |
+ * @subns: whether to match subns components | |
+ * @request: permission request | |
+ * @perms: Returns computed perms (NOT NULL) | |
+ * | |
+ * Returns: the state the match finished in, may be the none matching state | |
+ */ | |
+int aa_label_match(struct aa_profile *profile, struct aa_label *label, | |
+ unsigned int state, bool subns, u32 request, | |
+ struct aa_perms *perms) | |
+{ | |
+ int error = label_compound_match(profile, label, state, subns, request, | |
+ perms); | |
+ if (!error) | |
+ return error; | |
+ | |
+ *perms = allperms; | |
+ return label_components_match(profile, label, state, subns, request, | |
+ perms); | |
+} | |
+ | |
+ | |
+/** | |
+ * aa_update_label_name - update a label to have a stored name | |
+ * @ns: ns being viewed from (NOT NULL) | |
+ * @label: label to update (NOT NULL) | |
+ * @gfp: type of memory allocation | |
+ * | |
+ * Requires: labels_set(label) not locked in caller | |
+ * | |
+ * note: only updates the label name if it does not have a name already | |
+ * and if it is in the labelset | |
+ */ | |
+bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp) | |
+{ | |
+ struct aa_labelset *ls; | |
+ unsigned long flags; | |
+ char __counted *name; | |
+ bool res = false; | |
+ | |
+ AA_BUG(!ns); | |
+ AA_BUG(!label); | |
+ | |
+ if (label->hname || labels_ns(label) != ns) | |
+ return res; | |
+ | |
+ if (aa_label_acntsxprint(&name, ns, label, FLAGS_NONE, gfp) == -1) | |
+ return res; | |
+ | |
+ ls = labels_set(label); | |
+ write_lock_irqsave(&ls->lock, flags); | |
+ if (!label->hname && label->flags & FLAG_IN_TREE) { | |
+ label->hname = name; | |
+ res = true; | |
+ } else | |
+ aa_put_str(name); | |
+ write_unlock_irqrestore(&ls->lock, flags); | |
+ | |
+ return res; | |
+} | |
+ | |
+/* cached label name is present and visible | |
+ * @label->hname only exists if label is namespace hierachical */ | |
+static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label) | |
+{ | |
+ if (label->hname && labels_ns(label) == ns) | |
+ return true; | |
+ | |
+ return false; | |
+} | |
+ | |
+/* helper macro for snprint routines */ | |
+#define update_for_len(total, len, size, str) \ | |
+do { \ | |
+ AA_BUG(len < 0); \ | |
+ total += len; \ | |
+ len = min(len, size); \ | |
+ size -= len; \ | |
+ str += len; \ | |
+} while (0) | |
+ | |
+/** | |
+ * aa_profile_snxprint_profile - print a profile name to a buffer | |
+ * @str: buffer to write to. (MAY BE NULL if @size == 0) | |
+ * @size: size of buffer | |
+ * @ns: namespace profile is being viewed from | |
+ * @profile: profile to view (NOT NULL) | |
+ * @flags: whether to include the mode string | |
+ * | |
+ * Returns: size of name written or would be written if larger than | |
+ * available buffer | |
+ * | |
+ * Note: will not print anything if the profile is not visible | |
+ */ | |
+int aa_profile_snxprint(char *str, size_t size, struct aa_ns *ns, | |
+ struct aa_profile *profile, int flags) | |
+{ | |
+ const char *ns_name = ""; | |
+ | |
+ AA_BUG(!str && size != 0); | |
+ AA_BUG(!profile); | |
+ | |
+ if (!ns) | |
+ ns = profiles_ns(profile); | |
+ | |
+ if (ns != profile->ns) { | |
+ ns_name = aa_ns_name(ns, profile->ns, flags & FLAG_VIEW_SUBNS); | |
+ if (ns_name == aa_hidden_ns_name) { | |
+ if (flags & FLAG_HIDDEN_UNCONFINED) | |
+ return snprintf(str, size, "%s", "unconfined"); | |
+ return snprintf(str, size, "%s", ns_name); | |
+ } | |
+ } | |
+ | |
+ if ((flags & FLAG_SHOW_MODE) && profile != profile->ns->unconfined) { | |
+ const char *modestr = aa_profile_mode_names[profile->mode]; | |
+ if (strlen(ns_name)) | |
+ return snprintf(str, size, ":%s://%s (%s)", ns_name, | |
+ profile->base.hname, modestr); | |
+ return snprintf(str, size, "%s (%s)", profile->base.hname, | |
+ modestr); | |
+ } | |
+ | |
+ if (strlen(ns_name)) | |
+ return snprintf(str, size, ":%s://%s", ns_name, | |
+ profile->base.hname); | |
+ return snprintf(str, size, "%s", profile->base.hname); | |
+} | |
+ | |
+static const char *label_modename(struct aa_ns *ns, struct aa_label *label, | |
+ int flags) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct label_it i; | |
+ const char *modestr = NULL; | |
+ int count = 0; | |
+ | |
+ label_for_each(i, label, profile) { | |
+ if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) { | |
+ const char *tmp_modestr; | |
+ count++; | |
+ tmp_modestr = aa_profile_mode_names[profile->mode]; | |
+ if (!modestr) | |
+ modestr = tmp_modestr; | |
+ else if (modestr != tmp_modestr) | |
+ return "mixed"; | |
+ } | |
+ } | |
+ | |
+ if (count == 0) | |
+ return "-"; | |
+ | |
+ return modestr; | |
+} | |
+ | |
+/* if any visible label is not unconfined the display_mode returns true */ | |
+static inline bool display_mode(struct aa_ns *ns, struct aa_label *label, | |
+ int flags) | |
+{ | |
+ if ((flags & FLAG_SHOW_MODE)) { | |
+ struct aa_profile *profile; | |
+ struct label_it i; | |
+ | |
+ label_for_each(i, label, profile) { | |
+ if (aa_ns_visible(ns, profile->ns, | |
+ flags & FLAG_VIEW_SUBNS) && | |
+ profile != profile->ns->unconfined) | |
+ return true; | |
+ } | |
+ /* only ns->unconfined in set of profiles in ns */ | |
+ return false; | |
+ } | |
+ | |
+ return false; | |
+} | |
+ | |
+/** | |
+ * aa_label_snxprint - print a label name to a string buffer | |
+ * @str: buffer to write to. (MAY BE NULL if @size == 0) | |
+ * @size: size of buffer | |
+ * @ns: namespace profile is being viewed from | |
+ * @label: label to view (NOT NULL) | |
+ * @flags: whether to include the mode string | |
+ * | |
+ * Returns: size of name written or would be written if larger than | |
+ * available buffer | |
+ * | |
+ * Note: labels do not have to be strictly hierarchical to the ns as | |
+ * objects may be shared across different namespaces and thus | |
+ * pickup labeling from each ns. If a particular part of the | |
+ * label is not visible it will just be excluded. And if none | |
+ * of the label is visible "---" will be used. | |
+ */ | |
+int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns, | |
+ struct aa_label *label, int flags) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct label_it i; | |
+ int count = 0, total = 0; | |
+ size_t len; | |
+ | |
+ AA_BUG(!str && size != 0); | |
+ AA_BUG(!label); | |
+ | |
+ if (!ns) | |
+ ns = labels_ns(label); | |
+ | |
+ label_for_each(i, label, profile) { | |
+ if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) { | |
+ if (count > 0) { | |
+ len = snprintf(str, size, "//&"); | |
+ update_for_len(total, len, size, str); | |
+ } | |
+ len = aa_profile_snxprint(str, size, ns, profile, | |
+ flags & FLAG_VIEW_SUBNS); | |
+ update_for_len(total, len, size, str); | |
+ count++; | |
+ } | |
+ } | |
+ | |
+ if (count == 0) { | |
+ if (flags & FLAG_HIDDEN_UNCONFINED) | |
+ return snprintf(str, size, "%s", "unconfined"); | |
+ return snprintf(str, size, "%s", aa_hidden_ns_name); | |
+ } | |
+ | |
+ /* count == 1 && ... is for backwards compat where the mode | |
+ * is not displayed for 'unconfined' in the current ns | |
+ */ | |
+ if (display_mode(ns, label, flags)) { | |
+ len = snprintf(str, size, " (%s)", | |
+ label_modename(ns, label, flags)); | |
+ update_for_len(total, len, size, str); | |
+ } | |
+ | |
+ return total; | |
+} | |
+#undef update_for_len | |
+ | |
+/** | |
+ * aa_label_asxprint - allocate a string buffer and print label into it | |
+ * @strp: Returns - the allocated buffer with the label name. (NOT NULL) | |
+ * @ns: namespace profile is being viewed from | |
+ * @label: label to view (NOT NULL) | |
+ * @flags: flags controlling what label info is printed | |
+ * @gfp: kernel memory allocation type | |
+ * | |
+ * Returns: size of name written or would be written if larger than | |
+ * available buffer | |
+ */ | |
+int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label, | |
+ int flags, gfp_t gfp) | |
+{ | |
+ int size; | |
+ | |
+ AA_BUG(!strp); | |
+ AA_BUG(!label); | |
+ | |
+ size = aa_label_snxprint(NULL, 0, ns, label, flags); | |
+ if (size < 0) | |
+ return size; | |
+ | |
+ *strp = kmalloc(size + 1, gfp); | |
+ if (!*strp) | |
+ return -ENOMEM; | |
+ return aa_label_snxprint(*strp, size + 1, ns, label, flags); | |
+} | |
+ | |
+/** | |
+ * aa_label_acntsxprint - allocate a __counted string buffer and print label | |
+ * @strp: buffer to write to. (MAY BE NULL if @size == 0) | |
+ * @ns: namespace profile is being viewed from | |
+ * @label: label to view (NOT NULL) | |
+ * @flags: flags controlling what label info is printed | |
+ * @gfp: kernel memory allocation type | |
+ * | |
+ * Returns: size of name written or would be written if larger than | |
+ * available buffer | |
+ */ | |
+int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns, | |
+ struct aa_label *label, int flags, gfp_t gfp) | |
+{ | |
+ int size; | |
+ | |
+ AA_BUG(!strp); | |
+ AA_BUG(!label); | |
+ | |
+ size = aa_label_snxprint(NULL, 0, ns, label, flags); | |
+ if (size < 0) | |
+ return size; | |
+ | |
+ *strp = aa_str_alloc(size + 1, gfp); | |
+ if (!*strp) | |
+ return -ENOMEM; | |
+ return aa_label_snxprint(*strp, size + 1, ns, label, flags); | |
+} | |
+ | |
+ | |
+void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, | |
+ struct aa_label *label, int flags, gfp_t gfp) | |
+{ | |
+ const char *str; | |
+ char *name = NULL; | |
+ int len; | |
+ | |
+ AA_BUG(!ab); | |
+ AA_BUG(!label); | |
+ | |
+ if (!ns) | |
+ ns = labels_ns(label); | |
+ | |
+ if (!use_label_hname(ns, label) || display_mode(ns, label, flags)) { | |
+ labelstats_inc(audit_name_alloc); | |
+ len = aa_label_asxprint(&name, ns, label, flags, gfp); | |
+ if (len == -1) { | |
+ labelstats_inc(audit_name_fail); | |
+ AA_DEBUG("label print error"); | |
+ return; | |
+ } | |
+ str = name; | |
+ } else { | |
+ str = (char *) label->hname; | |
+ len = strlen(str); | |
+ } | |
+ if (audit_string_contains_control(str, len)) | |
+ audit_log_n_hex(ab, str, len); | |
+ else | |
+ audit_log_n_string(ab, str, len); | |
+ | |
+ kfree(name); | |
+} | |
+ | |
+void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, | |
+ struct aa_label *label, int flags, gfp_t gfp) | |
+{ | |
+ AA_BUG(!f); | |
+ AA_BUG(!label); | |
+ | |
+ if (!ns) | |
+ ns = labels_ns(label); | |
+ | |
+ if (!use_label_hname(ns, label)) { | |
+ char *str; | |
+ int len; | |
+ | |
+ labelstats_inc(seq_print_name_alloc); | |
+ len = aa_label_asxprint(&str, ns, label, flags, gfp); | |
+ if (len == -1) { | |
+ labelstats_inc(seq_print_name_fail); | |
+ AA_DEBUG("label print error"); | |
+ return; | |
+ } | |
+ seq_printf(f, "%s", str); | |
+ kfree(str); | |
+ } else if (display_mode(ns, label, flags)) | |
+ seq_printf(f, "%s (%s)", label->hname, | |
+ label_modename(ns, label, flags)); | |
+ else | |
+ seq_printf(f, "%s", label->hname); | |
+} | |
+ | |
+void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, | |
+ gfp_t gfp) | |
+{ | |
+ AA_BUG(!label); | |
+ | |
+ if (!ns) | |
+ ns = labels_ns(label); | |
+ | |
+ if (!use_label_hname(ns, label)) { | |
+ char *str; | |
+ int len; | |
+ | |
+ labelstats_inc(printk_name_alloc); | |
+ len = aa_label_asxprint(&str, ns, label, flags, gfp); | |
+ if (len == -1) { | |
+ labelstats_inc(printk_name_fail); | |
+ AA_DEBUG("label print error"); | |
+ return; | |
+ } | |
+ printk("%s", str); | |
+ kfree(str); | |
+ } else if (display_mode(ns, label, flags)) | |
+ printk("%s (%s)", label->hname, | |
+ label_modename(ns, label, flags)); | |
+ else | |
+ printk("%s", label->hname); | |
+} | |
+ | |
+void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp) | |
+{ | |
+ struct aa_ns *ns = aa_get_current_ns(); | |
+ aa_label_xaudit(ab, ns, label, FLAG_VIEW_SUBNS, gfp); | |
+ aa_put_ns(ns); | |
+} | |
+ | |
+void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp) | |
+{ | |
+ struct aa_ns *ns = aa_get_current_ns(); | |
+ aa_label_seq_xprint(f, ns, label, FLAG_VIEW_SUBNS, gfp); | |
+ aa_put_ns(ns); | |
+} | |
+ | |
+void aa_label_printk(struct aa_label *label, gfp_t gfp) | |
+{ | |
+ struct aa_ns *ns = aa_get_current_ns(); | |
+ aa_label_xprintk(ns, label, FLAG_VIEW_SUBNS, gfp); | |
+ aa_put_ns(ns); | |
+} | |
+ | |
+static int label_count_str_entries(const char *str) | |
+{ | |
+ const char *split; | |
+ int count = 1; | |
+ | |
+ AA_BUG(!str); | |
+ | |
+ for (split = strstr(str, "//&"); split; split = strstr(str, "//&")) { | |
+ count++; | |
+ str = split + 3; | |
+ } | |
+ | |
+ return count; | |
+} | |
+ | |
+/** | |
+ * aa_label_parse - parse, validate and convert a text string to a label | |
+ * @base: base label to use for lookups (NOT NULL) | |
+ * @str: null terminated text string (NOT NULL) | |
+ * @gfp: allocation type | |
+ * @create: true if should create compound labels if they don't exist | |
+ * @force_stack: true if should stack even if no leading & | |
+ * | |
+ * Returns: the matching refcounted label if present | |
+ * else ERRPTR | |
+ */ | |
+struct aa_label *aa_label_parse(struct aa_label *base, const char *str, | |
+ gfp_t gfp, bool create, bool force_stack) | |
+{ | |
+ DEFINE_VEC(profile, vec); | |
+ struct aa_label *label; | |
+ int i, len, stack = 0, error; | |
+ char *split; | |
+ | |
+ AA_BUG(!base); | |
+ AA_BUG(!str); | |
+ | |
+ str = skip_spaces(str); | |
+ len = label_count_str_entries(str); | |
+ if (*str == '&' || force_stack) { | |
+ /* stack on top of base */ | |
+ stack = base->size; | |
+ len += stack; | |
+ if (*str == '&') | |
+ str++; | |
+ } | |
+ error = vec_setup(profile, vec, len, gfp); | |
+ if (error) | |
+ return ERR_PTR(error); | |
+ | |
+ for (i = 0; i < stack; i++) | |
+ vec[i] = aa_get_profile(base->vec[i]); | |
+ | |
+ for (split = strstr(str, "//&"), i = stack; split && i < len; i++) { | |
+ vec[i] = aa_fqlookupn_profile(base, str, split - str); | |
+ if (!vec[i]) | |
+ goto fail; | |
+ str = split + 3; | |
+ split = strstr(str, "//&"); | |
+ } | |
+ /* last element doesn't have a split so this should be the case but just to be safe */ | |
+ if (i < len) { | |
+ vec[i] = aa_fqlookupn_profile(base, str, strlen(str)); | |
+ if (!vec[i]) | |
+ goto fail; | |
+ } | |
+ if (len == 1) | |
+ /* no need to free vec as len < LOCAL_VEC_ENTRIES */ | |
+ return &vec[0]->label; | |
+ | |
+ len -= aa_vec_unique(vec, len, VEC_FLAG_TERMINATE); | |
+ | |
+ if (create) | |
+ label = aa_vec_find_or_create_label(vec, len, gfp); | |
+ else | |
+ label = vec_find(vec, len); | |
+ if (!label) | |
+ goto fail; | |
+ | |
+out: | |
+ /* use adjusted len from after vec_unique, not original */ | |
+ vec_cleanup(profile, vec, len); | |
+ return label; | |
+ | |
+fail: | |
+ label = ERR_PTR(-ENOENT); | |
+ goto out; | |
+} | |
+ | |
+ | |
+/** | |
+ * aa_labelset_destroy - remove all labels from the label set | |
+ * @ls: label set to cleanup (NOT NULL) | |
+ * | |
+ * Labels that are removed from the set may still exist beyond the set | |
+ * being destroyed depending on their reference counting | |
+ */ | |
+void aa_labelset_destroy(struct aa_labelset *ls) | |
+{ | |
+ struct rb_node *node; | |
+ unsigned long flags; | |
+ | |
+ AA_BUG(!ls); | |
+ | |
+ write_lock_irqsave(&ls->lock, flags); | |
+ for (node = rb_first(&ls->root); node; node = rb_first(&ls->root)) { | |
+ struct aa_label *this = rb_entry(node, struct aa_label, node); | |
+ if (labels_ns(this) != root_ns) | |
+ __label_remove(this, | |
+ ns_unconfined(labels_ns(this)->parent)); | |
+ else | |
+ __label_remove(this, NULL); | |
+ } | |
+ write_unlock_irqrestore(&ls->lock, flags); | |
+} | |
+ | |
+/* | |
+ * @ls: labelset to init (NOT NULL) | |
+ */ | |
+void aa_labelset_init(struct aa_labelset *ls) | |
+{ | |
+ AA_BUG(!ls); | |
+ | |
+ rwlock_init(&ls->lock); | |
+ ls->root = RB_ROOT; | |
+ labelstats_init(&ls); | |
+} | |
+ | |
+static struct aa_label *labelset_next_stale(struct aa_labelset *ls) | |
+{ | |
+ struct aa_label *label; | |
+ struct rb_node *node; | |
+ unsigned long flags; | |
+ | |
+ AA_BUG(!ls); | |
+ | |
+ read_lock_irqsave(&ls->lock, flags); | |
+ | |
+ __labelset_for_each(ls, node) { | |
+ label = rb_entry(node, struct aa_label, node); | |
+ if ((label_is_stale(label) || vec_is_stale(label->vec, label->size)) && | |
+ aa_get_label_not0(label)) | |
+ goto out; | |
+ | |
+ } | |
+ label = NULL; | |
+ | |
+out: | |
+ read_unlock_irqrestore(&ls->lock, flags); | |
+ | |
+ return label; | |
+} | |
+ | |
+/** | |
+ * __label_update - insert updated version of @label into labelset | |
+ * @label - the label to update/repace | |
+ * | |
+ * Returns: new label that is up to date | |
+ * else NULL on failure | |
+ * | |
+ * Requires: @ns lock be held | |
+ * | |
+ * Note: worst case is the stale @label does not get updated and has | |
+ * to be updated at a later time. | |
+ */ | |
+static struct aa_label *__label_update(struct aa_label *label) | |
+{ | |
+ struct aa_label *new, *tmp; | |
+ struct aa_labelset *ls; | |
+ struct aa_profile *p; | |
+ struct label_it i; | |
+ unsigned long flags; | |
+ int invcount = 0; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!mutex_is_locked(&labels_ns(label)->lock)); | |
+ | |
+ new = aa_label_alloc(label->size, label->proxy, GFP_KERNEL); | |
+ if (!new) | |
+ return NULL; | |
+ | |
+ /* while holding the ns_lock will stop profile replacement, removal, | |
+ * and label updates, label merging and removal can be occuring | |
+ */ | |
+ ls = labels_set(label); | |
+ write_lock_irqsave(&ls->lock, flags); | |
+ label_for_each(i, label, p) { | |
+ new->vec[i.i] = aa_get_newest_profile(p); | |
+ if (&new->vec[i.i]->label.proxy != &p->label.proxy) | |
+ invcount++; | |
+ } | |
+ | |
+ /* updated stale label by being removed/renamed from labelset */ | |
+ if (invcount) { | |
+ new->size -= aa_vec_unique(&new->vec[0], new->size, | |
+ VEC_FLAG_TERMINATE); | |
+ if (labels_set(label) != labels_set(new)) { | |
+ write_unlock_irqrestore(&ls->lock, flags); | |
+ tmp = aa_label_insert(labels_set(new), new); | |
+ write_lock_irqsave(&ls->lock, flags); | |
+ goto remove; | |
+ } | |
+ } else | |
+ AA_BUG(labels_ns(label) != labels_ns(new)); | |
+ | |
+ tmp = __label_insert(labels_set(label), new, true); | |
+remove: | |
+ /* ensure label is removed, and redirected correctly */ | |
+ __label_remove(label, tmp); | |
+ write_unlock_irqrestore(&ls->lock, flags); | |
+ | |
+ aa_put_label(new); | |
+ | |
+ return tmp; | |
+} | |
+ | |
+/** | |
+ * __labelset_update - update labels in @ns | |
+ * @ns: namespace to update labels in (NOT NULL) | |
+ * | |
+ * Requires: @ns lock be held | |
+ * | |
+ * Walk the labelset ensuring that all labels are up to date and valid | |
+ * Any label that has a stale component is marked stale and replaced and | |
+ * by an updated version. | |
+ * | |
+ * If failures happen due to memory pressures then stale labels will | |
+ * be left in place until the next pass. | |
+ */ | |
+static void __labelset_update(struct aa_ns *ns) | |
+{ | |
+ struct aa_label *label; | |
+ | |
+ AA_BUG(!ns); | |
+ AA_BUG(!mutex_is_locked(&ns->lock)); | |
+ | |
+ do { | |
+ label = labelset_next_stale(&ns->labels); | |
+ if (label) { | |
+ struct aa_label *l; | |
+ l = __label_update(label); | |
+ aa_put_label(l); | |
+ aa_put_label(label); | |
+ } | |
+ } while (label); | |
+} | |
+ | |
+/** | |
+ * __aa_labelset_udate_subtree - update all labels with a stale component | |
+ * @ns: ns to start update at (NOT NULL) | |
+ * | |
+ * Requires: @ns lock be held | |
+ * | |
+ * Invalidates labels based on @p in @ns and any children namespaces. | |
+*/ | |
+void __aa_labelset_update_subtree(struct aa_ns *ns) | |
+{ | |
+ struct aa_ns *child; | |
+ | |
+ AA_BUG(!ns); | |
+ AA_BUG(!mutex_is_locked(&ns->lock)); | |
+ | |
+ __labelset_update(ns); | |
+ | |
+ list_for_each_entry(child, &ns->sub_ns, base.list) { | |
+ mutex_lock(&child->lock); | |
+ __aa_labelset_update_subtree(child); | |
+ mutex_unlock(&child->lock); | |
+ } | |
+} | |
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c | |
index c1827e0..52d4efc 100644 | |
--- a/security/apparmor/lib.c | |
+++ b/security/apparmor/lib.c | |
@@ -4,7 +4,7 @@ | |
* This file contains basic common functions used in AppArmor | |
* | |
* Copyright (C) 1998-2008 Novell/SUSE | |
- * Copyright 2009-2010 Canonical Ltd. | |
+ * Copyright 2009-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 | |
@@ -12,14 +12,23 @@ | |
* License. | |
*/ | |
+#include <linux/ctype.h> | |
#include <linux/mm.h> | |
#include <linux/slab.h> | |
#include <linux/string.h> | |
#include <linux/vmalloc.h> | |
-#include "include/audit.h" | |
#include "include/apparmor.h" | |
+#include "include/audit.h" | |
+#include "include/label.h" | |
+#include "include/lib.h" | |
+#include "include/perms.h" | |
+#include "include/policy.h" | |
+struct aa_perms nullperms; | |
+struct aa_perms allperms = { .allow = ALL_PERMS_MASK, | |
+ .quiet = ALL_PERMS_MASK, | |
+ .hide = ALL_PERMS_MASK }; | |
/** | |
* aa_split_fqname - split a fqname into a profile and namespace name | |
@@ -60,17 +69,65 @@ char *aa_split_fqname(char *fqname, char **ns_name) | |
} | |
/** | |
+ * skipn_spaces - Removes leading whitespace from @str. | |
+ * @str: The string to be stripped. | |
+ * | |
+ * Returns a pointer to the first non-whitespace character in @str. | |
+ * if all whitespace will return NULL | |
+ */ | |
+ | |
+static const char *skipn_spaces(const char *str, size_t n) | |
+{ | |
+ for (;n && isspace(*str); --n) | |
+ ++str; | |
+ if (n) | |
+ return (char *)str; | |
+ return NULL; | |
+} | |
+ | |
+const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, | |
+ size_t *ns_len) | |
+{ | |
+ const char *end = fqname + n; | |
+ const char *name = skipn_spaces(fqname, n); | |
+ if (!name) | |
+ return NULL; | |
+ *ns_name = NULL; | |
+ *ns_len = 0; | |
+ if (name[0] == ':') { | |
+ char *split = strnchr(&name[1], end - &name[1], ':'); | |
+ *ns_name = skipn_spaces(&name[1], end - &name[1]); | |
+ if (!*ns_name) | |
+ return NULL; | |
+ if (split) { | |
+ *ns_len = split - *ns_name; | |
+ if (*ns_len == 0) | |
+ *ns_name = NULL; | |
+ split++; | |
+ if (end - split > 1 && strncmp(split, "//", 2) == 0) | |
+ split += 2; | |
+ name = skipn_spaces(split, end - split); | |
+ } else { | |
+ /* a ns name without a following profile is allowed */ | |
+ name = NULL; | |
+ *ns_len = end - *ns_name; | |
+ } | |
+ } | |
+ if (name && *name == 0) | |
+ name = NULL; | |
+ | |
+ return name; | |
+} | |
+ | |
+/** | |
* aa_info_message - log a none profile related status message | |
* @str: message to log | |
*/ | |
void aa_info_message(const char *str) | |
{ | |
if (audit_enabled) { | |
- struct common_audit_data sa; | |
- struct apparmor_audit_data aad = {0,}; | |
- sa.type = LSM_AUDIT_DATA_NONE; | |
- sa.aad = &aad; | |
- aad.info = str; | |
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); | |
+ aad(&sa)->info = str; | |
aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL); | |
} | |
printk(KERN_INFO "AppArmor: %s\n", str); | |
@@ -104,3 +161,405 @@ void *__aa_kvmalloc(size_t size, gfp_t flags) | |
} | |
return buffer; | |
} | |
+ | |
+ | |
+__counted char *aa_str_alloc(int size, gfp_t gfp) | |
+{ | |
+ struct counted_str *str; | |
+ str = kmalloc(sizeof(struct counted_str) + size, gfp); | |
+ if (!str) | |
+ return NULL; | |
+ | |
+ kref_init(&str->count); | |
+ return str->name; | |
+} | |
+ | |
+void aa_str_kref(struct kref *kref) | |
+{ | |
+ kfree(container_of(kref, struct counted_str, count)); | |
+} | |
+ | |
+ | |
+const char aa_file_perm_chrs[] = "xwracd km l "; | |
+const char *aa_file_perm_names[] = { | |
+ "exec", | |
+ "write", | |
+ "read", | |
+ "append", | |
+ | |
+ "create", | |
+ "delete", | |
+ "open", | |
+ "rename", | |
+ | |
+ "setattr", | |
+ "getattr", | |
+ "setcred", | |
+ "getcred", | |
+ | |
+ "chmod", | |
+ "chown", | |
+ "chgrp", | |
+ "lock", | |
+ | |
+ "mmap", | |
+ "mprot", | |
+ "link", | |
+ "snapshot", | |
+ | |
+ "unknown", | |
+ "unknown", | |
+ "unknown", | |
+ "unknown", | |
+ | |
+ "unknown", | |
+ "unknown", | |
+ "unknown", | |
+ "unknown", | |
+ | |
+ "stack", | |
+ "change_onexec", | |
+ "change_profile", | |
+ "change_hat", | |
+}; | |
+ | |
+/** | |
+ * aa_perm_mask_to_str - convert a perm mask to its short string | |
+ * @str: character buffer to store string in (at least 10 characters) | |
+ * @mask: permission mask to convert | |
+ */ | |
+void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask) | |
+{ | |
+ unsigned int i, perm = 1; | |
+ for (i = 0; i < 32; perm <<= 1, i++) { | |
+ if (mask & perm) | |
+ *str++ = chrs[i]; | |
+ } | |
+ *str = '\0'; | |
+} | |
+ | |
+void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask) | |
+{ | |
+ const char *fmt = "%s"; | |
+ unsigned int i, perm = 1; | |
+ bool prev = false; | |
+ for (i = 0; i < 32; perm <<= 1, i++) { | |
+ if (mask & perm) { | |
+ audit_log_format(ab, fmt, names[i]); | |
+ if (!prev) { | |
+ prev = true; | |
+ fmt = " %s"; | |
+ } | |
+ } | |
+ } | |
+} | |
+ | |
+void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, | |
+ u32 chrsmask, const char **names, u32 namesmask) | |
+{ | |
+ char str[33]; | |
+ | |
+ audit_log_format(ab, "\""); | |
+ if ((mask & chrsmask) && chrs) { | |
+ aa_perm_mask_to_str(str, chrs, mask & chrsmask); | |
+ mask &= ~chrsmask; | |
+ audit_log_format(ab, "%s", str); | |
+ if (mask & namesmask) | |
+ audit_log_format(ab, " "); | |
+ } | |
+ if ((mask & namesmask) && names) | |
+ aa_audit_perm_names(ab, names, mask & namesmask); | |
+ audit_log_format(ab, "\""); | |
+} | |
+ | |
+/** | |
+ * aa_audit_perms_cb - generic callback fn for auditing perms | |
+ * @ab: audit buffer (NOT NULL) | |
+ * @va: audit struct to audit values of (NOT NULL) | |
+ */ | |
+static void aa_audit_perms_cb(struct audit_buffer *ab, void *va) | |
+{ | |
+ struct common_audit_data *sa = va; | |
+ | |
+ if (aad(sa)->request) { | |
+ audit_log_format(ab, " requested_mask="); | |
+ aa_audit_perm_mask(ab, aad(sa)->request, aa_file_perm_chrs, | |
+ PERMS_CHRS_MASK, aa_file_perm_names, | |
+ PERMS_NAMES_MASK); | |
+ } | |
+ if (aad(sa)->denied) { | |
+ audit_log_format(ab, "denied_mask="); | |
+ aa_audit_perm_mask(ab, aad(sa)->denied, aa_file_perm_chrs, | |
+ PERMS_CHRS_MASK, aa_file_perm_names, | |
+ PERMS_NAMES_MASK); | |
+ } | |
+ audit_log_format(ab, " peer="); | |
+ aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, | |
+ FLAGS_NONE, GFP_ATOMIC); | |
+} | |
+ | |
+/** | |
+ * aa_apply_modes_to_perms - apply namespace and profile flags to perms | |
+ * @profile: that perms where computed from | |
+ * @perms: perms to apply mode modifiers to | |
+ * | |
+ * TODO: split into profile and ns based flags for when accumulating perms | |
+ */ | |
+void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) | |
+{ | |
+ switch (AUDIT_MODE(profile)) { | |
+ case AUDIT_ALL: | |
+ perms->audit = ALL_PERMS_MASK; | |
+ /* fall through */ | |
+ case AUDIT_NOQUIET: | |
+ perms->quiet = 0; | |
+ break; | |
+ case AUDIT_QUIET: | |
+ perms->audit = 0; | |
+ /* fall through */ | |
+ case AUDIT_QUIET_DENIED: | |
+ perms->quiet = ALL_PERMS_MASK; | |
+ break; | |
+ } | |
+ | |
+ if (KILL_MODE(profile)) | |
+ perms->kill = ALL_PERMS_MASK; | |
+ else if (COMPLAIN_MODE(profile)) | |
+ perms->complain = ALL_PERMS_MASK; | |
+/* TODO: | |
+ else if (PROMPT_MODE(profile)) | |
+ perms->prompt = ALL_PERMS_MASK; | |
+*/ | |
+} | |
+ | |
+static u32 map_other(u32 x) | |
+{ | |
+ return ((x & 0x3) << 8) | /* SETATTR/GETATTR */ | |
+ ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */ | |
+ ((x & 0x60) << 19); /* SETOPT/GETOPT */ | |
+} | |
+ | |
+void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, | |
+ struct aa_perms *perms) | |
+{ | |
+ perms->deny = 0; | |
+ perms->kill = perms->stop = 0; | |
+ perms->complain = perms->cond = 0; | |
+ perms->hide = 0; | |
+ perms->prompt = 0; | |
+ perms->allow = dfa_user_allow(dfa, state); | |
+ perms->audit = dfa_user_audit(dfa, state); | |
+ perms->quiet = dfa_user_quiet(dfa, state); | |
+ | |
+ /* for v5 perm mapping in the policydb, the other set is used | |
+ * to extend the general perm set | |
+ */ | |
+ perms->allow |= map_other(dfa_other_allow(dfa, state)); | |
+ perms->audit |= map_other(dfa_other_audit(dfa, state)); | |
+ perms->quiet |= map_other(dfa_other_quiet(dfa, state)); | |
+// perms->xindex = dfa_user_xindex(dfa, state); | |
+} | |
+ | |
+/** | |
+ * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms | |
+ * @accum - perms struct to accumulate into | |
+ * @addend - perms struct to add to @accum | |
+ */ | |
+void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend) | |
+{ | |
+ accum->deny |= addend->deny; | |
+ accum->allow &= addend->allow & ~addend->deny; | |
+ accum->audit |= addend->audit & addend->allow; | |
+ accum->quiet &= addend->quiet & ~addend->allow; | |
+ accum->kill |= addend->kill & ~addend->allow; | |
+ accum->stop |= addend->stop & ~addend->allow; | |
+ accum->complain |= addend->complain & ~addend->allow & ~addend->deny; | |
+ accum->cond |= addend->cond & ~addend->allow & ~addend->deny; | |
+ accum->hide &= addend->hide & ~addend->allow; | |
+ accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny; | |
+} | |
+ | |
+/** | |
+ * aa_perms_accum - accumulate perms, masking off overlapping perms | |
+ * @accum - perms struct to accumulate into | |
+ * @addend - perms struct to add to @accum | |
+ */ | |
+void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend) | |
+{ | |
+ accum->deny |= addend->deny; | |
+ accum->allow &= addend->allow & ~accum->deny; | |
+ accum->audit |= addend->audit & accum->allow; | |
+ accum->quiet &= addend->quiet & ~accum->allow; | |
+ accum->kill |= addend->kill & ~accum->allow; | |
+ accum->stop |= addend->stop & ~accum->allow; | |
+ accum->complain |= addend->complain & ~accum->allow & ~accum->deny; | |
+ accum->cond |= addend->cond & ~accum->allow & ~accum->deny; | |
+ accum->hide &= addend->hide & ~accum->allow; | |
+ accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny; | |
+} | |
+ | |
+void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, | |
+ int type, u32 request, struct aa_perms *perms) | |
+{ | |
+ /* TODO: doesn't yet handle extended types */ | |
+ unsigned int state; | |
+ state = aa_dfa_next(profile->policy.dfa, | |
+ profile->policy.start[AA_CLASS_LABEL], | |
+ type); | |
+ aa_label_match(profile, label, state, false, request, perms); | |
+} | |
+ | |
+ | |
+/* currently unused */ | |
+int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, | |
+ u32 request, int type, u32 *deny, | |
+ struct common_audit_data *sa) | |
+{ | |
+ struct aa_perms perms; | |
+ aad(sa)->label = &profile->label; | |
+ aad(sa)->peer = &target->label; | |
+ aad(sa)->request = request; | |
+ | |
+ aa_profile_match_label(profile, &target->label, type, request, &perms); | |
+ aa_apply_modes_to_perms(profile, &perms); | |
+ *deny |= request & perms.deny; | |
+ return aa_check_perms(profile, &perms, request, sa, aa_audit_perms_cb); | |
+} | |
+ | |
+/** | |
+ * aa_check_perms - do audit mode selection based on perms set | |
+ * @profile: profile being checked | |
+ * @perms: perms computed for the request | |
+ * @request: requested perms | |
+ * @deny: Returns: explicit deny set | |
+ * @sa: initialized audit structure (MAY BE NULL if not auditing) | |
+ * @cb: callback fn for tpye specific fields (MAY BE NULL) | |
+ * | |
+ * Returns: 0 if permission else error code | |
+ * | |
+ * Note: profile audit modes need to be set before calling by setting the | |
+ * perm masks appropriately. | |
+ * | |
+ * If not auditing then complain mode is not enabled and the | |
+ * error code will indicate whether there was an explicit deny | |
+ * with a positive value. | |
+ */ | |
+int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, | |
+ u32 request, struct common_audit_data *sa, | |
+ void (*cb) (struct audit_buffer *, void *)) | |
+{ | |
+ int type, error; | |
+ bool stop = false; | |
+ u32 denied = request & (~perms->allow | perms->deny); | |
+ if (likely(!denied)) { | |
+ /* mask off perms that are not being force audited */ | |
+ request &= perms->audit; | |
+ if (!request || !sa) | |
+ return 0; | |
+ | |
+ type = AUDIT_APPARMOR_AUDIT; | |
+ error = 0; | |
+ } else { | |
+ error = -EACCES; | |
+ | |
+ if (denied & perms->kill) | |
+ type = AUDIT_APPARMOR_KILL; | |
+ else if (denied == (denied & perms->complain)) | |
+ type = AUDIT_APPARMOR_ALLOWED; | |
+ else | |
+ type = AUDIT_APPARMOR_DENIED; | |
+ | |
+ if (denied & perms->stop) | |
+ stop = true; | |
+ if (denied == (denied & perms->hide)) | |
+ error = -ENOENT; | |
+ | |
+ denied &= ~perms->quiet; | |
+ if (!sa || !denied) | |
+ return error; | |
+ } | |
+ | |
+ if (sa) { | |
+ aad(sa)->label = &profile->label; | |
+ aad(sa)->request = request; | |
+ aad(sa)->denied = denied; | |
+ aad(sa)->error = error; | |
+ aa_audit_msg(type, sa, cb); | |
+ } | |
+ | |
+ if (type == AUDIT_APPARMOR_ALLOWED) | |
+ error = 0; | |
+ | |
+ return error; | |
+} | |
+ | |
+const char *aa_imode_name(umode_t mode) | |
+{ | |
+ switch(mode & S_IFMT) { | |
+ case S_IFSOCK: | |
+ return "sock"; | |
+ case S_IFLNK: | |
+ return "link"; | |
+ case S_IFREG: | |
+ return "reg"; | |
+ case S_IFBLK: | |
+ return "blkdev"; | |
+ case S_IFDIR: | |
+ return "dir"; | |
+ case S_IFCHR: | |
+ return "chrdev"; | |
+ case S_IFIFO: | |
+ return "fifo"; | |
+ } | |
+ return "unknown"; | |
+} | |
+ | |
+/** | |
+ * aa_policy_init - initialize a policy structure | |
+ * @policy: policy to initialize (NOT NULL) | |
+ * @prefix: prefix name if any is required. (MAYBE NULL) | |
+ * @name: name of the policy, init will make a copy of it (NOT NULL) | |
+ * @gfp: allocation mode | |
+ * | |
+ * Note: this fn creates a copy of strings passed in | |
+ * | |
+ * Returns: true if policy init successful | |
+ */ | |
+bool aa_policy_init(struct aa_policy *policy, const char *prefix, | |
+ const char *name, gfp_t gfp) | |
+{ | |
+ char *hname; | |
+ | |
+ /* freed by policy_free */ | |
+ if (prefix) { | |
+ hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp); | |
+ if (hname) | |
+ sprintf(hname, "%s//%s", prefix, name); | |
+ } else { | |
+ hname = aa_str_alloc(strlen(name) + 1, gfp); | |
+ if (hname) | |
+ strcpy(hname, name); | |
+ } | |
+ if (!hname) | |
+ return 0; | |
+ policy->hname = hname; | |
+ /* base.name is a substring of fqname */ | |
+ policy->name = (char *) basename(policy->hname); | |
+ INIT_LIST_HEAD(&policy->list); | |
+ INIT_LIST_HEAD(&policy->profiles); | |
+ | |
+ return 1; | |
+} | |
+ | |
+/** | |
+ * aa_policy_destroy - free the elements referenced by @policy | |
+ * @policy: policy that is to have its elements freed (NOT NULL) | |
+ */ | |
+void aa_policy_destroy(struct aa_policy *policy) | |
+{ | |
+ AA_BUG(on_list_rcu(&policy->profiles)); | |
+ AA_BUG(on_list_rcu(&policy->list)); | |
+ | |
+ /* don't free name as its a subset of hname */ | |
+ aa_put_str(policy->hname); | |
+} | |
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c | |
index 41b8cb1..dd13b1a 100644 | |
--- a/security/apparmor/lsm.c | |
+++ b/security/apparmor/lsm.c | |
@@ -25,6 +25,7 @@ | |
#include <linux/user_namespace.h> | |
#include <net/sock.h> | |
+#include "include/af_unix.h" | |
#include "include/apparmor.h" | |
#include "include/apparmorfs.h" | |
#include "include/audit.h" | |
@@ -32,24 +33,29 @@ | |
#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" | |
+#include "include/mount.h" | |
/* Flag indicating whether initialization completed */ | |
int apparmor_initialized __initdata; | |
+DEFINE_PER_CPU(struct aa_buffers, aa_buffers); | |
+ | |
+ | |
/* | |
* LSM hook functions | |
*/ | |
/* | |
- * free the associated aa_task_cxt and put its profiles | |
+ * free the associated aa_task_ctx and put its labels | |
*/ | |
static void apparmor_cred_free(struct cred *cred) | |
{ | |
- aa_free_task_context(cred_cxt(cred)); | |
- cred_cxt(cred) = NULL; | |
+ aa_free_task_context(cred_ctx(cred)); | |
+ cred_ctx(cred) = NULL; | |
} | |
/* | |
@@ -58,27 +64,27 @@ static void apparmor_cred_free(struct cred *cred) | |
static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp) | |
{ | |
/* freed by apparmor_cred_free */ | |
- struct aa_task_cxt *cxt = aa_alloc_task_context(gfp); | |
- if (!cxt) | |
+ struct aa_task_ctx *ctx = aa_alloc_task_context(gfp); | |
+ if (!ctx) | |
return -ENOMEM; | |
- cred_cxt(cred) = cxt; | |
+ cred_ctx(cred) = ctx; | |
return 0; | |
} | |
/* | |
- * prepare new aa_task_cxt for modification by prepare_cred block | |
+ * prepare new aa_task_ctx for modification by prepare_cred block | |
*/ | |
static int apparmor_cred_prepare(struct cred *new, const struct cred *old, | |
gfp_t gfp) | |
{ | |
/* freed by apparmor_cred_free */ | |
- struct aa_task_cxt *cxt = aa_alloc_task_context(gfp); | |
- if (!cxt) | |
+ struct aa_task_ctx *ctx = aa_alloc_task_context(gfp); | |
+ if (!ctx) | |
return -ENOMEM; | |
- aa_dup_task_context(cxt, cred_cxt(old)); | |
- cred_cxt(new) = cxt; | |
+ aa_dup_task_context(ctx, cred_ctx(old)); | |
+ cred_ctx(new) = ctx; | |
return 0; | |
} | |
@@ -87,43 +93,71 @@ static int apparmor_cred_prepare(struct cred *new, const struct cred *old, | |
*/ | |
static void apparmor_cred_transfer(struct cred *new, const struct cred *old) | |
{ | |
- const struct aa_task_cxt *old_cxt = cred_cxt(old); | |
- struct aa_task_cxt *new_cxt = cred_cxt(new); | |
+ const struct aa_task_ctx *old_ctx = cred_ctx(old); | |
+ struct aa_task_ctx *new_ctx = cred_ctx(new); | |
- aa_dup_task_context(new_cxt, old_cxt); | |
+ aa_dup_task_context(new_ctx, old_ctx); | |
} | |
static int apparmor_ptrace_access_check(struct task_struct *child, | |
unsigned int mode) | |
{ | |
- return aa_ptrace(current, child, mode); | |
+ struct aa_label *tracer, *tracee; | |
+ int error; | |
+ | |
+ tracer = aa_begin_current_label(DO_UPDATE); | |
+ tracee = aa_get_task_label(child); | |
+ error = aa_may_ptrace(tracer, tracee, | |
+ mode == PTRACE_MODE_READ ? AA_PTRACE_READ : AA_PTRACE_TRACE); | |
+ aa_put_label(tracee); | |
+ aa_end_current_label(tracer); | |
+ | |
+ return error; | |
} | |
static int apparmor_ptrace_traceme(struct task_struct *parent) | |
{ | |
- return aa_ptrace(parent, current, PTRACE_MODE_ATTACH); | |
+ struct aa_label *tracer, *tracee; | |
+ int error; | |
+ | |
+ tracee = aa_begin_current_label(DO_UPDATE); | |
+ tracer = aa_get_task_label(parent); | |
+ error = aa_may_ptrace(tracer, tracee, AA_PTRACE_TRACE); | |
+ aa_put_label(tracer); | |
+ aa_end_current_label(tracee); | |
+ | |
+ return error; | |
} | |
/* Derived from security/commoncap.c:cap_capget */ | |
static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, | |
kernel_cap_t *inheritable, kernel_cap_t *permitted) | |
{ | |
- struct aa_profile *profile; | |
+ struct aa_label *label; | |
const struct cred *cred; | |
rcu_read_lock(); | |
cred = __task_cred(target); | |
- profile = aa_cred_profile(cred); | |
+ label = aa_get_newest_cred_label(cred); | |
/* | |
* cap_capget is stacked ahead of this and will | |
* initialize effective and permitted. | |
*/ | |
- if (!unconfined(profile) && !COMPLAIN_MODE(profile)) { | |
- *effective = cap_intersect(*effective, profile->caps.allow); | |
- *permitted = cap_intersect(*permitted, profile->caps.allow); | |
+ if (!unconfined(label)) { | |
+ struct aa_profile *profile; | |
+ struct label_it i; | |
+ label_for_each_confined(i, label, profile) { | |
+ if (COMPLAIN_MODE(profile)) | |
+ continue; | |
+ *effective = cap_intersect(*effective, | |
+ profile->caps.allow); | |
+ *permitted = cap_intersect(*permitted, | |
+ profile->caps.allow); | |
+ } | |
} | |
rcu_read_unlock(); | |
+ aa_put_label(label); | |
return 0; | |
} | |
@@ -131,12 +165,14 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, | |
static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, | |
int cap, int audit) | |
{ | |
- struct aa_profile *profile; | |
+ struct aa_label *label; | |
int error = 0; | |
- profile = aa_cred_profile(cred); | |
- if (!unconfined(profile)) | |
- error = aa_capable(profile, cap, audit); | |
+ label = aa_get_newest_cred_label(cred); | |
+ if (!unconfined(label)) | |
+ error = aa_capable(label, cap, audit); | |
+ aa_put_label(label); | |
+ | |
return error; | |
} | |
@@ -149,19 +185,42 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, | |
* | |
* Returns: %0 else error code if error or permission denied | |
*/ | |
-static int common_perm(int op, const struct path *path, u32 mask, | |
+static int common_perm(const char *op, const struct path *path, u32 mask, | |
struct path_cond *cond) | |
{ | |
- struct aa_profile *profile; | |
+ struct aa_label *label; | |
int error = 0; | |
- profile = __aa_current_profile(); | |
- if (!unconfined(profile)) | |
- error = aa_path_perm(op, profile, path, 0, mask, cond); | |
+ label = aa_begin_current_label(NO_UPDATE); | |
+ if (!unconfined(label)) | |
+ error = aa_path_perm(op, label, path, 0, mask, cond); | |
+ aa_end_current_label(label); | |
return error; | |
} | |
+static int common_perm_cond(const char *op, const struct path *path, u32 mask) | |
+{ | |
+ struct path_cond cond = { d_backing_inode(path->dentry)->i_uid, | |
+ d_backing_inode(path->dentry)->i_mode | |
+ }; | |
+ | |
+ if (!path_mediated_fs(path->dentry)) | |
+ return 0; | |
+ | |
+ return common_perm(op, path, mask, &cond); | |
+} | |
+ | |
+static void apparmor_inode_free_security(struct inode *inode) | |
+{ | |
+ struct aa_label *ctx = inode_ctx(inode); | |
+ | |
+ if (ctx) { | |
+ inode_ctx(inode) = NULL; | |
+ aa_put_label(ctx); | |
+ } | |
+} | |
+ | |
/** | |
* common_perm_dir_dentry - common permission wrapper when path is dir, dentry | |
* @op: operation being checked | |
@@ -172,7 +231,7 @@ static int common_perm(int op, const struct path *path, u32 mask, | |
* | |
* Returns: %0 else error code if error or permission denied | |
*/ | |
-static int common_perm_dir_dentry(int op, const struct path *dir, | |
+static int common_perm_dir_dentry(const char *op, const struct path *dir, | |
struct dentry *dentry, u32 mask, | |
struct path_cond *cond) | |
{ | |
@@ -182,25 +241,6 @@ static int common_perm_dir_dentry(int op, const struct path *dir, | |
} | |
/** | |
- * common_perm_path - common permission wrapper when mnt, dentry | |
- * @op: operation being checked | |
- * @path: location to check (NOT NULL) | |
- * @mask: requested permissions mask | |
- * | |
- * Returns: %0 else error code if error or permission denied | |
- */ | |
-static inline int common_perm_path(int op, const struct path *path, u32 mask) | |
-{ | |
- struct path_cond cond = { d_backing_inode(path->dentry)->i_uid, | |
- d_backing_inode(path->dentry)->i_mode | |
- }; | |
- if (!mediated_filesystem(path->dentry)) | |
- return 0; | |
- | |
- return common_perm(op, path, mask, &cond); | |
-} | |
- | |
-/** | |
* common_perm_rm - common permission wrapper for operations doing rm | |
* @op: operation being checked | |
* @dir: directory that the dentry is in (NOT NULL) | |
@@ -209,13 +249,13 @@ static inline int common_perm_path(int op, const struct path *path, u32 mask) | |
* | |
* Returns: %0 else error code if error or permission denied | |
*/ | |
-static int common_perm_rm(int op, const struct path *dir, | |
+static int common_perm_rm(const char *op, const struct path *dir, | |
struct dentry *dentry, u32 mask) | |
{ | |
struct inode *inode = d_backing_inode(dentry); | |
struct path_cond cond = { }; | |
- if (!inode || !mediated_filesystem(dentry)) | |
+ if (!inode || !path_mediated_fs(dentry)) | |
return 0; | |
cond.uid = inode->i_uid; | |
@@ -234,12 +274,12 @@ static int common_perm_rm(int op, const struct path *dir, | |
* | |
* Returns: %0 else error code if error or permission denied | |
*/ | |
-static int common_perm_create(int op, const struct path *dir, | |
+static int common_perm_create(const char *op, const struct path *dir, | |
struct dentry *dentry, u32 mask, umode_t mode) | |
{ | |
struct path_cond cond = { current_fsuid(), mode }; | |
- if (!mediated_filesystem(dir->dentry)) | |
+ if (!path_mediated_fs(dir->dentry)) | |
return 0; | |
return common_perm_dir_dentry(op, dir, dentry, mask, &cond); | |
@@ -270,7 +310,7 @@ static int apparmor_path_mknod(const struct path *dir, struct dentry *dentry, | |
static int apparmor_path_truncate(const struct path *path) | |
{ | |
- return common_perm_path(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE); | |
+ return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR); | |
} | |
static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry, | |
@@ -283,70 +323,74 @@ static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry, | |
static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_dir, | |
struct dentry *new_dentry) | |
{ | |
- struct aa_profile *profile; | |
+ struct aa_label *label; | |
int error = 0; | |
- if (!mediated_filesystem(old_dentry)) | |
+ if (!path_mediated_fs(old_dentry)) | |
return 0; | |
- profile = aa_current_profile(); | |
- if (!unconfined(profile)) | |
- error = aa_path_link(profile, old_dentry, new_dir, new_dentry); | |
+ label = aa_begin_current_label(DO_UPDATE); | |
+ if (!unconfined(label)) | |
+ error = aa_path_link(label, old_dentry, new_dir, new_dentry); | |
+ aa_end_current_label(label); | |
+ | |
return error; | |
} | |
static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_dentry, | |
const struct path *new_dir, struct dentry *new_dentry) | |
{ | |
- struct aa_profile *profile; | |
+ struct aa_label *label; | |
int error = 0; | |
- if (!mediated_filesystem(old_dentry)) | |
+ if (!path_mediated_fs(old_dentry)) | |
return 0; | |
- profile = aa_current_profile(); | |
- if (!unconfined(profile)) { | |
+ label = aa_begin_current_label(DO_UPDATE); | |
+ if (!unconfined(label)) { | |
struct path old_path = { old_dir->mnt, old_dentry }; | |
struct path new_path = { new_dir->mnt, new_dentry }; | |
struct path_cond cond = { d_backing_inode(old_dentry)->i_uid, | |
d_backing_inode(old_dentry)->i_mode | |
}; | |
- error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0, | |
- MAY_READ | AA_MAY_META_READ | MAY_WRITE | | |
- AA_MAY_META_WRITE | AA_MAY_DELETE, | |
+ error = aa_path_perm(OP_RENAME_SRC, label, &old_path, 0, | |
+ MAY_READ | AA_MAY_GETATTR | MAY_WRITE | | |
+ AA_MAY_SETATTR | AA_MAY_DELETE, | |
&cond); | |
if (!error) | |
- error = aa_path_perm(OP_RENAME_DEST, profile, &new_path, | |
- 0, MAY_WRITE | AA_MAY_META_WRITE | | |
+ error = aa_path_perm(OP_RENAME_DEST, label, &new_path, | |
+ 0, MAY_WRITE | AA_MAY_SETATTR | | |
AA_MAY_CREATE, &cond); | |
} | |
+ aa_end_current_label(label); | |
+ | |
return error; | |
} | |
static int apparmor_path_chmod(const struct path *path, umode_t mode) | |
{ | |
- return common_perm_path(OP_CHMOD, path, AA_MAY_CHMOD); | |
+ return common_perm_cond(OP_CHMOD, path, AA_MAY_CHMOD); | |
} | |
static int apparmor_path_chown(const struct path *path, kuid_t uid, kgid_t gid) | |
{ | |
- return common_perm_path(OP_CHOWN, path, AA_MAY_CHOWN); | |
+ return common_perm_cond(OP_CHOWN, path, AA_MAY_CHOWN); | |
} | |
static int apparmor_inode_getattr(const struct path *path) | |
{ | |
- return common_perm_path(OP_GETATTR, path, AA_MAY_META_READ); | |
+ return common_perm_cond(OP_GETATTR, path, AA_MAY_GETATTR); | |
} | |
static int apparmor_file_open(struct file *file, const struct cred *cred) | |
{ | |
- struct aa_file_cxt *fcxt = file->f_security; | |
- struct aa_profile *profile; | |
+ struct aa_file_ctx *fctx = file_ctx(file); | |
+ struct aa_label *label; | |
int error = 0; | |
- if (!mediated_filesystem(file->f_path.dentry)) | |
+ if (!path_mediated_fs(file->f_path.dentry)) | |
return 0; | |
/* If in exec, permission is handled by bprm hooks. | |
@@ -355,69 +399,61 @@ static int apparmor_file_open(struct file *file, const struct cred *cred) | |
* actually execute the image. | |
*/ | |
if (current->in_execve) { | |
- fcxt->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP; | |
+ fctx->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP; | |
return 0; | |
} | |
- profile = aa_cred_profile(cred); | |
- if (!unconfined(profile)) { | |
+ label = aa_get_newest_cred_label(cred); | |
+ if (!unconfined(label)) { | |
struct inode *inode = file_inode(file); | |
struct path_cond cond = { inode->i_uid, inode->i_mode }; | |
- error = aa_path_perm(OP_OPEN, profile, &file->f_path, 0, | |
+ error = aa_path_perm(OP_OPEN, label, &file->f_path, 0, | |
aa_map_file_to_perms(file), &cond); | |
/* todo cache full allowed permissions set and state */ | |
- fcxt->allow = aa_map_file_to_perms(file); | |
+ fctx->allow = aa_map_file_to_perms(file); | |
} | |
+ aa_put_label(label); | |
return error; | |
} | |
static int apparmor_file_alloc_security(struct file *file) | |
{ | |
+ int error = 0; | |
+ | |
/* freed by apparmor_file_free_security */ | |
- file->f_security = aa_alloc_file_context(GFP_KERNEL); | |
- if (!file->f_security) | |
- return -ENOMEM; | |
- return 0; | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ file->f_security = aa_alloc_file_ctx(label, GFP_KERNEL); | |
+ if (!file_ctx(file)) | |
+ error = -ENOMEM; | |
+ aa_end_current_label(label); | |
+ return error; | |
} | |
static void apparmor_file_free_security(struct file *file) | |
{ | |
- struct aa_file_cxt *cxt = file->f_security; | |
- | |
- aa_free_file_context(cxt); | |
+ aa_free_file_ctx(file_ctx(file)); | |
} | |
-static int common_file_perm(int op, struct file *file, u32 mask) | |
+static int common_file_perm(const char *op, struct file *file, u32 mask) | |
{ | |
- struct aa_file_cxt *fcxt = file->f_security; | |
- struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred); | |
+ struct aa_label *label; | |
int error = 0; | |
- BUG_ON(!fprofile); | |
- | |
- if (!file->f_path.mnt || | |
- !mediated_filesystem(file->f_path.dentry)) | |
- return 0; | |
- | |
- profile = __aa_current_profile(); | |
- | |
- /* revalidate access, if task is unconfined, or the cached cred | |
- * doesn't match or if the request is for more permissions than | |
- * was granted. | |
- * | |
- * Note: the test for !unconfined(fprofile) is to handle file | |
- * delegation from unconfined tasks | |
- */ | |
- if (!unconfined(profile) && !unconfined(fprofile) && | |
- ((fprofile != profile) || (mask & ~fcxt->allow))) | |
- error = aa_file_perm(op, profile, file, mask); | |
+ label = aa_begin_current_label(NO_UPDATE); | |
+ error = aa_file_perm(op, label, file, mask); | |
+ aa_end_current_label(label); | |
return error; | |
} | |
+static int apparmor_file_receive(struct file *file) | |
+{ | |
+ return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file)); | |
+} | |
+ | |
static int apparmor_file_permission(struct file *file, int mask) | |
{ | |
return common_file_perm(OP_FPERM, file, mask); | |
@@ -433,12 +469,12 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd) | |
return common_file_perm(OP_FLOCK, file, mask); | |
} | |
-static int common_mmap(int op, struct file *file, unsigned long prot, | |
+static int common_mmap(const char *op, struct file *file, unsigned long prot, | |
unsigned long flags) | |
{ | |
int mask = 0; | |
- if (!file || !file->f_security) | |
+ if (!file || !file_ctx(file)) | |
return 0; | |
if (prot & PROT_READ) | |
@@ -468,28 +504,87 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, | |
!(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); | |
} | |
+static int apparmor_sb_mount(const char *dev_name, const struct path *path, | |
+ const char *type, unsigned long flags, void *data) | |
+{ | |
+ struct aa_label *label; | |
+ int error = 0; | |
+ | |
+ /* Discard magic */ | |
+ if ((flags & MS_MGC_MSK) == MS_MGC_VAL) | |
+ flags &= ~MS_MGC_MSK; | |
+ | |
+ flags &= ~AA_MS_IGNORE_MASK; | |
+ | |
+ label = aa_begin_current_label(NO_UPDATE); | |
+ if (!unconfined(label)) { | |
+ if (flags & MS_REMOUNT) | |
+ error = aa_remount(label, path, flags, data); | |
+ else if (flags & MS_BIND) | |
+ error = aa_bind_mount(label, path, dev_name, flags); | |
+ else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | | |
+ MS_UNBINDABLE)) | |
+ error = aa_mount_change_type(label, path, flags); | |
+ else if (flags & MS_MOVE) | |
+ error = aa_move_mount(label, path, dev_name); | |
+ else | |
+ error = aa_new_mount(label, dev_name, path, type, | |
+ flags, data); | |
+ } | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+static int apparmor_sb_umount(struct vfsmount *mnt, int flags) | |
+{ | |
+ struct aa_label *label; | |
+ int error = 0; | |
+ | |
+ label = aa_begin_current_label(NO_UPDATE); | |
+ if (!unconfined(label)) | |
+ error = aa_umount(label, mnt, flags); | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+static int apparmor_sb_pivotroot(const struct path *old_path, | |
+ const struct path *new_path) | |
+{ | |
+ struct aa_label *label; | |
+ int error = 0; | |
+ | |
+ label = aa_get_current_label(); | |
+ if (!unconfined(label)) | |
+ error = aa_pivotroot(label, old_path, new_path); | |
+ aa_put_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
static int apparmor_getprocattr(struct task_struct *task, char *name, | |
char **value) | |
{ | |
int error = -ENOENT; | |
/* released below */ | |
const struct cred *cred = get_task_cred(task); | |
- struct aa_task_cxt *cxt = cred_cxt(cred); | |
- struct aa_profile *profile = NULL; | |
+ struct aa_task_ctx *ctx = cred_ctx(cred); | |
+ struct aa_label *label = NULL; | |
if (strcmp(name, "current") == 0) | |
- profile = aa_get_newest_profile(cxt->profile); | |
- else if (strcmp(name, "prev") == 0 && cxt->previous) | |
- profile = aa_get_newest_profile(cxt->previous); | |
- else if (strcmp(name, "exec") == 0 && cxt->onexec) | |
- profile = aa_get_newest_profile(cxt->onexec); | |
+ label = aa_get_newest_label(ctx->label); | |
+ else if (strcmp(name, "prev") == 0 && ctx->previous) | |
+ label = aa_get_newest_label(ctx->previous); | |
+ else if (strcmp(name, "exec") == 0 && ctx->onexec) | |
+ label = aa_get_newest_label(ctx->onexec); | |
else | |
error = -EINVAL; | |
- if (profile) | |
- error = aa_getprocattr(profile, value); | |
+ if (label) | |
+ error = aa_getprocattr(label, value); | |
- aa_put_profile(profile); | |
+ aa_put_label(label); | |
put_cred(cred); | |
return error; | |
@@ -498,11 +593,10 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, | |
static int apparmor_setprocattr(struct task_struct *task, char *name, | |
void *value, size_t size) | |
{ | |
- struct common_audit_data sa; | |
- struct apparmor_audit_data aad = {0,}; | |
char *command, *largs = NULL, *args = value; | |
size_t arg_size; | |
int error; | |
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETPROCATTR); | |
if (size == 0) | |
return -EINVAL; | |
@@ -538,17 +632,23 @@ static int apparmor_setprocattr(struct task_struct *task, char *name, | |
error = aa_setprocattr_changehat(args, arg_size, | |
AA_DO_TEST); | |
} else if (strcmp(command, "changeprofile") == 0) { | |
- error = aa_setprocattr_changeprofile(args, !AA_ONEXEC, | |
- !AA_DO_TEST); | |
+ error = aa_change_profile(args, !AA_ONEXEC, | |
+ !AA_DO_TEST, false); | |
} else if (strcmp(command, "permprofile") == 0) { | |
- error = aa_setprocattr_changeprofile(args, !AA_ONEXEC, | |
- AA_DO_TEST); | |
+ error = aa_change_profile(args, !AA_ONEXEC, AA_DO_TEST, | |
+ false); | |
+ } else if (strcmp(command, "stack") == 0) { | |
+ error = aa_change_profile(args, !AA_ONEXEC, !AA_DO_TEST, | |
+ true); | |
} else | |
goto fail; | |
} else if (strcmp(name, "exec") == 0) { | |
if (strcmp(command, "exec") == 0) | |
- error = aa_setprocattr_changeprofile(args, AA_ONEXEC, | |
- !AA_DO_TEST); | |
+ error = aa_change_profile(args, AA_ONEXEC, !AA_DO_TEST, | |
+ false); | |
+ else if (strcmp(command, "stack") == 0) | |
+ error = aa_change_profile(args, AA_ONEXEC, !AA_DO_TEST, | |
+ true); | |
else | |
goto fail; | |
} else | |
@@ -562,34 +662,500 @@ static int apparmor_setprocattr(struct task_struct *task, char *name, | |
return error; | |
fail: | |
- sa.type = LSM_AUDIT_DATA_NONE; | |
- sa.aad = &aad; | |
- aad.profile = aa_current_profile(); | |
- aad.op = OP_SETPROCATTR; | |
- aad.info = name; | |
- aad.error = error = -EINVAL; | |
+ aad(&sa)->label = aa_begin_current_label(DO_UPDATE); | |
+ aad(&sa)->info = name; | |
+ aad(&sa)->error = error = -EINVAL; | |
aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL); | |
+ aa_end_current_label(aad(&sa)->label); | |
goto out; | |
} | |
+/** | |
+ * apparmor_bprm_committing_creds - do task cleanup on committing new creds | |
+ * @bprm: binprm for the exec (NOT NULL) | |
+ */ | |
+void apparmor_bprm_committing_creds(struct linux_binprm *bprm) | |
+{ | |
+ struct aa_label *label = aa_current_raw_label(); | |
+ struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred); | |
+ | |
+ /* bail out if unconfined or not changing profile */ | |
+ if ((new_ctx->label->proxy == label->proxy) || | |
+ (unconfined(new_ctx->label))) | |
+ return; | |
+ | |
+ aa_inherit_files(bprm->cred, current->files); | |
+ | |
+ current->pdeath_signal = 0; | |
+ | |
+ /* reset soft limits and set hard limits for the new label */ | |
+ __aa_transition_rlimits(label, new_ctx->label); | |
+} | |
+ | |
+/** | |
+ * apparmor_bprm_commited_cred - do cleanup after new creds committed | |
+ * @bprm: binprm for the exec (NOT NULL) | |
+ */ | |
+void apparmor_bprm_committed_creds(struct linux_binprm *bprm) | |
+{ | |
+ /* TODO: cleanup signals - ipc mediation */ | |
+ return; | |
+} | |
+ | |
static int apparmor_task_setrlimit(struct task_struct *task, | |
unsigned int resource, struct rlimit *new_rlim) | |
{ | |
- struct aa_profile *profile = __aa_current_profile(); | |
+ struct aa_label *label = aa_begin_current_label(NO_UPDATE); | |
int error = 0; | |
- if (!unconfined(profile)) | |
- error = aa_task_setrlimit(profile, task, resource, new_rlim); | |
+ if (!unconfined(label)) | |
+ error = aa_task_setrlimit(label, task, resource, new_rlim); | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+/** | |
+ * apparmor_sk_alloc_security - allocate and attach the sk_security field | |
+ */ | |
+static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t flags) | |
+{ | |
+ struct aa_sk_ctx *ctx; | |
+ | |
+ ctx = kzalloc(sizeof(*ctx), flags); | |
+ if (!ctx) | |
+ return -ENOMEM; | |
+ | |
+ SK_CTX(sk) = ctx; | |
+ //??? set local too current??? | |
+ | |
+ return 0; | |
+} | |
+ | |
+/** | |
+ * apparmor_sk_free_security - free the sk_security field | |
+ */ | |
+static void apparmor_sk_free_security(struct sock *sk) | |
+{ | |
+ struct aa_sk_ctx *ctx = SK_CTX(sk); | |
+ | |
+ SK_CTX(sk) = NULL; | |
+ aa_put_label(ctx->label); | |
+ aa_put_label(ctx->peer); | |
+ path_put(&ctx->path); | |
+ kfree(ctx); | |
+} | |
+ | |
+/** | |
+ * apparmor_clone_security - clone the sk_security field | |
+ */ | |
+static void apparmor_sk_clone_security(const struct sock *sk, | |
+ struct sock *newsk) | |
+{ | |
+ struct aa_sk_ctx *ctx = SK_CTX(sk); | |
+ struct aa_sk_ctx *new = SK_CTX(newsk); | |
+ | |
+ new->label = aa_get_label(ctx->label); | |
+ new->peer = aa_get_label(ctx->peer); | |
+ new->path = ctx->path; | |
+ path_get(&new->path); | |
+} | |
+ | |
+static struct path *UNIX_FS_CONN_PATH(struct sock *sk, struct sock *newsk) | |
+{ | |
+ if (sk->sk_family == PF_UNIX && UNIX_FS(sk)) | |
+ return &unix_sk(sk)->path; | |
+ else if (newsk->sk_family == PF_UNIX && UNIX_FS(newsk)) | |
+ return &unix_sk(newsk)->path; | |
+ return NULL; | |
+} | |
+ | |
+/** | |
+ * apparmor_unix_stream_connect - check perms before making unix domain conn | |
+ * | |
+ * peer is locked when this hook is called | |
+ */ | |
+static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, | |
+ struct sock *newsk) | |
+{ | |
+ struct aa_sk_ctx *sk_ctx = SK_CTX(sk); | |
+ struct aa_sk_ctx *peer_ctx = SK_CTX(peer_sk); | |
+ struct aa_sk_ctx *new_ctx = SK_CTX(newsk); | |
+ struct aa_label *label; | |
+ struct path *path; | |
+ int error; | |
+ | |
+ label = aa_begin_current_label(NO_UPDATE); | |
+ error = aa_unix_peer_perm(label, OP_CONNECT, | |
+ (AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE), | |
+ sk, peer_sk, NULL); | |
+ if (!UNIX_FS(peer_sk)) { | |
+ last_error(error, | |
+ aa_unix_peer_perm(peer_ctx->label, OP_CONNECT, | |
+ (AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE), | |
+ peer_sk, sk, label)); | |
+ } | |
+ aa_end_current_label(label); | |
+ | |
+ if (error) | |
+ return error; | |
+ | |
+ /* label newsk if it wasn't labeled in post_create. Normally this | |
+ * would be done in sock_graft, but because we are directly looking | |
+ * at the peer_sk to obtain peer_labeling for unix socks this | |
+ * does not work | |
+ */ | |
+ if (!new_ctx->label) | |
+ new_ctx->label = aa_get_label(peer_ctx->label); | |
+ | |
+ /* Cross reference the peer labels for SO_PEERSEC */ | |
+ if (new_ctx->peer) | |
+ aa_put_label(new_ctx->peer); | |
+ | |
+ if (sk_ctx->peer) | |
+ aa_put_label(sk_ctx->peer); | |
+ | |
+ new_ctx->peer = aa_get_label(sk_ctx->label); | |
+ sk_ctx->peer = aa_get_label(peer_ctx->label); | |
+ | |
+ path = UNIX_FS_CONN_PATH(sk, peer_sk); | |
+ if (path) { | |
+ new_ctx->path = *path; | |
+ sk_ctx->path = *path; | |
+ path_get(path); | |
+ path_get(path); | |
+ } | |
+ return 0; | |
+} | |
+ | |
+/** | |
+ * apparmor_unix_may_send - check perms before conn or sending unix dgrams | |
+ * | |
+ * other is locked when this hook is called | |
+ * | |
+ * dgram connect calls may_send, peer setup but path not copied????? | |
+ */ | |
+static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) | |
+{ | |
+ struct aa_sk_ctx *peer_ctx = SK_CTX(peer->sk); | |
+ struct aa_label *label = aa_begin_current_label(NO_UPDATE); | |
+ int error; | |
+ | |
+ error = xcheck(aa_unix_peer_perm(label, OP_SENDMSG, AA_MAY_SEND, | |
+ sock->sk, peer->sk, NULL), | |
+ aa_unix_peer_perm(peer_ctx->label, OP_SENDMSG, AA_MAY_RECEIVE, | |
+ peer->sk, sock->sk, label)); | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_create - check perms before creating a new socket | |
+ */ | |
+static int apparmor_socket_create(int family, int type, int protocol, int kern) | |
+{ | |
+ struct aa_label *label; | |
+ int error = 0; | |
+ | |
+ label = aa_begin_current_label(DO_UPDATE); | |
+ if (!(kern || unconfined(label))) | |
+ error = aa_sock_create_perm(label, family, type, protocol); | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_post_create - setup the per-socket security struct | |
+ * | |
+ * Note: | |
+ * - kernel sockets currently labeled unconfined but we may want to | |
+ * move to a special kernel label | |
+ * - socket may not have sk here if created with sock_create_lite or | |
+ * sock_alloc. These should be accept cases which will be handled in | |
+ * sock_graft. | |
+ */ | |
+static int apparmor_socket_post_create(struct socket *sock, int family, | |
+ int type, int protocol, int kern) | |
+{ | |
+ struct aa_label *label; | |
+ | |
+ if (kern) { | |
+ struct aa_ns *ns = aa_get_current_ns(); | |
+ label = aa_get_label(ns_unconfined(ns)); | |
+ aa_put_ns(ns); | |
+ } else | |
+ label = aa_get_current_label(); | |
+ | |
+ if (sock->sk) { | |
+ struct aa_sk_ctx *ctx = SK_CTX(sock->sk); | |
+ aa_put_label(ctx->label); | |
+ ctx->label = aa_get_label(label); | |
+ } | |
+ aa_put_label(label); | |
+ | |
+ return 0; | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_bind - check perms before bind addr to socket | |
+ */ | |
+static int apparmor_socket_bind(struct socket *sock, | |
+ struct sockaddr *address, int addrlen) | |
+{ | |
+ return aa_sock_bind_perm(sock, address, addrlen); | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_connect - check perms before connecting @sock to @address | |
+ */ | |
+static int apparmor_socket_connect(struct socket *sock, | |
+ struct sockaddr *address, int addrlen) | |
+{ | |
+ return aa_sock_connect_perm(sock, address, addrlen); | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_list - check perms before allowing listen | |
+ */ | |
+static int apparmor_socket_listen(struct socket *sock, int backlog) | |
+{ | |
+ return aa_sock_listen_perm(sock, backlog); | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_accept - check perms before accepting a new connection. | |
+ * | |
+ * Note: while @newsock is created and has some information, the accept | |
+ * has not been done. | |
+ */ | |
+static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) | |
+{ | |
+ return aa_sock_accept_perm(sock, newsock); | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_sendmsg - check perms before sending msg to another socket | |
+ */ | |
+static int apparmor_socket_sendmsg(struct socket *sock, | |
+ struct msghdr *msg, int size) | |
+{ | |
+ int error = aa_sock_msg_perm(OP_SENDMSG, AA_MAY_SEND, sock, msg, size); | |
+ if (!error) { | |
+ /* TODO: setup delegation on scm rights | |
+ see smack for AF_INET, AF_INET6 */ | |
+ ; | |
+ } | |
return error; | |
} | |
+/** | |
+ * apparmor_socket_recvmsg - check perms before receiving a message | |
+ */ | |
+static int apparmor_socket_recvmsg(struct socket *sock, | |
+ struct msghdr *msg, int size, int flags) | |
+{ | |
+ return aa_sock_msg_perm(OP_RECVMSG, AA_MAY_RECEIVE, sock, msg, size); | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_getsockname - check perms before getting the local address | |
+ */ | |
+static int apparmor_socket_getsockname(struct socket *sock) | |
+{ | |
+ return aa_sock_perm(OP_GETSOCKNAME, AA_MAY_GETATTR, sock); | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_getpeername - check perms before getting remote address | |
+ */ | |
+static int apparmor_socket_getpeername(struct socket *sock) | |
+{ | |
+ return aa_sock_perm(OP_GETPEERNAME, AA_MAY_GETATTR, sock); | |
+} | |
+ | |
+/** | |
+ * apparmor_getsockopt - check perms before getting socket options | |
+ */ | |
+static int apparmor_socket_getsockopt(struct socket *sock, int level, | |
+ int optname) | |
+{ | |
+ return aa_sock_opt_perm(OP_GETSOCKOPT, AA_MAY_GETOPT, sock, | |
+ level, optname); | |
+} | |
+ | |
+/** | |
+ * apparmor_setsockopt - check perms before setting socket options | |
+ */ | |
+static int apparmor_socket_setsockopt(struct socket *sock, int level, | |
+ int optname) | |
+{ | |
+ return aa_sock_opt_perm(OP_SETSOCKOPT, AA_MAY_SETOPT, sock, | |
+ level, optname); | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_shutdown - check perms before shutting down @sock conn | |
+ */ | |
+static int apparmor_socket_shutdown(struct socket *sock, int how) | |
+{ | |
+ return aa_sock_perm(OP_SHUTDOWN, AA_MAY_SHUTDOWN, sock); | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_sock_recv_skb - check perms before associating skb to sk | |
+ * | |
+ * Note: can not sleep maybe called with locks held | |
+ | |
+dont want protocol specific in __skb_recv_datagram() | |
+to deny an incoming connection socket_sock_rcv_skb() | |
+ | |
+ */ | |
+static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) | |
+{ | |
+ /* TODO: */ | |
+ return 0; | |
+} | |
+ | |
+ | |
+static struct aa_label *sk_peer_label(struct sock *sk) | |
+{ | |
+ struct sock *peer_sk; | |
+ struct aa_sk_ctx *ctx = SK_CTX(sk); | |
+ | |
+ if (ctx->peer) | |
+ return ctx->peer; | |
+ | |
+ if (sk->sk_family != PF_UNIX) | |
+ return ERR_PTR(-ENOPROTOOPT); | |
+ | |
+ /* check for sockpair peering which does not go through | |
+ * security_unix_stream_connect | |
+ */ | |
+ peer_sk = unix_peer(sk); | |
+ if (peer_sk) { | |
+ ctx = SK_CTX(peer_sk); | |
+ if (ctx->label) | |
+ return ctx->label; | |
+ } | |
+ | |
+ return ERR_PTR(-ENOPROTOOPT); | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_getpeersec_stream - get security context of peer | |
+ * | |
+ * Note: for tcp only valid if using ipsec or cipso on lan | |
+ */ | |
+static int apparmor_socket_getpeersec_stream(struct socket *sock, | |
+ char __user *optval, | |
+ int __user *optlen, unsigned len) | |
+{ | |
+ char *name; | |
+ int slen, error = 0; | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ struct aa_label *peer = sk_peer_label(sock->sk); | |
+ | |
+ if (IS_ERR(peer)) | |
+ return PTR_ERR(peer); | |
+ | |
+ slen = aa_label_asxprint(&name, labels_ns(label), peer, | |
+ FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | | |
+ FLAG_HIDDEN_UNCONFINED, GFP_KERNEL); | |
+ /* don't include terminating \0 in slen, it breaks some apps */ | |
+ if (slen < 0) { | |
+ error = -ENOMEM; | |
+ } else { | |
+ if (slen > len) { | |
+ error = -ERANGE; | |
+ } else if (copy_to_user(optval, name, slen)) { | |
+ error = -EFAULT; | |
+ goto out; | |
+ } | |
+ if (put_user(slen, optlen)) | |
+ error = -EFAULT; | |
+ out: | |
+ kfree(name); | |
+ | |
+ } | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+/** | |
+ * apparmor_socket_getpeersec_dgram - get security label of packet | |
+ * @sock: the peer socket | |
+ * @skb: packet data | |
+ * @secid: pointer to where to put the secid of the packet | |
+ * | |
+ * Sets the netlabel socket state on sk from parent | |
+ */ | |
+static int apparmor_socket_getpeersec_dgram(struct socket *sock, | |
+ struct sk_buff *skb, u32 *secid) | |
+ | |
+{ | |
+ /* TODO: requires secid support, and netlabel */ | |
+ return -ENOPROTOOPT; | |
+} | |
+ | |
+/** | |
+ * apparmor_sock_graft - Initialize newly created socket | |
+ * @sk: child sock | |
+ * @parent: parent socket | |
+ * | |
+ * Note: could set off of SOCK_CTX(parent) but need to track inode and we can | |
+ * just set sk security information off of current creating process label | |
+ * Labeling of sk for accept case - probably should be sock based | |
+ * instead of task, because of the case where an implicitly labeled | |
+ * socket is shared by different tasks. | |
+ */ | |
+static void apparmor_sock_graft(struct sock *sk, struct socket *parent) | |
+{ | |
+ struct aa_sk_ctx *ctx = SK_CTX(sk); | |
+ if (!ctx->label) | |
+ ctx->label = aa_get_current_label(); | |
+} | |
+ | |
+static int apparmor_task_kill(struct task_struct *target, struct siginfo *info, | |
+ int sig, u32 secid) | |
+{ | |
+ struct aa_label *cl, *tl; | |
+ int error; | |
+ | |
+ if (secid) | |
+ /* TODO: after secid to label mapping is done. | |
+ * Dealing with USB IO specific behavior | |
+ */ | |
+ return 0; | |
+ cl = aa_begin_current_label(NO_UPDATE); | |
+ tl = aa_get_task_label(target); | |
+ error = aa_may_signal(cl, tl, sig); | |
+ aa_put_label(tl); | |
+ aa_end_current_label(cl); | |
+ | |
+ return error; | |
+} | |
+ | |
+#ifndef LSM_HOOKS_NAME | |
+#define LSM_HOOKS_NAME(X) //.name = (X), | |
+#endif | |
static struct security_hook_list apparmor_hooks[] = { | |
+ LSM_HOOKS_NAME("apparmor") | |
+ | |
LSM_HOOK_INIT(ptrace_access_check, apparmor_ptrace_access_check), | |
LSM_HOOK_INIT(ptrace_traceme, apparmor_ptrace_traceme), | |
LSM_HOOK_INIT(capget, apparmor_capget), | |
LSM_HOOK_INIT(capable, apparmor_capable), | |
+ LSM_HOOK_INIT(inode_free_security, apparmor_inode_free_security), | |
+ | |
+ LSM_HOOK_INIT(sb_mount, apparmor_sb_mount), | |
+ LSM_HOOK_INIT(sb_umount, apparmor_sb_umount), | |
+ LSM_HOOK_INIT(sb_pivotroot, apparmor_sb_pivotroot), | |
+ | |
LSM_HOOK_INIT(path_link, apparmor_path_link), | |
LSM_HOOK_INIT(path_unlink, apparmor_path_unlink), | |
LSM_HOOK_INIT(path_symlink, apparmor_path_symlink), | |
@@ -603,16 +1169,43 @@ static int apparmor_task_setrlimit(struct task_struct *task, | |
LSM_HOOK_INIT(inode_getattr, apparmor_inode_getattr), | |
LSM_HOOK_INIT(file_open, apparmor_file_open), | |
+ LSM_HOOK_INIT(file_receive, apparmor_file_receive), | |
LSM_HOOK_INIT(file_permission, apparmor_file_permission), | |
LSM_HOOK_INIT(file_alloc_security, apparmor_file_alloc_security), | |
LSM_HOOK_INIT(file_free_security, apparmor_file_free_security), | |
LSM_HOOK_INIT(mmap_file, apparmor_mmap_file), | |
+ LSM_HOOK_INIT(mmap_addr, cap_mmap_addr), | |
LSM_HOOK_INIT(file_mprotect, apparmor_file_mprotect), | |
LSM_HOOK_INIT(file_lock, apparmor_file_lock), | |
LSM_HOOK_INIT(getprocattr, apparmor_getprocattr), | |
LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), | |
+ LSM_HOOK_INIT(sk_alloc_security, apparmor_sk_alloc_security), | |
+ LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security), | |
+ LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security), | |
+ | |
+ LSM_HOOK_INIT(unix_stream_connect, apparmor_unix_stream_connect), | |
+ LSM_HOOK_INIT(unix_may_send, apparmor_unix_may_send), | |
+ | |
+ LSM_HOOK_INIT(socket_create, apparmor_socket_create), | |
+ LSM_HOOK_INIT(socket_post_create, apparmor_socket_post_create), | |
+ LSM_HOOK_INIT(socket_bind, apparmor_socket_bind), | |
+ LSM_HOOK_INIT(socket_connect, apparmor_socket_connect), | |
+ LSM_HOOK_INIT(socket_listen, apparmor_socket_listen), | |
+ LSM_HOOK_INIT(socket_accept, apparmor_socket_accept), | |
+ LSM_HOOK_INIT(socket_sendmsg, apparmor_socket_sendmsg), | |
+ LSM_HOOK_INIT(socket_recvmsg, apparmor_socket_recvmsg), | |
+ LSM_HOOK_INIT(socket_getsockname, apparmor_socket_getsockname), | |
+ LSM_HOOK_INIT(socket_getpeername, apparmor_socket_getpeername), | |
+ LSM_HOOK_INIT(socket_getsockopt, apparmor_socket_getsockopt), | |
+ LSM_HOOK_INIT(socket_setsockopt, apparmor_socket_setsockopt), | |
+ LSM_HOOK_INIT(socket_shutdown, apparmor_socket_shutdown), | |
+ LSM_HOOK_INIT(socket_sock_rcv_skb, apparmor_socket_sock_rcv_skb), | |
+ LSM_HOOK_INIT(socket_getpeersec_stream, apparmor_socket_getpeersec_stream), | |
+ LSM_HOOK_INIT(socket_getpeersec_dgram, apparmor_socket_getpeersec_dgram), | |
+ LSM_HOOK_INIT(sock_graft, apparmor_sock_graft), | |
+ | |
LSM_HOOK_INIT(cred_alloc_blank, apparmor_cred_alloc_blank), | |
LSM_HOOK_INIT(cred_free, apparmor_cred_free), | |
LSM_HOOK_INIT(cred_prepare, apparmor_cred_prepare), | |
@@ -624,6 +1217,7 @@ static int apparmor_task_setrlimit(struct task_struct *task, | |
LSM_HOOK_INIT(bprm_secureexec, apparmor_bprm_secureexec), | |
LSM_HOOK_INIT(task_setrlimit, apparmor_task_setrlimit), | |
+ LSM_HOOK_INIT(task_kill, apparmor_task_kill), | |
}; | |
/* | |
@@ -669,7 +1263,7 @@ static int apparmor_task_setrlimit(struct task_struct *task, | |
/* AppArmor global enforcement switch - complain, enforce, kill */ | |
enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE; | |
module_param_call(mode, param_set_mode, param_get_mode, | |
- &aa_g_profile_mode, S_IRUSR | S_IWUSR); | |
+ &aa_g_profile_mode, S_IRUGO | S_IWUSR); | |
#ifdef CONFIG_SECURITY_APPARMOR_HASH | |
/* whether policy verification hashing is enabled */ | |
@@ -679,19 +1273,18 @@ static int apparmor_task_setrlimit(struct task_struct *task, | |
/* Debug mode */ | |
bool aa_g_debug; | |
-module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR); | |
+module_param_named(debug, aa_g_debug, aabool, S_IRUGO | S_IWUSR); | |
/* Audit mode */ | |
enum audit_mode aa_g_audit; | |
-module_param_call(audit, param_set_audit, param_get_audit, | |
- &aa_g_audit, S_IRUSR | S_IWUSR); | |
+module_param_call(audit, param_set_audit, param_get_audit, &aa_g_audit, | |
+ S_IRUGO | S_IWUSR); | |
/* Determines if audit header is included in audited messages. This | |
* provides more context if the audit daemon is not running | |
*/ | |
bool aa_g_audit_header = 1; | |
-module_param_named(audit_header, aa_g_audit_header, aabool, | |
- S_IRUSR | S_IWUSR); | |
+module_param_named(audit_header, aa_g_audit_header, aabool, S_IRUGO | S_IWUSR); | |
/* lock out loading/removal of policy | |
* TODO: add in at boot loading of policy, which is the only way to | |
@@ -699,27 +1292,33 @@ static int apparmor_task_setrlimit(struct task_struct *task, | |
*/ | |
bool aa_g_lock_policy; | |
module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy, | |
- S_IRUSR | S_IWUSR); | |
+ S_IRUGO | S_IWUSR); | |
/* Syscall logging mode */ | |
bool aa_g_logsyscall; | |
-module_param_named(logsyscall, aa_g_logsyscall, aabool, S_IRUSR | S_IWUSR); | |
+module_param_named(logsyscall, aa_g_logsyscall, aabool, S_IRUGO | S_IWUSR); | |
/* Maximum pathname length before accesses will start getting rejected */ | |
unsigned int aa_g_path_max = 2 * PATH_MAX; | |
-module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR | S_IWUSR); | |
+module_param_named(path_max, aa_g_path_max, aauint, S_IRUGO | S_IWUSR); | |
/* Determines how paranoid loading of policy is and how much verification | |
* on the loaded policy is done. | |
+ * DEPRECATED: read only as strict checking of load is always done now | |
+ * that none root users (user namespaces) can load policy. | |
*/ | |
bool aa_g_paranoid_load = 1; | |
-module_param_named(paranoid_load, aa_g_paranoid_load, aabool, | |
- S_IRUSR | S_IWUSR); | |
+module_param_named(paranoid_load, aa_g_paranoid_load, aabool, S_IRUGO); | |
/* Boot time disable flag */ | |
static bool apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE; | |
module_param_named(enabled, apparmor_enabled, bool, S_IRUGO); | |
+/* Boot time to set use of default or unconfined as initial profile */ | |
+bool aa_g_unconfined_init = CONFIG_SECURITY_APPARMOR_UNCONFINED_INIT; | |
+module_param_named(unconfined, aa_g_unconfined_init, aabool, S_IRUGO); | |
+ | |
+ | |
static int __init apparmor_enabled_setup(char *str) | |
{ | |
unsigned long enabled; | |
@@ -741,8 +1340,10 @@ static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp | |
static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_admin_capable()) | |
return -EPERM; | |
+ if (!apparmor_enabled) | |
+ return -EINVAL; | |
return param_get_bool(buffer, kp); | |
} | |
@@ -750,13 +1351,17 @@ static int param_set_aabool(const char *val, const struct kernel_param *kp) | |
{ | |
if (!policy_admin_capable()) | |
return -EPERM; | |
+ if (!apparmor_enabled) | |
+ return -EINVAL; | |
return param_set_bool(val, kp); | |
} | |
static int param_get_aabool(char *buffer, const struct kernel_param *kp) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_admin_capable()) | |
return -EPERM; | |
+ if (!apparmor_enabled) | |
+ return -EINVAL; | |
return param_get_bool(buffer, kp); | |
} | |
@@ -764,24 +1369,26 @@ static int param_set_aauint(const char *val, const struct kernel_param *kp) | |
{ | |
if (!policy_admin_capable()) | |
return -EPERM; | |
+ if (!apparmor_enabled) | |
+ return -EINVAL; | |
return param_set_uint(val, kp); | |
} | |
static int param_get_aauint(char *buffer, const struct kernel_param *kp) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_admin_capable()) | |
return -EPERM; | |
+ if (!apparmor_enabled) | |
+ return -EINVAL; | |
return param_get_uint(buffer, kp); | |
} | |
static int param_get_audit(char *buffer, struct kernel_param *kp) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_admin_capable()) | |
return -EPERM; | |
- | |
if (!apparmor_enabled) | |
return -EINVAL; | |
- | |
return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]); | |
} | |
@@ -790,10 +1397,8 @@ static int param_set_audit(const char *val, struct kernel_param *kp) | |
int i; | |
if (!policy_admin_capable()) | |
return -EPERM; | |
- | |
if (!apparmor_enabled) | |
return -EINVAL; | |
- | |
if (!val) | |
return -EINVAL; | |
@@ -811,7 +1416,6 @@ static int param_get_mode(char *buffer, struct kernel_param *kp) | |
{ | |
if (!policy_admin_capable()) | |
return -EPERM; | |
- | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -823,10 +1427,8 @@ static int param_set_mode(const char *val, struct kernel_param *kp) | |
int i; | |
if (!policy_admin_capable()) | |
return -EPERM; | |
- | |
if (!apparmor_enabled) | |
return -EINVAL; | |
- | |
if (!val) | |
return -EINVAL; | |
@@ -845,21 +1447,63 @@ static int param_set_mode(const char *val, struct kernel_param *kp) | |
*/ | |
/** | |
- * set_init_cxt - set a task context and profile on the first task. | |
- * | |
- * TODO: allow setting an alternate profile than unconfined | |
+ * set_init_ctx - set a task context and profile on the first task. | |
*/ | |
-static int __init set_init_cxt(void) | |
+static int __init set_init_ctx(void) | |
{ | |
struct cred *cred = (struct cred *)current->real_cred; | |
- struct aa_task_cxt *cxt; | |
+ struct aa_task_ctx *ctx; | |
- cxt = aa_alloc_task_context(GFP_KERNEL); | |
- if (!cxt) | |
+ ctx = aa_alloc_task_context(GFP_KERNEL); | |
+ if (!ctx) | |
return -ENOMEM; | |
- cxt->profile = aa_get_profile(root_ns->unconfined); | |
- cred_cxt(cred) = cxt; | |
+ if (!aa_g_unconfined_init) { | |
+ ctx->label = aa_setup_default_label(); | |
+ if (!ctx->label) { | |
+ aa_free_task_context(ctx); | |
+ return -ENOMEM; | |
+ } | |
+ /* fs setup of default is done in aa_create_aafs() */ | |
+ } else | |
+ ctx->label = aa_get_label(ns_unconfined(root_ns)); | |
+ cred_ctx(cred) = ctx; | |
+ | |
+ return 0; | |
+} | |
+ | |
+static void destroy_buffers(void) | |
+{ | |
+ u32 i, j; | |
+ | |
+ for_each_possible_cpu(i) { | |
+ for_each_cpu_buffer(j) { | |
+ kfree(per_cpu(aa_buffers, i).buf[j]); | |
+ per_cpu(aa_buffers, i).buf[j] = NULL; | |
+ } | |
+ } | |
+} | |
+ | |
+static int __init alloc_buffers(void) | |
+{ | |
+ u32 i, j; | |
+ | |
+ for_each_possible_cpu(i) { | |
+ for_each_cpu_buffer(j) { | |
+ char *buffer; | |
+ if (cpu_to_node(i) > num_online_nodes()) | |
+ /* fallback to kmalloc for offline nodes */ | |
+ buffer = kmalloc(aa_g_path_max, GFP_KERNEL); | |
+ else | |
+ buffer = kmalloc_node(aa_g_path_max, GFP_KERNEL, | |
+ cpu_to_node(i)); | |
+ if (!buffer) { | |
+ destroy_buffers(); | |
+ return -ENOMEM; | |
+ } | |
+ per_cpu(aa_buffers, i).buf[j] = buffer; | |
+ } | |
+ } | |
return 0; | |
} | |
@@ -874,17 +1518,29 @@ static int __init apparmor_init(void) | |
return 0; | |
} | |
+ error = aa_setup_dfa_engine(); | |
+ if (error) { | |
+ AA_ERROR("Unable to setup dfa engine\n"); | |
+ goto alloc_out; | |
+ } | |
+ | |
error = aa_alloc_root_ns(); | |
if (error) { | |
AA_ERROR("Unable to allocate default profile namespace\n"); | |
goto alloc_out; | |
} | |
- error = set_init_cxt(); | |
+ error = alloc_buffers(); | |
+ if (error) { | |
+ AA_ERROR("Unable to allocate work buffers\n"); | |
+ goto buffers_out; | |
+ } | |
+ | |
+ error = set_init_ctx(); | |
if (error) { | |
AA_ERROR("Failed to set context on init task\n"); | |
aa_free_root_ns(); | |
- goto alloc_out; | |
+ goto buffers_out; | |
} | |
security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks)); | |
@@ -899,8 +1555,12 @@ static int __init apparmor_init(void) | |
return error; | |
+buffers_out: | |
+ destroy_buffers(); | |
+ | |
alloc_out: | |
aa_destroy_aafs(); | |
+ aa_teardown_dfa_engine(); | |
apparmor_enabled = 0; | |
return error; | |
diff --git a/security/apparmor/match.c b/security/apparmor/match.c | |
index 3f900fc..8f0806b 100644 | |
--- a/security/apparmor/match.c | |
+++ b/security/apparmor/match.c | |
@@ -20,11 +20,38 @@ | |
#include <linux/err.h> | |
#include <linux/kref.h> | |
-#include "include/apparmor.h" | |
+#include "include/lib.h" | |
#include "include/match.h" | |
#define base_idx(X) ((X) & 0xffffff) | |
+static char nulldfa_src[] = { | |
+ #include "nulldfa.in" | |
+}; | |
+struct aa_dfa *nulldfa; | |
+ | |
+int aa_setup_dfa_engine(void) | |
+{ | |
+ int error; | |
+ | |
+ nulldfa = aa_dfa_unpack(nulldfa_src, sizeof(nulldfa_src), | |
+ TO_ACCEPT1_FLAG(YYTD_DATA32) | | |
+ TO_ACCEPT2_FLAG(YYTD_DATA32)); | |
+ if (!IS_ERR(nulldfa)) | |
+ return 0; | |
+ | |
+ error = PTR_ERR(nulldfa); | |
+ nulldfa = NULL; | |
+ | |
+ return error; | |
+} | |
+ | |
+void aa_teardown_dfa_engine(void) | |
+{ | |
+ aa_put_dfa(nulldfa); | |
+ nulldfa = NULL; | |
+} | |
+ | |
/** | |
* unpack_table - unpack a dfa table (one of accept, default, base, next check) | |
* @blob: data to unpack (NOT NULL) | |
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c | |
new file mode 100644 | |
index 0000000..82430d1 | |
--- /dev/null | |
+++ b/security/apparmor/mount.c | |
@@ -0,0 +1,731 @@ | |
+/* | |
+ * 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 <linux/fs.h> | |
+#include <linux/mount.h> | |
+#include <linux/namei.h> | |
+ | |
+#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 (aad(sa)->mnt.type) { | |
+ audit_log_format(ab, " fstype="); | |
+ audit_log_untrustedstring(ab, aad(sa)->mnt.type); | |
+ } | |
+ if (aad(sa)->mnt.src_name) { | |
+ audit_log_format(ab, " srcname="); | |
+ audit_log_untrustedstring(ab, aad(sa)->mnt.src_name); | |
+ } | |
+ if (aad(sa)->mnt.trans) { | |
+ audit_log_format(ab, " trans="); | |
+ audit_log_untrustedstring(ab, aad(sa)->mnt.trans); | |
+ } | |
+ if (aad(sa)->mnt.flags) { | |
+ audit_log_format(ab, " flags=\""); | |
+ audit_mnt_flags(ab, aad(sa)->mnt.flags); | |
+ audit_log_format(ab, "\""); | |
+ } | |
+ if (aad(sa)->mnt.data) { | |
+ audit_log_format(ab, " options="); | |
+ audit_log_untrustedstring(ab, aad(sa)->mnt.data); | |
+ } | |
+} | |
+ | |
+/** | |
+ * audit_mount - handle the auditing of mount operations | |
+ * @profile: the profile being enforced (NOT NULL) | |
+ * @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, const char *op, const char *name, | |
+ const char *src_name, const char *type, | |
+ const char *trans, unsigned long flags, | |
+ const void *data, u32 request, struct aa_perms *perms, | |
+ const char *info, int error) | |
+{ | |
+ int audit_type = AUDIT_APPARMOR_AUTO; | |
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op); | |
+ | |
+ 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 error; | |
+ } | |
+ | |
+ aad(&sa)->name = name; | |
+ aad(&sa)->mnt.src_name = src_name; | |
+ aad(&sa)->mnt.type = type; | |
+ aad(&sa)->mnt.trans = trans; | |
+ aad(&sa)->mnt.flags = flags; | |
+ if (data && (perms->audit & AA_AUDIT_DATA)) | |
+ aad(&sa)->mnt.data = data; | |
+ aad(&sa)->info = info; | |
+ aad(&sa)->error = error; | |
+ | |
+ return aa_audit(audit_type, profile, &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 aa_perms compute_mnt_perms(struct aa_dfa *dfa, | |
+ unsigned int state) | |
+{ | |
+ struct aa_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 *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 aa_perms *perms) | |
+{ | |
+ unsigned int state; | |
+ | |
+ AA_BUG(!dfa); | |
+ AA_BUG(!perms); | |
+ | |
+ 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_MNT_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 aa_perms perms = { }; | |
+ const char *info = NULL; | |
+ int pos, error = -EACCES; | |
+ | |
+ AA_BUG(!profile); | |
+ | |
+ 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]; | |
+ goto audit; | |
+ } | |
+ error = 0; | |
+ | |
+audit: | |
+ return audit_mount(profile, OP_MOUNT, mntpnt, devname, type, NULL, | |
+ flags, data, AA_MAY_MOUNT, &perms, info, error); | |
+} | |
+ | |
+static int path_flags(struct aa_profile *profile, const struct path *path) | |
+{ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!path); | |
+ | |
+ return profile->path_flags | | |
+ (S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0); | |
+} | |
+ | |
+int aa_remount(struct aa_label *label, const struct path *path, | |
+ unsigned long flags, void *data) | |
+{ | |
+ struct aa_profile *profile; | |
+ const char *name, *info = NULL; | |
+ char *buffer = NULL; | |
+ bool binary; | |
+ int error; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!path); | |
+ | |
+ binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; | |
+ | |
+ get_buffers(buffer); | |
+ error = aa_path_name(path, path_flags(labels_profile(label), path), | |
+ buffer, &name, &info, | |
+ labels_profile(label)->disconnected); | |
+ if (error) { | |
+ error = audit_mount(labels_profile(label), OP_MOUNT, name, NULL, | |
+ NULL, NULL, flags, data, AA_MAY_MOUNT, | |
+ &nullperms, info, error); | |
+ goto out; | |
+ } | |
+ | |
+ error = fn_for_each_confined(label, profile, | |
+ match_mnt(profile, name, NULL, NULL, flags, data, | |
+ binary)); | |
+ | |
+out: | |
+ put_buffers(buffer); | |
+ | |
+ return error; | |
+} | |
+ | |
+int aa_bind_mount(struct aa_label *label, const struct path *path, | |
+ const char *dev_name, unsigned long flags) | |
+{ | |
+ struct aa_profile *profile; | |
+ char *buffer = NULL, *old_buffer = NULL; | |
+ const char *name, *old_name = NULL, *info = NULL; | |
+ struct path old_path; | |
+ int error; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!path); | |
+ | |
+ if (!dev_name || !*dev_name) | |
+ return -EINVAL; | |
+ | |
+ flags &= MS_REC | MS_BIND; | |
+ | |
+ error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); | |
+ if (error) | |
+ return error; | |
+ | |
+ get_buffers(buffer, old_buffer); | |
+ error = aa_path_name(path, path_flags(labels_profile(label), path), buffer, &name, | |
+ &info, labels_profile(label)->disconnected); | |
+ if (error) | |
+ goto error; | |
+ | |
+ error = aa_path_name(&old_path, path_flags(labels_profile(label), | |
+ &old_path), | |
+ old_buffer, &old_name, &info, | |
+ labels_profile(label)->disconnected); | |
+ path_put(&old_path); | |
+ if (error) | |
+ goto error; | |
+ | |
+ error = fn_for_each_confined(label, profile, | |
+ match_mnt(profile, name, old_name, NULL, flags, NULL, | |
+ false)); | |
+ | |
+out: | |
+ put_buffers(buffer, old_buffer); | |
+ | |
+ return error; | |
+ | |
+error: | |
+ error = fn_for_each(label, profile, | |
+ audit_mount(profile, OP_MOUNT, name, old_name, NULL, | |
+ NULL, flags, NULL, AA_MAY_MOUNT, &nullperms, | |
+ info, error)); | |
+ goto out; | |
+} | |
+ | |
+int aa_mount_change_type(struct aa_label *label, const struct path *path, | |
+ unsigned long flags) | |
+{ | |
+ struct aa_profile *profile; | |
+ char *buffer = NULL; | |
+ const char *name, *info = NULL; | |
+ int error; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!path); | |
+ | |
+ /* These are the flags allowed by do_change_type() */ | |
+ flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | | |
+ MS_UNBINDABLE); | |
+ | |
+ get_buffers(buffer); | |
+ error = aa_path_name(path, path_flags(labels_profile(label), path), | |
+ buffer, &name, &info, | |
+ labels_profile(label)->disconnected); | |
+ if (error) { | |
+ error = fn_for_each(label, profile, | |
+ audit_mount(profile, OP_MOUNT, name, NULL, | |
+ NULL, NULL, flags, NULL, | |
+ AA_MAY_MOUNT, &nullperms, info, | |
+ error)); | |
+ goto out; | |
+ } | |
+ | |
+ error = fn_for_each_confined(label, profile, | |
+ match_mnt(profile, name, NULL, NULL, flags, NULL, | |
+ false)); | |
+ | |
+out: | |
+ put_buffers(buffer); | |
+ | |
+ return error; | |
+} | |
+ | |
+int aa_move_mount(struct aa_label *label, const struct path *path, | |
+ const char *orig_name) | |
+{ | |
+ struct aa_profile *profile; | |
+ char *buffer = NULL, *old_buffer = NULL; | |
+ const char *name, *old_name = NULL, *info = NULL; | |
+ struct path old_path; | |
+ int error; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!path); | |
+ | |
+ if (!orig_name || !*orig_name) | |
+ return -EINVAL; | |
+ | |
+ error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); | |
+ if (error) | |
+ return error; | |
+ | |
+ get_buffers(buffer, old_buffer); | |
+ error = aa_path_name(path, path_flags(labels_profile(label), path), | |
+ buffer, &name, &info, | |
+ labels_profile(label)->disconnected); | |
+ if (error) | |
+ goto error; | |
+ | |
+ error = aa_path_name(&old_path, path_flags(labels_profile(label), | |
+ &old_path), | |
+ old_buffer, &old_name, &info, | |
+ labels_profile(label)->disconnected); | |
+ path_put(&old_path); | |
+ if (error) | |
+ goto error; | |
+ | |
+ error = fn_for_each_confined(label, profile, | |
+ match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, | |
+ false)); | |
+ | |
+out: | |
+ put_buffers(buffer, old_buffer); | |
+ | |
+ return error; | |
+ | |
+error: | |
+ error = fn_for_each(label, profile, | |
+ audit_mount(profile, OP_MOUNT, name, old_name, NULL, | |
+ NULL, MS_MOVE, NULL, AA_MAY_MOUNT, | |
+ &nullperms, info, error)); | |
+ goto out; | |
+} | |
+ | |
+int aa_new_mount(struct aa_label *label, const char *orig_dev_name, | |
+ const struct path *path, const char *type, unsigned long flags, | |
+ void *data) | |
+{ | |
+ struct aa_profile *profile; | |
+ char *buffer = NULL, *dev_buffer = NULL; | |
+ const char *name = NULL, *dev_name = NULL, *info = NULL; | |
+ bool binary = true; | |
+ int error; | |
+ int requires_dev = 0; | |
+ struct path dev_path; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!path); | |
+ | |
+ dev_name = orig_dev_name; | |
+ if (type) { | |
+ struct file_system_type *fstype; | |
+ 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) { | |
+ if (!dev_name || !*dev_name) | |
+ return -ENOENT; | |
+ | |
+ error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); | |
+ if (error) | |
+ return error; | |
+ } | |
+ } | |
+ | |
+ get_buffers(buffer, dev_buffer); | |
+ if (type && requires_dev) { | |
+ error = aa_path_name(&dev_path, | |
+ path_flags(labels_profile(label), | |
+ &dev_path), | |
+ dev_buffer, &dev_name, &info, | |
+ labels_profile(label)->disconnected); | |
+ path_put(&dev_path); | |
+ if (error) | |
+ goto error; | |
+ } | |
+ | |
+ error = aa_path_name(path, path_flags(labels_profile(label), path), | |
+ buffer, &name, &info, | |
+ labels_profile(label)->disconnected); | |
+ if (error) | |
+ goto error; | |
+ | |
+ error = fn_for_each_confined(label, profile, | |
+ match_mnt(profile, name, dev_name, type, flags, data, | |
+ binary)); | |
+ | |
+cleanup: | |
+ put_buffers(buffer, dev_buffer); | |
+ | |
+ return error; | |
+ | |
+error: | |
+ error = fn_for_each(label, profile, | |
+ audit_mount(labels_profile(label), OP_MOUNT, name, | |
+ dev_name, type, NULL, flags, data, | |
+ AA_MAY_MOUNT, &nullperms, info, error)); | |
+ goto cleanup; | |
+} | |
+ | |
+static int profile_umount(struct aa_profile *profile, const char *name) | |
+{ | |
+ struct aa_perms perms = { }; | |
+ const char *info = NULL; | |
+ unsigned int state; | |
+ int e = 0; | |
+ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!name); | |
+ | |
+ 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) | |
+ e = -EACCES; | |
+ | |
+ return audit_mount(profile, OP_UMOUNT, name, NULL, NULL, NULL, 0, NULL, | |
+ AA_MAY_UMOUNT, &perms, info, e); | |
+} | |
+ | |
+int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags) | |
+{ | |
+ struct aa_profile *profile; | |
+ char *buffer = NULL; | |
+ const char *name, *info = NULL; | |
+ int error; | |
+ struct path path = { mnt, mnt->mnt_root }; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!mnt); | |
+ | |
+ get_buffers(buffer); | |
+ error = aa_path_name(&path, path_flags(labels_profile(label), &path), | |
+ buffer, &name, &info, | |
+ labels_profile(label)->disconnected); | |
+ if (error) { | |
+ error = fn_for_each(label, profile, | |
+ audit_mount(profile, OP_UMOUNT, name, NULL, | |
+ NULL, NULL, 0, NULL, AA_MAY_UMOUNT, | |
+ &nullperms, info, error)); | |
+ goto out; | |
+ } | |
+ | |
+ error = fn_for_each_confined(label, profile, | |
+ profile_umount(profile, name)); | |
+ | |
+out: | |
+ put_buffers(buffer); | |
+ | |
+ return error; | |
+} | |
+ | |
+static int profile_pivotroot(struct aa_profile *profile, const char *new_name, | |
+ const char *old_name, struct aa_label **trans) | |
+{ | |
+ struct aa_label *target = NULL; | |
+ const char *trans_name = NULL; | |
+ struct aa_perms perms = { }; | |
+ const char *info = NULL; | |
+ unsigned int state; | |
+ int error = -EACCES; | |
+ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!new_name); | |
+ AA_BUG(!old_name); | |
+ AA_BUG(!trans); | |
+ | |
+ /* TODO: actual domain transition computation for multiple | |
+ * profiles | |
+ */ | |
+ 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, | |
+ &trans_name); | |
+ if (!target) | |
+ error = -ENOENT; | |
+ else | |
+ *trans = target; | |
+ } else | |
+ error = 0; | |
+ } | |
+ | |
+ error = audit_mount(profile, OP_PIVOTROOT, new_name, old_name, | |
+ NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT, | |
+ &perms, info, error); | |
+ if (!*trans) | |
+ aa_put_label(target); | |
+ | |
+ return error; | |
+} | |
+ | |
+int aa_pivotroot(struct aa_label *label, const struct path *old_path, | |
+ const struct path *new_path) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct aa_label *target = NULL; | |
+ char *old_buffer = NULL, *new_buffer = NULL; | |
+ const char *old_name, *new_name = NULL, *info = NULL; | |
+ int error; | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!old_path); | |
+ AA_BUG(!new_path); | |
+ | |
+ get_buffers(old_buffer, new_buffer); | |
+ error = aa_path_name(old_path, path_flags(labels_profile(label), | |
+ old_path), | |
+ old_buffer, &old_name, &info, | |
+ labels_profile(label)->disconnected); | |
+ if (error) | |
+ goto error; | |
+ error = aa_path_name(new_path, path_flags(labels_profile(label), | |
+ new_path), | |
+ new_buffer, &new_name, &info, | |
+ labels_profile(label)->disconnected); | |
+ if (error) | |
+ goto error; | |
+ error = fn_for_each(label, profile, | |
+ profile_pivotroot(profile, new_name, old_name, | |
+ &target)); | |
+out: | |
+ put_buffers(old_buffer, new_buffer); | |
+ | |
+ if (target) | |
+ error = aa_replace_current_label(target); | |
+ | |
+ return error; | |
+ | |
+error: | |
+ error = fn_for_each(label, profile, | |
+ audit_mount(profile, OP_PIVOTROOT, new_name, old_name, | |
+ NULL, NULL, | |
+ 0, NULL, AA_MAY_PIVOTROOT, &nullperms, info, | |
+ error)); | |
+ goto out; | |
+} | |
diff --git a/security/apparmor/net.c b/security/apparmor/net.c | |
new file mode 100644 | |
index 0000000..f9633ad | |
--- /dev/null | |
+++ b/security/apparmor/net.c | |
@@ -0,0 +1,357 @@ | |
+/* | |
+ * AppArmor security module | |
+ * | |
+ * This file contains AppArmor network mediation | |
+ * | |
+ * Copyright (C) 1998-2008 Novell/SUSE | |
+ * Copyright 2009-2014 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/af_unix.h" | |
+#include "include/apparmor.h" | |
+#include "include/audit.h" | |
+#include "include/context.h" | |
+#include "include/label.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), | |
+ AA_FS_FILE_BOOLEAN("af_unix", 1), | |
+ { } | |
+}; | |
+ | |
+static const char *net_mask_names[] = { | |
+ "unknown", | |
+ "send", | |
+ "receive", | |
+ "unknown", | |
+ | |
+ "create", | |
+ "shutdown", | |
+ "connect", | |
+ "unknown", | |
+ | |
+ "setattr", | |
+ "getattr", | |
+ "setcred", | |
+ "getcred", | |
+ | |
+ "chmod", | |
+ "chown", | |
+ "chgrp", | |
+ "lock", | |
+ | |
+ "mmap", | |
+ "mprot", | |
+ "unknown", | |
+ "unknown", | |
+ | |
+ "accept", | |
+ "bind", | |
+ "listen", | |
+ "unknown", | |
+ | |
+ "setopt", | |
+ "getopt", | |
+ "unknown", | |
+ "unknown", | |
+ | |
+ "unknown", | |
+ "unknown", | |
+ "unknown", | |
+ "unknown", | |
+}; | |
+ | |
+static void audit_unix_addr(struct audit_buffer *ab, const char *str, | |
+ struct sockaddr_un *addr, int addrlen) | |
+{ | |
+ int len = unix_addr_len(addrlen); | |
+ | |
+ if (!addr || len <= 0) { | |
+ audit_log_format(ab, " %s=none", str); | |
+ } else if (addr->sun_path[0]) { | |
+ audit_log_format(ab, " %s=", str); | |
+ audit_log_untrustedstring(ab, addr->sun_path); | |
+ } else { | |
+ audit_log_format(ab, " %s=\"@", str); | |
+ if (audit_string_contains_control(&addr->sun_path[1], len - 1)) | |
+ audit_log_n_hex(ab, &addr->sun_path[1], len - 1); | |
+ else | |
+ audit_log_format(ab, "%.*s", len - 1, | |
+ &addr->sun_path[1]); | |
+ audit_log_format(ab, "\""); | |
+ } | |
+} | |
+ | |
+static void audit_unix_sk_addr(struct audit_buffer *ab, const char *str, | |
+ struct sock *sk) | |
+{ | |
+ struct unix_sock *u = unix_sk(sk); | |
+ if (u && u->addr) | |
+ audit_unix_addr(ab, str, u->addr->name, u->addr->len); | |
+ else | |
+ audit_unix_addr(ab, str, NULL, 0); | |
+} | |
+ | |
+/* audit callback for net specific fields */ | |
+void audit_net_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[aad(sa)->net.type]) { | |
+ audit_log_string(ab, sock_type_names[aad(sa)->net.type]); | |
+ } else { | |
+ audit_log_format(ab, "\"unknown(%d)\"", aad(sa)->net.type); | |
+ } | |
+ audit_log_format(ab, " protocol=%d", aad(sa)->net.protocol); | |
+ | |
+ if (aad(sa)->request & NET_PERMS_MASK) { | |
+ audit_log_format(ab, " requested_mask="); | |
+ aa_audit_perm_mask(ab, aad(sa)->request, NULL, 0, | |
+ net_mask_names, NET_PERMS_MASK); | |
+ | |
+ if (aad(sa)->denied & NET_PERMS_MASK) { | |
+ audit_log_format(ab, " denied_mask="); | |
+ aa_audit_perm_mask(ab, aad(sa)->denied, NULL, 0, | |
+ net_mask_names, NET_PERMS_MASK); | |
+ } | |
+ } | |
+ if (sa->u.net->family == AF_UNIX) { | |
+ if ((aad(sa)->request & ~NET_PEER_MASK) && aad(sa)->net.addr) | |
+ audit_unix_addr(ab, "addr", | |
+ unix_addr(aad(sa)->net.addr), | |
+ aad(sa)->net.addrlen); | |
+ else | |
+ audit_unix_sk_addr(ab, "addr", sa->u.net->sk); | |
+ if (aad(sa)->request & NET_PEER_MASK) { | |
+ if (aad(sa)->net.addr) | |
+ audit_unix_addr(ab, "peer_addr", | |
+ unix_addr(aad(sa)->net.addr), | |
+ aad(sa)->net.addrlen); | |
+ else | |
+ audit_unix_sk_addr(ab, "peer_addr", | |
+ aad(sa)->net.peer_sk); | |
+ } | |
+ } | |
+ if (aad(sa)->peer) { | |
+ audit_log_format(ab, " peer="); | |
+ aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, | |
+ FLAGS_NONE, GFP_ATOMIC); | |
+ } | |
+} | |
+ | |
+ | |
+/* Generic af perm */ | |
+int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa, | |
+ u32 request, u16 family, int type) | |
+{ | |
+ struct aa_perms perms = { }; | |
+ | |
+ AA_BUG(family >= AF_MAX); | |
+ AA_BUG(type < 0 && type >= SOCK_MAX); | |
+ | |
+ if (profile_unconfined(profile)) | |
+ return 0; | |
+ | |
+ perms.allow = (profile->net.allow[family] & (1 << type)) ? | |
+ ALL_PERMS_MASK : 0; | |
+ perms.audit = (profile->net.audit[family] & (1 << type)) ? | |
+ ALL_PERMS_MASK : 0; | |
+ perms.quiet = (profile->net.quiet[family] & (1 << type)) ? | |
+ ALL_PERMS_MASK : 0; | |
+ aa_apply_modes_to_perms(profile, &perms); | |
+ | |
+ return aa_check_perms(profile, &perms, request, sa, audit_net_cb); | |
+} | |
+ | |
+static int aa_af_perm(struct aa_label *label, const char *op, u32 request, | |
+ u16 family, int type, int protocol) | |
+{ | |
+ struct aa_profile *profile; | |
+ DEFINE_AUDIT_NET(sa, op, NULL, family, type, protocol); | |
+ | |
+ return fn_for_each_confined(label, profile, | |
+ aa_profile_af_perm(profile, &sa, request, family, type)); | |
+} | |
+ | |
+static int aa_label_sk_perm(struct aa_label *label, const char *op, u32 request, | |
+ struct sock *sk) | |
+{ | |
+ struct aa_profile *profile; | |
+ DEFINE_AUDIT_SK(sa, op, sk); | |
+ | |
+ AA_BUG(!label); | |
+ AA_BUG(!sk); | |
+ | |
+ if (unconfined(label)) | |
+ return 0; | |
+ | |
+ return fn_for_each_confined(label, profile, | |
+ aa_profile_af_sk_perm(profile, &sa, request, sk)); | |
+} | |
+ | |
+static int aa_sk_perm(const char *op, u32 request, struct sock *sk) | |
+{ | |
+ struct aa_label *label; | |
+ int error; | |
+ | |
+ AA_BUG(!sk); | |
+ AA_BUG(in_interrupt()); | |
+ | |
+ /* TODO: switch to begin_current_label ???? */ | |
+ label = aa_begin_current_label(DO_UPDATE); | |
+ error = aa_label_sk_perm(label, op, request, sk); | |
+ aa_end_current_label(label); | |
+ | |
+ return error; | |
+} | |
+ | |
+#define af_select(FAMILY, FN, DEF_FN) \ | |
+({ \ | |
+ int __e; \ | |
+ switch ((FAMILY)) { \ | |
+ case AF_UNIX: \ | |
+ __e = aa_unix_ ## FN; \ | |
+ break; \ | |
+ default: \ | |
+ __e = DEF_FN; \ | |
+ } \ | |
+ __e; \ | |
+}) | |
+ | |
+/* TODO: push into lsm.c ???? */ | |
+ | |
+/* revaliation, get/set attr, shutdown */ | |
+int aa_sock_perm(const char *op, u32 request, struct socket *sock) | |
+{ | |
+ AA_BUG(!sock); | |
+ AA_BUG(!sock->sk); | |
+ AA_BUG(in_interrupt()); | |
+ | |
+ return af_select(sock->sk->sk_family, | |
+ sock_perm(op, request, sock), | |
+ aa_sk_perm(op, request, sock->sk)); | |
+} | |
+ | |
+int aa_sock_create_perm(struct aa_label *label, int family, int type, | |
+ int protocol) | |
+{ | |
+ AA_BUG(!label); | |
+ /* TODO: .... */ | |
+ AA_BUG(in_interrupt()); | |
+ | |
+ return af_select(family, | |
+ create_perm(label, family, type, protocol), | |
+ aa_af_perm(label, OP_CREATE, AA_MAY_CREATE, family, | |
+ type, protocol)); | |
+} | |
+ | |
+int aa_sock_bind_perm(struct socket *sock, struct sockaddr *address, | |
+ int addrlen) | |
+{ | |
+ AA_BUG(!sock); | |
+ AA_BUG(!sock->sk); | |
+ AA_BUG(!address); | |
+ /* TODO: .... */ | |
+ AA_BUG(in_interrupt()); | |
+ | |
+ return af_select(sock->sk->sk_family, | |
+ bind_perm(sock, address, addrlen), | |
+ aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk)); | |
+} | |
+ | |
+int aa_sock_connect_perm(struct socket *sock, struct sockaddr *address, | |
+ int addrlen) | |
+{ | |
+ AA_BUG(!sock); | |
+ AA_BUG(!sock->sk); | |
+ AA_BUG(!address); | |
+ /* TODO: .... */ | |
+ AA_BUG(in_interrupt()); | |
+ | |
+ return af_select(sock->sk->sk_family, | |
+ connect_perm(sock, address, addrlen), | |
+ aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk)); | |
+} | |
+ | |
+int aa_sock_listen_perm(struct socket *sock, int backlog) | |
+{ | |
+ AA_BUG(!sock); | |
+ AA_BUG(!sock->sk); | |
+ /* TODO: .... */ | |
+ AA_BUG(in_interrupt()); | |
+ | |
+ return af_select(sock->sk->sk_family, | |
+ listen_perm(sock, backlog), | |
+ aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk)); | |
+} | |
+ | |
+/* ability of sock to connect, not peer address binding */ | |
+int aa_sock_accept_perm(struct socket *sock, struct socket *newsock) | |
+{ | |
+ AA_BUG(!sock); | |
+ AA_BUG(!sock->sk); | |
+ AA_BUG(!newsock); | |
+ /* TODO: .... */ | |
+ AA_BUG(in_interrupt()); | |
+ | |
+ return af_select(sock->sk->sk_family, | |
+ accept_perm(sock, newsock), | |
+ aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk)); | |
+} | |
+ | |
+/* sendmsg, recvmsg */ | |
+int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, | |
+ struct msghdr *msg, int size) | |
+{ | |
+ AA_BUG(!sock); | |
+ AA_BUG(!sock->sk); | |
+ AA_BUG(!msg); | |
+ /* TODO: .... */ | |
+ AA_BUG(in_interrupt()); | |
+ | |
+ return af_select(sock->sk->sk_family, | |
+ msg_perm(op, request, sock, msg, size), | |
+ aa_sk_perm(op, request, sock->sk)); | |
+} | |
+ | |
+/* revaliation, get/set attr, opt */ | |
+int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, int level, | |
+ int optname) | |
+{ | |
+ AA_BUG(!sock); | |
+ AA_BUG(!sock->sk); | |
+ AA_BUG(in_interrupt()); | |
+ | |
+ return af_select(sock->sk->sk_family, | |
+ opt_perm(op, request, sock, level, optname), | |
+ aa_sk_perm(op, request, sock->sk)); | |
+} | |
+ | |
+int aa_sock_file_perm(struct aa_label *label, const char *op, u32 request, | |
+ struct socket *sock) | |
+{ | |
+ AA_BUG(!label); | |
+ AA_BUG(!sock); | |
+ AA_BUG(!sock->sk); | |
+ | |
+ return af_select(sock->sk->sk_family, | |
+ file_perm(label, op, request, sock), | |
+ aa_label_sk_perm(label, op, request, sock->sk)); | |
+} | |
diff --git a/security/apparmor/nulldfa.in b/security/apparmor/nulldfa.in | |
new file mode 100644 | |
index 0000000..3cb3802 | |
--- /dev/null | |
+++ b/security/apparmor/nulldfa.in | |
@@ -0,0 +1 @@ | |
+0x1B, 0x5E, 0x78, 0x3D, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x04, 0x90, 0x00, 0x00, 0x6E, 0x6F, 0x74, 0x66, 0x6C, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |
diff --git a/security/apparmor/path.c b/security/apparmor/path.c | |
index a8fc7d0..9e95f8b 100644 | |
--- a/security/apparmor/path.c | |
+++ b/security/apparmor/path.c | |
@@ -50,7 +50,7 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen) | |
* namespace root. | |
*/ | |
static int disconnect(const struct path *path, char *buf, char **name, | |
- int flags) | |
+ int flags, const char *disconnected) | |
{ | |
int error = 0; | |
@@ -63,9 +63,14 @@ static int disconnect(const struct path *path, char *buf, char **name, | |
error = -EACCES; | |
if (**name == '/') | |
*name = *name + 1; | |
- } else if (**name != '/') | |
- /* CONNECT_PATH with missing root */ | |
- error = prepend(name, *name - buf, "/", 1); | |
+ } else { | |
+ if (**name != '/') | |
+ /* CONNECT_PATH with missing root */ | |
+ error = prepend(name, *name - buf, "/", 1); | |
+ if (!error && disconnected) | |
+ error = prepend(name, *name - buf, disconnected, | |
+ strlen(disconnected)); | |
+ } | |
return error; | |
} | |
@@ -74,9 +79,9 @@ static int disconnect(const struct path *path, char *buf, char **name, | |
* d_namespace_path - lookup a name associated with a given path | |
* @path: path to lookup (NOT NULL) | |
* @buf: buffer to store path to (NOT NULL) | |
- * @buflen: length of @buf | |
* @name: Returns - pointer for start of path name with in @buf (NOT NULL) | |
* @flags: flags controlling path lookup | |
+ * @disconnected: string to prefix to disconnected paths | |
* | |
* Handle path name lookup. | |
* | |
@@ -84,12 +89,14 @@ static int disconnect(const struct path *path, char *buf, char **name, | |
* When no error the path name is returned in @name which points to | |
* to a position in @buf | |
*/ | |
-static int d_namespace_path(const struct path *path, char *buf, int buflen, | |
- char **name, int flags) | |
+static int d_namespace_path(const struct path *path, char *buf, char **name, | |
+ int flags, const char *disconnected) | |
{ | |
char *res; | |
int error = 0; | |
int connected = 1; | |
+ int isdir = (flags & PATH_IS_DIR) ? 1 : 0; | |
+ int buflen = aa_g_path_max - isdir; | |
if (path->mnt->mnt_flags & MNT_INTERNAL) { | |
/* it's not mounted anywhere */ | |
@@ -104,10 +111,12 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, | |
/* TODO: convert over to using a per namespace | |
* control instead of hard coded /proc | |
*/ | |
- return prepend(name, *name - buf, "/proc", 5); | |
+ error = prepend(name, *name - buf, "/proc", 5); | |
+ goto out; | |
} else | |
- return disconnect(path, buf, name, flags); | |
- return 0; | |
+ error = disconnect(path, buf, name, flags, | |
+ disconnected); | |
+ goto out; | |
} | |
/* resolve paths relative to chroot?*/ | |
@@ -126,8 +135,11 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, | |
* be returned. | |
*/ | |
if (!res || IS_ERR(res)) { | |
- if (PTR_ERR(res) == -ENAMETOOLONG) | |
- return -ENAMETOOLONG; | |
+ if (PTR_ERR(res) == -ENAMETOOLONG) { | |
+ error = -ENAMETOOLONG; | |
+ *name = buf; | |
+ goto out; | |
+ } | |
connected = 0; | |
res = dentry_path_raw(path->dentry, buf, buflen); | |
if (IS_ERR(res)) { | |
@@ -153,56 +165,27 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, | |
} | |
if (!connected) | |
- error = disconnect(path, buf, name, flags); | |
+ error = disconnect(path, buf, name, flags, disconnected); | |
out: | |
- return error; | |
-} | |
- | |
-/** | |
- * get_name_to_buffer - get the pathname to a buffer ensure dir / is appended | |
- * @path: path to get name for (NOT NULL) | |
- * @flags: flags controlling path lookup | |
- * @buffer: buffer to put name in (NOT NULL) | |
- * @size: size of buffer | |
- * @name: Returns - contains position of path name in @buffer (NOT NULL) | |
- * | |
- * Returns: %0 else error on failure | |
- */ | |
-static int get_name_to_buffer(const struct path *path, int flags, char *buffer, | |
- int size, char **name, const char **info) | |
-{ | |
- int adjust = (flags & PATH_IS_DIR) ? 1 : 0; | |
- int error = d_namespace_path(path, buffer, size - adjust, name, flags); | |
- | |
- if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0') | |
- /* | |
- * Append "/" to the pathname. The root directory is a special | |
- * case; it already ends in slash. | |
- */ | |
- strcpy(&buffer[size - 2], "/"); | |
- | |
- if (info && error) { | |
- if (error == -ENOENT) | |
- *info = "Failed name lookup - deleted entry"; | |
- else if (error == -EACCES) | |
- *info = "Failed name lookup - disconnected path"; | |
- else if (error == -ENAMETOOLONG) | |
- *info = "Failed name lookup - name too long"; | |
- else | |
- *info = "Failed name lookup"; | |
- } | |
+ /* | |
+ * Append "/" to the pathname. The root directory is a special | |
+ * case; it already ends in slash. | |
+ */ | |
+ if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/')) | |
+ strcpy(&buf[aa_g_path_max - 2], "/"); | |
return error; | |
} | |
/** | |
- * aa_path_name - compute the pathname of a file | |
+ * aa_path_name - get the pathname to a buffer ensure dir / is appended | |
* @path: path the file (NOT NULL) | |
* @flags: flags controlling path name generation | |
- * @buffer: buffer that aa_get_name() allocated (NOT NULL) | |
+ * @buffer: buffer to put name in (NOT NULL) | |
* @name: Returns - the generated path name if !error (NOT NULL) | |
* @info: Returns - information on why the path lookup failed (MAYBE NULL) | |
+ * @disconnected: string to prepend to disconnected paths | |
* | |
* @name is a pointer to the beginning of the pathname (which usually differs | |
* from the beginning of the buffer), or NULL. If there is an error @name | |
@@ -215,33 +198,24 @@ static int get_name_to_buffer(const struct path *path, int flags, char *buffer, | |
* | |
* Returns: %0 else error code if could retrieve name | |
*/ | |
-int aa_path_name(const struct path *path, int flags, char **buffer, | |
- const char **name, const char **info) | |
+int aa_path_name(const struct path *path, int flags, char *buffer, | |
+ const char **name, const char **info, const char *disconnected) | |
{ | |
- char *buf, *str = NULL; | |
- int size = 256; | |
- int error; | |
- | |
- *name = NULL; | |
- *buffer = NULL; | |
- for (;;) { | |
- /* freed by caller */ | |
- buf = kmalloc(size, GFP_KERNEL); | |
- if (!buf) | |
- return -ENOMEM; | |
- | |
- error = get_name_to_buffer(path, flags, buf, size, &str, info); | |
- if (error != -ENAMETOOLONG) | |
- break; | |
- | |
- kfree(buf); | |
- size <<= 1; | |
- if (size > aa_g_path_max) | |
- return -ENAMETOOLONG; | |
- *info = NULL; | |
+ char *str = NULL; | |
+ int error = d_namespace_path(path, buffer, &str, flags, disconnected); | |
+ | |
+ | |
+ if (info && error) { | |
+ if (error == -ENOENT) | |
+ *info = "Failed name lookup - deleted entry"; | |
+ else if (error == -EACCES) | |
+ *info = "Failed name lookup - disconnected path"; | |
+ else if (error == -ENAMETOOLONG) | |
+ *info = "Failed name lookup - name too long"; | |
+ else | |
+ *info = "Failed name lookup"; | |
} | |
- *buffer = buf; | |
- *name = str; | |
+ *name = str; | |
return error; | |
} | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index 179e68d..1c9d4c7 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -76,12 +76,14 @@ | |
#include <linux/slab.h> | |
#include <linux/spinlock.h> | |
#include <linux/string.h> | |
+#include <linux/user_namespace.h> | |
#include "include/apparmor.h" | |
#include "include/capability.h" | |
#include "include/context.h" | |
#include "include/file.h" | |
#include "include/ipc.h" | |
+#include "include/label.h" | |
#include "include/match.h" | |
#include "include/path.h" | |
#include "include/policy.h" | |
@@ -89,9 +91,9 @@ | |
#include "include/resource.h" | |
-/* root profile namespace */ | |
-struct aa_namespace *root_ns; | |
- | |
+/* Note: mode names must be unique in the first character because of | |
+ * modechrs used to print modes on compound labels on some interfaces | |
+ */ | |
const char *const aa_profile_mode_names[] = { | |
"enforce", | |
"complain", | |
@@ -99,322 +101,9 @@ | |
"unconfined", | |
}; | |
-/** | |
- * hname_tail - find the last component of an hname | |
- * @name: hname to find the base profile name component of (NOT NULL) | |
- * | |
- * Returns: the tail (base profile name) name component of an hname | |
- */ | |
-static const char *hname_tail(const char *hname) | |
-{ | |
- char *split; | |
- hname = strim((char *)hname); | |
- for (split = strstr(hname, "//"); split; split = strstr(hname, "//")) | |
- hname = split + 2; | |
- | |
- return hname; | |
-} | |
- | |
-/** | |
- * policy_init - initialize a policy structure | |
- * @policy: policy to initialize (NOT NULL) | |
- * @prefix: prefix name if any is required. (MAYBE NULL) | |
- * @name: name of the policy, init will make a copy of it (NOT NULL) | |
- * | |
- * Note: this fn creates a copy of strings passed in | |
- * | |
- * Returns: true if policy init successful | |
- */ | |
-static bool policy_init(struct aa_policy *policy, const char *prefix, | |
- const char *name) | |
-{ | |
- /* freed by policy_free */ | |
- if (prefix) { | |
- policy->hname = kmalloc(strlen(prefix) + strlen(name) + 3, | |
- GFP_KERNEL); | |
- if (policy->hname) | |
- sprintf(policy->hname, "%s//%s", prefix, name); | |
- } else | |
- policy->hname = kstrdup(name, GFP_KERNEL); | |
- if (!policy->hname) | |
- return 0; | |
- /* base.name is a substring of fqname */ | |
- policy->name = (char *)hname_tail(policy->hname); | |
- INIT_LIST_HEAD(&policy->list); | |
- INIT_LIST_HEAD(&policy->profiles); | |
- | |
- return 1; | |
-} | |
- | |
-/** | |
- * policy_destroy - free the elements referenced by @policy | |
- * @policy: policy that is to have its elements freed (NOT NULL) | |
- */ | |
-static void policy_destroy(struct aa_policy *policy) | |
-{ | |
- /* still contains profiles -- invalid */ | |
- if (on_list_rcu(&policy->profiles)) { | |
- AA_ERROR("%s: internal error, " | |
- "policy '%s' still contains profiles\n", | |
- __func__, policy->name); | |
- BUG(); | |
- } | |
- if (on_list_rcu(&policy->list)) { | |
- AA_ERROR("%s: internal error, policy '%s' still on list\n", | |
- __func__, policy->name); | |
- BUG(); | |
- } | |
- | |
- /* don't free name as its a subset of hname */ | |
- kzfree(policy->hname); | |
-} | |
/** | |
- * __policy_find - find a policy by @name on a policy list | |
- * @head: list to search (NOT NULL) | |
- * @name: name to search for (NOT NULL) | |
- * | |
- * Requires: rcu_read_lock be held | |
- * | |
- * Returns: unrefcounted policy that match @name or NULL if not found | |
- */ | |
-static struct aa_policy *__policy_find(struct list_head *head, const char *name) | |
-{ | |
- struct aa_policy *policy; | |
- | |
- list_for_each_entry_rcu(policy, head, list) { | |
- if (!strcmp(policy->name, name)) | |
- return policy; | |
- } | |
- return NULL; | |
-} | |
- | |
-/** | |
- * __policy_strn_find - find a policy that's name matches @len chars of @str | |
- * @head: list to search (NOT NULL) | |
- * @str: string to search for (NOT NULL) | |
- * @len: length of match required | |
- * | |
- * Requires: rcu_read_lock be held | |
- * | |
- * Returns: unrefcounted policy that match @str or NULL if not found | |
- * | |
- * if @len == strlen(@strlen) then this is equiv to __policy_find | |
- * other wise it allows searching for policy by a partial match of name | |
- */ | |
-static struct aa_policy *__policy_strn_find(struct list_head *head, | |
- const char *str, int len) | |
-{ | |
- struct aa_policy *policy; | |
- | |
- list_for_each_entry_rcu(policy, head, list) { | |
- if (aa_strneq(policy->name, str, len)) | |
- return policy; | |
- } | |
- | |
- return NULL; | |
-} | |
- | |
-/* | |
- * Routines for AppArmor namespaces | |
- */ | |
- | |
-static const char *hidden_ns_name = "---"; | |
-/** | |
- * aa_ns_visible - test if @view is visible from @curr | |
- * @curr: namespace to treat as the parent (NOT NULL) | |
- * @view: namespace to test if visible from @curr (NOT NULL) | |
- * | |
- * Returns: true if @view is visible from @curr else false | |
- */ | |
-bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view) | |
-{ | |
- if (curr == view) | |
- return true; | |
- | |
- for ( ; view; view = view->parent) { | |
- if (view->parent == curr) | |
- return true; | |
- } | |
- return false; | |
-} | |
- | |
-/** | |
- * aa_na_name - Find the ns name to display for @view from @curr | |
- * @curr - current namespace (NOT NULL) | |
- * @view - namespace attempting to view (NOT NULL) | |
- * | |
- * Returns: name of @view visible from @curr | |
- */ | |
-const char *aa_ns_name(struct aa_namespace *curr, struct aa_namespace *view) | |
-{ | |
- /* if view == curr then the namespace name isn't displayed */ | |
- if (curr == view) | |
- return ""; | |
- | |
- if (aa_ns_visible(curr, view)) { | |
- /* at this point if a ns is visible it is in a view ns | |
- * thus the curr ns.hname is a prefix of its name. | |
- * Only output the virtualized portion of the name | |
- * Add + 2 to skip over // separating curr hname prefix | |
- * from the visible tail of the views hname | |
- */ | |
- return view->base.hname + strlen(curr->base.hname) + 2; | |
- } else | |
- return hidden_ns_name; | |
-} | |
- | |
-/** | |
- * alloc_namespace - allocate, initialize and return a new namespace | |
- * @prefix: parent namespace name (MAYBE NULL) | |
- * @name: a preallocated name (NOT NULL) | |
- * | |
- * Returns: refcounted namespace or NULL on failure. | |
- */ | |
-static struct aa_namespace *alloc_namespace(const char *prefix, | |
- const char *name) | |
-{ | |
- struct aa_namespace *ns; | |
- | |
- ns = kzalloc(sizeof(*ns), GFP_KERNEL); | |
- AA_DEBUG("%s(%p)\n", __func__, ns); | |
- if (!ns) | |
- return NULL; | |
- if (!policy_init(&ns->base, prefix, name)) | |
- goto fail_ns; | |
- | |
- INIT_LIST_HEAD(&ns->sub_ns); | |
- mutex_init(&ns->lock); | |
- | |
- /* released by free_namespace */ | |
- ns->unconfined = aa_alloc_profile("unconfined"); | |
- if (!ns->unconfined) | |
- goto fail_unconfined; | |
- | |
- ns->unconfined->flags = PFLAG_IX_ON_NAME_ERROR | | |
- PFLAG_IMMUTABLE | PFLAG_NS_COUNT; | |
- ns->unconfined->mode = APPARMOR_UNCONFINED; | |
- | |
- /* ns and ns->unconfined share ns->unconfined refcount */ | |
- ns->unconfined->ns = ns; | |
- | |
- atomic_set(&ns->uniq_null, 0); | |
- | |
- return ns; | |
- | |
-fail_unconfined: | |
- kzfree(ns->base.hname); | |
-fail_ns: | |
- kzfree(ns); | |
- return NULL; | |
-} | |
- | |
-/** | |
- * free_namespace - free a profile namespace | |
- * @ns: the namespace to free (MAYBE NULL) | |
- * | |
- * Requires: All references to the namespace must have been put, if the | |
- * namespace was referenced by a profile confining a task, | |
- */ | |
-static void free_namespace(struct aa_namespace *ns) | |
-{ | |
- if (!ns) | |
- return; | |
- | |
- policy_destroy(&ns->base); | |
- aa_put_namespace(ns->parent); | |
- | |
- ns->unconfined->ns = NULL; | |
- aa_free_profile(ns->unconfined); | |
- kzfree(ns); | |
-} | |
- | |
-/** | |
- * __aa_find_namespace - find a namespace on a list by @name | |
- * @head: list to search for namespace on (NOT NULL) | |
- * @name: name of namespace to look for (NOT NULL) | |
- * | |
- * Returns: unrefcounted namespace | |
- * | |
- * Requires: rcu_read_lock be held | |
- */ | |
-static struct aa_namespace *__aa_find_namespace(struct list_head *head, | |
- const char *name) | |
-{ | |
- return (struct aa_namespace *)__policy_find(head, name); | |
-} | |
- | |
-/** | |
- * aa_find_namespace - look up a profile namespace on the namespace list | |
- * @root: namespace to search in (NOT NULL) | |
- * @name: name of namespace to find (NOT NULL) | |
- * | |
- * Returns: a refcounted namespace on the list, or NULL if no namespace | |
- * called @name exists. | |
- * | |
- * refcount released by caller | |
- */ | |
-struct aa_namespace *aa_find_namespace(struct aa_namespace *root, | |
- const char *name) | |
-{ | |
- struct aa_namespace *ns = NULL; | |
- | |
- rcu_read_lock(); | |
- ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); | |
- rcu_read_unlock(); | |
- | |
- return ns; | |
-} | |
- | |
-/** | |
- * aa_prepare_namespace - find an existing or create a new namespace of @name | |
- * @name: the namespace to find or add (MAYBE NULL) | |
- * | |
- * Returns: refcounted namespace or NULL if failed to create one | |
- */ | |
-static struct aa_namespace *aa_prepare_namespace(const char *name) | |
-{ | |
- struct aa_namespace *ns, *root; | |
- | |
- root = aa_current_profile()->ns; | |
- | |
- mutex_lock(&root->lock); | |
- | |
- /* if name isn't specified the profile is loaded to the current ns */ | |
- if (!name) { | |
- /* released by caller */ | |
- ns = aa_get_namespace(root); | |
- goto out; | |
- } | |
- | |
- /* try and find the specified ns and if it doesn't exist create it */ | |
- /* released by caller */ | |
- ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); | |
- if (!ns) { | |
- ns = alloc_namespace(root->base.hname, name); | |
- if (!ns) | |
- goto out; | |
- if (__aa_fs_namespace_mkdir(ns, ns_subns_dir(root), name)) { | |
- AA_ERROR("Failed to create interface for ns %s\n", | |
- ns->base.name); | |
- free_namespace(ns); | |
- ns = NULL; | |
- goto out; | |
- } | |
- ns->parent = aa_get_namespace(root); | |
- list_add_rcu(&ns->base.list, &root->sub_ns); | |
- /* add list ref */ | |
- aa_get_namespace(ns); | |
- } | |
-out: | |
- mutex_unlock(&root->lock); | |
- | |
- /* return ref */ | |
- return ns; | |
-} | |
- | |
-/** | |
- * __list_add_profile - add a profile to a list | |
+ * __add_profile - add a profiles to list and label tree | |
* @list: list to add it to (NOT NULL) | |
* @profile: the profile to add (NOT NULL) | |
* | |
@@ -422,12 +111,21 @@ static struct aa_namespace *aa_prepare_namespace(const char *name) | |
* | |
* Requires: namespace lock be held, or list not be shared | |
*/ | |
-static void __list_add_profile(struct list_head *list, | |
- struct aa_profile *profile) | |
+static void __add_profile(struct list_head *list, struct aa_profile *profile) | |
{ | |
+ struct aa_label *l; | |
+ | |
+ AA_BUG(!list); | |
+ AA_BUG(!profile); | |
+ AA_BUG(!profile->ns); | |
+ AA_BUG(!mutex_is_locked(&profile->ns->lock)); | |
+ | |
list_add_rcu(&profile->base.list, list); | |
/* get list reference */ | |
aa_get_profile(profile); | |
+ l = aa_label_insert(&profile->ns->labels, &profile->label); | |
+ AA_BUG(l != &profile->label); | |
+ aa_put_label(l); | |
} | |
/** | |
@@ -444,11 +142,15 @@ static void __list_add_profile(struct list_head *list, | |
*/ | |
static void __list_remove_profile(struct aa_profile *profile) | |
{ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!profile->ns); | |
+ AA_BUG(!mutex_is_locked(&profile->ns->lock)); | |
+ | |
list_del_rcu(&profile->base.list); | |
aa_put_profile(profile); | |
} | |
-static void __profile_list_release(struct list_head *head); | |
+void __aa_profile_list_release(struct list_head *head); | |
/** | |
* __remove_profile - remove old profile, and children | |
@@ -458,124 +160,31 @@ static void __list_remove_profile(struct aa_profile *profile) | |
*/ | |
static void __remove_profile(struct aa_profile *profile) | |
{ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!profile->ns); | |
+ AA_BUG(!mutex_is_locked(&profile->ns->lock)); | |
+ | |
/* release any children lists first */ | |
- __profile_list_release(&profile->base.profiles); | |
+ __aa_profile_list_release(&profile->base.profiles); | |
/* released by free_profile */ | |
- __aa_update_replacedby(profile, profile->ns->unconfined); | |
+ aa_label_remove(&profile->label); | |
__aa_fs_profile_rmdir(profile); | |
__list_remove_profile(profile); | |
} | |
/** | |
- * __profile_list_release - remove all profiles on the list and put refs | |
+ * __aa_profile_list_release - remove all profiles on the list and put refs | |
* @head: list of profiles (NOT NULL) | |
* | |
* Requires: namespace lock be held | |
*/ | |
-static void __profile_list_release(struct list_head *head) | |
+void __aa_profile_list_release(struct list_head *head) | |
{ | |
struct aa_profile *profile, *tmp; | |
list_for_each_entry_safe(profile, tmp, head, base.list) | |
__remove_profile(profile); | |
} | |
-static void __ns_list_release(struct list_head *head); | |
- | |
-/** | |
- * destroy_namespace - remove everything contained by @ns | |
- * @ns: namespace to have it contents removed (NOT NULL) | |
- */ | |
-static void destroy_namespace(struct aa_namespace *ns) | |
-{ | |
- if (!ns) | |
- return; | |
- | |
- mutex_lock(&ns->lock); | |
- /* release all profiles in this namespace */ | |
- __profile_list_release(&ns->base.profiles); | |
- | |
- /* release all sub namespaces */ | |
- __ns_list_release(&ns->sub_ns); | |
- | |
- if (ns->parent) | |
- __aa_update_replacedby(ns->unconfined, ns->parent->unconfined); | |
- __aa_fs_namespace_rmdir(ns); | |
- mutex_unlock(&ns->lock); | |
-} | |
- | |
-/** | |
- * __remove_namespace - remove a namespace and all its children | |
- * @ns: namespace to be removed (NOT NULL) | |
- * | |
- * Requires: ns->parent->lock be held and ns removed from parent. | |
- */ | |
-static void __remove_namespace(struct aa_namespace *ns) | |
-{ | |
- /* remove ns from namespace list */ | |
- list_del_rcu(&ns->base.list); | |
- destroy_namespace(ns); | |
- aa_put_namespace(ns); | |
-} | |
- | |
-/** | |
- * __ns_list_release - remove all profile namespaces on the list put refs | |
- * @head: list of profile namespaces (NOT NULL) | |
- * | |
- * Requires: namespace lock be held | |
- */ | |
-static void __ns_list_release(struct list_head *head) | |
-{ | |
- struct aa_namespace *ns, *tmp; | |
- list_for_each_entry_safe(ns, tmp, head, base.list) | |
- __remove_namespace(ns); | |
- | |
-} | |
- | |
-/** | |
- * aa_alloc_root_ns - allocate the root profile namespace | |
- * | |
- * Returns: %0 on success else error | |
- * | |
- */ | |
-int __init aa_alloc_root_ns(void) | |
-{ | |
- /* released by aa_free_root_ns - used as list ref*/ | |
- root_ns = alloc_namespace(NULL, "root"); | |
- if (!root_ns) | |
- return -ENOMEM; | |
- | |
- return 0; | |
-} | |
- | |
- /** | |
- * aa_free_root_ns - free the root profile namespace | |
- */ | |
-void __init aa_free_root_ns(void) | |
- { | |
- struct aa_namespace *ns = root_ns; | |
- root_ns = NULL; | |
- | |
- destroy_namespace(ns); | |
- aa_put_namespace(ns); | |
-} | |
- | |
- | |
-static void free_replacedby(struct aa_replacedby *r) | |
-{ | |
- if (r) { | |
- /* r->profile will not be updated any more as r is dead */ | |
- aa_put_profile(rcu_dereference_protected(r->profile, true)); | |
- kzfree(r); | |
- } | |
-} | |
- | |
- | |
-void aa_free_replacedby_kref(struct kref *kref) | |
-{ | |
- struct aa_replacedby *r = container_of(kref, struct aa_replacedby, | |
- count); | |
- free_replacedby(r); | |
-} | |
/** | |
* aa_free_profile - free a profile | |
@@ -595,89 +204,81 @@ void aa_free_profile(struct aa_profile *profile) | |
return; | |
/* free children profiles */ | |
- policy_destroy(&profile->base); | |
+ aa_policy_destroy(&profile->base); | |
aa_put_profile(rcu_access_pointer(profile->parent)); | |
- aa_put_namespace(profile->ns); | |
+ aa_put_ns(profile->ns); | |
kzfree(profile->rename); | |
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); | |
aa_put_dfa(profile->xmatch); | |
aa_put_dfa(profile->policy.dfa); | |
- aa_put_replacedby(profile->replacedby); | |
kzfree(profile->hash); | |
kzfree(profile); | |
} | |
/** | |
- * aa_free_profile_rcu - free aa_profile by rcu (called by aa_free_profile_kref) | |
- * @head: rcu_head callback for freeing of a profile (NOT NULL) | |
- */ | |
-static void aa_free_profile_rcu(struct rcu_head *head) | |
-{ | |
- struct aa_profile *p = container_of(head, struct aa_profile, rcu); | |
- if (p->flags & PFLAG_NS_COUNT) | |
- free_namespace(p->ns); | |
- else | |
- aa_free_profile(p); | |
-} | |
- | |
-/** | |
- * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile) | |
- * @kr: kref callback for freeing of a profile (NOT NULL) | |
- */ | |
-void aa_free_profile_kref(struct kref *kref) | |
-{ | |
- struct aa_profile *p = container_of(kref, struct aa_profile, count); | |
- call_rcu(&p->rcu, aa_free_profile_rcu); | |
-} | |
- | |
-/** | |
* aa_alloc_profile - allocate, initialize and return a new profile | |
* @hname: name of the profile (NOT NULL) | |
+ * @gfp: allocation type | |
* | |
* Returns: refcount profile or NULL on failure | |
*/ | |
-struct aa_profile *aa_alloc_profile(const char *hname) | |
+struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, | |
+ gfp_t gfp) | |
{ | |
struct aa_profile *profile; | |
/* freed by free_profile - usually through aa_put_profile */ | |
- profile = kzalloc(sizeof(*profile), GFP_KERNEL); | |
+ profile = kzalloc(sizeof(*profile) + sizeof (struct aa_profile *) * 2, | |
+ gfp); | |
if (!profile) | |
return NULL; | |
- profile->replacedby = kzalloc(sizeof(struct aa_replacedby), GFP_KERNEL); | |
- if (!profile->replacedby) | |
+ if (!aa_policy_init(&profile->base, NULL, hname, gfp)) | |
goto fail; | |
- kref_init(&profile->replacedby->count); | |
- | |
- if (!policy_init(&profile->base, NULL, hname)) | |
+ if (!aa_label_init(&profile->label, 1)) | |
goto fail; | |
- kref_init(&profile->count); | |
+ | |
+ /* update being set needed by fs interface */ | |
+ if (!proxy) { | |
+ proxy = aa_alloc_proxy(&profile->label, gfp); | |
+ if (!proxy) | |
+ goto fail; | |
+ } else | |
+ aa_get_proxy(proxy); | |
+ profile->label.proxy = proxy; | |
+ | |
+ profile->label.hname = profile->base.hname; | |
+ profile->label.flags |= FLAG_PROFILE; | |
+ profile->label.vec[0] = profile; | |
/* refcount released by caller */ | |
return profile; | |
fail: | |
- kzfree(profile->replacedby); | |
- kzfree(profile); | |
+ aa_free_profile(profile); | |
return NULL; | |
} | |
/** | |
- * aa_new_null_profile - create a new null-X learning profile | |
+ * aa_null_profile - create or find a null-X learning profile | |
* @parent: profile that caused this profile to be created (NOT NULL) | |
* @hat: true if the null- learning profile is a hat | |
+ * @base: name to base the null profile off of | |
+ * @gfp: type of allocation | |
* | |
- * Create a null- complain mode profile used in learning mode. The name of | |
- * the profile is unique and follows the format of parent//null-<uniq>. | |
+ * Find/Create a null- complain mode profile used in learning mode. The | |
+ * name of the profile is unique and follows the format of parent//null-XXX. | |
+ * where XXX is based on the @name or if that fails or is not supplied | |
+ * a unique number | |
* | |
* null profiles are added to the profile list but the list does not | |
* hold a count on them so that they are automatically released when | |
@@ -685,43 +286,86 @@ struct aa_profile *aa_alloc_profile(const char *hname) | |
* | |
* Returns: new refcounted profile else NULL on failure | |
*/ | |
-struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat) | |
+struct aa_profile *aa_null_profile(struct aa_profile *parent, bool hat, | |
+ const char *base, gfp_t gfp) | |
{ | |
- struct aa_profile *profile = NULL; | |
+ struct aa_profile *profile; | |
char *name; | |
- int uniq = atomic_inc_return(&parent->ns->uniq_null); | |
- /* freed below */ | |
- name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, GFP_KERNEL); | |
+ AA_BUG(!parent); | |
+ | |
+ if (base) { | |
+ name = kmalloc(strlen(parent->base.hname) + 8 + strlen(base), | |
+ gfp); | |
+ if (name) { | |
+ sprintf(name, "%s//null-%s", parent->base.hname, base); | |
+ goto name; | |
+ } | |
+ /* fall through to try shorter uniq */ | |
+ } | |
+ | |
+ name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, gfp); | |
if (!name) | |
- goto fail; | |
- sprintf(name, "%s//null-%x", parent->base.hname, uniq); | |
+ return NULL; | |
+ sprintf(name, "%s//null-%x", parent->base.hname, | |
+ atomic_inc_return(&parent->ns->uniq_null)); | |
- profile = aa_alloc_profile(name); | |
- kfree(name); | |
+name: | |
+ /* lookup to see if this is a dup creation */ | |
+ profile = aa_find_child(parent, basename(name)); | |
+ if (profile) | |
+ goto out; | |
+ | |
+ profile = aa_alloc_profile(name, NULL, gfp); | |
if (!profile) | |
goto fail; | |
profile->mode = APPARMOR_COMPLAIN; | |
- profile->flags = PFLAG_NULL; | |
+ profile->label.flags |= FLAG_NULL; | |
if (hat) | |
- profile->flags |= PFLAG_HAT; | |
+ profile->label.flags |= FLAG_HAT; | |
/* released on free_profile */ | |
rcu_assign_pointer(profile->parent, aa_get_profile(parent)); | |
- profile->ns = aa_get_namespace(parent->ns); | |
+ profile->ns = aa_get_ns(parent->ns); | |
+ profile->file.dfa = aa_get_dfa(nulldfa); | |
+ profile->policy.dfa = aa_get_dfa(nulldfa); | |
mutex_lock(&profile->ns->lock); | |
- __list_add_profile(&parent->base.profiles, profile); | |
+ __add_profile(&parent->base.profiles, profile); | |
mutex_unlock(&profile->ns->lock); | |
/* refcount released by caller */ | |
+out: | |
+ kfree(name); | |
return profile; | |
fail: | |
+ aa_free_profile(profile); | |
return NULL; | |
} | |
+/** | |
+ * aa_setup_default_label - create the initial default label | |
+ */ | |
+struct aa_label *aa_setup_default_label(void) | |
+{ | |
+ struct aa_profile *profile = aa_alloc_profile("default", NULL, | |
+ GFP_KERNEL); | |
+ if (!profile) | |
+ return NULL; | |
+ | |
+ /* the default profile pretends to be unconfined until it is replaced */ | |
+ profile->label.flags |= FLAG_IX_ON_NAME_ERROR | FLAG_UNCONFINED; | |
+ profile->mode = APPARMOR_UNCONFINED; | |
+ | |
+ profile->ns = aa_get_ns(root_ns); | |
+ | |
+ __add_profile(&root_ns->base.profiles, profile); | |
+ | |
+ return &profile->label; | |
+} | |
+ | |
/* TODO: profile accounting - setup in remove */ | |
/** | |
@@ -788,8 +432,7 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) | |
* | |
* Returns: unrefcounted policy or NULL if not found | |
*/ | |
-static struct aa_policy *__lookup_parent(struct aa_namespace *ns, | |
- const char *hname) | |
+static struct aa_policy *__lookup_parent(struct aa_ns *ns, const char *hname) | |
{ | |
struct aa_policy *policy; | |
struct aa_profile *profile = NULL; | |
@@ -812,9 +455,10 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns, | |
} | |
/** | |
- * __lookup_profile - lookup the profile matching @hname | |
+ * __lookupn_profile - lookup the profile matching @hname | |
* @base: base list to start looking up profile name from (NOT NULL) | |
* @hname: hierarchical profile name (NOT NULL) | |
+ * @n: length of @hname | |
* | |
* Requires: rcu_read_lock be held | |
* | |
@@ -822,53 +466,95 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns, | |
* | |
* Do a relative name lookup, recursing through profile tree. | |
*/ | |
-static struct aa_profile *__lookup_profile(struct aa_policy *base, | |
- const char *hname) | |
+static struct aa_profile *__lookupn_profile(struct aa_policy *base, | |
+ const char *hname, size_t n) | |
{ | |
struct aa_profile *profile = NULL; | |
- char *split; | |
+ const char *split; | |
- for (split = strstr(hname, "//"); split;) { | |
+ for (split = strnstr(hname, "//", n); split; | |
+ split = strnstr(hname, "//", n)) { | |
profile = __strn_find_child(&base->profiles, hname, | |
split - hname); | |
if (!profile) | |
return NULL; | |
base = &profile->base; | |
+ n -= split + 2 - hname; | |
hname = split + 2; | |
- split = strstr(hname, "//"); | |
} | |
- profile = __find_child(&base->profiles, hname); | |
+ if (n) | |
+ return __strn_find_child(&base->profiles, hname, n); | |
+ return NULL; | |
+} | |
- return profile; | |
+static struct aa_profile *__lookup_profile(struct aa_policy *base, | |
+ const char *hname) | |
+{ | |
+ return __lookupn_profile(base, hname, strlen(hname)); | |
} | |
/** | |
* aa_lookup_profile - find a profile by its full or partial name | |
* @ns: the namespace to start from (NOT NULL) | |
* @hname: name to do lookup on. Does not contain namespace prefix (NOT NULL) | |
+ * @n: size of @hname | |
* | |
* Returns: refcounted profile or NULL if not found | |
*/ | |
-struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname) | |
+struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname, | |
+ size_t n) | |
{ | |
struct aa_profile *profile; | |
rcu_read_lock(); | |
do { | |
- profile = __lookup_profile(&ns->base, hname); | |
+ profile = __lookupn_profile(&ns->base, hname, n); | |
} while (profile && !aa_get_profile_not0(profile)); | |
rcu_read_unlock(); | |
/* the unconfined profile is not in the regular profile list */ | |
- if (!profile && strcmp(hname, "unconfined") == 0) | |
+ if (!profile && strncmp(hname, "unconfined", n) == 0) | |
profile = aa_get_newest_profile(ns->unconfined); | |
/* refcount released by caller */ | |
return profile; | |
} | |
+struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *hname) | |
+{ | |
+ return aa_lookupn_profile(ns, hname, strlen(hname)); | |
+} | |
+ | |
+struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, | |
+ const char *fqname, size_t n) | |
+{ | |
+ struct aa_profile *profile; | |
+ struct aa_ns *ns; | |
+ const char *name, *ns_name; | |
+ size_t ns_len; | |
+ | |
+ name = aa_splitn_fqname(fqname, n, &ns_name, &ns_len); | |
+ if (ns_name) { | |
+ ns = aa_findn_ns(labels_ns(base), ns_name, ns_len); | |
+ if (!ns) | |
+ return NULL; | |
+ } else | |
+ ns = aa_get_ns(labels_ns(base)); | |
+ | |
+ if (name) | |
+ profile = aa_lookupn_profile(ns, name, n - (name - fqname)); | |
+ else if (ns) | |
+ /* default profile for ns, currently unconfined */ | |
+ profile = aa_get_newest_profile(ns->unconfined); | |
+ else | |
+ profile = NULL; | |
+ aa_put_ns(ns); | |
+ | |
+ return profile; | |
+} | |
+ | |
/** | |
* replacement_allowed - test to see if replacement is allowed | |
* @profile: profile to test if it can be replaced (MAYBE NULL) | |
@@ -881,7 +567,7 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, | |
const char **info) | |
{ | |
if (profile) { | |
- if (profile->flags & PFLAG_IMMUTABLE) { | |
+ if (profile->label.flags & FLAG_IMMUTIBLE) { | |
*info = "cannot replace immutible profile"; | |
return -EPERM; | |
} else if (noreplace) { | |
@@ -892,74 +578,94 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, | |
return 0; | |
} | |
+/* audit callback for net specific fields */ | |
+static void audit_cb(struct audit_buffer *ab, void *va) | |
+{ | |
+ struct common_audit_data *sa = va; | |
+ | |
+ if (aad(sa)->iface.ns) { | |
+ audit_log_format(ab, " ns="); | |
+ audit_log_untrustedstring(ab, aad(sa)->iface.ns); | |
+ } | |
+} | |
+ | |
/** | |
- * aa_audit_policy - Do auditing of policy changes | |
+ * audit_policy - Do auditing of policy changes | |
+ * @label: label to check if it can manage policy | |
* @op: policy operation being performed | |
- * @gfp: memory allocation flags | |
- * @name: name of profile being manipulated (NOT NULL) | |
+ * @profile: name of profile being manipulated (NOT NULL) | |
* @info: any extra information to be audited (MAYBE NULL) | |
* @error: error code | |
* | |
* Returns: the error to be returned after audit is done | |
*/ | |
-static int audit_policy(int op, gfp_t gfp, const char *name, const char *info, | |
- int error) | |
+static int audit_policy(struct aa_label *label, const char *op, | |
+ const char *ns_name, const char *name, | |
+ const char *info, int error) | |
{ | |
- struct common_audit_data sa; | |
- struct apparmor_audit_data aad = {0,}; | |
- sa.type = LSM_AUDIT_DATA_NONE; | |
- sa.aad = &aad; | |
- aad.op = op; | |
- aad.name = name; | |
- aad.info = info; | |
- aad.error = error; | |
- | |
- return aa_audit(AUDIT_APPARMOR_STATUS, __aa_current_profile(), gfp, | |
- &sa, NULL); | |
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op); | |
+ // aad(&sa)->op = op; | |
+ aad(&sa)->iface.ns = ns_name; | |
+ aad(&sa)->name = name; | |
+ aad(&sa)->info = info; | |
+ aad(&sa)->error = error; | |
+ aad(&sa)->label = label; | |
+ | |
+ aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, audit_cb); | |
+ | |
+ return error; | |
} | |
-bool policy_view_capable(void) | |
+bool policy_admin_capable(void) | |
{ | |
struct user_namespace *user_ns = current_user_ns(); | |
+ struct aa_ns *ns = aa_get_current_ns(); | |
bool response = false; | |
- if (ns_capable(user_ns, CAP_MAC_ADMIN)) | |
+ if (ns_capable(user_ns, CAP_MAC_ADMIN) && | |
+ (user_ns == &init_user_ns || | |
+ (user_ns->level == 1 && ns != root_ns))) | |
response = true; | |
+ aa_put_ns(ns); | |
return response; | |
} | |
-bool policy_admin_capable(void) | |
-{ | |
- return policy_view_capable() && !aa_g_lock_policy; | |
-} | |
- | |
/** | |
* aa_may_manage_policy - can the current task manage policy | |
+ * @label: label to check if it can manage policy | |
* @op: the policy manipulation operation being done | |
* | |
- * Returns: true if the task is allowed to manipulate policy | |
+ * Returns: 0 if the task is allowed to manipulate policy else error | |
*/ | |
-bool aa_may_manage_policy(int op) | |
+int aa_may_manage_policy(struct aa_label *label, u32 mask) | |
{ | |
+ const char *op; | |
+ | |
+ if (mask & AA_MAY_REMOVE_POLICY) | |
+ op = OP_PROF_RM; | |
+ else if (mask & AA_MAY_REPLACE_POLICY) | |
+ op = OP_PROF_REPL; | |
+ else | |
+ op = OP_PROF_LOAD; | |
+ | |
/* check if loading policy is locked out */ | |
- if (aa_g_lock_policy) { | |
- audit_policy(op, GFP_KERNEL, NULL, "policy_locked", -EACCES); | |
- return 0; | |
- } | |
+ if (aa_g_lock_policy) | |
+ return audit_policy(label, op, NULL, NULL, "policy_locked", | |
+ -EACCES); | |
- if (!policy_admin_capable()) { | |
- audit_policy(op, GFP_KERNEL, NULL, "not policy admin", -EACCES); | |
- return 0; | |
- } | |
+ if (!policy_admin_capable()) | |
+ return audit_policy(label, op, NULL, NULL, "not policy admin", | |
+ -EACCES); | |
- return 1; | |
+ /* TODO: add fine grained mediation of policy loads */ | |
+ return 0; | |
} | |
static struct aa_profile *__list_lookup_parent(struct list_head *lh, | |
struct aa_profile *profile) | |
{ | |
- const char *base = hname_tail(profile->base.hname); | |
+ const char *base = basename(profile->base.hname); | |
long len = base - profile->base.hname; | |
struct aa_load_ent *ent; | |
@@ -983,7 +689,6 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh, | |
* __replace_profile - replace @old with @new on a list | |
* @old: profile to be replaced (NOT NULL) | |
* @new: profile to replace @old with (NOT NULL) | |
- * @share_replacedby: transfer @old->replacedby to @new | |
* | |
* Will duplicate and refcount elements that @new inherits from @old | |
* and will inherit @old children. | |
@@ -992,8 +697,7 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh, | |
* | |
* Requires: namespace list lock be held, or list not be shared | |
*/ | |
-static void __replace_profile(struct aa_profile *old, struct aa_profile *new, | |
- bool share_replacedby) | |
+static void __replace_profile(struct aa_profile *old, struct aa_profile *new) | |
{ | |
struct aa_profile *child, *tmp; | |
@@ -1008,7 +712,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, | |
p = __find_child(&new->base.profiles, child->base.name); | |
if (p) { | |
/* @p replaces @child */ | |
- __replace_profile(child, p, share_replacedby); | |
+ __replace_profile(child, p); | |
continue; | |
} | |
@@ -1026,14 +730,8 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, | |
struct aa_profile *parent = aa_deref_parent(old); | |
rcu_assign_pointer(new->parent, aa_get_profile(parent)); | |
} | |
- __aa_update_replacedby(old, new); | |
- if (share_replacedby) { | |
- aa_put_replacedby(new->replacedby); | |
- new->replacedby = aa_get_replacedby(old->replacedby); | |
- } else if (!rcu_access_pointer(new->replacedby->profile)) | |
- /* aafs interface uses replacedby */ | |
- rcu_assign_pointer(new->replacedby->profile, | |
- aa_get_profile(new)); | |
+ aa_label_replace(&old->label, &new->label); | |
+ /* migrate dents must come after label replacement b/c update */ | |
__aa_fs_profile_migrate_dents(old, new); | |
if (list_empty(&new->base.list)) { | |
@@ -1055,7 +753,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, | |
* | |
* Returns: profile to replace (no ref) on success else ptr error | |
*/ | |
-static int __lookup_replace(struct aa_namespace *ns, const char *hname, | |
+static int __lookup_replace(struct aa_ns *ns, const char *hname, | |
bool noreplace, struct aa_profile **p, | |
const char **info) | |
{ | |
@@ -1071,25 +769,57 @@ static int __lookup_replace(struct aa_namespace *ns, const char *hname, | |
return 0; | |
} | |
+static void share_name(struct aa_profile *old, struct aa_profile *new) | |
+{ | |
+ aa_put_str(new->base.hname); | |
+ aa_get_str(old->base.hname); | |
+ new->base.hname = old->base.hname; | |
+ new->base.name = old->base.name; | |
+ new->label.hname = old->label.hname; | |
+} | |
+ | |
+/* Update to newest version of parent after previous replacements | |
+ * Returns: unrefcount newest version of parent | |
+ */ | |
+static struct aa_profile *update_to_newest_parent(struct aa_profile *new) | |
+{ | |
+ struct aa_profile *parent, *newest; | |
+ parent = rcu_dereference_protected(new->parent, | |
+ mutex_is_locked(&new->ns->lock)); | |
+ newest = aa_get_newest_profile(parent); | |
+ | |
+ /* parent replaced in this atomic set? */ | |
+ if (newest != parent) { | |
+ aa_put_profile(parent); | |
+ rcu_assign_pointer(new->parent, newest); | |
+ } else | |
+ aa_put_profile(newest); | |
+ | |
+ return newest; | |
+} | |
+ | |
/** | |
* aa_replace_profiles - replace profile(s) on the profile list | |
+ * @label: label that is attempting to load/replace policy | |
+ * @mask: permission mask | |
* @udata: serialized data stream (NOT NULL) | |
* @size: size of the serialized data stream | |
- * @noreplace: true if only doing addition, no replacement allowed | |
* | |
* unpack and replace a profile on the profile list and uses of that profile | |
- * by any aa_task_cxt. If the profile does not exist on the profile list | |
+ * by any aa_task_ctx. If the profile does not exist on the profile list | |
* it is added. | |
* | |
* Returns: size of data consumed else error code on failure. | |
*/ | |
-ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |
+ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, | |
+ size_t size) | |
{ | |
const char *ns_name, *info = NULL; | |
- struct aa_namespace *ns = NULL; | |
+ struct aa_ns *ns = NULL; | |
struct aa_load_ent *ent, *tmp; | |
- int op = OP_PROF_REPL; | |
- ssize_t error; | |
+ const char *op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD; | |
+ ssize_t count, error; | |
+ | |
LIST_HEAD(lh); | |
/* released below */ | |
@@ -1097,33 +827,61 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |
if (error) | |
goto out; | |
- /* released below */ | |
- ns = aa_prepare_namespace(ns_name); | |
- if (!ns) { | |
- error = audit_policy(op, GFP_KERNEL, ns_name, | |
- "failed to prepare namespace", -ENOMEM); | |
- goto free; | |
+ /* ensure that profiles are all for the same ns | |
+ * TODO: update locking to remove this constaint. All profiles in | |
+ * the load set must succeed as a set or the load will | |
+ * fail. Sort ent list and take ns locks in hierarchy order | |
+ */ | |
+ count = 0; | |
+ list_for_each_entry(ent, &lh, list) { | |
+ if (ns_name) { | |
+ if (ent->ns_name && | |
+ strcmp(ent->ns_name, ns_name) != 0) { | |
+ info = "policy load has mixed namespaces"; | |
+ error = -EACCES; | |
+ goto fail; | |
+ } | |
+ } else if (ent->ns_name) { | |
+ if (count) { | |
+ info = "policy load has mixed namespaces"; | |
+ error = -EACCES; | |
+ goto fail; | |
+ } | |
+ ns_name = ent->ns_name; | |
+ } else | |
+ count++; | |
} | |
+ if (ns_name) { | |
+ ns = aa_prepare_ns(labels_ns(label), ns_name); | |
+ if (!ns) { | |
+ info = "failed to prepare namespace"; | |
+ error = -ENOMEM; | |
+ goto fail; | |
+ } | |
+ } else | |
+ ns = aa_get_ns(labels_ns(label)); | |
mutex_lock(&ns->lock); | |
/* setup parent and ns info */ | |
list_for_each_entry(ent, &lh, list) { | |
struct aa_policy *policy; | |
- error = __lookup_replace(ns, ent->new->base.hname, noreplace, | |
+ | |
+ error = __lookup_replace(ns, ent->new->base.hname, | |
+ !(mask & AA_MAY_REPLACE_POLICY), | |
&ent->old, &info); | |
if (error) | |
goto fail_lock; | |
if (ent->new->rename) { | |
error = __lookup_replace(ns, ent->new->rename, | |
- noreplace, &ent->rename, | |
- &info); | |
+ !(mask & AA_MAY_REPLACE_POLICY), | |
+ &ent->rename, &info); | |
if (error) | |
goto fail_lock; | |
} | |
/* released when @new is freed */ | |
- ent->new->ns = aa_get_namespace(ns); | |
+ ent->new->ns = aa_get_ns(ns); | |
if (ent->old || ent->rename) | |
continue; | |
@@ -1148,14 +906,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |
/* create new fs entries for introspection if needed */ | |
list_for_each_entry(ent, &lh, list) { | |
- if (ent->old) { | |
- /* inherit old interface files */ | |
- | |
- /* if (ent->rename) | |
- TODO: support rename */ | |
- /* } else if (ent->rename) { | |
- TODO: support rename */ | |
- } else { | |
+ if (!ent->old) { | |
struct dentry *parent; | |
if (rcu_access_pointer(ent->new->parent)) { | |
struct aa_profile *p; | |
@@ -1167,7 +918,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |
} | |
if (error) { | |
- info = "failed to create "; | |
+ info = "failed to create"; | |
goto fail_lock; | |
} | |
} | |
@@ -1177,50 +928,33 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |
list_del_init(&ent->list); | |
op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL; | |
- audit_policy(op, GFP_ATOMIC, ent->new->base.hname, NULL, error); | |
+ audit_policy(label, op, ns_name, ent->new->base.hname, NULL, error); | |
if (ent->old) { | |
- __replace_profile(ent->old, ent->new, 1); | |
- if (ent->rename) { | |
- /* aafs interface uses replacedby */ | |
- struct aa_replacedby *r = ent->new->replacedby; | |
- rcu_assign_pointer(r->profile, | |
- aa_get_profile(ent->new)); | |
- __replace_profile(ent->rename, ent->new, 0); | |
- } | |
+ share_name(ent->old, ent->new); | |
+ __replace_profile(ent->old, ent->new); | |
+ if (ent->rename) | |
+ __replace_profile(ent->rename, ent->new); | |
} else if (ent->rename) { | |
- /* aafs interface uses replacedby */ | |
- rcu_assign_pointer(ent->new->replacedby->profile, | |
- aa_get_profile(ent->new)); | |
- __replace_profile(ent->rename, ent->new, 0); | |
- } else if (ent->new->parent) { | |
- struct aa_profile *parent, *newest; | |
- parent = aa_deref_parent(ent->new); | |
- newest = aa_get_newest_profile(parent); | |
- | |
- /* parent replaced in this atomic set? */ | |
- if (newest != parent) { | |
- aa_get_profile(newest); | |
- rcu_assign_pointer(ent->new->parent, newest); | |
- aa_put_profile(parent); | |
- } | |
- /* aafs interface uses replacedby */ | |
- rcu_assign_pointer(ent->new->replacedby->profile, | |
- aa_get_profile(ent->new)); | |
- __list_add_profile(&newest->base.profiles, ent->new); | |
- aa_put_profile(newest); | |
+ /* TODO: case not actually supported yet */ | |
+ ; | |
} else { | |
- /* aafs interface uses replacedby */ | |
- rcu_assign_pointer(ent->new->replacedby->profile, | |
- aa_get_profile(ent->new)); | |
- __list_add_profile(&ns->base.profiles, ent->new); | |
+ struct list_head *lh; | |
+ if (rcu_access_pointer(ent->new->parent)) { | |
+ struct aa_profile *parent; | |
+ parent = update_to_newest_parent(ent->new); | |
+ lh = &parent->base.profiles; | |
+ } else | |
+ lh = &ns->base.profiles; | |
+ __add_profile(lh, ent->new); | |
} | |
aa_load_ent_free(ent); | |
} | |
+ __aa_labelset_update_subtree(ns); | |
mutex_unlock(&ns->lock); | |
out: | |
- aa_put_namespace(ns); | |
+ aa_put_ns(ns); | |
if (error) | |
return error; | |
@@ -1231,7 +965,8 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |
/* audit cause of failure */ | |
op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL; | |
- audit_policy(op, GFP_KERNEL, ent->new->base.hname, info, error); | |
+fail: | |
+ audit_policy(label, op, ns_name, ent->new->base.hname, info, error); | |
/* audit status that rest of profiles in the atomic set failed too */ | |
info = "valid profile in failed atomic policy load"; | |
list_for_each_entry(tmp, &lh, list) { | |
@@ -1241,9 +976,8 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |
continue; | |
} | |
op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL; | |
- audit_policy(op, GFP_KERNEL, tmp->new->base.hname, info, error); | |
+ audit_policy(label, op, ns_name, tmp->new->base.hname, info, error); | |
} | |
-free: | |
list_for_each_entry_safe(ent, tmp, &lh, list) { | |
list_del_init(&ent->list); | |
aa_load_ent_free(ent); | |
@@ -1254,6 +988,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |
/** | |
* aa_remove_profiles - remove profile(s) from the system | |
+ * @label: label attempting to remove policy | |
* @fqname: name of the profile or namespace to remove (NOT NULL) | |
* @size: size of the name | |
* | |
@@ -1264,11 +999,12 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | |
* | |
* Returns: size of data consume else error code if fails | |
*/ | |
-ssize_t aa_remove_profiles(char *fqname, size_t size) | |
+ssize_t aa_remove_profiles(struct aa_label *label, char *fqname, size_t size) | |
{ | |
- struct aa_namespace *root, *ns = NULL; | |
+ struct aa_ns *root = NULL, *ns = NULL; | |
struct aa_profile *profile = NULL; | |
const char *name = fqname, *info = NULL; | |
+ char *ns_name = NULL; | |
ssize_t error = 0; | |
if (*fqname == 0) { | |
@@ -1277,13 +1013,12 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) | |
goto fail; | |
} | |
- root = aa_current_profile()->ns; | |
+ root = labels_ns(label); | |
if (fqname[0] == ':') { | |
- char *ns_name; | |
name = aa_split_fqname(fqname, &ns_name); | |
/* released below */ | |
- ns = aa_find_namespace(root, ns_name); | |
+ ns = aa_find_ns(root, ns_name); | |
if (!ns) { | |
info = "namespace does not exist"; | |
error = -ENOENT; | |
@@ -1291,12 +1026,12 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) | |
} | |
} else | |
/* released below */ | |
- ns = aa_get_namespace(root); | |
+ ns = aa_get_ns(root); | |
if (!name) { | |
/* remove namespace - can only happen if fqname[0] == ':' */ | |
mutex_lock(&ns->parent->lock); | |
- __remove_namespace(ns); | |
+ __aa_remove_ns(ns); | |
mutex_unlock(&ns->parent->lock); | |
} else { | |
/* remove profile */ | |
@@ -1309,20 +1044,19 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) | |
} | |
name = profile->base.hname; | |
__remove_profile(profile); | |
+ __aa_labelset_update_subtree(ns); | |
mutex_unlock(&ns->lock); | |
} | |
/* don't fail removal if audit fails */ | |
- (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); | |
- aa_put_namespace(ns); | |
+ (void) audit_policy(label, OP_PROF_RM, ns_name, name, info, error); | |
aa_put_profile(profile); | |
return size; | |
fail_ns_lock: | |
mutex_unlock(&ns->lock); | |
- aa_put_namespace(ns); | |
fail: | |
- (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); | |
+ (void) audit_policy(label, OP_PROF_RM, ns_name, name, info, error); | |
return error; | |
} | |
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c | |
new file mode 100644 | |
index 0000000..d06e664 | |
--- /dev/null | |
+++ b/security/apparmor/policy_ns.c | |
@@ -0,0 +1,323 @@ | |
+/* | |
+ * AppArmor security module | |
+ * | |
+ * This file contains AppArmor policy manipulation functions | |
+ * | |
+ * Copyright (C) 1998-2008 Novell/SUSE | |
+ * Copyright 2009-2015 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. | |
+ * | |
+ * AppArmor policy namespaces, allow for different sets of policies | |
+ * to be loaded for tasks within the namespace. | |
+ */ | |
+ | |
+#include <linux/list.h> | |
+#include <linux/mutex.h> | |
+#include <linux/slab.h> | |
+#include <linux/string.h> | |
+ | |
+#include "include/apparmor.h" | |
+#include "include/context.h" | |
+#include "include/policy_ns.h" | |
+#include "include/label.h" | |
+#include "include/policy.h" | |
+ | |
+/* root profile namespace */ | |
+struct aa_ns *root_ns; | |
+const char *aa_hidden_ns_name = "---"; | |
+ | |
+/** | |
+ * aa_ns_visible - test if @view is visible from @curr | |
+ * @curr: namespace to treat as the parent (NOT NULL) | |
+ * @view: namespace to test if visible from @curr (NOT NULL) | |
+ * @subns: whether view of a subns is allowed | |
+ * | |
+ * Returns: true if @view is visible from @curr else false | |
+ */ | |
+bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns) | |
+{ | |
+ if (curr == view) | |
+ return true; | |
+ | |
+ if (!subns) | |
+ return false; | |
+ | |
+ for ( ; view; view = view->parent) { | |
+ if (view->parent == curr) | |
+ return true; | |
+ } | |
+ | |
+ return false; | |
+} | |
+ | |
+/** | |
+ * aa_na_name - Find the ns name to display for @view from @curr | |
+ * @curr - current namespace (NOT NULL) | |
+ * @view - namespace attempting to view (NOT NULL) | |
+ * @subns - are subns visible | |
+ * | |
+ * Returns: name of @view visible from @curr | |
+ */ | |
+const char *aa_ns_name(struct aa_ns *curr, struct aa_ns *view, bool subns) | |
+{ | |
+ /* if view == curr then the namespace name isn't displayed */ | |
+ if (curr == view) | |
+ return ""; | |
+ | |
+ if (aa_ns_visible(curr, view, subns)) { | |
+ /* at this point if a ns is visible it is in a view ns | |
+ * thus the curr ns.hname is a prefix of its name. | |
+ * Only output the virtualized portion of the name | |
+ * Add + 2 to skip over // separating curr hname prefix | |
+ * from the visible tail of the views hname | |
+ */ | |
+ return view->base.hname + strlen(curr->base.hname) + 2; | |
+ } else | |
+ return aa_hidden_ns_name; | |
+} | |
+ | |
+/** | |
+ * alloc_ns - allocate, initialize and return a new namespace | |
+ * @prefix: parent namespace name (MAYBE NULL) | |
+ * @name: a preallocated name (NOT NULL) | |
+ * | |
+ * Returns: refcounted namespace or NULL on failure. | |
+ */ | |
+static struct aa_ns *alloc_ns(const char *prefix, const char *name) | |
+{ | |
+ struct aa_ns *ns; | |
+ | |
+ ns = kzalloc(sizeof(*ns), GFP_KERNEL); | |
+ AA_DEBUG("%s(%p)\n", __func__, ns); | |
+ if (!ns) | |
+ return NULL; | |
+ if (!aa_policy_init(&ns->base, prefix, name, GFP_KERNEL)) | |
+ goto fail_ns; | |
+ | |
+ INIT_LIST_HEAD(&ns->sub_ns); | |
+ mutex_init(&ns->lock); | |
+ | |
+ /* released by free_namespace */ | |
+ ns->unconfined = aa_alloc_profile("unconfined", NULL, GFP_KERNEL); | |
+ if (!ns->unconfined) | |
+ goto fail_unconfined; | |
+ | |
+ ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR | | |
+ FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED; | |
+ ns->unconfined->mode = APPARMOR_UNCONFINED; | |
+ | |
+ /* ns and ns->unconfined share ns->unconfined refcount */ | |
+ ns->unconfined->ns = ns; | |
+ | |
+ atomic_set(&ns->uniq_null, 0); | |
+ | |
+ aa_labelset_init(&ns->labels); | |
+ | |
+ return ns; | |
+ | |
+fail_unconfined: | |
+ kzfree(ns->base.hname); | |
+fail_ns: | |
+ kzfree(ns); | |
+ return NULL; | |
+} | |
+ | |
+/** | |
+ * aa_free_ns - free a profile namespace | |
+ * @ns: the namespace to free (MAYBE NULL) | |
+ * | |
+ * Requires: All references to the namespace must have been put, if the | |
+ * namespace was referenced by a profile confining a task, | |
+ */ | |
+void aa_free_ns(struct aa_ns *ns) | |
+{ | |
+ if (!ns) | |
+ return; | |
+ | |
+ aa_policy_destroy(&ns->base); | |
+ aa_labelset_destroy(&ns->labels); | |
+ aa_put_ns(ns->parent); | |
+ | |
+ ns->unconfined->ns = NULL; | |
+ aa_free_profile(ns->unconfined); | |
+ kzfree(ns); | |
+} | |
+ | |
+/** | |
+ * __aa_findn_ns - find a namespace on a list by @name | |
+ * @head: list to search for namespace on (NOT NULL) | |
+ * @name: name of namespace to look for (NOT NULL) | |
+ * @n: length of @name | |
+ * Returns: unrefcounted namespace | |
+ * | |
+ * Requires: rcu_read_lock be held | |
+ */ | |
+static struct aa_ns *__aa_findn_ns(struct list_head *head, const char *name, | |
+ size_t n) | |
+{ | |
+ return (struct aa_ns *)__policy_strn_find(head, name, n); | |
+} | |
+ | |
+/** | |
+ * aa_find_ns - look up a profile namespace on the namespace list | |
+ * @root: namespace to search in (NOT NULL) | |
+ * @name: name of namespace to find (NOT NULL) | |
+ * @n: length of @name | |
+ * | |
+ * Returns: a refcounted namespace on the list, or NULL if no namespace | |
+ * called @name exists. | |
+ * | |
+ * refcount released by caller | |
+ */ | |
+struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n) | |
+{ | |
+ struct aa_ns *ns = NULL; | |
+ | |
+ rcu_read_lock(); | |
+ ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, n)); | |
+ rcu_read_unlock(); | |
+ | |
+ return ns; | |
+} | |
+ | |
+/** | |
+ * aa_find_ns - look up a profile namespace on the namespace list | |
+ * @root: namespace to search in (NOT NULL) | |
+ * @name: name of namespace to find (NOT NULL) | |
+ * | |
+ * Returns: a refcounted namespace on the list, or NULL if no namespace | |
+ * called @name exists. | |
+ * | |
+ * refcount released by caller | |
+ */ | |
+struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name) | |
+{ | |
+ return aa_findn_ns(root, name, strlen(name)); | |
+} | |
+ | |
+/** | |
+ * aa_prepare_ns - find an existing or create a new namespace of @name | |
+ * @root: ns to treat as root | |
+ * @name: the namespace to find or add (NOT NULL) | |
+ * | |
+ * Returns: refcounted namespace or NULL if failed to create one | |
+ */ | |
+struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name) | |
+{ | |
+ struct aa_ns *ns; | |
+ | |
+ mutex_lock(&root->lock); | |
+ /* try and find the specified ns and if it doesn't exist create it */ | |
+ /* released by caller */ | |
+ ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, strlen(name))); | |
+ if (!ns) { | |
+ ns = alloc_ns(root->base.hname, name); | |
+ if (!ns) | |
+ goto out; | |
+ mutex_lock(&ns->lock); | |
+ if (__aa_fs_ns_mkdir(ns, ns_subns_dir(root), name)) { | |
+ AA_ERROR("Failed to create interface for ns %s\n", | |
+ ns->base.name); | |
+ mutex_unlock(&ns->lock); | |
+ aa_free_ns(ns); | |
+ ns = NULL; | |
+ goto out; | |
+ } | |
+ ns->parent = aa_get_ns(root); | |
+ ns->level = root->level + 1; | |
+ list_add_rcu(&ns->base.list, &root->sub_ns); | |
+ /* add list ref */ | |
+ aa_get_ns(ns); | |
+ mutex_unlock(&ns->lock); | |
+ } | |
+out: | |
+ mutex_unlock(&root->lock); | |
+ | |
+ /* return ref */ | |
+ return ns; | |
+} | |
+ | |
+static void __ns_list_release(struct list_head *head); | |
+ | |
+/** | |
+ * destroy_namespace - remove everything contained by @ns | |
+ * @ns: namespace to have it contents removed (NOT NULL) | |
+ */ | |
+static void destroy_ns(struct aa_ns *ns) | |
+{ | |
+ if (!ns) | |
+ return; | |
+ | |
+ mutex_lock(&ns->lock); | |
+ /* release all profiles in this namespace */ | |
+ __aa_profile_list_release(&ns->base.profiles); | |
+ | |
+ /* release all sub namespaces */ | |
+ __ns_list_release(&ns->sub_ns); | |
+ | |
+ if (ns->parent) | |
+ __aa_proxy_redirect(ns_unconfined(ns), | |
+ ns_unconfined(ns->parent)); | |
+ __aa_fs_ns_rmdir(ns); | |
+ mutex_unlock(&ns->lock); | |
+} | |
+ | |
+/** | |
+ * __aa_remove_ns - remove a namespace and all its children | |
+ * @ns: namespace to be removed (NOT NULL) | |
+ * | |
+ * Requires: ns->parent->lock be held and ns removed from parent. | |
+ */ | |
+void __aa_remove_ns(struct aa_ns *ns) | |
+{ | |
+ /* remove ns from namespace list */ | |
+ list_del_rcu(&ns->base.list); | |
+ destroy_ns(ns); | |
+ aa_put_ns(ns); | |
+} | |
+ | |
+/** | |
+ * __ns_list_release - remove all profile namespaces on the list put refs | |
+ * @head: list of profile namespaces (NOT NULL) | |
+ * | |
+ * Requires: namespace lock be held | |
+ */ | |
+static void __ns_list_release(struct list_head *head) | |
+{ | |
+ struct aa_ns *ns, *tmp; | |
+ list_for_each_entry_safe(ns, tmp, head, base.list) | |
+ __aa_remove_ns(ns); | |
+ | |
+} | |
+ | |
+/** | |
+ * aa_alloc_root_ns - allocate the root profile namespace | |
+ * | |
+ * Returns: %0 on success else error | |
+ * | |
+ */ | |
+int __init aa_alloc_root_ns(void) | |
+{ | |
+ /* released by aa_free_root_ns - used as list ref*/ | |
+ root_ns = alloc_ns(NULL, "root"); | |
+ if (!root_ns) | |
+ return -ENOMEM; | |
+ | |
+ return 0; | |
+} | |
+ | |
+ /** | |
+ * aa_free_root_ns - free the root profile namespace | |
+ */ | |
+void __init aa_free_root_ns(void) | |
+ { | |
+ struct aa_ns *ns = root_ns; | |
+ root_ns = NULL; | |
+ | |
+ destroy_ns(ns); | |
+ aa_put_ns(ns); | |
+} | |
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c | |
index 1381206..acbcee4 100644 | |
--- a/security/apparmor/policy_unpack.c | |
+++ b/security/apparmor/policy_unpack.c | |
@@ -20,15 +20,25 @@ | |
#include <asm/unaligned.h> | |
#include <linux/ctype.h> | |
#include <linux/errno.h> | |
+#include <linux/string.h> | |
#include "include/apparmor.h" | |
#include "include/audit.h" | |
#include "include/context.h" | |
#include "include/crypto.h" | |
#include "include/match.h" | |
+#include "include/path.h" | |
#include "include/policy.h" | |
#include "include/policy_unpack.h" | |
+#define K_ABI_MASK 0x3ff | |
+#define FORCE_COMPLAIN_FLAG 0x800 | |
+#define VERSION_CMP(OP, X, Y) (((X) & K_ABI_MASK) OP ((Y) & K_ABI_MASK)) | |
+ | |
+#define v5 5 /* base version */ | |
+#define v6 6 /* per entry policydb mediation check */ | |
+#define v7 7 /* full network masking */ | |
+ | |
/* | |
* The AppArmor interface treats data as a type byte followed by the | |
* actual data. The interface has the notion of a a named entry | |
@@ -70,18 +80,23 @@ struct aa_ext { | |
static void audit_cb(struct audit_buffer *ab, void *va) | |
{ | |
struct common_audit_data *sa = va; | |
- if (sa->aad->iface.target) { | |
- struct aa_profile *name = sa->aad->iface.target; | |
+ | |
+ if (aad(sa)->iface.ns) { | |
+ audit_log_format(ab, " ns="); | |
+ audit_log_untrustedstring(ab, aad(sa)->iface.ns); | |
+ } | |
+ if (aad(sa)->name) { | |
audit_log_format(ab, " name="); | |
- audit_log_untrustedstring(ab, name->base.hname); | |
+ audit_log_untrustedstring(ab, aad(sa)->name); | |
} | |
- if (sa->aad->iface.pos) | |
- audit_log_format(ab, " offset=%ld", sa->aad->iface.pos); | |
+ if (aad(sa)->iface.pos) | |
+ audit_log_format(ab, " offset=%ld", aad(sa)->iface.pos); | |
} | |
/** | |
* audit_iface - do audit message for policy unpacking/load/replace/remove | |
* @new: profile if it has been allocated (MAYBE NULL) | |
+ * @ns_name: name of the ns the profile is to be loaded to (MAY BE NULL) | |
* @name: name of the profile being manipulated (MAYBE NULL) | |
* @info: any extra info about the failure (MAYBE NULL) | |
* @e: buffer position info | |
@@ -89,23 +104,24 @@ static void audit_cb(struct audit_buffer *ab, void *va) | |
* | |
* Returns: %0 or error | |
*/ | |
-static int audit_iface(struct aa_profile *new, const char *name, | |
- const char *info, struct aa_ext *e, int error) | |
+static int audit_iface(struct aa_profile *new, const char *ns_name, | |
+ const char *name, const char *info, struct aa_ext *e, | |
+ int error) | |
{ | |
- struct aa_profile *profile = __aa_current_profile(); | |
- struct common_audit_data sa; | |
- struct apparmor_audit_data aad = {0,}; | |
- sa.type = LSM_AUDIT_DATA_NONE; | |
- sa.aad = &aad; | |
+ struct aa_profile *profile = labels_profile(aa_current_raw_label()); | |
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); | |
if (e) | |
- aad.iface.pos = e->pos - e->start; | |
- aad.iface.target = new; | |
- aad.name = name; | |
- aad.info = info; | |
- aad.error = error; | |
- | |
- return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa, | |
- audit_cb); | |
+ aad(&sa)->iface.pos = e->pos - e->start; | |
+ | |
+ aad(&sa)->iface.ns = ns_name; | |
+ if (new) | |
+ aad(&sa)->name = new->base.hname; | |
+ else | |
+ aad(&sa)->name = name; | |
+ aad(&sa)->info = info; | |
+ aad(&sa)->error = error; | |
+ | |
+ return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb); | |
} | |
/* test if read will be in packed data bounds */ | |
@@ -193,6 +209,19 @@ static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name) | |
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)) { | |
@@ -340,12 +369,7 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e) | |
((e->pos - e->start) & 7); | |
size_t pad = ALIGN(sz, 8) - sz; | |
int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | | |
- TO_ACCEPT2_FLAG(YYTD_DATA32); | |
- | |
- | |
- if (aa_g_paranoid_load) | |
- flags |= DFA_FLAG_VERIFY_STATES; | |
- | |
+ TO_ACCEPT2_FLAG(YYTD_DATA32) | DFA_FLAG_VERIFY_STATES; | |
dfa = aa_dfa_unpack(blob + pad, size - pad, flags); | |
if (IS_ERR(dfa)) | |
@@ -389,7 +413,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) | |
profile->file.trans.size = size; | |
for (i = 0; i < size; i++) { | |
char *str; | |
- int c, j, size2 = unpack_strdup(e, &str, NULL); | |
+ int c, j, pos, size2 = unpack_strdup(e, &str, NULL); | |
/* unpack_strdup verifies that the last character is | |
* null termination byte. | |
*/ | |
@@ -401,19 +425,24 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) | |
goto fail; | |
/* count internal # of internal \0 */ | |
- for (c = j = 0; j < size2 - 2; j++) { | |
- if (!str[j]) | |
+ for (c = j = 0; j < size2 - 1; j++) { | |
+ if (!str[j]) { | |
+ pos = j; | |
c++; | |
+ } | |
} | |
if (*str == ':') { | |
+ /* first character after : must be valid */ | |
+ if (!str[1]) | |
+ goto fail; | |
/* beginning with : requires an embedded \0, | |
* verify that exactly 1 internal \0 exists | |
* trailing \0 already verified by unpack_strdup | |
*/ | |
- if (c != 1) | |
- goto fail; | |
- /* first character after : must be valid */ | |
- if (!str[1]) | |
+ if (c == 1) | |
+ /* convert \0 back to : for label_parse */ | |
+ str[pos] = ':'; | |
+ else if (c > 1) | |
goto fail; | |
} else if (c) | |
/* fail - all other cases with embedded \0 */ | |
@@ -472,21 +501,35 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) | |
* | |
* NOTE: unpack profile sets audit struct if there is a failure | |
*/ | |
-static struct aa_profile *unpack_profile(struct aa_ext *e) | |
+static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) | |
{ | |
struct aa_profile *profile = NULL; | |
- const char *name = NULL; | |
+ const char *tmpname, *tmpns = NULL, *name = NULL; | |
+ const char *info = "failed to unpack profile"; | |
+ size_t size = 0, ns_len; | |
int i, error = -EPROTO; | |
kernel_cap_t tmpcap; | |
u32 tmp; | |
+ *ns_name = NULL; | |
+ | |
/* check that we have the right struct being passed */ | |
if (!unpack_nameX(e, AA_STRUCT, "profile")) | |
goto fail; | |
if (!unpack_str(e, &name, NULL)) | |
goto fail; | |
+ if (*name == '\0') | |
+ goto fail; | |
+ | |
+ tmpname = aa_splitn_fqname(name, strlen(name), &tmpns, &ns_len); | |
+ if (tmpns) { | |
+ *ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL); | |
+ if (!*ns_name) | |
+ goto fail; | |
+ name = tmpname; | |
+ } | |
- profile = aa_alloc_profile(name); | |
+ profile = aa_alloc_profile(name, NULL, GFP_KERNEL); | |
if (!profile) | |
return ERR_PTR(-ENOMEM); | |
@@ -510,16 +553,19 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) | |
profile->xmatch_len = tmp; | |
} | |
+ /* disconnected attachment string is optional */ | |
+ (void) unpack_str(e, &profile->disconnected, "disconnected"); | |
+ | |
/* per profile debug flags (complain, audit) */ | |
if (!unpack_nameX(e, AA_STRUCT, "flags")) | |
goto fail; | |
if (!unpack_u32(e, &tmp, NULL)) | |
goto fail; | |
if (tmp & PACKED_FLAG_HAT) | |
- profile->flags |= PFLAG_HAT; | |
+ profile->label.flags |= FLAG_HAT; | |
if (!unpack_u32(e, &tmp, NULL)) | |
goto fail; | |
- if (tmp == PACKED_MODE_COMPLAIN) | |
+ if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG)) | |
profile->mode = APPARMOR_COMPLAIN; | |
else if (tmp == PACKED_MODE_KILL) | |
profile->mode = APPARMOR_KILL; | |
@@ -534,11 +580,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) | |
goto fail; | |
/* path_flags is optional */ | |
- if (unpack_u32(e, &profile->path_flags, "path_flags")) | |
- profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED; | |
- else | |
+ if (!unpack_u32(e, &profile->path_flags, "path_flags")) | |
/* set a default value if path_flags field is not present */ | |
- profile->path_flags = PFLAG_MEDIATE_DELETED; | |
+ profile->path_flags = PATH_MEDIATE_DELETED; | |
if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL)) | |
goto fail; | |
@@ -576,6 +620,37 @@ 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; | |
+ } | |
+ if (VERSION_CMP(<, e->version, v7)) { | |
+ /* old policy always allowed these too */ | |
+ 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); | |
@@ -599,7 +674,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) | |
} | |
if (!unpack_nameX(e, AA_STRUCTEND, NULL)) | |
goto fail; | |
- } | |
+ } else | |
+ profile->policy.dfa = aa_get_dfa(nulldfa); | |
/* get file rules */ | |
profile->file.dfa = unpack_dfa(e); | |
@@ -607,11 +683,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) | |
error = PTR_ERR(profile->file.dfa); | |
profile->file.dfa = NULL; | |
goto fail; | |
- } | |
- | |
- if (!unpack_u32(e, &profile->file.start, "dfa_start")) | |
- /* default start state */ | |
- profile->file.start = DFA_START; | |
+ } else if (profile->file.dfa) { | |
+ if (!unpack_u32(e, &profile->file.start, "dfa_start")) | |
+ /* default start state */ | |
+ profile->file.start = DFA_START; | |
+ } else if (profile->policy.dfa && | |
+ profile->policy.start[AA_CLASS_FILE]) { | |
+ profile->file.dfa = aa_get_dfa(profile->policy.dfa); | |
+ profile->file.start = profile->policy.start[AA_CLASS_FILE]; | |
+ } else | |
+ profile->file.dfa = aa_get_dfa(nulldfa); | |
if (!unpack_trans_table(e, profile)) | |
goto fail; | |
@@ -626,7 +707,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) | |
name = NULL; | |
else if (!name) | |
name = "unknown"; | |
- audit_iface(profile, name, "failed to unpack profile", e, error); | |
+ audit_iface(profile, NULL, name, info, e, error); | |
aa_free_profile(profile); | |
return ERR_PTR(error); | |
@@ -649,24 +730,32 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) | |
/* get the interface version */ | |
if (!unpack_u32(e, &e->version, "version")) { | |
if (required) { | |
- audit_iface(NULL, NULL, "invalid profile format", e, | |
- error); | |
- return error; | |
- } | |
- | |
- /* check that the interface version is currently supported */ | |
- if (e->version != 5) { | |
- audit_iface(NULL, NULL, "unsupported interface version", | |
+ audit_iface(NULL, NULL, NULL, "invalid profile format", | |
e, error); | |
return error; | |
} | |
} | |
+ /* Check that the interface version is currently supported. | |
+ * if not specified use previous version | |
+ * Mask off everything that is not kernel abi version | |
+ */ | |
+ if (VERSION_CMP(<, e->version, v5) && VERSION_CMP(>, e->version, v7)) { | |
+ audit_iface(NULL, NULL, NULL, "unsupported interface version", | |
+ e, error); | |
+ return error; | |
+ } | |
/* read the namespace if present */ | |
if (unpack_str(e, &name, "namespace")) { | |
+ if (*name == '\0') { | |
+ audit_iface(NULL, NULL, NULL, "invalid namespace name", | |
+ e, error); | |
+ return error; | |
+ } | |
if (*ns && strcmp(*ns, name)) | |
- audit_iface(NULL, NULL, "invalid ns change", e, error); | |
+ audit_iface(NULL, NULL, NULL, "invalid ns change", e, | |
+ error); | |
else if (!*ns) | |
*ns = name; | |
} | |
@@ -705,14 +794,12 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) | |
*/ | |
static int verify_profile(struct aa_profile *profile) | |
{ | |
- if (aa_g_paranoid_load) { | |
- if (profile->file.dfa && | |
- !verify_dfa_xindex(profile->file.dfa, | |
- profile->file.trans.size)) { | |
- audit_iface(profile, NULL, "Invalid named transition", | |
- NULL, -EPROTO); | |
- return -EPROTO; | |
- } | |
+ if (profile->file.dfa && | |
+ !verify_dfa_xindex(profile->file.dfa, | |
+ profile->file.trans.size)) { | |
+ audit_iface(profile, NULL, NULL, | |
+ "Invalid named transition", NULL, -EPROTO); | |
+ return -EPROTO; | |
} | |
return 0; | |
@@ -724,6 +811,7 @@ void aa_load_ent_free(struct aa_load_ent *ent) | |
aa_put_profile(ent->rename); | |
aa_put_profile(ent->old); | |
aa_put_profile(ent->new); | |
+ kfree(ent->ns_name); | |
kzfree(ent); | |
} | |
} | |
@@ -762,13 +850,14 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) | |
*ns = NULL; | |
while (e.pos < e.end) { | |
+ char *ns_name = NULL; | |
void *start; | |
error = verify_header(&e, e.pos == e.start, ns); | |
if (error) | |
goto fail; | |
start = e.pos; | |
- profile = unpack_profile(&e); | |
+ profile = unpack_profile(&e, &ns_name); | |
if (IS_ERR(profile)) { | |
error = PTR_ERR(profile); | |
goto fail; | |
@@ -778,7 +867,8 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) | |
if (error) | |
goto fail_profile; | |
- error = aa_calc_profile_hash(profile, e.version, start, | |
+ if (aa_g_hash_policy) | |
+ error = aa_calc_profile_hash(profile, e.version, start, | |
e.pos - start); | |
if (error) | |
goto fail_profile; | |
@@ -790,6 +880,7 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) | |
} | |
ent->new = profile; | |
+ ent->ns_name = ns_name; | |
list_add_tail(&ent->list, lh); | |
} | |
diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c | |
index b125acc..65bf9e9 100644 | |
--- a/security/apparmor/procattr.c | |
+++ b/security/apparmor/procattr.c | |
@@ -33,50 +33,41 @@ | |
* | |
* Returns: size of string placed in @string else error code on failure | |
*/ | |
-int aa_getprocattr(struct aa_profile *profile, char **string) | |
+int aa_getprocattr(struct aa_label *label, char **string) | |
{ | |
- char *str; | |
- int len = 0, mode_len = 0, ns_len = 0, name_len; | |
- const char *mode_str = aa_profile_mode_names[profile->mode]; | |
- const char *ns_name = NULL; | |
- struct aa_namespace *ns = profile->ns; | |
- struct aa_namespace *current_ns = __aa_current_profile()->ns; | |
- char *s; | |
- | |
- if (!aa_ns_visible(current_ns, ns)) | |
- return -EACCES; | |
- | |
- ns_name = aa_ns_name(current_ns, ns); | |
- ns_len = strlen(ns_name); | |
+ struct aa_ns *ns = labels_ns(label); | |
+ struct aa_ns *current_ns = aa_get_current_ns(); | |
+ int len; | |
- /* if the visible ns_name is > 0 increase size for : :// seperator */ | |
- if (ns_len) | |
- ns_len += 4; | |
+ if (!aa_ns_visible(current_ns, ns, true)) { | |
+ aa_put_ns(current_ns); | |
+ return -EACCES; | |
+ } | |
- /* unconfined profiles don't have a mode string appended */ | |
- if (!unconfined(profile)) | |
- mode_len = strlen(mode_str) + 3; /* + 3 for _() */ | |
+ len = aa_label_snxprint(NULL, 0, current_ns, label, | |
+ FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | | |
+ FLAG_HIDDEN_UNCONFINED); | |
+ AA_BUG(len < 0); | |
- name_len = strlen(profile->base.hname); | |
- len = mode_len + ns_len + name_len + 1; /* + 1 for \n */ | |
- s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */ | |
- if (!str) | |
+ *string = kmalloc(len + 2, GFP_KERNEL); | |
+ if (!*string) { | |
+ aa_put_ns(current_ns); | |
return -ENOMEM; | |
+ } | |
- if (ns_len) { | |
- /* skip over prefix current_ns->base.hname and separating // */ | |
- sprintf(s, ":%s://", ns_name); | |
- s += ns_len; | |
+ len = aa_label_snxprint(*string, len + 2, current_ns, label, | |
+ FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | | |
+ FLAG_HIDDEN_UNCONFINED); | |
+ if (len < 0) { | |
+ aa_put_ns(current_ns); | |
+ return len; | |
} | |
- if (unconfined(profile)) | |
- /* mode string not being appended */ | |
- sprintf(s, "%s\n", profile->base.hname); | |
- else | |
- sprintf(s, "%s (%s)\n", profile->base.hname, mode_str); | |
- *string = str; | |
- | |
- /* NOTE: len does not include \0 of string, not saved as part of file */ | |
- return len; | |
+ | |
+ (*string)[len] = '\n'; | |
+ (*string)[len + 1] = 0; | |
+ | |
+ aa_put_ns(current_ns); | |
+ return len + 1; | |
} | |
/** | |
@@ -87,13 +78,13 @@ int aa_getprocattr(struct aa_profile *profile, char **string) | |
* | |
* Returns: start position of name after token else NULL on failure | |
*/ | |
-static char *split_token_from_name(int op, char *args, u64 * token) | |
+static char *split_token_from_name(const char *op, char *args, u64 * token) | |
{ | |
char *name; | |
*token = simple_strtoull(args, &name, 16); | |
if ((name == args) || *name != '^') { | |
- AA_ERROR("%s: Invalid input '%s'", op_table[op], args); | |
+ AA_ERROR("%s: Invalid input '%s'", op, args); | |
return ERR_PTR(-EINVAL); | |
} | |
@@ -138,28 +129,13 @@ int aa_setprocattr_changehat(char *args, size_t size, int test) | |
for (count = 0; (hat < end) && count < 16; ++count) { | |
char *next = hat + strlen(hat) + 1; | |
hats[count] = hat; | |
+ AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d hat '%s'\n" | |
+ , __func__, current->pid, token, count, hat); | |
hat = next; | |
} | |
- } | |
- | |
- AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n", | |
- __func__, token, hat ? hat : NULL); | |
+ } else | |
+ AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n", | |
+ __func__, current->pid, token, count, "<NULL>"); | |
return aa_change_hat(hats, count, token, test); | |
} | |
- | |
-/** | |
- * aa_setprocattr_changeprofile - handle procattr interface to changeprofile | |
- * @fqname: args received from writting to /proc/<pid>/attr/current (NOT NULL) | |
- * @onexec: true if change_profile should be delayed until exec | |
- * @test: true if this is a test of change_profile permissions | |
- * | |
- * Returns: %0 or error code if change_profile fails | |
- */ | |
-int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test) | |
-{ | |
- char *name, *ns_name; | |
- | |
- name = aa_split_fqname(fqname, &ns_name); | |
- return aa_change_profile(ns_name, name, onexec, test); | |
-} | |
diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c | |
index 67a6072..3888f2b 100644 | |
--- a/security/apparmor/resource.c | |
+++ b/security/apparmor/resource.c | |
@@ -35,7 +35,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) | |
struct common_audit_data *sa = va; | |
audit_log_format(ab, " rlimit=%s value=%lu", | |
- rlim_names[sa->aad->rlim.rlim], sa->aad->rlim.max); | |
+ rlim_names[aad(sa)->rlim.rlim], aad(sa)->rlim.max); | |
} | |
/** | |
@@ -50,17 +50,11 @@ static void audit_cb(struct audit_buffer *ab, void *va) | |
static int audit_resource(struct aa_profile *profile, unsigned int resource, | |
unsigned long value, int error) | |
{ | |
- struct common_audit_data sa; | |
- struct apparmor_audit_data aad = {0,}; | |
- | |
- sa.type = LSM_AUDIT_DATA_NONE; | |
- sa.aad = &aad; | |
- aad.op = OP_SETRLIMIT, | |
- aad.rlim.rlim = resource; | |
- aad.rlim.max = value; | |
- aad.error = error; | |
- return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_KERNEL, &sa, | |
- audit_cb); | |
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT); | |
+ aad(&sa)->rlim.rlim = resource; | |
+ aad(&sa)->rlim.max = value; | |
+ aad(&sa)->error = error; | |
+ return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb); | |
} | |
/** | |
@@ -77,9 +71,20 @@ int aa_map_resource(int resource) | |
return rlim_map[resource]; | |
} | |
+static int profile_setrlimit(struct aa_profile *profile, unsigned int resource, | |
+ struct rlimit *new_rlim) | |
+{ | |
+ int e = 0; | |
+ | |
+ if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max > | |
+ profile->rlimits.limits[resource].rlim_max) | |
+ e = -EACCES; | |
+ return audit_resource(profile, resource, new_rlim->rlim_max, e); | |
+} | |
+ | |
/** | |
* aa_task_setrlimit - test permission to set an rlimit | |
- * @profile - profile confining the task (NOT NULL) | |
+ * @label - label confining the task (NOT NULL) | |
* @task - task the resource is being set on | |
* @resource - the resource being set | |
* @new_rlim - the new resource limit (NOT NULL) | |
@@ -88,14 +93,15 @@ int aa_map_resource(int resource) | |
* | |
* Returns: 0 or error code if setting resource failed | |
*/ | |
-int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, | |
+int aa_task_setrlimit(struct aa_label *label, struct task_struct *task, | |
unsigned int resource, struct rlimit *new_rlim) | |
{ | |
- struct aa_profile *task_profile; | |
+ struct aa_profile *profile; | |
+ struct aa_label *peer; | |
int error = 0; | |
rcu_read_lock(); | |
- task_profile = aa_get_profile(aa_cred_profile(__task_cred(task))); | |
+ peer = aa_get_newest_cred_label(__task_cred(task)); | |
rcu_read_unlock(); | |
/* TODO: extend resource control to handle other (non current) | |
@@ -104,53 +110,67 @@ int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, | |
* the same profile or that the task setting the resource of another | |
* task has CAP_SYS_RESOURCE. | |
*/ | |
- if ((profile != task_profile && | |
- aa_capable(profile, CAP_SYS_RESOURCE, 1)) || | |
- (profile->rlimits.mask & (1 << resource) && | |
- new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max)) | |
- error = -EACCES; | |
- aa_put_profile(task_profile); | |
- | |
- return audit_resource(profile, resource, new_rlim->rlim_max, error); | |
+ if (label != peer && | |
+ !aa_capable(label, CAP_SYS_RESOURCE, SECURITY_CAP_NOAUDIT)) | |
+ error = fn_for_each(label, profile, | |
+ audit_resource(profile, resource, | |
+ new_rlim->rlim_max, EACCES)); | |
+ else | |
+ error = fn_for_each_confined(label, profile, | |
+ profile_setrlimit(profile, resource, new_rlim)); | |
+ aa_put_label(peer); | |
+ | |
+ return error; | |
} | |
/** | |
* __aa_transition_rlimits - apply new profile rlimits | |
- * @old: old profile on task (NOT NULL) | |
- * @new: new profile with rlimits to apply (NOT NULL) | |
+ * @old_l: old label on task (NOT NULL) | |
+ * @new_l: new label with rlimits to apply (NOT NULL) | |
*/ | |
-void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new) | |
+void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) | |
{ | |
unsigned int mask = 0; | |
struct rlimit *rlim, *initrlim; | |
- int i; | |
+ struct aa_profile *old, *new; | |
+ struct label_it i; | |
+ | |
+ old = labels_profile(old_l); | |
+ new = labels_profile(new_l); | |
- /* for any rlimits the profile controlled reset the soft limit | |
- * to the less of the tasks hard limit and the init tasks soft limit | |
+ /* for any rlimits the profile controlled, reset the soft limit | |
+ * to the lesser of the tasks hard limit and the init tasks soft limit | |
*/ | |
- if (old->rlimits.mask) { | |
- for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { | |
- if (old->rlimits.mask & mask) { | |
- rlim = current->signal->rlim + i; | |
- initrlim = init_task.signal->rlim + i; | |
- rlim->rlim_cur = min(rlim->rlim_max, | |
- initrlim->rlim_cur); | |
+ label_for_each_confined(i, old_l, old) { | |
+ if (old->rlimits.mask) { | |
+ int j; | |
+ for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, | |
+ mask <<= 1) { | |
+ if (old->rlimits.mask & mask) { | |
+ rlim = current->signal->rlim + j; | |
+ initrlim = init_task.signal->rlim + j; | |
+ rlim->rlim_cur = min(rlim->rlim_max, | |
+ initrlim->rlim_cur); | |
+ } | |
} | |
} | |
} | |
/* set any new hard limits as dictated by the new profile */ | |
- if (!new->rlimits.mask) | |
- return; | |
- for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { | |
- if (!(new->rlimits.mask & mask)) | |
+ label_for_each_confined(i, new_l, new) { | |
+ int j; | |
+ if (!new->rlimits.mask) | |
continue; | |
- | |
- rlim = current->signal->rlim + i; | |
- rlim->rlim_max = min(rlim->rlim_max, | |
- new->rlimits.limits[i].rlim_max); | |
- /* soft limit should not exceed hard limit */ | |
- rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); | |
+ for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { | |
+ if (!(new->rlimits.mask & mask)) | |
+ continue; | |
+ | |
+ rlim = current->signal->rlim + j; | |
+ rlim->rlim_max = min(rlim->rlim_max, | |
+ new->rlimits.limits[j].rlim_max); | |
+ /* soft limit should not exceed hard limit */ | |
+ rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); | |
+ } | |
} | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From a6051f5cff3d6feef7e02c4f051ddf2e1da1d021 Mon Sep 17 00:00:00 2001 | |
From: Tyler Hicks <tyhicks@canonical.com> | |
Date: Wed, 16 Mar 2016 19:19:10 -0500 | |
Subject: [PATCH 10/76] UBUNTU: SAUCE: add a sysctl to enable unprivileged user | |
ns AppArmor policy loading | |
BugLink: http://bugs.launchpad.net/bugs/1379535 | |
Disabled by default until the AppArmor kernel code is deemed safe enough | |
to handle untrusted policy. Only developers of container technologies | |
should turn this on until that time. | |
If this sysctl is set to non-zero and a process with CAP_MAC_ADMIN in | |
the root namespace has created an AppArmor policy namespace, | |
unprivileged processes will be able to change to a profile in the | |
newly created AppArmor policy namespace and, if the profile allows | |
CAP_MAC_ADMIN and appropriate file permissions, will be able to load | |
policy in the respective policy namespace. | |
Signed-off-by: Tyler Hicks <tyhicks@canonical.com> | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/include/policy.h | 2 ++ | |
security/apparmor/lsm.c | 48 ++++++++++++++++++++++++++++++++++++++ | |
security/apparmor/policy.c | 4 +++- | |
3 files changed, 53 insertions(+), 1 deletion(-) | |
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h | |
index 10b71c0..c01bacb 100644 | |
--- a/security/apparmor/include/policy.h | |
+++ b/security/apparmor/include/policy.h | |
@@ -34,6 +34,8 @@ | |
struct aa_ns; | |
+extern int unprivileged_userns_apparmor_policy; | |
+ | |
extern const char *const aa_profile_mode_names[]; | |
#define APPARMOR_MODE_NAMES_MAX_INDEX 4 | |
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c | |
index dd13b1a..83f53fe 100644 | |
--- a/security/apparmor/lsm.c | |
+++ b/security/apparmor/lsm.c | |
@@ -23,6 +23,7 @@ | |
#include <linux/sysctl.h> | |
#include <linux/audit.h> | |
#include <linux/user_namespace.h> | |
+#include <linux/kmemleak.h> | |
#include <net/sock.h> | |
#include "include/af_unix.h" | |
@@ -1508,6 +1509,46 @@ static int __init alloc_buffers(void) | |
return 0; | |
} | |
+#ifdef CONFIG_SYSCTL | |
+static int apparmor_dointvec(struct ctl_table *table, int write, | |
+ void __user *buffer, size_t *lenp, loff_t *ppos) | |
+{ | |
+ if (!policy_admin_capable()) | |
+ return -EPERM; | |
+ if (!apparmor_enabled) | |
+ return -EINVAL; | |
+ | |
+ return proc_dointvec(table, write, buffer, lenp, ppos); | |
+} | |
+ | |
+static struct ctl_path apparmor_sysctl_path[] = { | |
+ { .procname = "kernel", }, | |
+ { } | |
+}; | |
+ | |
+static struct ctl_table apparmor_sysctl_table[] = { | |
+ { | |
+ .procname = "unprivileged_userns_apparmor_policy", | |
+ .data = &unprivileged_userns_apparmor_policy, | |
+ .maxlen = sizeof(int), | |
+ .mode = 0600, | |
+ .proc_handler = apparmor_dointvec, | |
+ }, | |
+ { } | |
+}; | |
+ | |
+static int __init apparmor_init_sysctl(void) | |
+{ | |
+ return register_sysctl_paths(apparmor_sysctl_path, | |
+ apparmor_sysctl_table) ? 0 : -ENOMEM; | |
+} | |
+#else | |
+static inline int apparmor_init_sysctl(void) | |
+{ | |
+ return 0; | |
+} | |
+#endif /* CONFIG_SYSCTL */ | |
+ | |
static int __init apparmor_init(void) | |
{ | |
int error; | |
@@ -1530,6 +1571,13 @@ static int __init apparmor_init(void) | |
goto alloc_out; | |
} | |
+ error = apparmor_init_sysctl(); | |
+ if (error) { | |
+ AA_ERROR("Unable to register sysctls\n"); | |
+ goto alloc_out; | |
+ | |
+ } | |
+ | |
error = alloc_buffers(); | |
if (error) { | |
AA_ERROR("Unable to allocate work buffers\n"); | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index 1c9d4c7..3f2c61e 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -90,6 +90,7 @@ | |
#include "include/policy_unpack.h" | |
#include "include/resource.h" | |
+int unprivileged_userns_apparmor_policy = 0; | |
/* Note: mode names must be unique in the first character because of | |
* modechrs used to print modes on compound labels on some interfaces | |
@@ -624,7 +625,8 @@ bool policy_admin_capable(void) | |
if (ns_capable(user_ns, CAP_MAC_ADMIN) && | |
(user_ns == &init_user_ns || | |
- (user_ns->level == 1 && ns != root_ns))) | |
+ (unprivileged_userns_apparmor_policy != 0 && | |
+ user_ns->level == 1 && ns != root_ns))) | |
response = true; | |
aa_put_ns(ns); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From ac53f7e5194126655005b6c024a75c941d22f65f Mon Sep 17 00:00:00 2001 | |
From: Tyler Hicks <tyhicks@canonical.com> | |
Date: Wed, 23 Mar 2016 16:26:20 -0500 | |
Subject: [PATCH 11/76] UBUNTU: SAUCE: apparmor: Allow ns_root processes to | |
open profiles file | |
BugLink: https://launchpad.net/bugs/1560583 | |
Change the apparmorfs profiles file permissions check to better match | |
the old requirements before the apparmorfs permissions were changed to | |
allow profile loads inside of confined, first-level user namespaces. | |
Historically, the profiles file has been readable by the root user and | |
group. A recent change added the requirement that the process have the | |
CAP_MAC_ADMIN capability. This is a problem for confined processes since | |
keeping the 'capability mac_admin,' rule out of the AppArmor profile is | |
often desired. | |
This patch replaces the CAP_MAC_ADMIN requirement with a requirement | |
that the process is root in its user namespace. | |
Signed-off-by: Tyler Hicks <tyhicks@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 2 +- | |
security/apparmor/include/policy.h | 1 + | |
security/apparmor/policy.c | 17 +++++++++++++++++ | |
3 files changed, 19 insertions(+), 1 deletion(-) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index 9c2b4e2..dc77f6d 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -992,7 +992,7 @@ static int seq_show_profile(struct seq_file *f, void *p) | |
static int profiles_open(struct inode *inode, struct file *file) | |
{ | |
- if (!policy_admin_capable()) | |
+ if (!aa_may_open_profiles()) | |
return -EACCES; | |
return seq_open(file, &aa_fs_profiles_op); | |
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h | |
index c01bacb..af2685f 100644 | |
--- a/security/apparmor/include/policy.h | |
+++ b/security/apparmor/include/policy.h | |
@@ -282,6 +282,7 @@ static inline int AUDIT_MODE(struct aa_profile *profile) | |
bool policy_view_capable(void); | |
bool policy_admin_capable(void); | |
+bool aa_may_open_profiles(void); | |
int aa_may_manage_policy(struct aa_label *label, u32 mask); | |
#endif /* __AA_POLICY_H */ | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index 3f2c61e..bab3d0c 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -625,6 +625,23 @@ bool policy_admin_capable(void) | |
if (ns_capable(user_ns, CAP_MAC_ADMIN) && | |
(user_ns == &init_user_ns || | |
+ (user_ns->level == 1 && ns != root_ns))) | |
+ response = true; | |
+ aa_put_ns(ns); | |
+ | |
+ return response; | |
+} | |
+ | |
+bool aa_may_open_profiles(void) | |
+{ | |
+ struct user_namespace *user_ns = current_user_ns(); | |
+ struct aa_ns *ns = aa_get_current_ns(); | |
+ bool root_in_user_ns = uid_eq(current_euid(), make_kuid(user_ns, 0)) || | |
+ in_egroup_p(make_kgid(user_ns, 0)); | |
+ bool response = false; | |
+ | |
+ if (root_in_user_ns && | |
+ (user_ns == &init_user_ns || | |
(unprivileged_userns_apparmor_policy != 0 && | |
user_ns->level == 1 && ns != root_ns))) | |
response = true; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 137f5a42c06f7533d31200bcff267697ebbff051 Mon Sep 17 00:00:00 2001 | |
From: Tyler Hicks <tyhicks@canonical.com> | |
Date: Wed, 23 Mar 2016 16:41:33 -0500 | |
Subject: [PATCH 12/76] UBUNTU: SAUCE: apparmor: Consult sysctl when reading | |
profiles in a user ns | |
BugLink: https://launchpad.net/bugs/1560583 | |
Check the value of the unprivileged_userns_apparmor_policy sysctl when a | |
namespace root process attempts to read the apparmorfs profiles file. | |
Signed-off-by: Tyler Hicks <tyhicks@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/policy.c | 3 ++- | |
1 file changed, 2 insertions(+), 1 deletion(-) | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index bab3d0c..57135d5 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -625,7 +625,8 @@ bool policy_admin_capable(void) | |
if (ns_capable(user_ns, CAP_MAC_ADMIN) && | |
(user_ns == &init_user_ns || | |
- (user_ns->level == 1 && ns != root_ns))) | |
+ (unprivileged_userns_apparmor_policy != 0 && | |
+ user_ns->level == 1 && ns != root_ns))) | |
response = true; | |
aa_put_ns(ns); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 47264635c1bd36ab56d1067a258768c34e217c47 Mon Sep 17 00:00:00 2001 | |
From: Tyler Hicks <tyhicks@canonical.com> | |
Date: Tue, 5 Apr 2016 12:35:23 -0500 | |
Subject: [PATCH 13/76] UBUNTU: SAUCE: apparmor: Fix FTBFS due to bad include | |
path | |
When header files in security/apparmor/includes/ pull in other header | |
files in that directory, they should only include the file name. This | |
fixes a build failure reported by Tycho when using `make bindeb-pkg` to | |
build the Ubuntu kernel tree but, confusingly, isn't seen when building | |
with `fakeroot debian/rules binary-generic`. | |
Signed-off-by: Tyler Hicks <tyhicks@canonical.com> | |
Reported-by: Tycho Andersen <tycho.andersen@canonical.com> | |
Cc: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/include/domain.h | 2 +- | |
1 file changed, 1 insertion(+), 1 deletion(-) | |
diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h | |
index 89cfa75..b589655 100644 | |
--- a/security/apparmor/include/domain.h | |
+++ b/security/apparmor/include/domain.h | |
@@ -15,7 +15,7 @@ | |
#include <linux/binfmts.h> | |
#include <linux/types.h> | |
-#include "include/label.h" | |
+#include "label.h" | |
#ifndef __AA_DOMAIN_H | |
#define __AA_DOMAIN_H | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From f1c9090bb925d41c7f2dac1afeae0ff1cab9f8d6 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Fri, 16 Sep 2016 12:39:08 -0700 | |
Subject: [PATCH 14/76] fixup backout policy view capable for forward port | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/include/policy.h | 1 - | |
security/apparmor/lsm.c | 2 ++ | |
2 files changed, 2 insertions(+), 1 deletion(-) | |
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h | |
index af2685f..5e563d7 100644 | |
--- a/security/apparmor/include/policy.h | |
+++ b/security/apparmor/include/policy.h | |
@@ -280,7 +280,6 @@ static inline int AUDIT_MODE(struct aa_profile *profile) | |
return profile->audit; | |
} | |
-bool policy_view_capable(void); | |
bool policy_admin_capable(void); | |
bool aa_may_open_profiles(void); | |
int aa_may_manage_policy(struct aa_label *label, u32 mask); | |
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c | |
index 83f53fe..01d8653 100644 | |
--- a/security/apparmor/lsm.c | |
+++ b/security/apparmor/lsm.c | |
@@ -1336,6 +1336,8 @@ static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp | |
{ | |
if (!policy_admin_capable()) | |
return -EPERM; | |
+ if (aa_g_lock_policy) | |
+ return -EACCES; | |
return param_set_bool(val, kp); | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 32c9b18facd76223171bd5d6d1a91e5606515a71 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Mon, 23 May 2016 11:52:18 -0700 | |
Subject: [PATCH 15/76] UBUNTU: SAUCE: apparmor: Fix __label_update proxy | |
comparison test | |
The comparing the proxy pointer, not the address of the labels proxy pointer. | |
BugLink: http://bugs.launchpad.net/bugs/1615878 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/label.c | 2 +- | |
1 file changed, 1 insertion(+), 1 deletion(-) | |
diff --git a/security/apparmor/label.c b/security/apparmor/label.c | |
index 21c9d6f..c453fc8 100644 | |
--- a/security/apparmor/label.c | |
+++ b/security/apparmor/label.c | |
@@ -1993,7 +1993,7 @@ static struct aa_label *__label_update(struct aa_label *label) | |
write_lock_irqsave(&ls->lock, flags); | |
label_for_each(i, label, p) { | |
new->vec[i.i] = aa_get_newest_profile(p); | |
- if (&new->vec[i.i]->label.proxy != &p->label.proxy) | |
+ if (new->vec[i.i]->label.proxy != p->label.proxy) | |
invcount++; | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 7a93d4542607e44ff7338d8c0a6b6172ac9b3744 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Thu, 21 Jul 2016 11:12:38 -0700 | |
Subject: [PATCH 16/76] UBUNTU: SAUCE: apparmor: fix stack trace when removing | |
namespace with profiles | |
BugLink: http://bugs.launchpad.net/bugs/1593874 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/policy_ns.c | 6 +++++- | |
1 file changed, 5 insertions(+), 1 deletion(-) | |
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c | |
index d06e664..19adb24 100644 | |
--- a/security/apparmor/policy_ns.c | |
+++ b/security/apparmor/policy_ns.c | |
@@ -259,9 +259,13 @@ static void destroy_ns(struct aa_ns *ns) | |
/* release all sub namespaces */ | |
__ns_list_release(&ns->sub_ns); | |
- if (ns->parent) | |
+ if (ns->parent) { | |
+ unsigned long flags; | |
+ write_lock_irqsave(&ns->labels.lock, flags); | |
__aa_proxy_redirect(ns_unconfined(ns), | |
ns_unconfined(ns->parent)); | |
+ write_unlock_irqrestore(&ns->labels.lock, flags); | |
+ } | |
__aa_fs_ns_rmdir(ns); | |
mutex_unlock(&ns->lock); | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 60326df56524fafb20d84081a3cad15bf35214e6 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Mon, 23 May 2016 12:01:26 -0700 | |
Subject: [PATCH 17/76] UBUNTU: SAUCE: apparmor: Fix new to old label | |
comparison for domain transitions | |
For the purposes of inherit we should be treating a profile/label transition | |
to its replacement as if the replacement is the profile/label. | |
So make the comparison based off of the label proxy, not the label itself. | |
BugLink: http://bugs.launchpad.net/bugs/1615880 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/domain.c | 4 ++-- | |
1 file changed, 2 insertions(+), 2 deletions(-) | |
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c | |
index 765c9c8..814f8cd 100644 | |
--- a/security/apparmor/domain.c | |
+++ b/security/apparmor/domain.c | |
@@ -517,7 +517,7 @@ static struct aa_label *profile_transition(struct aa_profile *profile, | |
if (perms.allow & MAY_EXEC) { | |
/* exec permission determine how to transition */ | |
new = x_to_label(profile, name, perms.xindex, &target, &info); | |
- if (new == &profile->label && info) { | |
+ if (new && new->proxy == profile->label.proxy && info) { | |
/* hack ix fallback - improve how this is detected */ | |
goto audit; | |
} else if (!new) { | |
@@ -759,7 +759,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) | |
bprm->unsafe |= AA_SECURE_X_NEEDED; | |
} | |
- if (label != new) { | |
+ if (label->proxy != new->proxy) { | |
/* when transitioning clear unsafe personality bits */ | |
if (DEBUG_ON) { | |
dbg_printk("apparmor: clearing unsafe personality " | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 8c4a3e1fa5e0f7cb754fcc766fef841d70a56797 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Mon, 23 May 2016 12:04:57 -0700 | |
Subject: [PATCH 18/76] UBUNTU: SAUCE: apparmor: Fix label build for onexec | |
stacking. | |
The label build for onexec when crossing a namespace boundry is not | |
quite correct. The label needs to be built per profile and not based | |
on the whole label because the onexec transition only applies to | |
profiles within the ns. Where merging against the label could include | |
profile that are transitioned via the profile_transition callback | |
and should not be in the final label. | |
BugLink: http://bugs.launchpad.net/bugs/1615881 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/domain.c | 3 ++- | |
1 file changed, 2 insertions(+), 1 deletion(-) | |
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c | |
index 814f8cd..35ed7c2 100644 | |
--- a/security/apparmor/domain.c | |
+++ b/security/apparmor/domain.c | |
@@ -644,7 +644,8 @@ static struct aa_label *handle_onexec(struct aa_label *label, | |
if (error) | |
return ERR_PTR(error); | |
new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, | |
- aa_label_merge(label, onexec, | |
+ aa_label_merge(&profile->label, | |
+ onexec, | |
GFP_ATOMIC), | |
profile_transition(profile, xname, | |
cond, unsafe)); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 01d87cf6b6422e64f66fd388c34b4a552d718a44 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Fri, 19 Aug 2016 03:20:32 -0700 | |
Subject: [PATCH 19/76] UBUNTU: SAUCE: apparmor: profiles in one ns can affect | |
mediation in another ns | |
When the ns hierarchy a//foo and b//foo are compared the are | |
incorrectly identified as being the same as they have the same depth | |
and the same basename. | |
Instead make sure to compare the full hname to distinguish this case. | |
BugLink: http://bugs.launchpad.net/bugs/1615887 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/label.c | 6 +++--- | |
1 file changed, 3 insertions(+), 3 deletions(-) | |
diff --git a/security/apparmor/label.c b/security/apparmor/label.c | |
index c453fc8..0a1dabd 100644 | |
--- a/security/apparmor/label.c | |
+++ b/security/apparmor/label.c | |
@@ -112,8 +112,8 @@ static int ns_cmp(struct aa_ns *a, struct aa_ns *b) | |
AA_BUG(!a); | |
AA_BUG(!b); | |
- AA_BUG(!a->base.name); | |
- AA_BUG(!b->base.name); | |
+ AA_BUG(!a->base.hname); | |
+ AA_BUG(!b->base.hname); | |
if (a == b) | |
return 0; | |
@@ -122,7 +122,7 @@ static int ns_cmp(struct aa_ns *a, struct aa_ns *b) | |
if (res) | |
return res; | |
- return strcmp(a->base.name, b->base.name); | |
+ return strcmp(a->base.hname, b->base.hname); | |
} | |
/** | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 9f8fdb437bcbf499159e5cd954397cb344511d90 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Mon, 22 Aug 2016 00:47:01 -0700 | |
Subject: [PATCH 20/76] UBUNTU: SAUCE: apparmor: reduction of vec to single | |
entry is just that entry | |
If the result of a merge/update/parse is a vec with a single entry | |
we should not be returning a reference label, but just the label | |
it self. | |
BugLink: http://bugs.launchpad.net/bugs/1615889 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/label.c | 17 +++++++++++++++++ | |
1 file changed, 17 insertions(+) | |
diff --git a/security/apparmor/label.c b/security/apparmor/label.c | |
index 0a1dabd..144d759 100644 | |
--- a/security/apparmor/label.c | |
+++ b/security/apparmor/label.c | |
@@ -1077,6 +1077,12 @@ static struct aa_label *label_merge_insert(struct aa_label *new, | |
if (invcount) { | |
new->size -= aa_vec_unique(&new->vec[0], new->size, | |
VEC_FLAG_TERMINATE); | |
+ /* TODO: deal with reference labels */ | |
+ if (new->size == 1) { | |
+ label = aa_get_label(&new->vec[0]->label); | |
+ aa_put_label(new); | |
+ return label; | |
+ } | |
} else if (!stale) { | |
/* merge could be same as a || b, note: it is not possible | |
* for new->size == a->size == b->size unless a == b */ | |
@@ -1876,6 +1882,11 @@ struct aa_label *aa_label_parse(struct aa_label *base, const char *str, | |
return &vec[0]->label; | |
len -= aa_vec_unique(vec, len, VEC_FLAG_TERMINATE); | |
+ /* TODO: deal with reference labels */ | |
+ if (len == 1) { | |
+ label = aa_get_label(&vec[0]->label); | |
+ goto out; | |
+ } | |
if (create) | |
label = aa_vec_find_or_create_label(vec, len, gfp); | |
@@ -2001,6 +2012,12 @@ static struct aa_label *__label_update(struct aa_label *label) | |
if (invcount) { | |
new->size -= aa_vec_unique(&new->vec[0], new->size, | |
VEC_FLAG_TERMINATE); | |
+ /* TODO: deal with reference labels */ | |
+ if (new->size == 1) { | |
+ tmp = aa_get_label(&new->vec[0]->label); | |
+ AA_BUG(tmp == label); | |
+ goto remove; | |
+ } | |
if (labels_set(label) != labels_set(new)) { | |
write_unlock_irqrestore(&ls->lock, flags); | |
tmp = aa_label_insert(labels_set(new), new); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 803d358317cc3d1ccf0c80023aece57b577630ba Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Mon, 22 Aug 2016 14:14:48 -0700 | |
Subject: [PATCH 21/76] UBUNTU: SAUCE: apparmor: fix vec_unique for vectors | |
larger than 8 | |
the vec_unique path for large vectors is broken, leading to oopses | |
when a file handle is shared between 8 different security domains, and | |
then a profile replacement/removal causing a label invalidation (ie. not | |
all replacements) is done. | |
BugLink: http://bugs.launchpad.net/bugs/1579135 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/label.c | 2 +- | |
1 file changed, 1 insertion(+), 1 deletion(-) | |
diff --git a/security/apparmor/label.c b/security/apparmor/label.c | |
index 144d759..c11ca99 100644 | |
--- a/security/apparmor/label.c | |
+++ b/security/apparmor/label.c | |
@@ -229,7 +229,7 @@ static inline int unique(struct aa_profile **vec, int n) | |
AA_BUG(!vec); | |
pos = 0; | |
- for (i = 1; 1 < n; i++) { | |
+ for (i = 1; i < n; i++) { | |
int res = profile_cmp(vec[pos], vec[i]); | |
AA_BUG(res > 0, "vec not sorted"); | |
if (res == 0) { | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 93422b9ad4430a0207c12e61f627251e32a4a134 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Wed, 22 Jun 2016 18:01:08 -0700 | |
Subject: [PATCH 22/76] UBUNTU: SAUCE: apparmor: fix: parameters can be changed | |
after policy is locked | |
the policy_lock parameter is a one way switch that prevents policy | |
from being further modified. Unfortunately some of the module parameters | |
can effectively modify policy by turning off enforcement. | |
split policy_admin_capable into a view check and a full admin check, | |
and update the admin check to test the policy_lock parameter. | |
BugLink: http://bugs.launchpad.net/bugs/1615895 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/include/policy.h | 1 + | |
security/apparmor/lsm.c | 12 +++++------- | |
security/apparmor/policy.c | 7 ++++++- | |
3 files changed, 12 insertions(+), 8 deletions(-) | |
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h | |
index 5e563d7..af2685f 100644 | |
--- a/security/apparmor/include/policy.h | |
+++ b/security/apparmor/include/policy.h | |
@@ -280,6 +280,7 @@ static inline int AUDIT_MODE(struct aa_profile *profile) | |
return profile->audit; | |
} | |
+bool policy_view_capable(void); | |
bool policy_admin_capable(void); | |
bool aa_may_open_profiles(void); | |
int aa_may_manage_policy(struct aa_label *label, u32 mask); | |
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c | |
index 01d8653..88db25c 100644 | |
--- a/security/apparmor/lsm.c | |
+++ b/security/apparmor/lsm.c | |
@@ -1336,14 +1336,12 @@ static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp | |
{ | |
if (!policy_admin_capable()) | |
return -EPERM; | |
- if (aa_g_lock_policy) | |
- return -EACCES; | |
return param_set_bool(val, kp); | |
} | |
static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp) | |
{ | |
- if (!policy_admin_capable()) | |
+ if (!policy_view_capable()) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1361,7 +1359,7 @@ static int param_set_aabool(const char *val, const struct kernel_param *kp) | |
static int param_get_aabool(char *buffer, const struct kernel_param *kp) | |
{ | |
- if (!policy_admin_capable()) | |
+ if (!policy_view_capable()) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1379,7 +1377,7 @@ static int param_set_aauint(const char *val, const struct kernel_param *kp) | |
static int param_get_aauint(char *buffer, const struct kernel_param *kp) | |
{ | |
- if (!policy_admin_capable()) | |
+ if (!policy_view_capable()) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1388,7 +1386,7 @@ static int param_get_aauint(char *buffer, const struct kernel_param *kp) | |
static int param_get_audit(char *buffer, struct kernel_param *kp) | |
{ | |
- if (!policy_admin_capable()) | |
+ if (!policy_view_capable()) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1417,7 +1415,7 @@ static int param_set_audit(const char *val, struct kernel_param *kp) | |
static int param_get_mode(char *buffer, struct kernel_param *kp) | |
{ | |
- if (!policy_admin_capable()) | |
+ if (!policy_view_capable()) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index 57135d5..52abc2f 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -617,7 +617,7 @@ static int audit_policy(struct aa_label *label, const char *op, | |
return error; | |
} | |
-bool policy_admin_capable(void) | |
+bool policy_view_capable(void) | |
{ | |
struct user_namespace *user_ns = current_user_ns(); | |
struct aa_ns *ns = aa_get_current_ns(); | |
@@ -633,6 +633,11 @@ bool policy_admin_capable(void) | |
return response; | |
} | |
+bool policy_admin_capable(void) | |
+{ | |
+ return policy_view_capable() && !aa_g_lock_policy; | |
+} | |
+ | |
bool aa_may_open_profiles(void) | |
{ | |
struct user_namespace *user_ns = current_user_ns(); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From b42f83b6a5311bfa7753372c8288eda5274733a5 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Fri, 8 Jul 2016 09:58:05 -0700 | |
Subject: [PATCH 23/76] UBUNTU: SAUCE: apparmor: special case unconfined when | |
determining the mode | |
when viewing a stack involving unconfined from across a ns boundary | |
the mode is reported as mixed. | |
Eg. | |
lxc-container-default//&:lxdns1://unconfined (mixed) | |
This is because the unconfined profile is in the special unconfined | |
mode. Which will result in a (mixed) mode for any stack with profiles | |
in enforcing or complain mode. | |
This can however lead to confusion as to what mode is being used as | |
mixed is also used for enforcing stacked with complain. Since unconfined | |
doesn't affect the stack just special case it. | |
BugLink: http://bugs.launchpad.net/bugs/1615890 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/label.c | 22 ++++++++++++++-------- | |
1 file changed, 14 insertions(+), 8 deletions(-) | |
diff --git a/security/apparmor/label.c b/security/apparmor/label.c | |
index c11ca99..ce150a8 100644 | |
--- a/security/apparmor/label.c | |
+++ b/security/apparmor/label.c | |
@@ -1535,25 +1535,31 @@ static const char *label_modename(struct aa_ns *ns, struct aa_label *label, | |
{ | |
struct aa_profile *profile; | |
struct label_it i; | |
- const char *modestr = NULL; | |
- int count = 0; | |
+ int mode = -1, count = 0; | |
label_for_each(i, label, profile) { | |
if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) { | |
- const char *tmp_modestr; | |
+ if (profile->mode == APPARMOR_UNCONFINED) | |
+ /* special case unconfined so stacks with | |
+ * unconfined don't report as mixed. ie. | |
+ * profile_foo//&:ns1://unconfined (mixed) | |
+ */ | |
+ continue; | |
count++; | |
- tmp_modestr = aa_profile_mode_names[profile->mode]; | |
- if (!modestr) | |
- modestr = tmp_modestr; | |
- else if (modestr != tmp_modestr) | |
+ if (mode == -1) | |
+ mode = profile->mode; | |
+ else if (mode != profile->mode) | |
return "mixed"; | |
} | |
} | |
if (count == 0) | |
return "-"; | |
+ if (mode == -1) | |
+ /* everything was unconfined */ | |
+ mode = APPARMOR_UNCONFINED; | |
- return modestr; | |
+ return aa_profile_mode_names[mode]; | |
} | |
/* if any visible label is not unconfined the display_mode returns true */ | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 55f5e3c5761f7f7b67c82e14da4d62c12aff5e42 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Sun, 10 Jul 2016 23:12:38 -0700 | |
Subject: [PATCH 24/76] UBUNTU: SAUCE: apparmor: deleted dentries can be | |
disconnected | |
BugLink: http://bugs.launchpad.net/bugs/1615892 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/path.c | 6 +++--- | |
1 file changed, 3 insertions(+), 3 deletions(-) | |
diff --git a/security/apparmor/path.c b/security/apparmor/path.c | |
index 9e95f8b..3511a0f 100644 | |
--- a/security/apparmor/path.c | |
+++ b/security/apparmor/path.c | |
@@ -152,6 +152,9 @@ static int d_namespace_path(const struct path *path, char *buf, char **name, | |
*name = res; | |
+ if (!connected) | |
+ error = disconnect(path, buf, name, flags, disconnected); | |
+ | |
/* Handle two cases: | |
* 1. A deleted dentry && profile is not allowing mediation of deleted | |
* 2. On some filesystems, newly allocated dentries appear to the | |
@@ -164,9 +167,6 @@ static int d_namespace_path(const struct path *path, char *buf, char **name, | |
goto out; | |
} | |
- if (!connected) | |
- error = disconnect(path, buf, name, flags, disconnected); | |
- | |
out: | |
/* | |
* Append "/" to the pathname. The root directory is a special | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 5274e5f7952c2217d67d87e1ea4b28476403930f Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Thu, 4 Aug 2016 04:35:21 -0700 | |
Subject: [PATCH 25/76] UBUNTU: SAUCE: apparmor: Fix auditing behavior for | |
change_hat probing | |
change_hat using probing to find and transition to the first available | |
hat. Hats missing as part of this probe are expected and should not | |
be logged except in complain mode. | |
BugLink: http://bugs.launchpad.net/bugs/1615893 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/domain.c | 18 +++++++++++++----- | |
1 file changed, 13 insertions(+), 5 deletions(-) | |
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c | |
index 35ed7c2..a701534 100644 | |
--- a/security/apparmor/domain.c | |
+++ b/security/apparmor/domain.c | |
@@ -931,12 +931,20 @@ static struct aa_label *change_hat(struct aa_label *label, const char *hats[], | |
error = -ECHILD; | |
fail: | |
- fn_for_each_in_ns(label, profile, | |
- /* no target as it has failed to be found or built */ | |
+ label_for_each_in_ns(it, labels_ns(label), label, profile) { | |
+ /* | |
+ * no target as it has failed to be found or built | |
+ * | |
+ * change_hat uses probing and should not log failures | |
+ * related to missing hats | |
+ */ | |
/* TODO: get rid of GLOBAL_ROOT_UID */ | |
- aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, | |
- AA_MAY_CHANGEHAT, name, NULL, NULL, | |
- GLOBAL_ROOT_UID, info, error)); | |
+ if (count > 1 || COMPLAIN_MODE(profile)) { | |
+ aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, | |
+ AA_MAY_CHANGEHAT, name, NULL, NULL, | |
+ GLOBAL_ROOT_UID, info, error); | |
+ } | |
+ } | |
return (ERR_PTR(error)); | |
build: | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 715aec7ec4f5ab7b51baf40f836b48f1446db064 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Mon, 23 May 2016 02:31:04 -0700 | |
Subject: [PATCH 26/76] apparmor: fix: Rework the iter loop for label_update | |
ensure that label_update works with unterminated vectors | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/label.c | 10 ++++------ | |
1 file changed, 4 insertions(+), 6 deletions(-) | |
diff --git a/security/apparmor/label.c b/security/apparmor/label.c | |
index ce150a8..6c76628 100644 | |
--- a/security/apparmor/label.c | |
+++ b/security/apparmor/label.c | |
@@ -1991,10 +1991,8 @@ static struct aa_label *__label_update(struct aa_label *label) | |
{ | |
struct aa_label *new, *tmp; | |
struct aa_labelset *ls; | |
- struct aa_profile *p; | |
- struct label_it i; | |
unsigned long flags; | |
- int invcount = 0; | |
+ int i, invcount = 0; | |
AA_BUG(!label); | |
AA_BUG(!mutex_is_locked(&labels_ns(label)->lock)); | |
@@ -2008,9 +2006,9 @@ static struct aa_label *__label_update(struct aa_label *label) | |
*/ | |
ls = labels_set(label); | |
write_lock_irqsave(&ls->lock, flags); | |
- label_for_each(i, label, p) { | |
- new->vec[i.i] = aa_get_newest_profile(p); | |
- if (new->vec[i.i]->label.proxy != p->label.proxy) | |
+ for (i = 0; i < label->size; i++) { | |
+ new->vec[i] = aa_get_newest_profile(label->vec[i]); | |
+ if (new->vec[i]->label.proxy != label->vec[i]->label.proxy) | |
invcount++; | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 73db42dd36aac31332745a966da480dc8000b4eb Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Thu, 18 Aug 2016 16:42:34 -0700 | |
Subject: [PATCH 27/76] apparmor: add more assertions for updates/merges to | |
help catch errors | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/label.c | 7 +++++++ | |
1 file changed, 7 insertions(+) | |
diff --git a/security/apparmor/label.c b/security/apparmor/label.c | |
index 6c76628..738fe52 100644 | |
--- a/security/apparmor/label.c | |
+++ b/security/apparmor/label.c | |
@@ -1061,8 +1061,11 @@ static struct aa_label *label_merge_insert(struct aa_label *new, | |
AA_BUG(new->size < a->size + b->size); | |
label_for_each_in_merge(i, a, b, next) { | |
+ AA_BUG(!next); | |
if (profile_is_stale(next)) { | |
new->vec[k] = aa_get_newest_profile(next); | |
+ AA_BUG(!new->vec[k]->label.proxy); | |
+ AA_BUG(!new->vec[k]->label.proxy->label); | |
if (next->label.proxy != new->vec[k]->label.proxy) | |
invcount++; | |
k++; | |
@@ -2007,7 +2010,11 @@ static struct aa_label *__label_update(struct aa_label *label) | |
ls = labels_set(label); | |
write_lock_irqsave(&ls->lock, flags); | |
for (i = 0; i < label->size; i++) { | |
+ AA_BUG(!label->vec[i]); | |
new->vec[i] = aa_get_newest_profile(label->vec[i]); | |
+ AA_BUG(!new->vec[i]); | |
+ AA_BUG(!new->vec[i]->label.proxy); | |
+ AA_BUG(!new->vec[i]->label.proxy->label); | |
if (new->vec[i]->label.proxy != label->vec[i]->label.proxy) | |
invcount++; | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 1dc25941d0c23f1822ee0be9cc4e60bc01c0de49 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Thu, 4 Aug 2016 02:46:09 -0700 | |
Subject: [PATCH 28/76] apparmor: Make pivot root transitions work with | |
stacking | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/mount.c | 52 +++++++++++++++++++++++++++++++---------------- | |
1 file changed, 34 insertions(+), 18 deletions(-) | |
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c | |
index 82430d1..d10a956 100644 | |
--- a/security/apparmor/mount.c | |
+++ b/security/apparmor/mount.c | |
@@ -638,8 +638,13 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags) | |
return error; | |
} | |
-static int profile_pivotroot(struct aa_profile *profile, const char *new_name, | |
- const char *old_name, struct aa_label **trans) | |
+/* helper fn for transition on pivotroot | |
+ * | |
+ * Returns: label for transition or ERR_PTR. Does not return NULL | |
+ */ | |
+static struct aa_label *build_pivotroot(struct aa_profile *profile, | |
+ const char *new_name, | |
+ const char *old_name) | |
{ | |
struct aa_label *target = NULL; | |
const char *trans_name = NULL; | |
@@ -651,7 +656,6 @@ static int profile_pivotroot(struct aa_profile *profile, const char *new_name, | |
AA_BUG(!profile); | |
AA_BUG(!new_name); | |
AA_BUG(!old_name); | |
- AA_BUG(!trans); | |
/* TODO: actual domain transition computation for multiple | |
* profiles | |
@@ -664,24 +668,25 @@ static int profile_pivotroot(struct aa_profile *profile, const char *new_name, | |
perms = compute_mnt_perms(profile->policy.dfa, state); | |
if (AA_MAY_PIVOTROOT & perms.allow) { | |
+ error = 0; | |
if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { | |
target = x_table_lookup(profile, perms.xindex, | |
&trans_name); | |
if (!target) | |
error = -ENOENT; | |
- else | |
- *trans = target; | |
- } else | |
- error = 0; | |
+ } | |
} | |
error = audit_mount(profile, OP_PIVOTROOT, new_name, old_name, | |
NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT, | |
&perms, info, error); | |
- if (!*trans) | |
+ if (error) { | |
aa_put_label(target); | |
+ return ERR_PTR(error); | |
+ } else if (target) | |
+ return target; | |
- return error; | |
+ return aa_get_label(&profile->label); | |
} | |
int aa_pivotroot(struct aa_label *label, const struct path *old_path, | |
@@ -703,25 +708,36 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path, | |
old_buffer, &old_name, &info, | |
labels_profile(label)->disconnected); | |
if (error) | |
- goto error; | |
+ goto fail; | |
error = aa_path_name(new_path, path_flags(labels_profile(label), | |
new_path), | |
new_buffer, &new_name, &info, | |
labels_profile(label)->disconnected); | |
if (error) | |
- goto error; | |
- error = fn_for_each(label, profile, | |
- profile_pivotroot(profile, new_name, old_name, | |
- &target)); | |
+ goto fail; | |
+ | |
+ target = fn_label_build(label, profile, GFP_ATOMIC, | |
+ build_pivotroot(profile, new_name, old_name)); | |
+ if (!target) { | |
+ info = "label build failed"; | |
+ error = -ENOMEM; | |
+ goto fail; | |
+ } else if (!IS_ERR(target)) { | |
+ error = aa_replace_current_label(target); | |
+ if (error) { | |
+ /* TODO: audit target */ | |
+ aa_put_label(target); | |
+ goto out; | |
+ } | |
+ } else | |
+ /* already audited error */ | |
+ error = PTR_ERR(target); | |
out: | |
put_buffers(old_buffer, new_buffer); | |
- if (target) | |
- error = aa_replace_current_label(target); | |
- | |
return error; | |
-error: | |
+fail: | |
error = fn_for_each(label, profile, | |
audit_mount(profile, OP_PIVOTROOT, new_name, old_name, | |
NULL, NULL, | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 1e5389ebbe0a40b37a1783d5e3eb9f7aecf8d66f Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Tue, 2 Aug 2016 03:49:35 -0700 | |
Subject: [PATCH 29/76] apparmor: convert delegating deleted files to mediate | |
deleted files | |
This is a semantic change that may need to be reverted but we can not | |
properly do delegation atm and doing blind delegation is a security | |
hole. | |
Files that have the necessary labeling can still be delegated however | |
mediation will be required for deleted files that need to be revalidated. | |
Note: we code is setup to specify DELEGATE_DELETED but aliases it on | |
the backend to MEDIATE_DELETED. This will have to be partially reverted/ | |
changed for profile replacement causing a revalidation. | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/file.c | 29 ++++++++--------------------- | |
security/apparmor/path.c | 2 +- | |
2 files changed, 9 insertions(+), 22 deletions(-) | |
diff --git a/security/apparmor/file.c b/security/apparmor/file.c | |
index d2473b6..fb8f2c5 100644 | |
--- a/security/apparmor/file.c | |
+++ b/security/apparmor/file.c | |
@@ -167,17 +167,13 @@ static inline bool is_deleted(struct dentry *dentry) | |
static int path_name(const char *op, struct aa_label *label, | |
const struct path *path, int flags, char *buffer, | |
- const char**name, struct path_cond *cond, u32 request, | |
- bool delegate_deleted) | |
+ const char**name, struct path_cond *cond, u32 request) | |
{ | |
struct aa_profile *profile; | |
const char *info = NULL; | |
int error = aa_path_name(path, flags, buffer, name, &info, | |
labels_profile(label)->disconnected); | |
if (error) { | |
- if (error == -ENOENT && is_deleted(path->dentry) && | |
- delegate_deleted) | |
- return 0; | |
fn_for_each_confined(label, profile, | |
aa_audit_file(profile, &nullperms, op, request, *name, | |
NULL, NULL, cond->uid, info, error)); | |
@@ -326,8 +322,8 @@ int aa_path_perm(const char *op, struct aa_label *label, | |
(S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); | |
get_buffers(buffer); | |
- error = path_name(op, label, path, flags, buffer, &name, cond, | |
- request, true); | |
+ error = path_name(op, label, path, flags | PATH_DELEGATE_DELETED, | |
+ buffer, &name, cond, request); | |
if (!error) | |
error = fn_for_each_confined(label, profile, | |
__aa_path_perm(op, profile, name, request, cond, | |
@@ -461,14 +457,14 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry, | |
get_buffers(buffer, buffer2); | |
error = path_name(OP_LINK, label, &link, | |
labels_profile(label)->path_flags, buffer, | |
- &lname, &cond, AA_MAY_LINK, false); | |
+ &lname, &cond, AA_MAY_LINK); | |
if (error) | |
goto out; | |
/* buffer2 freed below, tname is pointer in buffer2 */ | |
error = path_name(OP_LINK, label, &target, | |
labels_profile(label)->path_flags, buffer2, &tname, | |
- &cond, AA_MAY_LINK, false); | |
+ &cond, AA_MAY_LINK); | |
if (error) | |
goto out; | |
@@ -526,17 +522,9 @@ static int __file_path_perm(const char *op, struct aa_label *label, | |
(S_ISDIR(cond.mode) ? PATH_IS_DIR : 0); | |
get_buffers(buffer); | |
- error = path_name(op, label, &file->f_path, flags, buffer, &name, &cond, | |
- request, true); | |
- if (error) { | |
- if (error == 1) | |
- /* Access to open files that are deleted are | |
- * given a pass (implicit delegation) | |
- */ | |
- /* TODO not needed when full perms cached */ | |
- error = 0; | |
- goto out; | |
- } | |
+ error = path_name(op, label, &file->f_path, | |
+ flags | PATH_DELEGATE_DELETED, buffer, &name, &cond, | |
+ request); | |
/* check every profile in task label not in current cache */ | |
error = fn_for_each_not_in_set(flabel, label, profile, | |
@@ -557,7 +545,6 @@ static int __file_path_perm(const char *op, struct aa_label *label, | |
if (!error) | |
update_file_ctx(file_ctx(file), label, request); | |
-out: | |
put_buffers(buffer); | |
return error; | |
diff --git a/security/apparmor/path.c b/security/apparmor/path.c | |
index 3511a0f..3061334 100644 | |
--- a/security/apparmor/path.c | |
+++ b/security/apparmor/path.c | |
@@ -162,7 +162,7 @@ static int d_namespace_path(const struct path *path, char *buf, char **name, | |
* allocated. | |
*/ | |
if (d_unlinked(path->dentry) && d_is_positive(path->dentry) && | |
- !(flags & PATH_MEDIATE_DELETED)) { | |
+ !(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) { | |
error = -ENOENT; | |
goto out; | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From d8480b6980847ce5112f2a99ca449a0d02d1b7de Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Fri, 18 Mar 2016 08:42:41 -0700 | |
Subject: [PATCH 30/76] apparmor: add missing parens. not a bug fix but highly | |
recommended | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/domain.c | 2 +- | |
1 file changed, 1 insertion(+), 1 deletion(-) | |
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c | |
index a701534..ee4b6ef 100644 | |
--- a/security/apparmor/domain.c | |
+++ b/security/apparmor/domain.c | |
@@ -731,7 +731,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) | |
* subsets are allowed even when no_new_privs is set because this | |
* aways results in a further reduction of permissions. | |
*/ | |
- if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS && | |
+ if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && | |
!unconfined(label) && !aa_label_is_subset(new, label)) { | |
error = -EPERM; | |
info = "no new privs"; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From f49980cda79fda69e6b9435c33d2897e47c5feac Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Tue, 9 Aug 2016 13:47:43 -0700 | |
Subject: [PATCH 31/76] apparmor: add a stack_version file to allow detection | |
of bug fixes | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 1 + | |
1 file changed, 1 insertion(+) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index dc77f6d..38d1650 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -1034,6 +1034,7 @@ static int profiles_release(struct inode *inode, struct file *file) | |
AA_FS_FILE_BOOLEAN("change_onexec", 1), | |
AA_FS_FILE_BOOLEAN("change_profile", 1), | |
AA_FS_FILE_BOOLEAN("stack", 1), | |
+ AA_FS_FILE_STRING("version", "1.1"), | |
{ } | |
}; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 4a56ff91895c07afd97012faacec2d7be73c9ea7 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Tue, 2 Aug 2016 03:10:23 -0700 | |
Subject: [PATCH 32/76] apparmor: push path lookup into mediation loop | |
Due each profile having its own flags that determine name construction | |
we need to do the path lookup based on each profiles flags and namespace. | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/domain.c | 112 ++++++++------ | |
security/apparmor/file.c | 103 +++++++------ | |
security/apparmor/include/file.h | 6 +- | |
security/apparmor/mount.c | 310 +++++++++++++++++---------------------- | |
4 files changed, 259 insertions(+), 272 deletions(-) | |
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c | |
index ee4b6ef..27ffaf5 100644 | |
--- a/security/apparmor/domain.c | |
+++ b/security/apparmor/domain.c | |
@@ -488,27 +488,41 @@ static struct aa_label *x_to_label(struct aa_profile *profile, | |
} | |
static struct aa_label *profile_transition(struct aa_profile *profile, | |
- const char *name, | |
- struct path_cond *cond, | |
+ const struct linux_binprm *bprm, | |
+ char *buffer, struct path_cond *cond, | |
bool *secure_exec) | |
{ | |
struct aa_label *new = NULL; | |
- const char *info = NULL; | |
+ const char *info = NULL, *name = NULL, *target = NULL; | |
unsigned int state = profile->file.start; | |
struct aa_perms perms = {}; | |
- const char *target = NULL; | |
int error = 0; | |
+ AA_BUG(!profile); | |
+ AA_BUG(!bprm); | |
+ AA_BUG(!buffer); | |
+ | |
+ error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, | |
+ &name, &info, profile->disconnected); | |
+ if (error) { | |
+ if (profile_unconfined(profile) || | |
+ (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { | |
+ AA_DEBUG("name lookup ix on error"); | |
+ error = 0; | |
+ new = aa_get_newest_label(&profile->label); | |
+ } | |
+ name = bprm->filename; | |
+ goto audit; | |
+ } | |
+ | |
if (profile_unconfined(profile)) { | |
new = find_attach(profile->ns, &profile->ns->base.profiles, | |
name); | |
if (new) { | |
AA_DEBUG("unconfined attached to new label"); | |
- | |
return new; | |
} | |
AA_DEBUG("unconfined exec no attachment"); | |
- | |
return aa_get_newest_label(&profile->label); | |
} | |
@@ -565,14 +579,20 @@ static struct aa_label *profile_transition(struct aa_profile *profile, | |
} | |
static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, | |
- bool stack, const char *xname, struct path_cond *cond, | |
+ bool stack, const struct linux_binprm *bprm, | |
+ char *buffer, struct path_cond *cond, | |
bool *secure_exec) | |
{ | |
unsigned int state = profile->file.start; | |
struct aa_perms perms = {}; | |
- const char *info = "change_profile onexec"; | |
+ const char *xname = NULL, *info = "change_profile onexec"; | |
int error = -EACCES; | |
+ AA_BUG(!profile); | |
+ AA_BUG(!onexec); | |
+ AA_BUG(!bprm); | |
+ AA_BUG(!buffer); | |
+ | |
if (profile_unconfined(profile)) { | |
/* change_profile on exec already granted */ | |
/* | |
@@ -583,6 +603,18 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, | |
return 0; | |
} | |
+ error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, | |
+ &xname, &info, profile->disconnected); | |
+ if (error) { | |
+ if (profile_unconfined(profile) || | |
+ (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { | |
+ AA_DEBUG("name lookup ix on error"); | |
+ error = 0; | |
+ } | |
+ xname = bprm->filename; | |
+ goto audit; | |
+ } | |
+ | |
/* find exec permissions for name */ | |
state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms); | |
if (!(perms.allow & AA_MAY_ONEXEC)) { | |
@@ -618,46 +650,52 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, | |
static struct aa_label *handle_onexec(struct aa_label *label, | |
struct aa_label *onexec, bool stack, | |
- const char *xname, | |
- struct path_cond *cond, | |
+ const struct linux_binprm *bprm, | |
+ char *buffer, struct path_cond *cond, | |
bool *unsafe) | |
{ | |
struct aa_profile *profile; | |
struct aa_label *new; | |
int error; | |
+ AA_BUG(!label); | |
+ AA_BUG(!onexec); | |
+ AA_BUG(!bprm); | |
+ AA_BUG(!buffer); | |
+ | |
if (!stack) { | |
error = fn_for_each_in_ns(label, profile, | |
profile_onexec(profile, onexec, stack, | |
- xname, cond, unsafe)); | |
+ bprm, buffer, cond, unsafe)); | |
if (error) | |
return ERR_PTR(error); | |
new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, | |
- aa_get_newest_label(onexec), | |
- profile_transition(profile, xname, | |
- cond, unsafe)); | |
+ aa_get_newest_label(onexec), | |
+ profile_transition(profile, bprm, buffer, | |
+ cond, unsafe)); | |
+ | |
} else { | |
/* TODO: determine how much we want to losen this */ | |
error = fn_for_each_in_ns(label, profile, | |
- profile_onexec(profile, onexec, stack, xname, | |
- cond, unsafe)); | |
+ profile_onexec(profile, onexec, stack, bprm, | |
+ buffer, cond, unsafe)); | |
if (error) | |
return ERR_PTR(error); | |
new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, | |
- aa_label_merge(&profile->label, | |
- onexec, | |
- GFP_ATOMIC), | |
- profile_transition(profile, xname, | |
- cond, unsafe)); | |
+ aa_label_merge(&profile->label, onexec, | |
+ GFP_ATOMIC), | |
+ profile_transition(profile, bprm, buffer, | |
+ cond, unsafe)); | |
} | |
if (new) | |
return new; | |
+ /* TODO: get rid of GLOBAL_ROOT_UID */ | |
error = fn_for_each_in_ns(label, profile, | |
aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC, | |
- AA_MAY_ONEXEC, xname, NULL, onexec, | |
- GLOBAL_ROOT_UID, | |
+ AA_MAY_ONEXEC, bprm->filename, NULL, | |
+ onexec, GLOBAL_ROOT_UID, | |
"failed to build target label", -ENOMEM)); | |
return ERR_PTR(error); | |
} | |
@@ -676,7 +714,6 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) | |
struct aa_label *label, *new = NULL; | |
struct aa_profile *profile; | |
char *buffer = NULL; | |
- const char *xname = NULL; | |
const char *info = NULL; | |
int error = 0; | |
bool unsafe = false; | |
@@ -692,28 +729,17 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) | |
AA_BUG(!ctx); | |
label = aa_get_newest_label(ctx->label); | |
- profile = labels_profile(label); | |
- /* buffer freed below, xname is pointer into buffer */ | |
+ /* buffer freed below, name is pointer into buffer */ | |
get_buffers(buffer); | |
- error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, | |
- &xname, &info, profile->disconnected); | |
- if (error) { | |
- if (profile_unconfined(profile) || | |
- (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) | |
- error = 0; | |
- xname = bprm->filename; | |
- goto audit; | |
- } | |
- | |
/* Test for onexec first as onexec override other x transitions. */ | |
if (ctx->onexec) | |
- new = handle_onexec(label, ctx->onexec, ctx->token, xname, | |
- &cond, &unsafe); | |
+ new = handle_onexec(label, ctx->onexec, ctx->token, | |
+ bprm, buffer, &cond, &unsafe); | |
else | |
new = fn_label_build(label, profile, GFP_ATOMIC, | |
- profile_transition(profile, xname, &cond, | |
- &unsafe)); | |
+ profile_transition(profile, bprm, buffer, | |
+ &cond, &unsafe)); | |
AA_BUG(!new); | |
if (IS_ERR(new)) { | |
@@ -753,7 +779,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) | |
if (unsafe) { | |
if (DEBUG_ON) { | |
dbg_printk("scrubbing environment variables for %s " | |
- "label=", xname); | |
+ "label=", bprm->filename); | |
aa_label_printk(new, GFP_ATOMIC); | |
dbg_printk("\n"); | |
} | |
@@ -764,7 +790,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) | |
/* when transitioning clear unsafe personality bits */ | |
if (DEBUG_ON) { | |
dbg_printk("apparmor: clearing unsafe personality " | |
- "bits. %s label=", xname); | |
+ "bits. %s label=", bprm->filename); | |
aa_label_printk(new, GFP_ATOMIC); | |
dbg_printk("\n"); | |
} | |
@@ -786,7 +812,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) | |
audit: | |
error = fn_for_each(label, profile, | |
aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC, | |
- xname, NULL, new, | |
+ bprm->filename, NULL, new, | |
file_inode(bprm->file)->i_uid, info, | |
error)); | |
aa_put_label(new); | |
diff --git a/security/apparmor/file.c b/security/apparmor/file.c | |
index fb8f2c5..546f768 100644 | |
--- a/security/apparmor/file.c | |
+++ b/security/apparmor/file.c | |
@@ -286,6 +286,7 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, | |
struct aa_perms *perms) | |
{ | |
int e = 0; | |
+ | |
if (profile_unconfined(profile) || | |
((flags & PATH_SOCK_COND) && !PROFILE_MEDIATES_AF(profile, AF_UNIX))) | |
return 0; | |
@@ -296,6 +297,27 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, | |
cond->uid, NULL, e); | |
} | |
+ | |
+static int profile_path_perm(const char *op, struct aa_profile *profile, | |
+ const struct path *path, char *buffer, u32 request, | |
+ struct path_cond *cond, int flags, | |
+ struct aa_perms *perms) | |
+{ | |
+ const char *name; | |
+ int error; | |
+ | |
+ if (profile_unconfined(profile)) | |
+ return 0; | |
+ | |
+ error = path_name(op, &profile->label, path, | |
+ flags | profile->path_flags, buffer, &name, cond, | |
+ request); | |
+ if (error) | |
+ return error; | |
+ return __aa_path_perm(op, profile, name, request, cond, flags, | |
+ perms); | |
+} | |
+ | |
/** | |
* aa_path_perm - do permissions check & audit for @path | |
* @op: operation being checked | |
@@ -312,22 +334,15 @@ int aa_path_perm(const char *op, struct aa_label *label, | |
struct path_cond *cond) | |
{ | |
struct aa_perms perms = {}; | |
- char *buffer = NULL; | |
- const char *name; | |
struct aa_profile *profile; | |
+ char *buffer = NULL; | |
int error; | |
- /* TODO: fix path lookup flags */ | |
- flags |= labels_profile(label)->path_flags | | |
- (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); | |
+ flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); | |
get_buffers(buffer); | |
- | |
- error = path_name(op, label, path, flags | PATH_DELEGATE_DELETED, | |
- buffer, &name, cond, request); | |
- if (!error) | |
- error = fn_for_each_confined(label, profile, | |
- __aa_path_perm(op, profile, name, request, cond, | |
- flags, &perms)); | |
+ error = fn_for_each_confined(label, profile, | |
+ profile_path_perm(op, profile, path, buffer, request, | |
+ cond, flags, &perms)); | |
put_buffers(buffer); | |
return error; | |
@@ -353,15 +368,30 @@ static inline bool xindex_is_subset(u32 link, u32 target) | |
return 1; | |
} | |
-static int profile_path_link(struct aa_profile *profile, const char *lname, | |
- const char *tname, struct path_cond *cond) | |
+static int profile_path_link(struct aa_profile *profile, | |
+ const struct path *link, char *buffer, | |
+ const struct path *target, char *buffer2, | |
+ struct path_cond *cond) | |
{ | |
+ const char *lname, *tname = NULL; | |
struct aa_perms lperms, perms; | |
const char *info = NULL; | |
u32 request = AA_MAY_LINK; | |
unsigned int state; | |
- int e = -EACCES; | |
+ int error; | |
+ error = path_name(OP_LINK, &profile->label, link, profile->path_flags, | |
+ buffer, &lname, cond, AA_MAY_LINK); | |
+ if (error) | |
+ goto audit; | |
+ | |
+ /* buffer2 freed below, tname is pointer in buffer2 */ | |
+ error = path_name(OP_LINK, &profile->label, target, profile->path_flags, | |
+ buffer2, &tname, cond, AA_MAY_LINK); | |
+ if (error) | |
+ goto audit; | |
+ | |
+ error = -EACCES; | |
/* aa_str_perms - handles the case of the dfa being NULL */ | |
state = aa_str_perms(profile->file.dfa, profile->file.start, lname, | |
cond, &lperms); | |
@@ -412,11 +442,11 @@ static int profile_path_link(struct aa_profile *profile, const char *lname, | |
} | |
done_tests: | |
- e = 0; | |
+ error = 0; | |
audit: | |
return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname, | |
- NULL, cond->uid, info, e); | |
+ NULL, cond->uid, info, error); | |
} | |
/** | |
@@ -447,31 +477,14 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry, | |
d_backing_inode(old_dentry)->i_mode | |
}; | |
char *buffer = NULL, *buffer2 = NULL; | |
- const char *lname, *tname = NULL; | |
struct aa_profile *profile; | |
int error; | |
- /* TODO: fix path lookup flags, auditing of failed path for profile */ | |
- profile = labels_profile(label); | |
/* buffer freed below, lname is pointer in buffer */ | |
get_buffers(buffer, buffer2); | |
- error = path_name(OP_LINK, label, &link, | |
- labels_profile(label)->path_flags, buffer, | |
- &lname, &cond, AA_MAY_LINK); | |
- if (error) | |
- goto out; | |
- | |
- /* buffer2 freed below, tname is pointer in buffer2 */ | |
- error = path_name(OP_LINK, label, &target, | |
- labels_profile(label)->path_flags, buffer2, &tname, | |
- &cond, AA_MAY_LINK); | |
- if (error) | |
- goto out; | |
- | |
error = fn_for_each_confined(label, profile, | |
- profile_path_link(profile, lname, tname, &cond)); | |
- | |
-out: | |
+ profile_path_link(profile, &link, buffer, &target, | |
+ buffer2, &cond)); | |
put_buffers(buffer, buffer2); | |
return error; | |
@@ -508,7 +521,6 @@ static int __file_path_perm(const char *op, struct aa_label *label, | |
.uid = file_inode(file)->i_uid, | |
.mode = file_inode(file)->i_mode | |
}; | |
- const char *name; | |
char *buffer; | |
int flags, error; | |
@@ -517,19 +529,13 @@ static int __file_path_perm(const char *op, struct aa_label *label, | |
/* TODO: check for revocation on stale profiles */ | |
return 0; | |
- /* TODO: fix path lookup flags */ | |
- flags = PATH_DELEGATE_DELETED | labels_profile(label)->path_flags | | |
- (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0); | |
+ flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0); | |
get_buffers(buffer); | |
- error = path_name(op, label, &file->f_path, | |
- flags | PATH_DELEGATE_DELETED, buffer, &name, &cond, | |
- request); | |
- | |
/* check every profile in task label not in current cache */ | |
error = fn_for_each_not_in_set(flabel, label, profile, | |
- __aa_path_perm(op, profile, name, request, &cond, 0, | |
- &perms)); | |
+ profile_path_perm(op, profile, &file->f_path, buffer, | |
+ request, &cond, flags, &perms)); | |
if (denied) { | |
/* check every profile in file label that was not tested | |
* in the initial check above. | |
@@ -539,8 +545,9 @@ static int __file_path_perm(const char *op, struct aa_label *label, | |
/* TODO: don't audit here */ | |
last_error(error, | |
fn_for_each_not_in_set(label, flabel, profile, | |
- __aa_path_perm(op, profile, name, request, | |
- &cond, 0, &perms))); | |
+ profile_path_perm(op, profile, &file->f_path, | |
+ buffer, request, &cond, flags, | |
+ &perms))); | |
} | |
if (!error) | |
update_file_ctx(file_ctx(file), label, request); | |
diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h | |
index 0fb7e00..9a62b6e 100644 | |
--- a/security/apparmor/include/file.h | |
+++ b/security/apparmor/include/file.h | |
@@ -191,9 +191,9 @@ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, | |
const char *name, struct path_cond *cond, | |
struct aa_perms *perms); | |
-int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, | |
- u32 request, struct path_cond *cond, int flags, | |
- struct aa_perms *perms); | |
+int __aa_path_perm(const char *op, struct aa_profile *profile, | |
+ const char *name, u32 request, struct path_cond *cond, | |
+ int flags, struct aa_perms *perms); | |
int aa_path_perm(const char *op, struct aa_label *label, | |
const struct path *path, int flags, u32 request, | |
struct path_cond *cond); | |
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c | |
index d10a956..5ca4ccf 100644 | |
--- a/security/apparmor/mount.c | |
+++ b/security/apparmor/mount.c | |
@@ -292,30 +292,54 @@ static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, | |
return 4; | |
} | |
+ | |
+static int path_flags(struct aa_profile *profile, const struct path *path) | |
+{ | |
+ AA_BUG(!profile); | |
+ AA_BUG(!path); | |
+ | |
+ return profile->path_flags | | |
+ (S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0); | |
+} | |
+ | |
/** | |
- * match_mnt - handle path matching for mount | |
+ * match_mnt_path_str - 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) | |
+ * @mntpath: for the mntpnt (NOT NULL) | |
+ * @buffer: buffer to be used to lookup mntpath | |
+ * @devnme: string for the devname/src_name (MAY BE NULL OR ERRPTR) | |
* @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 | |
+ * @devinfo: error str if (IS_ERR(@devname)) | |
* | |
* 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) | |
+static int match_mnt_path_str(struct aa_profile *profile, const struct path *mntpath, | |
+ char *buffer, const char *devname, | |
+ const char *type, unsigned long flags, | |
+ void *data, bool binary, const char *devinfo) | |
{ | |
struct aa_perms perms = { }; | |
- const char *info = NULL; | |
- int pos, error = -EACCES; | |
+ const char *mntpnt = NULL, *info = NULL; | |
+ int pos, error; | |
AA_BUG(!profile); | |
+ AA_BUG(!mntpath); | |
+ AA_BUG(!buffer); | |
+ | |
+ error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer, | |
+ &mntpnt, &info, profile->disconnected); | |
+ if (error) | |
+ goto audit; | |
+ if (IS_ERR(devname)) { | |
+ error = PTR_ERR(devname); | |
+ info = devinfo; | |
+ goto audit; | |
+ } | |
+ error = -EACCES; | |
pos = do_match_mnt(profile->policy.dfa, | |
profile->policy.start[AA_CLASS_MOUNT], | |
mntpnt, devname, type, flags, data, binary, &perms); | |
@@ -330,20 +354,47 @@ static int match_mnt(struct aa_profile *profile, const char *mntpnt, | |
flags, data, AA_MAY_MOUNT, &perms, info, error); | |
} | |
-static int path_flags(struct aa_profile *profile, const struct path *path) | |
+/** | |
+ * match_mnt - handle path matching for mount | |
+ * @profile: the confining profile | |
+ * @mntpath: for the mntpnt (NOT NULL) | |
+ * @buffer: buffer to be used to lookup mntpath | |
+ * @devpath: path devname/src_name (MAYBE NULL) | |
+ * @devbuffer: buffer to be used to lookup devname/src_name | |
+ * @type: string for the dev type (MAYBE NULL) | |
+ * @flags: mount flags to match | |
+ * @data: fs mount data (MAYBE NULL) | |
+ * @binary: whether @data is binary | |
+ * | |
+ * Returns: 0 on success else error | |
+ */ | |
+static int match_mnt(struct aa_profile *profile, const struct path *path, | |
+ char *buffer, struct path *devpath, char *devbuffer, | |
+ const char *type, unsigned long flags, void *data, | |
+ bool binary) | |
{ | |
+ const char *devname = NULL, *info = NULL; | |
+ int error = -EACCES; | |
+ | |
AA_BUG(!profile); | |
- AA_BUG(!path); | |
+ AA_BUG(devpath && !devbuffer); | |
- return profile->path_flags | | |
- (S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0); | |
+ if (devpath) { | |
+ error = aa_path_name(devpath, path_flags(profile, devpath), | |
+ devbuffer, &devname, &info, | |
+ profile->disconnected); | |
+ if (error) | |
+ devname = ERR_PTR(error); | |
+ } | |
+ | |
+ return match_mnt_path_str(profile, path, buffer, devname, type, flags, | |
+ data, binary, info); | |
} | |
int aa_remount(struct aa_label *label, const struct path *path, | |
unsigned long flags, void *data) | |
{ | |
struct aa_profile *profile; | |
- const char *name, *info = NULL; | |
char *buffer = NULL; | |
bool binary; | |
int error; | |
@@ -354,21 +405,9 @@ int aa_remount(struct aa_label *label, const struct path *path, | |
binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; | |
get_buffers(buffer); | |
- error = aa_path_name(path, path_flags(labels_profile(label), path), | |
- buffer, &name, &info, | |
- labels_profile(label)->disconnected); | |
- if (error) { | |
- error = audit_mount(labels_profile(label), OP_MOUNT, name, NULL, | |
- NULL, NULL, flags, data, AA_MAY_MOUNT, | |
- &nullperms, info, error); | |
- goto out; | |
- } | |
- | |
error = fn_for_each_confined(label, profile, | |
- match_mnt(profile, name, NULL, NULL, flags, data, | |
- binary)); | |
- | |
-out: | |
+ match_mnt(profile, path, buffer, NULL, NULL, NULL, | |
+ flags, data, binary)); | |
put_buffers(buffer); | |
return error; | |
@@ -379,7 +418,6 @@ int aa_bind_mount(struct aa_label *label, const struct path *path, | |
{ | |
struct aa_profile *profile; | |
char *buffer = NULL, *old_buffer = NULL; | |
- const char *name, *old_name = NULL, *info = NULL; | |
struct path old_path; | |
int error; | |
@@ -396,34 +434,13 @@ int aa_bind_mount(struct aa_label *label, const struct path *path, | |
return error; | |
get_buffers(buffer, old_buffer); | |
- error = aa_path_name(path, path_flags(labels_profile(label), path), buffer, &name, | |
- &info, labels_profile(label)->disconnected); | |
- if (error) | |
- goto error; | |
- | |
- error = aa_path_name(&old_path, path_flags(labels_profile(label), | |
- &old_path), | |
- old_buffer, &old_name, &info, | |
- labels_profile(label)->disconnected); | |
- path_put(&old_path); | |
- if (error) | |
- goto error; | |
- | |
error = fn_for_each_confined(label, profile, | |
- match_mnt(profile, name, old_name, NULL, flags, NULL, | |
- false)); | |
- | |
-out: | |
+ match_mnt(profile, path, buffer, &old_path, old_buffer, | |
+ NULL, flags, NULL, false)); | |
put_buffers(buffer, old_buffer); | |
+ path_put(&old_path); | |
return error; | |
- | |
-error: | |
- error = fn_for_each(label, profile, | |
- audit_mount(profile, OP_MOUNT, name, old_name, NULL, | |
- NULL, flags, NULL, AA_MAY_MOUNT, &nullperms, | |
- info, error)); | |
- goto out; | |
} | |
int aa_mount_change_type(struct aa_label *label, const struct path *path, | |
@@ -431,7 +448,6 @@ int aa_mount_change_type(struct aa_label *label, const struct path *path, | |
{ | |
struct aa_profile *profile; | |
char *buffer = NULL; | |
- const char *name, *info = NULL; | |
int error; | |
AA_BUG(!label); | |
@@ -442,23 +458,9 @@ int aa_mount_change_type(struct aa_label *label, const struct path *path, | |
MS_UNBINDABLE); | |
get_buffers(buffer); | |
- error = aa_path_name(path, path_flags(labels_profile(label), path), | |
- buffer, &name, &info, | |
- labels_profile(label)->disconnected); | |
- if (error) { | |
- error = fn_for_each(label, profile, | |
- audit_mount(profile, OP_MOUNT, name, NULL, | |
- NULL, NULL, flags, NULL, | |
- AA_MAY_MOUNT, &nullperms, info, | |
- error)); | |
- goto out; | |
- } | |
- | |
error = fn_for_each_confined(label, profile, | |
- match_mnt(profile, name, NULL, NULL, flags, NULL, | |
- false)); | |
- | |
-out: | |
+ match_mnt(profile, path, buffer, NULL, NULL, NULL, | |
+ flags, NULL, false)); | |
put_buffers(buffer); | |
return error; | |
@@ -469,7 +471,6 @@ int aa_move_mount(struct aa_label *label, const struct path *path, | |
{ | |
struct aa_profile *profile; | |
char *buffer = NULL, *old_buffer = NULL; | |
- const char *name, *old_name = NULL, *info = NULL; | |
struct path old_path; | |
int error; | |
@@ -484,53 +485,29 @@ int aa_move_mount(struct aa_label *label, const struct path *path, | |
return error; | |
get_buffers(buffer, old_buffer); | |
- error = aa_path_name(path, path_flags(labels_profile(label), path), | |
- buffer, &name, &info, | |
- labels_profile(label)->disconnected); | |
- if (error) | |
- goto error; | |
- | |
- error = aa_path_name(&old_path, path_flags(labels_profile(label), | |
- &old_path), | |
- old_buffer, &old_name, &info, | |
- labels_profile(label)->disconnected); | |
- path_put(&old_path); | |
- if (error) | |
- goto error; | |
- | |
error = fn_for_each_confined(label, profile, | |
- match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, | |
- false)); | |
- | |
-out: | |
+ match_mnt(profile, path, buffer, &old_path, old_buffer, | |
+ NULL, MS_MOVE, NULL, false)); | |
put_buffers(buffer, old_buffer); | |
+ path_put(&old_path); | |
return error; | |
- | |
-error: | |
- error = fn_for_each(label, profile, | |
- audit_mount(profile, OP_MOUNT, name, old_name, NULL, | |
- NULL, MS_MOVE, NULL, AA_MAY_MOUNT, | |
- &nullperms, info, error)); | |
- goto out; | |
} | |
-int aa_new_mount(struct aa_label *label, const char *orig_dev_name, | |
+int aa_new_mount(struct aa_label *label, const char *dev_name, | |
const struct path *path, const char *type, unsigned long flags, | |
void *data) | |
{ | |
struct aa_profile *profile; | |
char *buffer = NULL, *dev_buffer = NULL; | |
- const char *name = NULL, *dev_name = NULL, *info = NULL; | |
bool binary = true; | |
int error; | |
int requires_dev = 0; | |
- struct path dev_path; | |
+ struct path tmp_path, *dev_path = NULL; | |
AA_BUG(!label); | |
AA_BUG(!path); | |
- dev_name = orig_dev_name; | |
if (type) { | |
struct file_system_type *fstype; | |
fstype = get_fs_type(type); | |
@@ -544,73 +521,62 @@ int aa_new_mount(struct aa_label *label, const char *orig_dev_name, | |
if (!dev_name || !*dev_name) | |
return -ENOENT; | |
- error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); | |
+ error = kern_path(dev_name, LOOKUP_FOLLOW, &tmp_path); | |
if (error) | |
return error; | |
+ dev_path = &tmp_path; | |
} | |
} | |
get_buffers(buffer, dev_buffer); | |
- if (type && requires_dev) { | |
- error = aa_path_name(&dev_path, | |
- path_flags(labels_profile(label), | |
- &dev_path), | |
- dev_buffer, &dev_name, &info, | |
- labels_profile(label)->disconnected); | |
- path_put(&dev_path); | |
- if (error) | |
- goto error; | |
+ if (dev_path) { | |
+ error = fn_for_each_confined(label, profile, | |
+ match_mnt(profile, path, buffer, dev_path, dev_buffer, | |
+ type, flags, data, binary)); | |
+ } else { | |
+ error = fn_for_each_confined(label, profile, | |
+ match_mnt_path_str(profile, path, buffer, dev_name, | |
+ type, flags, data, binary, NULL)); | |
} | |
- | |
- error = aa_path_name(path, path_flags(labels_profile(label), path), | |
- buffer, &name, &info, | |
- labels_profile(label)->disconnected); | |
- if (error) | |
- goto error; | |
- | |
- error = fn_for_each_confined(label, profile, | |
- match_mnt(profile, name, dev_name, type, flags, data, | |
- binary)); | |
- | |
-cleanup: | |
put_buffers(buffer, dev_buffer); | |
+ if (dev_path) | |
+ path_put(dev_path); | |
return error; | |
- | |
-error: | |
- error = fn_for_each(label, profile, | |
- audit_mount(labels_profile(label), OP_MOUNT, name, | |
- dev_name, type, NULL, flags, data, | |
- AA_MAY_MOUNT, &nullperms, info, error)); | |
- goto cleanup; | |
} | |
-static int profile_umount(struct aa_profile *profile, const char *name) | |
+static int profile_umount(struct aa_profile *profile, struct path *path, | |
+ char *buffer) | |
{ | |
struct aa_perms perms = { }; | |
- const char *info = NULL; | |
+ const char *name = NULL, *info = NULL; | |
unsigned int state; | |
- int e = 0; | |
+ int error; | |
AA_BUG(!profile); | |
- AA_BUG(!name); | |
+ AA_BUG(!path); | |
+ | |
+ error = aa_path_name(path, path_flags(profile, path), buffer, &name, | |
+ &info, profile->disconnected); | |
+ if (error) | |
+ goto audit; | |
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) | |
- e = -EACCES; | |
+ error = -EACCES; | |
+audit: | |
return audit_mount(profile, OP_UMOUNT, name, NULL, NULL, NULL, 0, NULL, | |
- AA_MAY_UMOUNT, &perms, info, e); | |
+ AA_MAY_UMOUNT, &perms, info, error); | |
} | |
int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags) | |
{ | |
struct aa_profile *profile; | |
char *buffer = NULL; | |
- const char *name, *info = NULL; | |
int error; | |
struct path path = { mnt, mnt->mnt_root }; | |
@@ -618,21 +584,8 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags) | |
AA_BUG(!mnt); | |
get_buffers(buffer); | |
- error = aa_path_name(&path, path_flags(labels_profile(label), &path), | |
- buffer, &name, &info, | |
- labels_profile(label)->disconnected); | |
- if (error) { | |
- error = fn_for_each(label, profile, | |
- audit_mount(profile, OP_UMOUNT, name, NULL, | |
- NULL, NULL, 0, NULL, AA_MAY_UMOUNT, | |
- &nullperms, info, error)); | |
- goto out; | |
- } | |
- | |
error = fn_for_each_confined(label, profile, | |
- profile_umount(profile, name)); | |
- | |
-out: | |
+ profile_umount(profile, &path, buffer)); | |
put_buffers(buffer); | |
return error; | |
@@ -643,23 +596,34 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags) | |
* Returns: label for transition or ERR_PTR. Does not return NULL | |
*/ | |
static struct aa_label *build_pivotroot(struct aa_profile *profile, | |
- const char *new_name, | |
- const char *old_name) | |
+ struct path *new_path, | |
+ char *new_buffer, | |
+ struct path *old_path, | |
+ char *old_buffer) | |
{ | |
- struct aa_label *target = NULL; | |
+ const char *old_name, *new_name = NULL, *info = NULL; | |
const char *trans_name = NULL; | |
+ struct aa_label *target = NULL; | |
struct aa_perms perms = { }; | |
- const char *info = NULL; | |
unsigned int state; | |
- int error = -EACCES; | |
+ int error; | |
AA_BUG(!profile); | |
- AA_BUG(!new_name); | |
- AA_BUG(!old_name); | |
+ AA_BUG(!new_path); | |
+ AA_BUG(!old_path); | |
+ | |
+ error = aa_path_name(old_path, path_flags(profile, old_path), | |
+ old_buffer, &old_name, &info, | |
+ profile->disconnected); | |
+ if (error) | |
+ goto audit; | |
+ error = aa_path_name(new_path, path_flags(profile, new_path), | |
+ new_buffer, &new_name, &info, | |
+ profile->disconnected); | |
+ if (error) | |
+ goto audit; | |
- /* TODO: actual domain transition computation for multiple | |
- * profiles | |
- */ | |
+ error = -EACCES; | |
state = aa_dfa_match(profile->policy.dfa, | |
profile->policy.start[AA_CLASS_MOUNT], | |
new_name); | |
@@ -677,6 +641,7 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, | |
} | |
} | |
+audit: | |
error = audit_mount(profile, OP_PIVOTROOT, new_name, old_name, | |
NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT, | |
&perms, info, error); | |
@@ -694,8 +659,7 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path, | |
{ | |
struct aa_profile *profile; | |
struct aa_label *target = NULL; | |
- char *old_buffer = NULL, *new_buffer = NULL; | |
- const char *old_name, *new_name = NULL, *info = NULL; | |
+ char *old_buffer = NULL, *new_buffer = NULL, *info = NULL; | |
int error; | |
AA_BUG(!label); | |
@@ -703,21 +667,9 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path, | |
AA_BUG(!new_path); | |
get_buffers(old_buffer, new_buffer); | |
- error = aa_path_name(old_path, path_flags(labels_profile(label), | |
- old_path), | |
- old_buffer, &old_name, &info, | |
- labels_profile(label)->disconnected); | |
- if (error) | |
- goto fail; | |
- error = aa_path_name(new_path, path_flags(labels_profile(label), | |
- new_path), | |
- new_buffer, &new_name, &info, | |
- labels_profile(label)->disconnected); | |
- if (error) | |
- goto fail; | |
- | |
target = fn_label_build(label, profile, GFP_ATOMIC, | |
- build_pivotroot(profile, new_name, old_name)); | |
+ build_pivotroot(profile, new_path, new_buffer, | |
+ old_path, old_buffer)); | |
if (!target) { | |
info = "label build failed"; | |
error = -ENOMEM; | |
@@ -738,8 +690,10 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path, | |
return error; | |
fail: | |
+ /* TODO: add back in auditing of new_name and old_name */ | |
error = fn_for_each(label, profile, | |
- audit_mount(profile, OP_PIVOTROOT, new_name, old_name, | |
+ audit_mount(profile, OP_PIVOTROOT, NULL /*new_name */, | |
+ NULL /* old_name */, | |
NULL, NULL, | |
0, NULL, AA_MAY_PIVOTROOT, &nullperms, info, | |
error)); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From b7a8dee774e05a0737f29db660151e2b1c8ca41b Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Fri, 12 Aug 2016 08:08:33 -0700 | |
Subject: [PATCH 33/76] apparmor: default to allowing unprivileged userns | |
policy | |
To disable set kernel/unprivileged_userns_apparmor_policy = 0 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/policy.c | 2 +- | |
1 file changed, 1 insertion(+), 1 deletion(-) | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index 52abc2f..562c8a7 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -90,7 +90,7 @@ | |
#include "include/policy_unpack.h" | |
#include "include/resource.h" | |
-int unprivileged_userns_apparmor_policy = 0; | |
+int unprivileged_userns_apparmor_policy = 1; | |
/* Note: mode names must be unique in the first character because of | |
* modechrs used to print modes on compound labels on some interfaces | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From e5dc51387e00c96091dc162b02ea5da15bd1a3c7 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Sun, 14 Aug 2016 15:01:12 -0700 | |
Subject: [PATCH 34/76] apparmor: fix: permissions test to view and manage | |
policy | |
Drop may_open_profiles and unify with policy_view_capable() | |
Adjust policy_view_capable() so that it is slightly less restricted. | |
user_namespaces can now manage policy iff | |
- the task has cap_mac_admin in the namespace | |
- the user_namespace->level == apparmor policy_namespace->level. | |
This ensures a usernamespace can never be used to manage the | |
system namespace, and can only be used to manage the namespace at its | |
view level. | |
If for some reason a user namespace is setup without an apparmor | |
policy namespace it will not be able to manage or view policy. | |
However this also means an extra level of apparmor policy namespaces | |
can not be setup and used with user namespaces at this time. | |
ie. this blocks user confinement stacking, and user defined policy | |
use cases from being used with user namespaces atm. | |
Add the ability to output a debug message in relation to | |
capable(cap_mac_admin) && | |
policy_locked | |
as it is possible for these to cause failures that are not audited and | |
thus hard to trace down. | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 2 +- | |
security/apparmor/policy.c | 26 ++++++++------------------ | |
2 files changed, 9 insertions(+), 19 deletions(-) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index 38d1650..feb501f 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -992,7 +992,7 @@ static int seq_show_profile(struct seq_file *f, void *p) | |
static int profiles_open(struct inode *inode, struct file *file) | |
{ | |
- if (!aa_may_open_profiles()) | |
+ if (!policy_view_capable()) | |
return -EACCES; | |
return seq_open(file, &aa_fs_profiles_op); | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index 562c8a7..e9eb8ea 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -621,12 +621,14 @@ bool policy_view_capable(void) | |
{ | |
struct user_namespace *user_ns = current_user_ns(); | |
struct aa_ns *ns = aa_get_current_ns(); | |
+ bool root_in_user_ns = uid_eq(current_euid(), make_kuid(user_ns, 0)) || | |
+ in_egroup_p(make_kgid(user_ns, 0)); | |
bool response = false; | |
- if (ns_capable(user_ns, CAP_MAC_ADMIN) && | |
+ if (root_in_user_ns && | |
(user_ns == &init_user_ns || | |
(unprivileged_userns_apparmor_policy != 0 && | |
- user_ns->level == 1 && ns != root_ns))) | |
+ user_ns->level == ns->level))) | |
response = true; | |
aa_put_ns(ns); | |
@@ -635,25 +637,13 @@ bool policy_view_capable(void) | |
bool policy_admin_capable(void) | |
{ | |
- return policy_view_capable() && !aa_g_lock_policy; | |
-} | |
- | |
-bool aa_may_open_profiles(void) | |
-{ | |
struct user_namespace *user_ns = current_user_ns(); | |
- struct aa_ns *ns = aa_get_current_ns(); | |
- bool root_in_user_ns = uid_eq(current_euid(), make_kuid(user_ns, 0)) || | |
- in_egroup_p(make_kgid(user_ns, 0)); | |
- bool response = false; | |
+ bool capable = ns_capable(user_ns, CAP_MAC_ADMIN); | |
- if (root_in_user_ns && | |
- (user_ns == &init_user_ns || | |
- (unprivileged_userns_apparmor_policy != 0 && | |
- user_ns->level == 1 && ns != root_ns))) | |
- response = true; | |
- aa_put_ns(ns); | |
+ AA_DEBUG("cap_mac_admin? %d\n", capable); | |
+ AA_DEBUG("policy locked? %d\n", aa_g_lock_policy); | |
- return response; | |
+ return policy_view_capable() && capable && !aa_g_lock_policy; | |
} | |
/** | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From a5a257f75f4f50b363b7b5ebe5376d9b96b3ae64 Mon Sep 17 00:00:00 2001 | |
From: William Hua <william.hua@canonical.com> | |
Date: Thu, 28 Jul 2016 18:12:00 +0000 | |
Subject: [PATCH 35/76] UBUNTU: SAUCE: apparmor: add data query support | |
Allow AppArmor to store and retrieve arbitrary free-form data. This | |
is needed for the dconf proxy. | |
Signed-off-by: William Hua <william.hua@canonical.com> | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 116 +++++++++++++++++++++++++++++++++++-- | |
security/apparmor/include/policy.h | 17 +++++- | |
security/apparmor/policy.c | 22 +++++++ | |
security/apparmor/policy_unpack.c | 67 +++++++++++++++++++++ | |
4 files changed, 216 insertions(+), 6 deletions(-) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index feb501f..b22ccd7 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -235,6 +235,98 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, | |
} | |
/** | |
+ * query_data - queries a policy and writes its data to buf | |
+ * @buf: the resulting data is stored here (NOT NULL) | |
+ * @buf_len: size of buf | |
+ * @query: query string used to retrieve data | |
+ * @query_len: size of query including second NUL byte | |
+ * | |
+ * The buffers pointed to by buf and query may overlap. The query buffer is | |
+ * parsed before buf is written to. | |
+ * | |
+ * The query should look like "<LABEL>\0<KEY>\0", where <LABEL> is the name of | |
+ * the security confinement context and <KEY> is the name of the data to | |
+ * retrieve. <LABEL> and <KEY> must not be NUL-terminated. | |
+ * | |
+ * Don't expect the contents of buf to be preserved on failure. | |
+ * | |
+ * Returns: number of characters written to buf or -errno on failure | |
+ */ | |
+static ssize_t query_data(char *buf, size_t buf_len, | |
+ char *query, size_t query_len) | |
+{ | |
+ char *out; | |
+ const char *key; | |
+ struct label_it i; | |
+ struct aa_label *label, *curr; | |
+ struct aa_profile *profile; | |
+ struct aa_data *data; | |
+ u32 bytes; | |
+ u32 blocks; | |
+ u32 size; | |
+ | |
+ if (!query_len) | |
+ return -EINVAL; /* need a query */ | |
+ | |
+ key = query + strnlen(query, query_len) + 1; | |
+ if (key + 1 >= query + query_len) | |
+ return -EINVAL; /* not enough space for a non-empty key */ | |
+ if (key + strnlen(key, query + query_len - key) >= query + query_len) | |
+ return -EINVAL; /* must end with NUL */ | |
+ | |
+ if (buf_len < sizeof(bytes) + sizeof(blocks)) | |
+ return -EINVAL; /* not enough space */ | |
+ | |
+ curr = aa_begin_current_label(DO_UPDATE); | |
+ label = aa_label_parse(curr, query, GFP_KERNEL, false, false); | |
+ aa_end_current_label(curr); | |
+ if (IS_ERR(label)) | |
+ return PTR_ERR(label); | |
+ | |
+ /* We are going to leave space for two numbers. The first is the total | |
+ * number of bytes we are writing after the first number. This is so | |
+ * users can read the full output without reallocation. | |
+ * | |
+ * The second number is the number of data blocks we're writing. An | |
+ * application might be confined by multiple policies having data in | |
+ * the same key. | |
+ */ | |
+ memset(buf, 0, sizeof(bytes) + sizeof(blocks)); | |
+ out = buf + sizeof(bytes) + sizeof(blocks); | |
+ | |
+ blocks = 0; | |
+ label_for_each_confined(i, label, profile) { | |
+ if (!profile->data) | |
+ continue; | |
+ | |
+ data = rhashtable_lookup_fast(profile->data, &key, | |
+ profile->data->p); | |
+ | |
+ if (data) { | |
+ if (out + sizeof(size) + data->size > buf + buf_len) { | |
+ aa_put_label(label); | |
+ return -EINVAL; /* not enough space */ | |
+ } | |
+ size = __cpu_to_le32(data->size); | |
+ memcpy(out, &size, sizeof(size)); | |
+ out += sizeof(size); | |
+ memcpy(out, data->data, data->size); | |
+ out += data->size; | |
+ blocks++; | |
+ } | |
+ } | |
+ aa_put_label(label); | |
+ | |
+ bytes = out - buf - sizeof(bytes); | |
+ bytes = __cpu_to_le32(bytes); | |
+ blocks = __cpu_to_le32(blocks); | |
+ memcpy(buf, &bytes, sizeof(bytes)); | |
+ memcpy(buf + sizeof(bytes), &blocks, sizeof(blocks)); | |
+ | |
+ return out - buf; | |
+} | |
+ | |
+/** | |
* query_label - queries a label and writes permissions to buf | |
* @buf: the resulting permissions string is stored here (NOT NULL) | |
* @buf_len: size of buf | |
@@ -309,18 +401,27 @@ static ssize_t query_label(char *buf, size_t buf_len, | |
#define QUERY_CMD_PROFILE_LEN 8 | |
#define QUERY_CMD_LABELALL "labelall\0" | |
#define QUERY_CMD_LABELALL_LEN 9 | |
+#define QUERY_CMD_DATA "data\0" | |
+#define QUERY_CMD_DATA_LEN 5 | |
/** | |
- * aa_write_access - generic permissions query | |
+ * aa_write_access - generic permissions and data query | |
* @file: pointer to open apparmorfs/access file | |
* @ubuf: user buffer containing the complete query string (NOT NULL) | |
* @count: size of ubuf | |
* @ppos: position in the file (MUST BE ZERO) | |
* | |
- * Allows for one permission query per open(), write(), and read() sequence. | |
- * The only query currently supported is a label-based query. For this query | |
- * ubuf must begin with "label\0", followed by the profile query specific | |
- * format described in the query_label() function documentation. | |
+ * Allows for one permissions or data query per open(), write(), and read() | |
+ * sequence. The only queries currently supported are label-based queries for | |
+ * permissions or data. | |
+ * | |
+ * For permissions queries, ubuf must begin with "label\0", followed by the | |
+ * profile query specific format described in the query_label() function | |
+ * documentation. | |
+ * | |
+ * For data queries, ubuf must have the form "data\0<LABEL>\0<KEY>\0", where | |
+ * <LABEL> is the name of the security confinement context and <KEY> is the | |
+ * name of the data to retrieve. | |
* | |
* Returns: number of bytes written or -errno on failure | |
*/ | |
@@ -352,6 +453,11 @@ static ssize_t aa_write_access(struct file *file, const char __user *ubuf, | |
len = query_label(buf, SIMPLE_TRANSACTION_LIMIT, | |
buf + QUERY_CMD_LABELALL_LEN, | |
count - QUERY_CMD_LABELALL_LEN, false); | |
+ } else if (count > QUERY_CMD_DATA_LEN && | |
+ !memcmp(buf, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) { | |
+ len = query_data(buf, SIMPLE_TRANSACTION_LIMIT, | |
+ buf + QUERY_CMD_DATA_LEN, | |
+ count - QUERY_CMD_DATA_LEN); | |
} else | |
len = -EINVAL; | |
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h | |
index af2685f..0686712 100644 | |
--- a/security/apparmor/include/policy.h | |
+++ b/security/apparmor/include/policy.h | |
@@ -18,6 +18,7 @@ | |
#include <linux/capability.h> | |
#include <linux/cred.h> | |
#include <linux/kref.h> | |
+#include <linux/rhashtable.h> | |
#include <linux/sched.h> | |
#include <linux/slab.h> | |
#include <linux/socket.h> | |
@@ -78,6 +79,19 @@ struct aa_policydb { | |
}; | |
+/* struct aa_data - generic data structure | |
+ * key: name for retrieving this data | |
+ * size: size of data in bytes | |
+ * data: binary data | |
+ * head: reserved for rhashtable | |
+ */ | |
+struct aa_data { | |
+ char *key; | |
+ size_t size; | |
+ char *data; | |
+ struct rhash_head head; | |
+}; | |
+ | |
/* struct aa_profile - basic confinement data | |
* @base - base components of the profile (name, refcount, lists, lock ...) | |
* @label - label this profile is an extension of | |
@@ -97,9 +111,9 @@ struct aa_policydb { | |
* @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 | |
* @dirname: name of the profile dir in apparmorfs | |
+ * @data: hashtable for free-form policy aa_data | |
* | |
* The AppArmor profile contains the basic confinement data. Each profile | |
* has a name, and exists in a namespace. The @name and @exec_match are | |
@@ -138,6 +152,7 @@ struct aa_profile { | |
unsigned char *hash; | |
char *dirname; | |
struct dentry *dents[AAFS_PROF_SIZEOF]; | |
+ struct rhashtable *data; | |
struct aa_label label; | |
}; | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index e9eb8ea..4d860d8 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -102,6 +102,19 @@ | |
"unconfined", | |
}; | |
+/** | |
+ * aa_free_data - free a data blob | |
+ * @ptr: data to free | |
+ * @arg: unused | |
+ */ | |
+static void aa_free_data(void *ptr, void *arg) | |
+{ | |
+ struct aa_data *data = ptr; | |
+ | |
+ kzfree(data->data); | |
+ kzfree(data->key); | |
+ kzfree(data); | |
+} | |
/** | |
* __add_profile - add a profiles to list and label tree | |
@@ -199,6 +212,8 @@ void __aa_profile_list_release(struct list_head *head) | |
*/ | |
void aa_free_profile(struct aa_profile *profile) | |
{ | |
+ struct rhashtable *rht; | |
+ | |
AA_DEBUG("%s(%p)\n", __func__, profile); | |
if (!profile) | |
@@ -220,6 +235,13 @@ void aa_free_profile(struct aa_profile *profile) | |
aa_put_dfa(profile->xmatch); | |
aa_put_dfa(profile->policy.dfa); | |
+ if (profile->data) { | |
+ rht = profile->data; | |
+ profile->data = NULL; | |
+ rhashtable_free_and_destroy(rht, aa_free_data, NULL); | |
+ kzfree(rht); | |
+ } | |
+ | |
kzfree(profile->hash); | |
kzfree(profile); | |
} | |
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c | |
index acbcee4..029ae14 100644 | |
--- a/security/apparmor/policy_unpack.c | |
+++ b/security/apparmor/policy_unpack.c | |
@@ -21,6 +21,7 @@ | |
#include <linux/ctype.h> | |
#include <linux/errno.h> | |
#include <linux/string.h> | |
+#include <linux/jhash.h> | |
#include "include/apparmor.h" | |
#include "include/audit.h" | |
@@ -495,6 +496,30 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) | |
return 0; | |
} | |
+static void *kvmemdup(const void *src, size_t len) | |
+{ | |
+ void *p = kvmalloc(len); | |
+ | |
+ if (p) | |
+ memcpy(p, src, len); | |
+ return p; | |
+} | |
+ | |
+static u32 strhash(const void *data, u32 len, u32 seed) | |
+{ | |
+ const char * const *key = data; | |
+ | |
+ return jhash(*key, strlen(*key), seed); | |
+} | |
+ | |
+static int datacmp(struct rhashtable_compare_arg *arg, const void *obj) | |
+{ | |
+ const struct aa_data *data = obj; | |
+ const char * const *key = arg->key; | |
+ | |
+ return strcmp(data->key, *key); | |
+} | |
+ | |
/** | |
* unpack_profile - unpack a serialized profile | |
* @e: serialized data extent information (NOT NULL) | |
@@ -507,6 +532,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) | |
const char *tmpname, *tmpns = NULL, *name = NULL; | |
const char *info = "failed to unpack profile"; | |
size_t size = 0, ns_len; | |
+ struct rhashtable_params params = { 0 }; | |
+ char *key = NULL; | |
+ struct aa_data *data; | |
int i, error = -EPROTO; | |
kernel_cap_t tmpcap; | |
u32 tmp; | |
@@ -697,6 +725,45 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) | |
if (!unpack_trans_table(e, profile)) | |
goto fail; | |
+ if (unpack_nameX(e, AA_STRUCT, "data")) { | |
+ profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL); | |
+ if (!profile->data) | |
+ goto fail; | |
+ | |
+ params.nelem_hint = 3; | |
+ params.key_len = sizeof(void *); | |
+ params.key_offset = offsetof(struct aa_data, key); | |
+ params.head_offset = offsetof(struct aa_data, head); | |
+ params.hashfn = strhash; | |
+ params.obj_cmpfn = datacmp; | |
+ | |
+ if (rhashtable_init(profile->data, ¶ms)) | |
+ goto fail; | |
+ | |
+ while (unpack_strdup(e, &key, NULL)) { | |
+ data = kzalloc(sizeof(*data), GFP_KERNEL); | |
+ if (!data) { | |
+ kzfree(key); | |
+ goto fail; | |
+ } | |
+ | |
+ data->key = key; | |
+ data->size = unpack_blob(e, &data->data, NULL); | |
+ data->data = kvmemdup(data->data, data->size); | |
+ if (data->size && !data->data) { | |
+ kzfree(data->key); | |
+ kzfree(data); | |
+ goto fail; | |
+ } | |
+ | |
+ rhashtable_insert_fast(profile->data, &data->head, | |
+ profile->data->p); | |
+ } | |
+ | |
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL)) | |
+ goto fail; | |
+ } | |
+ | |
if (!unpack_nameX(e, AA_STRUCTEND, NULL)) | |
goto fail; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 5f50262646d9b8c859aa581c003904aaca5d3303 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Wed, 24 Aug 2016 16:07:07 -0700 | |
Subject: [PATCH 36/76] apparmor: Add Basic ns cross check condition for ipc | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
security/apparmor/include/perms.h | 26 ++++++++++++++++---------- | |
1 file changed, 16 insertions(+), 10 deletions(-) | |
diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h | |
index 175fe13..b7cd839 100644 | |
--- a/security/apparmor/include/perms.h | |
+++ b/security/apparmor/include/perms.h | |
@@ -104,14 +104,24 @@ struct aa_perms { | |
}) | |
-/* TODO: update for labels pointing to labels instead of profiles | |
-* Note: this only works for profiles from a single namespace | |
-*/ | |
+/* | |
+ * TODO: update for labels pointing to labels instead of profiles | |
+ * TODO: optimize the walk, currently does subwalk of L2 for each P in L1 | |
+ * gah this doesn't allow for label compound check!!!! | |
+ */ | |
+#define xcheck_ns_profile_profile(P1, P2, FN, args...) \ | |
+({ \ | |
+ int ____e = 0; \ | |
+ if (P1->ns == P2->ns) \ | |
+ ____e = FN((P1), (P2), args); \ | |
+ (____e); \ | |
+}) | |
-#define xcheck_profile_label(P, L, FN, args...) \ | |
+#define xcheck_ns_profile_label(P, L, FN, args...) \ | |
({ \ | |
struct aa_profile *__p2; \ | |
- fn_for_each((L), __p2, FN((P), __p2, args)); \ | |
+ fn_for_each((L), __p2, \ | |
+ xcheck_ns_profile_profile((P), __p2, (FN), args)); \ | |
}) | |
#define xcheck_ns_labels(L1, L2, FN, args...) \ | |
@@ -120,13 +130,9 @@ struct aa_perms { | |
fn_for_each((L1), __p1, FN(__p1, (L2), args)); \ | |
}) | |
-/* todo: fix to handle multiple namespaces */ | |
-#define xcheck_labels(L1, L2, FN, args...) \ | |
- xcheck_ns_labels((L1), (L2), FN, args) | |
- | |
/* Do the cross check but applying FN at the profiles level */ | |
#define xcheck_labels_profiles(L1, L2, FN, args...) \ | |
- xcheck_ns_labels((L1), (L2), xcheck_profile_label, (FN), args) | |
+ xcheck_ns_labels((L1), (L2), xcheck_ns_profile_label, (FN), args) | |
#define FINAL_CHECK true | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 36ffaf27a3ed25d46e09eb78fc9e1f290dec8255 Mon Sep 17 00:00:00 2001 | |
From: Jay Vosburgh <jay.vosburgh@canonical.com> | |
Date: Wed, 11 Nov 2015 13:04:50 +0000 | |
Subject: [PATCH 37/76] UBUNTU: SAUCE: fan: add VXLAN implementation | |
Generify the fan mapping support and utilise that to implement fan | |
mappings over vxlan transport. | |
Expose the existance of this functionality (when the module is loaded) | |
via an additional sysctl marker. | |
Signed-off-by: Jay Vosburgh <jay.vosburgh@canonical.com> | |
[apw@canonical.com: added feature marker for fan over vxlan.] | |
Signed-off-by: Andy Whitcroft <apw@canonical.com> | |
Conflicts: | |
drivers/net/vxlan.c | |
include/uapi/linux/if_link.h | |
net/ipv4/ipip.c | |
Signed-off-by: Andy Whitcroft <apw@canonical.com> | |
--- | |
drivers/net/vxlan.c | 247 +++++++++++++++++++++++++++++++++++++++++ | |
include/net/ip_tunnels.h | 19 +++- | |
include/net/vxlan.h | 2 + | |
include/uapi/linux/if_link.h | 1 + | |
include/uapi/linux/if_tunnel.h | 2 +- | |
net/ipv4/ip_tunnel.c | 7 +- | |
net/ipv4/ipip.c | 243 ++++++++++++++++++++++++++++++---------- | |
7 files changed, 455 insertions(+), 66 deletions(-) | |
diff --git a/drivers/net/vxlan.c b/drivers/net/vxlan.c | |
index d4f495b..6d1a485 100644 | |
--- a/drivers/net/vxlan.c | |
+++ b/drivers/net/vxlan.c | |
@@ -16,6 +16,7 @@ | |
#include <linux/slab.h> | |
#include <linux/udp.h> | |
#include <linux/igmp.h> | |
+#include <linux/inetdevice.h> | |
#include <linux/if_ether.h> | |
#include <linux/ethtool.h> | |
#include <net/arp.h> | |
@@ -87,6 +88,167 @@ static inline bool vxlan_collect_metadata(struct vxlan_sock *vs) | |
ip_tunnel_collect_metadata(); | |
} | |
+static struct ip_fan_map *vxlan_fan_find_map(struct vxlan_dev *vxlan, __be32 daddr) | |
+{ | |
+ struct ip_fan_map *fan_map; | |
+ | |
+ rcu_read_lock(); | |
+ list_for_each_entry_rcu(fan_map, &vxlan->fan.fan_maps, list) { | |
+ if (fan_map->overlay == | |
+ (daddr & inet_make_mask(fan_map->overlay_prefix))) { | |
+ rcu_read_unlock(); | |
+ return fan_map; | |
+ } | |
+ } | |
+ rcu_read_unlock(); | |
+ | |
+ return NULL; | |
+} | |
+ | |
+static void vxlan_fan_flush_map(struct vxlan_dev *vxlan) | |
+{ | |
+ struct ip_fan_map *fan_map; | |
+ | |
+ list_for_each_entry_rcu(fan_map, &vxlan->fan.fan_maps, list) { | |
+ list_del_rcu(&fan_map->list); | |
+ kfree_rcu(fan_map, rcu); | |
+ } | |
+} | |
+ | |
+static int vxlan_fan_del_map(struct vxlan_dev *vxlan, __be32 overlay) | |
+{ | |
+ struct ip_fan_map *fan_map; | |
+ | |
+ fan_map = vxlan_fan_find_map(vxlan, overlay); | |
+ if (!fan_map) | |
+ return -ENOENT; | |
+ | |
+ list_del_rcu(&fan_map->list); | |
+ kfree_rcu(fan_map, rcu); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int vxlan_fan_add_map(struct vxlan_dev *vxlan, struct ifla_fan_map *map) | |
+{ | |
+ __be32 overlay_mask, underlay_mask; | |
+ struct ip_fan_map *fan_map; | |
+ | |
+ overlay_mask = inet_make_mask(map->overlay_prefix); | |
+ underlay_mask = inet_make_mask(map->underlay_prefix); | |
+ | |
+ netdev_dbg(vxlan->dev, "vfam: map: o %x/%d u %x/%d om %x um %x\n", | |
+ map->overlay, map->overlay_prefix, | |
+ map->underlay, map->underlay_prefix, | |
+ overlay_mask, underlay_mask); | |
+ | |
+ if ((map->overlay & ~overlay_mask) || (map->underlay & ~underlay_mask)) | |
+ return -EINVAL; | |
+ | |
+ if (!(map->overlay & overlay_mask) && (map->underlay & underlay_mask)) | |
+ return -EINVAL; | |
+ | |
+ /* Special case: overlay 0 and underlay 0: flush all mappings */ | |
+ if (!map->overlay && !map->underlay) { | |
+ vxlan_fan_flush_map(vxlan); | |
+ return 0; | |
+ } | |
+ | |
+ /* Special case: overlay set and underlay 0: clear map for overlay */ | |
+ if (!map->underlay) | |
+ return vxlan_fan_del_map(vxlan, map->overlay); | |
+ | |
+ if (vxlan_fan_find_map(vxlan, map->overlay)) | |
+ return -EEXIST; | |
+ | |
+ fan_map = kmalloc(sizeof(*fan_map), GFP_KERNEL); | |
+ fan_map->underlay = map->underlay; | |
+ fan_map->overlay = map->overlay; | |
+ fan_map->underlay_prefix = map->underlay_prefix; | |
+ fan_map->overlay_mask = ntohl(overlay_mask); | |
+ fan_map->overlay_prefix = map->overlay_prefix; | |
+ | |
+ list_add_tail_rcu(&fan_map->list, &vxlan->fan.fan_maps); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int vxlan_parse_fan_map(struct nlattr *data[], struct vxlan_dev *vxlan) | |
+{ | |
+ struct ifla_fan_map *map; | |
+ struct nlattr *attr; | |
+ int rem, rv; | |
+ | |
+ nla_for_each_nested(attr, data[IFLA_IPTUN_FAN_MAP], rem) { | |
+ map = nla_data(attr); | |
+ rv = vxlan_fan_add_map(vxlan, map); | |
+ if (rv) | |
+ return rv; | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int vxlan_fan_build_rdst(struct vxlan_dev *vxlan, struct sk_buff *skb, | |
+ struct vxlan_rdst *fan_rdst) | |
+{ | |
+ struct ip_fan_map *f_map; | |
+ union vxlan_addr *va; | |
+ u32 daddr, underlay; | |
+ struct arphdr *arp; | |
+ void *arp_ptr; | |
+ struct ethhdr *eth; | |
+ struct iphdr *iph; | |
+ | |
+ eth = eth_hdr(skb); | |
+ switch (eth->h_proto) { | |
+ case htons(ETH_P_IP): | |
+ iph = ip_hdr(skb); | |
+ if (!iph) | |
+ return -EINVAL; | |
+ daddr = iph->daddr; | |
+ break; | |
+ case htons(ETH_P_ARP): | |
+ arp = arp_hdr(skb); | |
+ if (!arp) | |
+ return -EINVAL; | |
+ arp_ptr = arp + 1; | |
+ netdev_dbg(vxlan->dev, | |
+ "vfbr: arp sha %pM sip %pI4 tha %pM tip %pI4\n", | |
+ arp_ptr, arp_ptr + skb->dev->addr_len, | |
+ arp_ptr + skb->dev->addr_len + 4, | |
+ arp_ptr + (skb->dev->addr_len * 2) + 4); | |
+ arp_ptr += (skb->dev->addr_len * 2) + 4; | |
+ memcpy(&daddr, arp_ptr, 4); | |
+ break; | |
+ default: | |
+ netdev_dbg(vxlan->dev, "vfbr: unknown eth p %x\n", eth->h_proto); | |
+ return -EINVAL; | |
+ } | |
+ | |
+ f_map = vxlan_fan_find_map(vxlan, daddr); | |
+ if (!f_map) | |
+ return -EINVAL; | |
+ | |
+ daddr = ntohl(daddr); | |
+ underlay = ntohl(f_map->underlay); | |
+ if (!underlay) | |
+ return -EINVAL; | |
+ | |
+ memset(fan_rdst, 0, sizeof(*fan_rdst)); | |
+ va = &fan_rdst->remote_ip; | |
+ va->sa.sa_family = AF_INET; | |
+ fan_rdst->remote_vni = vxlan->default_dst.remote_vni; | |
+ va->sin.sin_addr.s_addr = htonl(underlay | | |
+ ((daddr & ~f_map->overlay_mask) >> | |
+ (32 - f_map->overlay_prefix - | |
+ (32 - f_map->underlay_prefix)))); | |
+ netdev_dbg(vxlan->dev, "vfbr: daddr %x ul %x dst %x\n", | |
+ daddr, underlay, va->sin.sin_addr.s_addr); | |
+ | |
+ return 0; | |
+} | |
+ | |
#if IS_ENABLED(CONFIG_IPV6) | |
static inline | |
bool vxlan_addr_equal(const union vxlan_addr *a, const union vxlan_addr *b) | |
@@ -2044,6 +2206,13 @@ static void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, | |
goto rt_tx_error; | |
} | |
+ if (fan_has_map(&vxlan->fan) && rt->rt_flags & RTCF_LOCAL) { | |
+ netdev_dbg(dev, "discard fan to localhost %pI4\n", | |
+ &dst->sin.sin_addr.s_addr); | |
+ ip_rt_put(rt); | |
+ goto tx_free; | |
+ } | |
+ | |
/* Bypass encapsulation if the destination is local */ | |
if (!info && rt->rt_flags & RTCF_LOCAL && | |
!(rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))) { | |
@@ -2203,7 +2372,22 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev) | |
#endif | |
} | |
+ if (fan_has_map(&vxlan->fan)) { | |
+ struct vxlan_rdst fan_rdst; | |
+ | |
+ netdev_dbg(vxlan->dev, "vxlan_xmit p %x d %pM\n", | |
+ eth->h_proto, eth->h_dest); | |
+ if (vxlan_fan_build_rdst(vxlan, skb, &fan_rdst)) { | |
+ dev->stats.tx_dropped++; | |
+ kfree_skb(skb); | |
+ return NETDEV_TX_OK; | |
+ } | |
+ vxlan_xmit_one(skb, dev, &fan_rdst, 0); | |
+ return NETDEV_TX_OK; | |
+ } | |
+ | |
eth = eth_hdr(skb); | |
+ | |
f = vxlan_find_mac(vxlan, eth->h_dest); | |
did_rsc = false; | |
@@ -2569,6 +2753,8 @@ static void vxlan_setup(struct net_device *dev) | |
for (h = 0; h < FDB_HASH_SIZE; ++h) | |
INIT_HLIST_HEAD(&vxlan->fdb_head[h]); | |
+ | |
+ INIT_LIST_HEAD(&vxlan->fan.fan_maps); | |
} | |
static void vxlan_ether_setup(struct net_device *dev) | |
@@ -2940,7 +3126,9 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev, | |
static int vxlan_newlink(struct net *src_net, struct net_device *dev, | |
struct nlattr *tb[], struct nlattr *data[]) | |
{ | |
+ struct vxlan_dev *vxlan = netdev_priv(dev); | |
struct vxlan_config conf; | |
+ int err; | |
memset(&conf, 0, sizeof(conf)); | |
@@ -2957,6 +3145,12 @@ static int vxlan_newlink(struct net *src_net, struct net_device *dev, | |
conf.remote_ip.sa.sa_family = AF_INET6; | |
} | |
+ if (data[IFLA_VXLAN_FAN_MAP]) { | |
+ err = vxlan_parse_fan_map(data, vxlan); | |
+ if (err) | |
+ return err; | |
+ } | |
+ | |
if (data[IFLA_VXLAN_LOCAL]) { | |
conf.saddr.sin.sin_addr.s_addr = nla_get_in_addr(data[IFLA_VXLAN_LOCAL]); | |
conf.saddr.sa.sa_family = AF_INET; | |
@@ -3092,6 +3286,7 @@ static size_t vxlan_get_size(const struct net_device *dev) | |
nla_total_size(sizeof(__u8)) + /* IFLA_VXLAN_UDP_ZERO_CSUM6_RX */ | |
nla_total_size(sizeof(__u8)) + /* IFLA_VXLAN_REMCSUM_TX */ | |
nla_total_size(sizeof(__u8)) + /* IFLA_VXLAN_REMCSUM_RX */ | |
+ nla_total_size(sizeof(struct ip_fan_map) * 256) + | |
0; | |
} | |
@@ -3138,6 +3333,26 @@ static int vxlan_fill_info(struct sk_buff *skb, const struct net_device *dev) | |
} | |
} | |
+ if (fan_has_map(&vxlan->fan)) { | |
+ struct nlattr *fan_nest; | |
+ struct ip_fan_map *fan_map; | |
+ | |
+ fan_nest = nla_nest_start(skb, IFLA_VXLAN_FAN_MAP); | |
+ if (!fan_nest) | |
+ goto nla_put_failure; | |
+ list_for_each_entry_rcu(fan_map, &vxlan->fan.fan_maps, list) { | |
+ struct ifla_fan_map map; | |
+ | |
+ map.underlay = fan_map->underlay; | |
+ map.underlay_prefix = fan_map->underlay_prefix; | |
+ map.overlay = fan_map->overlay; | |
+ map.overlay_prefix = fan_map->overlay_prefix; | |
+ if (nla_put(skb, IFLA_FAN_MAPPING, sizeof(map), &map)) | |
+ goto nla_put_failure; | |
+ } | |
+ nla_nest_end(skb, fan_nest); | |
+ } | |
+ | |
if (nla_put_u8(skb, IFLA_VXLAN_TTL, vxlan->cfg.ttl) || | |
nla_put_u8(skb, IFLA_VXLAN_TOS, vxlan->cfg.tos) || | |
nla_put_be32(skb, IFLA_VXLAN_LABEL, vxlan->cfg.label) || | |
@@ -3297,6 +3512,22 @@ static __net_init int vxlan_init_net(struct net *net) | |
return 0; | |
} | |
+#ifdef CONFIG_SYSCTL | |
+static struct ctl_table_header *vxlan_fan_header; | |
+static unsigned int vxlan_fan_version = 4; | |
+ | |
+static struct ctl_table vxlan_fan_sysctls[] = { | |
+ { | |
+ .procname = "vxlan", | |
+ .data = &vxlan_fan_version, | |
+ .maxlen = sizeof(vxlan_fan_version), | |
+ .mode = 0444, | |
+ .proc_handler = proc_dointvec, | |
+ }, | |
+ {}, | |
+}; | |
+#endif /* CONFIG_SYSCTL */ | |
+ | |
static void __net_exit vxlan_exit_net(struct net *net) | |
{ | |
struct vxlan_net *vn = net_generic(net, vxlan_net_id); | |
@@ -3348,7 +3579,20 @@ static int __init vxlan_init_module(void) | |
if (rc) | |
goto out3; | |
+#ifdef CONFIG_SYSCTL | |
+ vxlan_fan_header = register_net_sysctl(&init_net, "net/fan", | |
+ vxlan_fan_sysctls); | |
+ if (!vxlan_fan_header) { | |
+ rc = -ENOMEM; | |
+ goto sysctl_failed; | |
+ } | |
+#endif /* CONFIG_SYSCTL */ | |
+ | |
return 0; | |
+#ifdef CONFIG_SYSCTL | |
+sysctl_failed: | |
+ rtnl_link_unregister(&vxlan_link_ops); | |
+#endif /* CONFIG_SYSCTL */ | |
out3: | |
unregister_netdevice_notifier(&vxlan_notifier_block); | |
out2: | |
@@ -3360,6 +3604,9 @@ static int __init vxlan_init_module(void) | |
static void __exit vxlan_cleanup_module(void) | |
{ | |
+#ifdef CONFIG_SYSCTL | |
+ unregister_net_sysctl_table(vxlan_fan_header); | |
+#endif /* CONFIG_SYSCTL */ | |
rtnl_link_unregister(&vxlan_link_ops); | |
unregister_netdevice_notifier(&vxlan_notifier_block); | |
unregister_pernet_subsys(&vxlan_net_ops); | |
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h | |
index 47848db..c716b69 100644 | |
--- a/include/net/ip_tunnels.h | |
+++ b/include/net/ip_tunnels.h | |
@@ -107,9 +107,18 @@ struct ip_tunnel_prl_entry { | |
*/ | |
#define FAN_OVERLAY_CNT 256 | |
+struct ip_fan_map { | |
+ __be32 underlay; | |
+ __be32 overlay; | |
+ u16 underlay_prefix; | |
+ u16 overlay_prefix; | |
+ u32 overlay_mask; | |
+ struct list_head list; | |
+ struct rcu_head rcu; | |
+}; | |
+ | |
struct ip_tunnel_fan { | |
-/* u32 __rcu *map;*/ | |
- u32 map[FAN_OVERLAY_CNT]; | |
+ struct list_head fan_maps; | |
}; | |
struct ip_tunnel { | |
@@ -165,7 +174,11 @@ struct ip_tunnel { | |
#define TUNNEL_NOCACHE __cpu_to_be16(0x2000) | |
#define TUNNEL_OPTIONS_PRESENT (TUNNEL_GENEVE_OPT | TUNNEL_VXLAN_OPT) | |
-#define TUNNEL_FAN __cpu_to_be16(0x4000) | |
+ | |
+static inline int fan_has_map(const struct ip_tunnel_fan *fan) | |
+{ | |
+ return !list_empty(&fan->fan_maps); | |
+} | |
struct tnl_ptk_info { | |
__be16 flags; | |
diff --git a/include/net/vxlan.h b/include/net/vxlan.h | |
index 308adc4..4eb56f8 100644 | |
--- a/include/net/vxlan.h | |
+++ b/include/net/vxlan.h | |
@@ -234,6 +234,8 @@ struct vxlan_dev { | |
struct vxlan_rdst default_dst; /* default destination */ | |
u32 flags; /* VXLAN_F_* in vxlan.h */ | |
+ struct ip_tunnel_fan fan; | |
+ | |
struct timer_list age_timer; | |
spinlock_t hash_lock; | |
unsigned int addrcnt; | |
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h | |
index b4fba66..957019d 100644 | |
--- a/include/uapi/linux/if_link.h | |
+++ b/include/uapi/linux/if_link.h | |
@@ -498,6 +498,7 @@ enum { | |
IFLA_VXLAN_COLLECT_METADATA, | |
IFLA_VXLAN_LABEL, | |
IFLA_VXLAN_GPE, | |
+ IFLA_VXLAN_FAN_MAP = 33, | |
__IFLA_VXLAN_MAX | |
}; | |
#define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) | |
diff --git a/include/uapi/linux/if_tunnel.h b/include/uapi/linux/if_tunnel.h | |
index 423e4fa..6ae69d9 100644 | |
--- a/include/uapi/linux/if_tunnel.h | |
+++ b/include/uapi/linux/if_tunnel.h | |
@@ -164,7 +164,7 @@ enum { | |
#define IFLA_FAN_MAX (__IFLA_FAN_MAX - 1) | |
-struct ip_tunnel_fan_map { | |
+struct ifla_fan_map { | |
__be32 underlay; | |
__be32 overlay; | |
__u16 underlay_prefix; | |
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c | |
index 11f55a1..bdafd67 100644 | |
--- a/net/ipv4/ip_tunnel.c | |
+++ b/net/ipv4/ip_tunnel.c | |
@@ -1100,11 +1100,6 @@ int ip_tunnel_newlink(struct net_device *dev, struct nlattr *tb[], | |
} | |
EXPORT_SYMBOL_GPL(ip_tunnel_newlink); | |
-static int ip_tunnel_is_fan(struct ip_tunnel *tunnel) | |
-{ | |
- return tunnel->parms.i_flags & TUNNEL_FAN; | |
-} | |
- | |
int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[], | |
struct ip_tunnel_parm *p) | |
{ | |
@@ -1114,7 +1109,7 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[], | |
struct ip_tunnel_net *itn = net_generic(net, tunnel->ip_tnl_net_id); | |
if (dev == itn->fb_tunnel_dev) | |
- return ip_tunnel_is_fan(tunnel) ? 0 : -EINVAL; | |
+ return fan_has_map(&tunnel->fan) ? 0 : -EINVAL; | |
t = ip_tunnel_find(itn, p, dev->type); | |
diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c | |
index 591caad..aa9e2c7 100644 | |
--- a/net/ipv4/ipip.c | |
+++ b/net/ipv4/ipip.c | |
@@ -107,6 +107,7 @@ | |
#include <linux/netfilter_ipv4.h> | |
#include <linux/if_ether.h> | |
#include <linux/inetdevice.h> | |
+#include <linux/rculist.h> | |
#include <net/sock.h> | |
#include <net/ip.h> | |
@@ -246,37 +247,144 @@ static int mplsip_rcv(struct sk_buff *skb) | |
} | |
#endif | |
-static int ipip_tunnel_is_fan(struct ip_tunnel *tunnel) | |
+static struct ip_fan_map *ipip_fan_find_map(struct ip_tunnel *t, __be32 daddr) | |
{ | |
- return tunnel->parms.i_flags & TUNNEL_FAN; | |
+ struct ip_fan_map *fan_map; | |
+ | |
+ rcu_read_lock(); | |
+ list_for_each_entry_rcu(fan_map, &t->fan.fan_maps, list) { | |
+ if (fan_map->overlay == | |
+ (daddr & inet_make_mask(fan_map->overlay_prefix))) { | |
+ rcu_read_unlock(); | |
+ return fan_map; | |
+ } | |
+ } | |
+ rcu_read_unlock(); | |
+ | |
+ return NULL; | |
} | |
-/* | |
- * Determine fan tunnel endpoint to send packet to, based on the inner IP | |
- * address. For an overlay (inner) address Y.A.B.C, the transformation is | |
- * F.G.A.B, where "F" and "G" are the first two octets of the underlay | |
- * network (the network portion of a /16), "A" and "B" are the low order | |
- * two octets of the underlay network host (the host portion of a /16), | |
- * and "Y" is a configured first octet of the overlay network. | |
+/* Determine fan tunnel endpoint to send packet to, based on the inner IP | |
+ * address. | |
+ * | |
+ * Given a /8 overlay and /16 underlay, for an overlay (inner) address | |
+ * Y.A.B.C, the transformation is F.G.A.B, where "F" and "G" are the first | |
+ * two octets of the underlay network (the network portion of a /16), "A" | |
+ * and "B" are the low order two octets of the underlay network host (the | |
+ * host portion of a /16), and "Y" is a configured first octet of the | |
+ * overlay network. | |
+ * | |
+ * E.g., underlay host 10.88.3.4/16 with an overlay of 99.0.0.0/8 would | |
+ * host overlay subnet 99.3.4.0/24. An overlay network datagram from | |
+ * 99.3.4.5 to 99.6.7.8, would be directed to underlay host 10.88.6.7, | |
+ * which hosts overlay network subnet 99.6.7.0/24. This transformation is | |
+ * described in detail further below. | |
+ * | |
+ * Using netmasks for the overlay and underlay other than /8 and /16, as | |
+ * shown above, can yield larger (or smaller) overlay subnets, with the | |
+ * trade-off of allowing fewer (or more) underlay hosts to participate. | |
+ * | |
+ * The size of each overlay network subnet is defined by the total of the | |
+ * network mask of the overlay plus the size of host portion of the | |
+ * underlay network. In the above example, /8 + /16 = /24. | |
+ * | |
+ * E.g., consider underlay host 10.99.238.5/20 and overlay 99.0.0.0/8. In | |
+ * this case, the network portion of the underlay is 10.99.224.0/20, and | |
+ * the host portion is 0.0.14.5 (12 bits). To determine the overlay | |
+ * network subnet, the 12 bits of host portion are left shifted 12 bits | |
+ * (/20 - /8) and ORed with the overlay subnet prefix. This yields an | |
+ * overlay subnet of 99.224.80/20, composed of 8 bits overlay, followed by | |
+ * 12 bits underlay. This yields 12 bits in the overlay network portion, | |
+ * allowing for 4094 addresses in each overlay network subnet. The | |
+ * trade-off is that fewer hosts may participate in the underlay network, | |
+ * as its host address size has shrunk from 16 bits (65534 addresses) in | |
+ * the first example to 12 bits (4094 addresses) here. | |
+ * | |
+ * For fewer hosts per overlay subnet (permitting a larger number of | |
+ * underlay hosts to participate), the underlay netmask may be made | |
+ * smaller. | |
+ * | |
+ * E.g., underlay host 10.111.1.2/12 (network 10.96.0.0/12, host portion | |
+ * is 0.15.1.2, 20 bits) with an overlay of 33.0.0.0/8 would left shift | |
+ * the 20 bits of host by 4 (so that it's highest order bit is adjacent to | |
+ * the lowest order bit of the /8 overlay). This yields an overlay subnet | |
+ * of 33.240.16.32/28 (8 bits overlay, 20 bits from the host portion of | |
+ * the underlay). This provides more addresses for the underlay network | |
+ * (approximately 2^20), but each host's segment of the overlay provides | |
+ * only 4 bits of addresses (14 usable). | |
+ * | |
+ * It is also possible to adjust the overlay subnet. | |
+ * | |
+ * For an overlay of 240.0.0.0/5 and underlay of 10.88.0.0/20, consider | |
+ * underlay host 10.88.129.2; the 12 bits of host, 0.0.1.2, are left | |
+ * shifted 15 bits (/20 - /5), yielding an overlay network of | |
+ * 240.129.0.0/17. An underlay host of 10.88.244.215 would yield an | |
+ * overlay network of 242.107.128.0/17. | |
+ * | |
+ * For an overlay of 100.64.0.0/10 and underlay of 10.224.220.0/24, for | |
+ * underlay host 10.224.220.10, the underlay host portion (.10) is left | |
+ * shifted 14 bits, yielding an overlay network subnet of 100.66.128.0/18. | |
+ * This would permit 254 addresses on the underlay, with each overlay | |
+ * segment providing approximately 2^14 - 2 addresses (16382). | |
+ * | |
+ * For packets being encapsulated, the overlay network destination IP | |
+ * address is deconstructed into its overlay and underlay-derived | |
+ * portions. The underlay portion (determined by the overlay mask and | |
+ * overlay subnet mask) is right shifted according to the size of the | |
+ * underlay network mask. This value is then ORed with the network | |
+ * portion of the underlay network to produce the underlay network | |
+ * destination for the encapsulated datagram. | |
+ * | |
+ * For example, using the initial example of underlay 10.88.3.4/16 and | |
+ * overlay 99.0.0.0/8, with underlay host 10.88.3.4/16 providing overlay | |
+ * subnet 99.3.4.0/24 with specfic host 99.3.4.5. A datagram from | |
+ * 99.3.4.5 to 99.6.7.8 would first have the underlay host derived portion | |
+ * of the address extracted. This is a number of bits equal to underlay | |
+ * network host portion. In the destination address, the highest order of | |
+ * these bits is one bit lower than the lowest order bit from the overlay | |
+ * network mask. | |
+ * | |
+ * Using the sample value, 99.6.7.8, the overlay mask is /8, and the | |
+ * underlay mask is /16 (leaving 16 bits for the host portion). The bits | |
+ * to be shifted are the middle two octets, 0.6.7.0, as this is 99.6.7.8 | |
+ * ANDed with the mask 0x00ffff00 (which is 16 bits, the highest order of | |
+ * which is 1 bit lower than the lowest order overlay address bit). | |
* | |
- * E.g., underlay host 10.88.3.4 with an overlay of 99 would host overlay | |
- * subnet 99.3.4.0/24. An overlay network datagram from 99.3.4.5 to | |
- * 99.6.7.8, would be directed to underlay host 10.88.6.7, which hosts | |
- * overlay network 99.6.7.0/24. | |
+ * These octets, 0.6.7.0, are then right shifted 8 bits, yielding 0.0.6.7. | |
+ * This value is then ORed with the underlay network portion, | |
+ * 10.88.0.0/16, providing 10.88.6.7 as the final underlay destination for | |
+ * the encapuslated datagram. | |
+ * | |
+ * Another transform using the final example: overlay 100.64.0.0/10 and | |
+ * underlay 10.224.220.0/24. Consider overlay address 100.66.128.1 | |
+ * sending a datagram to 100.66.200.5. In this case, 8 bits (the host | |
+ * portion size of 10.224.220.0/24) beginning after the 100.64/10 overlay | |
+ * prefix are masked off, yielding 0.2.192.0. This is right shifted 14 | |
+ * (32 - 10 - (32 - 24), i.e., the number of bits between the overlay | |
+ * network portion and the underlay host portion) bits, yielding 0.0.0.11. | |
+ * This is ORed with the underlay network portion, 10.224.220.0/24, giving | |
+ * the underlay destination of 10.224.220.11 for overlay destination | |
+ * 100.66.200.5. | |
*/ | |
static int ipip_build_fan_iphdr(struct ip_tunnel *tunnel, struct sk_buff *skb, struct iphdr *iph) | |
{ | |
- unsigned int overlay; | |
+ struct ip_fan_map *f_map; | |
u32 daddr, underlay; | |
+ f_map = ipip_fan_find_map(tunnel, ip_hdr(skb)->daddr); | |
+ if (!f_map) | |
+ return -ENOENT; | |
+ | |
daddr = ntohl(ip_hdr(skb)->daddr); | |
- overlay = daddr >> 24; | |
- underlay = tunnel->fan.map[overlay]; | |
+ underlay = ntohl(f_map->underlay); | |
if (!underlay) | |
return -EINVAL; | |
*iph = tunnel->parms.iph; | |
- iph->daddr = htonl(underlay | ((daddr >> 8) & 0x0000ffff)); | |
+ iph->daddr = htonl(underlay | | |
+ ((daddr & ~f_map->overlay_mask) >> | |
+ (32 - f_map->overlay_prefix - | |
+ (32 - f_map->underlay_prefix)))); | |
return 0; | |
} | |
@@ -311,7 +419,7 @@ static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb, | |
if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP4)) | |
goto tx_error; | |
- if (ipip_tunnel_is_fan(tunnel)) { | |
+ if (fan_has_map(&tunnel->fan)) { | |
if (ipip_build_fan_iphdr(tunnel, skb, &fiph)) | |
goto tx_error; | |
tiph = &fiph; | |
@@ -394,6 +502,8 @@ static bool ipip_tunnel_ioctl_verify_protocol(u8 ipproto) | |
static void ipip_tunnel_setup(struct net_device *dev) | |
{ | |
+ struct ip_tunnel *t = netdev_priv(dev); | |
+ | |
dev->netdev_ops = &ipip_netdev_ops; | |
dev->type = ARPHRD_TUNNEL; | |
@@ -405,6 +515,7 @@ static void ipip_tunnel_setup(struct net_device *dev) | |
dev->features |= IPIP_FEATURES; | |
dev->hw_features |= IPIP_FEATURES; | |
ip_tunnel_setup(dev, ipip_net_id); | |
+ INIT_LIST_HEAD(&t->fan.fan_maps); | |
} | |
static int ipip_tunnel_init(struct net_device *dev) | |
@@ -508,41 +619,65 @@ static bool ipip_netlink_encap_parms(struct nlattr *data[], | |
return ret; | |
} | |
-static void ipip_fan_free_map(struct ip_tunnel *t) | |
+static void ipip_fan_flush_map(struct ip_tunnel *t) | |
{ | |
- memset(&t->fan.map, 0, sizeof(t->fan.map)); | |
+ struct ip_fan_map *fan_map; | |
+ | |
+ list_for_each_entry_rcu(fan_map, &t->fan.fan_maps, list) { | |
+ list_del_rcu(&fan_map->list); | |
+ kfree_rcu(fan_map, rcu); | |
+ } | |
} | |
-static int ipip_fan_set_map(struct ip_tunnel *t, struct ip_tunnel_fan_map *map) | |
+static int ipip_fan_del_map(struct ip_tunnel *t, __be32 overlay) | |
{ | |
- u32 overlay, overlay_mask, underlay, underlay_mask; | |
+ struct ip_fan_map *fan_map; | |
- if ((map->underlay_prefix && map->underlay_prefix != 16) || | |
- (map->overlay_prefix && map->overlay_prefix != 8)) | |
- return -EINVAL; | |
+ fan_map = ipip_fan_find_map(t, overlay); | |
+ if (!fan_map) | |
+ return -ENOENT; | |
- overlay = ntohl(map->overlay); | |
- overlay_mask = ntohl(inet_make_mask(map->overlay_prefix)); | |
+ list_del_rcu(&fan_map->list); | |
+ kfree_rcu(fan_map, rcu); | |
- underlay = ntohl(map->underlay); | |
- underlay_mask = ntohl(inet_make_mask(map->underlay_prefix)); | |
+ return 0; | |
+} | |
- if ((overlay & ~overlay_mask) || (underlay & ~underlay_mask)) | |
- return -EINVAL; | |
+static int ipip_fan_add_map(struct ip_tunnel *t, struct ifla_fan_map *map) | |
+{ | |
+ __be32 overlay_mask, underlay_mask; | |
+ struct ip_fan_map *fan_map; | |
- if (!(overlay & overlay_mask) && (underlay & underlay_mask)) | |
+ overlay_mask = inet_make_mask(map->overlay_prefix); | |
+ underlay_mask = inet_make_mask(map->underlay_prefix); | |
+ | |
+ if ((map->overlay & ~overlay_mask) || (map->underlay & ~underlay_mask)) | |
return -EINVAL; | |
- t->parms.i_flags |= TUNNEL_FAN; | |
+ if (!(map->overlay & overlay_mask) && (map->underlay & underlay_mask)) | |
+ return -EINVAL; | |
- /* Special case: overlay 0 and underlay 0 clears all mappings */ | |
- if (!overlay && !underlay) { | |
- ipip_fan_free_map(t); | |
+ /* Special case: overlay 0 and underlay 0: flush all mappings */ | |
+ if (!map->overlay && !map->underlay) { | |
+ ipip_fan_flush_map(t); | |
return 0; | |
} | |
+ | |
+ /* Special case: overlay set and underlay 0: clear map for overlay */ | |
+ if (!map->underlay) | |
+ return ipip_fan_del_map(t, map->overlay); | |
+ | |
+ if (ipip_fan_find_map(t, map->overlay)) | |
+ return -EEXIST; | |
- overlay >>= (32 - map->overlay_prefix); | |
- t->fan.map[overlay] = underlay; | |
+ fan_map = kmalloc(sizeof(*fan_map), GFP_KERNEL); | |
+ fan_map->underlay = map->underlay; | |
+ fan_map->overlay = map->overlay; | |
+ fan_map->underlay_prefix = map->underlay_prefix; | |
+ fan_map->overlay_mask = ntohl(overlay_mask); | |
+ fan_map->overlay_prefix = map->overlay_prefix; | |
+ | |
+ list_add_tail_rcu(&fan_map->list, &t->fan.fan_maps); | |
return 0; | |
} | |
@@ -551,7 +686,7 @@ static int ipip_fan_set_map(struct ip_tunnel *t, struct ip_tunnel_fan_map *map) | |
static int ipip_netlink_fan(struct nlattr *data[], struct ip_tunnel *t, | |
struct ip_tunnel_parm *parms) | |
{ | |
- struct ip_tunnel_fan_map *map; | |
+ struct ifla_fan_map *map; | |
struct nlattr *attr; | |
int rem, rv; | |
@@ -563,7 +698,7 @@ static int ipip_netlink_fan(struct nlattr *data[], struct ip_tunnel *t, | |
nla_for_each_nested(attr, data[IFLA_IPTUN_FAN_MAP], rem) { | |
map = nla_data(attr); | |
- rv = ipip_fan_set_map(t, map); | |
+ rv = ipip_fan_add_map(t, map); | |
if (rv) | |
return rv; | |
} | |
@@ -577,7 +712,6 @@ static int ipip_newlink(struct net *src_net, struct net_device *dev, | |
struct ip_tunnel *t = netdev_priv(dev); | |
struct ip_tunnel_parm p; | |
struct ip_tunnel_encap ipencap; | |
- struct ip_tunnel *t = netdev_priv(dev); | |
int err; | |
if (ipip_netlink_encap_parms(data, &ipencap)) { | |
@@ -652,7 +786,7 @@ static size_t ipip_get_size(const struct net_device *dev) | |
/* IFLA_IPTUN_COLLECT_METADATA */ | |
nla_total_size(0) + | |
/* IFLA_IPTUN_FAN_MAP */ | |
- nla_total_size(sizeof(struct ip_tunnel_fan_map)) * 256 + | |
+ nla_total_size(sizeof(struct ifla_fan_map)) * 256 + | |
0; | |
} | |
@@ -684,25 +818,22 @@ static int ipip_fill_info(struct sk_buff *skb, const struct net_device *dev) | |
if (tunnel->collect_md) | |
if (nla_put_flag(skb, IFLA_IPTUN_COLLECT_METADATA)) | |
goto nla_put_failure; | |
- if (tunnel->parms.i_flags & TUNNEL_FAN) { | |
+ if (fan_has_map(&tunnel->fan)) { | |
struct nlattr *fan_nest; | |
- int i; | |
+ struct ip_fan_map *fan_map; | |
fan_nest = nla_nest_start(skb, IFLA_IPTUN_FAN_MAP); | |
if (!fan_nest) | |
goto nla_put_failure; | |
- for (i = 0; i < 256; i++) { | |
- if (tunnel->fan.map[i]) { | |
- struct ip_tunnel_fan_map map; | |
- | |
- map.underlay = htonl(tunnel->fan.map[i]); | |
- map.underlay_prefix = 16; | |
- map.overlay = htonl(i << 24); | |
- map.overlay_prefix = 8; | |
- if (nla_put(skb, IFLA_FAN_MAPPING, | |
- sizeof(map), &map)) | |
- goto nla_put_failure; | |
- } | |
+ list_for_each_entry_rcu(fan_map, &tunnel->fan.fan_maps, list) { | |
+ struct ifla_fan_map map; | |
+ | |
+ map.underlay = fan_map->underlay; | |
+ map.underlay_prefix = fan_map->underlay_prefix; | |
+ map.overlay = fan_map->overlay; | |
+ map.overlay_prefix = fan_map->overlay_prefix; | |
+ if (nla_put(skb, IFLA_FAN_MAPPING, sizeof(map), &map)) | |
+ goto nla_put_failure; | |
} | |
nla_nest_end(skb, fan_nest); | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 622dfd3603fda249254af5d1ae7043be06840ff4 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Tue, 19 Jan 2016 13:12:02 -0600 | |
Subject: [PATCH 38/76] UBUNTU: SAUCE: overlayfs: Skip permission checking for | |
trusted.overlayfs.* xattrs | |
The original mounter had CAP_SYS_ADMIN in the user namespace | |
where the mount happened, and the vfs has validated that the user | |
has permission to do the requested operation. This is sufficient | |
for allowing the kernel to write these specific xattrs, so we can | |
bypass the permission checks for these xattrs. | |
To support this, export __vfs_setxattr_noperm and add an similar | |
__vfs_removexattr_noperm which is also exported. Use these when | |
setting or removing trusted.overlayfs.* xattrs. | |
BugLink: http://bugs.launchpad.net/bugs/1531747 | |
BugLink: http://bugs.launchpad.net/bugs/1534961 | |
BugLink: http://bugs.launchpad.net/bugs/1535150 | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
fs/overlayfs/overlayfs.h | 16 ++++++++++++++-- | |
fs/xattr.c | 33 ++++++++++++++++++++++++++++++++- | |
include/linux/xattr.h | 1 + | |
3 files changed, 47 insertions(+), 3 deletions(-) | |
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h | |
index e218e74..099cb8c 100644 | |
--- a/fs/overlayfs/overlayfs.h | |
+++ b/fs/overlayfs/overlayfs.h | |
@@ -95,7 +95,13 @@ static inline int ovl_do_symlink(struct inode *dir, struct dentry *dentry, | |
static inline int ovl_do_setxattr(struct dentry *dentry, const char *name, | |
const void *value, size_t size, int flags) | |
{ | |
- int err = vfs_setxattr(dentry, name, value, size, flags); | |
+ struct inode *inode = dentry->d_inode; | |
+ int err; | |
+ | |
+ inode_lock(inode); | |
+ err = __vfs_setxattr_noperm(dentry, name, value, size, flags); | |
+ inode_unlock(inode); | |
+ | |
pr_debug("setxattr(%pd2, \"%s\", \"%*s\", 0x%x) = %i\n", | |
dentry, name, (int) size, (char *) value, flags, err); | |
return err; | |
@@ -103,7 +109,13 @@ static inline int ovl_do_setxattr(struct dentry *dentry, const char *name, | |
static inline int ovl_do_removexattr(struct dentry *dentry, const char *name) | |
{ | |
- int err = vfs_removexattr(dentry, name); | |
+ struct inode *inode = dentry->d_inode; | |
+ int err; | |
+ | |
+ inode_lock(inode); | |
+ err = __vfs_removexattr_noperm(dentry, name); | |
+ inode_unlock(inode); | |
+ | |
pr_debug("removexattr(%pd2, \"%s\") = %i\n", dentry, name, err); | |
return err; | |
} | |
diff --git a/fs/xattr.c b/fs/xattr.c | |
index 2d13b4e..afd9343 100644 | |
--- a/fs/xattr.c | |
+++ b/fs/xattr.c | |
@@ -202,6 +202,7 @@ int __vfs_setxattr_noperm(struct dentry *dentry, const char *name, | |
return error; | |
} | |
+EXPORT_SYMBOL_GPL(__vfs_setxattr_noperm); | |
int | |
@@ -364,6 +365,36 @@ int __vfs_setxattr_noperm(struct dentry *dentry, const char *name, | |
} | |
EXPORT_SYMBOL_GPL(vfs_listxattr); | |
+/** | |
+ * __vfs_removexattr_noperm - perform removexattr operation without | |
+ * performing permission checks. | |
+ * | |
+ * @dentry - object to perform setxattr on | |
+ * @name - xattr name to set | |
+ * | |
+ * returns the result of the internal setxattr or setsecurity operations. | |
+ * | |
+ * This function requires the caller to lock the inode's i_mutex before it | |
+ * is executed. It also assumes that the caller will make the appropriate | |
+ * permission checks. | |
+ */ | |
+int __vfs_removexattr_noperm(struct dentry *dentry, const char *name) | |
+{ | |
+ struct inode *inode = dentry->d_inode; | |
+ int error = -EOPNOTSUPP; | |
+ | |
+ if (inode->i_op->removexattr) { | |
+ error = inode->i_op->removexattr(dentry, name); | |
+ if (!error) { | |
+ fsnotify_xattr(dentry); | |
+ evm_inode_post_removexattr(dentry, name); | |
+ } | |
+ } | |
+ | |
+ return error; | |
+} | |
+EXPORT_SYMBOL_GPL(__vfs_removexattr_noperm); | |
+ | |
int | |
__vfs_removexattr(struct dentry *dentry, const char *name) | |
{ | |
@@ -394,7 +425,7 @@ int __vfs_setxattr_noperm(struct dentry *dentry, const char *name, | |
if (error) | |
goto out; | |
- error = __vfs_removexattr(dentry, name); | |
+ error = __vfs_removexattr_noperm(dentry, name); | |
if (!error) { | |
fsnotify_xattr(dentry); | |
diff --git a/include/linux/xattr.h b/include/linux/xattr.h | |
index e77605a..6fb2dfc 100644 | |
--- a/include/linux/xattr.h | |
+++ b/include/linux/xattr.h | |
@@ -53,6 +53,7 @@ struct xattr { | |
int __vfs_setxattr_noperm(struct dentry *, const char *, const void *, size_t, int); | |
int vfs_setxattr(struct dentry *, const char *, const void *, size_t, int); | |
int __vfs_removexattr(struct dentry *, const char *); | |
+int __vfs_removexattr_noperm(struct dentry *dentry, const char *name); | |
int vfs_removexattr(struct dentry *, const char *); | |
ssize_t generic_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From cbd0a9cfd25f30df9bc2d0d89851ae2a87823ab5 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Thu, 21 Jan 2016 11:52:04 -0600 | |
Subject: [PATCH 39/76] UBUNTU: SAUCE: overlayfs: Be more careful about copying | |
up sxid files | |
When an overlayfs filesystem's lowerdir is on a nosuid filesystem | |
but the upperdir is not, it's possible to copy up an sxid file or | |
stick directory into upperdir without changing the mode by | |
opening the file rw in the overlayfs mount without writing to it. | |
This makes it possible to bypass the nosuid restriction on the | |
lowerdir mount. | |
It's a bad idea in general to let the mounter copy up a sxid file | |
if the mounter wouldn't have had permission to create the sxid | |
file in the first place. Therefore change ovl_set_xattr to | |
exclude these bits when initially setting the mode, then set the | |
full mode after setting the user for the inode. This allows copy | |
up for non-sxid files to work as before but causes copy up to | |
fail for the cases where the user could not have created the sxid | |
inode in upperdir. | |
BugLink: http://bugs.launchpad.net/bugs/1534961 | |
BugLink: http://bugs.launchpad.net/bugs/1535150 | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
fs/overlayfs/copy_up.c | 19 ++++++++++++++++++- | |
1 file changed, 18 insertions(+), 1 deletion(-) | |
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c | |
index 36795ee..5b5ee09 100644 | |
--- a/fs/overlayfs/copy_up.c | |
+++ b/fs/overlayfs/copy_up.c | |
@@ -202,10 +202,19 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat) | |
{ | |
int err = 0; | |
+ /* | |
+ * For the most part we want to set the mode bits before setting | |
+ * the user, otherwise the current context might lack permission | |
+ * for setting the mode. However for sxid/sticky bits we want | |
+ * the operation to fail if the current user isn't privileged | |
+ * towards the resulting inode. So we first set the mode but | |
+ * exclude the sxid/sticky bits, then set the user, then set the | |
+ * mode again if any of the sxid/sticky bits are set. | |
+ */ | |
if (!S_ISLNK(stat->mode)) { | |
struct iattr attr = { | |
.ia_valid = ATTR_MODE, | |
- .ia_mode = stat->mode, | |
+ .ia_mode = stat->mode & ~(S_ISUID|S_ISGID|S_ISVTX), | |
}; | |
err = notify_change(upperdentry, &attr, NULL); | |
} | |
@@ -217,6 +226,14 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat) | |
}; | |
err = notify_change(upperdentry, &attr, NULL); | |
} | |
+ if (!err && !S_ISLNK(stat->mode) && | |
+ (stat->mode & (S_ISUID|S_ISGID|S_ISVTX))) { | |
+ struct iattr attr = { | |
+ .ia_valid = ATTR_MODE, | |
+ .ia_mode = stat->mode, | |
+ }; | |
+ err = notify_change(upperdentry, &attr, NULL); | |
+ } | |
if (!err) | |
ovl_set_timestamps(upperdentry, stat); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 3927991e47be62fe8db56e76c71731d77b072a27 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Thu, 21 Jan 2016 15:37:53 -0600 | |
Subject: [PATCH 40/76] UBUNTU: SAUCE: overlayfs: Propogate nosuid from lower | |
and upper mounts | |
An overlayfs mount using an upper or lower directory from a | |
nosuid filesystem bypasses this restriction. Change this so | |
that if any lower or upper directory is nosuid at mount time the | |
overlayfs superblock is marked nosuid. This requires some | |
additions at the vfs level since nosuid currently only applies to | |
mounts, so a SB_I_NOSUID flag is added along with a helper | |
function to check a path for nosuid in both the mount and the | |
superblock. | |
BugLink: http://bugs.launchpad.net/bugs/1534961 | |
BugLink: http://bugs.launchpad.net/bugs/1535150 | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
fs/exec.c | 9 ++++++++- | |
fs/overlayfs/super.c | 6 ++++++ | |
include/linux/fs.h | 2 ++ | |
security/commoncap.c | 2 +- | |
security/selinux/hooks.c | 2 +- | |
5 files changed, 18 insertions(+), 3 deletions(-) | |
diff --git a/fs/exec.c b/fs/exec.c | |
index 6e034ff..02d0ac64 100644 | |
--- a/fs/exec.c | |
+++ b/fs/exec.c | |
@@ -107,6 +107,13 @@ bool path_noexec(const struct path *path) | |
(path->mnt->mnt_sb->s_iflags & SB_I_NOEXEC); | |
} | |
+bool path_nosuid(const struct path *path) | |
+{ | |
+ return !mnt_may_suid(path->mnt) || | |
+ (path->mnt->mnt_sb->s_iflags & SB_I_NOSUID); | |
+} | |
+EXPORT_SYMBOL(path_nosuid); | |
+ | |
#ifdef CONFIG_USELIB | |
/* | |
* Note that a shared library must be both readable and executable due to | |
@@ -1475,7 +1482,7 @@ static void bprm_fill_uid(struct linux_binprm *bprm) | |
bprm->cred->euid = current_euid(); | |
bprm->cred->egid = current_egid(); | |
- if (!mnt_may_suid(bprm->file->f_path.mnt)) | |
+ if (path_nosuid(&bprm->file->f_path)) | |
return; | |
if (task_no_new_privs(current)) | |
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c | |
index 0e10085..7adc3f1 100644 | |
--- a/fs/overlayfs/super.c | |
+++ b/fs/overlayfs/super.c | |
@@ -1248,6 +1248,9 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) | |
sb->s_time_gran = ufs->upper_mnt->mnt_sb->s_time_gran; | |
+ if (ufs->upper_mnt->mnt_flags & MNT_NOSUID) | |
+ sb->s_iflags |= SB_I_NOSUID; | |
+ | |
ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry); | |
err = PTR_ERR(ufs->workdir); | |
if (IS_ERR(ufs->workdir)) { | |
@@ -1296,6 +1299,9 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) | |
*/ | |
mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME; | |
+ if (mnt->mnt_flags & MNT_NOSUID) | |
+ sb->s_iflags |= SB_I_NOSUID; | |
+ | |
ufs->lower_mnt[ufs->numlower] = mnt; | |
ufs->numlower++; | |
} | |
diff --git a/include/linux/fs.h b/include/linux/fs.h | |
index dc0478c..cdec4d0 100644 | |
--- a/include/linux/fs.h | |
+++ b/include/linux/fs.h | |
@@ -1313,6 +1313,7 @@ struct fasync_struct { | |
#define SB_I_CGROUPWB 0x00000001 /* cgroup-aware writeback enabled */ | |
#define SB_I_NOEXEC 0x00000002 /* Ignore executables on this fs */ | |
#define SB_I_NODEV 0x00000004 /* Ignore devices on this fs */ | |
+#define SB_I_NOSUID 0x00000008 /* Ignore suid on this fs */ | |
/* sb->s_iflags to limit user namespace mounts */ | |
#define SB_I_USERNS_VISIBLE 0x00000010 /* fstype already mounted */ | |
@@ -3207,6 +3208,7 @@ static inline bool dir_relax_shared(struct inode *inode) | |
} | |
extern bool path_noexec(const struct path *path); | |
+extern bool path_nosuid(const struct path *path); | |
extern void inode_nohighmem(struct inode *inode); | |
#endif /* _LINUX_FS_H */ | |
diff --git a/security/commoncap.c b/security/commoncap.c | |
index 8df676f..ab5af4c 100644 | |
--- a/security/commoncap.c | |
+++ b/security/commoncap.c | |
@@ -448,7 +448,7 @@ static int get_file_caps(struct linux_binprm *bprm, bool *effective, bool *has_c | |
if (!file_caps_enabled) | |
return 0; | |
- if (!mnt_may_suid(bprm->file->f_path.mnt)) | |
+ if (path_nosuid(&bprm->file->f_path)) | |
return 0; | |
/* | |
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c | |
index c2da45a..9751dfd 100644 | |
--- a/security/selinux/hooks.c | |
+++ b/security/selinux/hooks.c | |
@@ -2277,7 +2277,7 @@ static int check_nnp_nosuid(const struct linux_binprm *bprm, | |
const struct task_security_struct *new_tsec) | |
{ | |
int nnp = (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS); | |
- int nosuid = !mnt_may_suid(bprm->file->f_path.mnt); | |
+ int nosuid = path_nosuid(&bprm->file->f_path); | |
int rc; | |
if (!nnp && !nosuid) | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 5b43dacf4a554336713b70316fa966c68b5d6491 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Mon, 27 Jul 2015 09:16:54 -0500 | |
Subject: [PATCH 41/76] UBUNTU: SAUCE: overlayfs: Enable user namespace mounts | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
Signed-off-by: Leann Ogasawara <leann.ogasawara@canonical.com> | |
--- | |
fs/overlayfs/super.c | 1 + | |
1 file changed, 1 insertion(+) | |
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c | |
index 7adc3f1..48c2681 100644 | |
--- a/fs/overlayfs/super.c | |
+++ b/fs/overlayfs/super.c | |
@@ -1398,6 +1398,7 @@ static struct dentry *ovl_mount(struct file_system_type *fs_type, int flags, | |
.name = "overlay", | |
.mount = ovl_mount, | |
.kill_sb = kill_anon_super, | |
+ .fs_flags = FS_USERNS_MOUNT, | |
}; | |
MODULE_ALIAS_FS("overlay"); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From e6e6bed27041f9e421f20b09c5f901daed02c668 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Wed, 14 Sep 2016 15:23:55 -0700 | |
Subject: [PATCH 42/76] apparmor: add interface to be able to grab loaded | |
policy | |
Check point/restore needs to be able to grab policy currently loaded | |
into the kernel. | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 156 +++++++++++++++++++++++++++--- | |
security/apparmor/crypto.c | 37 +++++++ | |
security/apparmor/include/apparmorfs.h | 5 + | |
security/apparmor/include/crypto.h | 5 + | |
security/apparmor/include/policy.h | 5 +- | |
security/apparmor/include/policy_unpack.h | 27 +++++- | |
security/apparmor/policy.c | 12 ++- | |
security/apparmor/policy_unpack.c | 29 ++++-- | |
8 files changed, 248 insertions(+), 28 deletions(-) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index b22ccd7..b11ce28 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -35,6 +35,7 @@ | |
#include "include/policy.h" | |
#include "include/resource.h" | |
#include "include/lib.h" | |
+#include "include/policy_unpack.h" | |
/** | |
* aa_mangle_name - mangle a profile name to std profile layout form | |
@@ -85,11 +86,12 @@ static int mangle_name(const char *name, char *target) | |
* Returns: kernel buffer containing copy of user buffer data or an | |
* ERR_PTR on failure. | |
*/ | |
-static char *aa_simple_write_to_buffer(const char __user *userbuf, | |
- size_t alloc_size, size_t copy_size, | |
- loff_t *pos) | |
+static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, | |
+ size_t alloc_size, | |
+ size_t copy_size, | |
+ loff_t *pos) | |
{ | |
- char *data; | |
+ struct aa_loaddata *data; | |
BUG_ON(copy_size > alloc_size); | |
@@ -98,11 +100,15 @@ static char *aa_simple_write_to_buffer(const char __user *userbuf, | |
return ERR_PTR(-ESPIPE); | |
/* freed by caller to simple_write_to_buffer */ | |
- data = kvmalloc(alloc_size); | |
+ data = kvmalloc(sizeof(*data) + alloc_size); | |
if (data == NULL) | |
return ERR_PTR(-ENOMEM); | |
+ kref_init(&data->count); | |
+ data->size = copy_size; | |
+ data->hash = NULL; | |
+ data->abi = 0; | |
- if (copy_from_user(data, userbuf, copy_size)) { | |
+ if (copy_from_user(data->data, userbuf, copy_size)) { | |
kvfree(data); | |
return ERR_PTR(-EFAULT); | |
} | |
@@ -115,7 +121,7 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, | |
{ | |
struct aa_label *label; | |
ssize_t error; | |
- char *data; | |
+ struct aa_loaddata *data; | |
label = aa_begin_current_label(DO_UPDATE); | |
@@ -129,8 +135,8 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, | |
data = aa_simple_write_to_buffer(buf, size, size, pos); | |
error = PTR_ERR(data); | |
if (!IS_ERR(data)) { | |
- error = aa_replace_profiles(label, mask, data, size); | |
- kvfree(data); | |
+ error = aa_replace_profiles(label, mask, data); | |
+ aa_put_loaddata(data); | |
} | |
aa_end_current_label(label); | |
@@ -166,9 +172,9 @@ static ssize_t profile_replace(struct file *f, const char __user *buf, | |
static ssize_t profile_remove(struct file *f, const char __user *buf, | |
size_t size, loff_t *pos) | |
{ | |
+ struct aa_loaddata *data; | |
struct aa_label *label; | |
ssize_t error; | |
- char *data; | |
label = aa_begin_current_label(DO_UPDATE); | |
/* high level check about policy management - fine grained in | |
@@ -186,9 +192,9 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, | |
error = PTR_ERR(data); | |
if (!IS_ERR(data)) { | |
- data[size] = 0; | |
- error = aa_remove_profiles(label, data, size); | |
- kvfree(data); | |
+ data->data[size] = 0; | |
+ error = aa_remove_profiles(label, data->data, size); | |
+ aa_put_loaddata(data); | |
} | |
aa_end_current_label(label); | |
@@ -643,6 +649,102 @@ static int aa_fs_seq_hash_open(struct inode *inode, struct file *file) | |
.release = single_release, | |
}; | |
+static int rawdata_release(struct inode *inode, struct file *file) | |
+{ | |
+ /* TODO: switch to loaddata when profile switched to symlink */ | |
+ aa_put_proxy(file->private_data); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int aa_fs_seq_raw_abi_show(struct seq_file *seq, void *v) | |
+{ | |
+ struct aa_proxy *proxy = seq->private; | |
+ struct aa_label *label = aa_get_label_rcu(&proxy->label); | |
+ struct aa_profile *profile = labels_profile(label); | |
+ | |
+ if (profile->rawdata->abi) { | |
+ seq_printf(seq, "v%d", profile->rawdata->abi); | |
+ seq_puts(seq, "\n"); | |
+ } | |
+ aa_put_label(label); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int aa_fs_seq_raw_abi_open(struct inode *inode, struct file *file) | |
+{ | |
+ return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_abi_show); | |
+} | |
+ | |
+static const struct file_operations aa_fs_seq_raw_abi_fops = { | |
+ .owner = THIS_MODULE, | |
+ .open = aa_fs_seq_raw_abi_open, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = aa_fs_seq_profile_release, | |
+}; | |
+ | |
+static int aa_fs_seq_raw_hash_show(struct seq_file *seq, void *v) | |
+{ | |
+ struct aa_proxy *proxy = seq->private; | |
+ struct aa_label *label = aa_get_label_rcu(&proxy->label); | |
+ struct aa_profile *profile = labels_profile(label); | |
+ unsigned int i, size = aa_hash_size(); | |
+ | |
+ if (profile->rawdata->hash) { | |
+ for (i = 0; i < size; i++) | |
+ seq_printf(seq, "%.2x", profile->rawdata->hash[i]); | |
+ seq_puts(seq, "\n"); | |
+ } | |
+ aa_put_label(label); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int aa_fs_seq_raw_hash_open(struct inode *inode, struct file *file) | |
+{ | |
+ return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_hash_show); | |
+} | |
+ | |
+static const struct file_operations aa_fs_seq_raw_hash_fops = { | |
+ .owner = THIS_MODULE, | |
+ .open = aa_fs_seq_raw_hash_open, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = aa_fs_seq_profile_release, | |
+}; | |
+ | |
+static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, | |
+ loff_t *ppos) | |
+{ | |
+ struct aa_proxy *proxy = file->private_data; | |
+ struct aa_label *label = aa_get_label_rcu(&proxy->label); | |
+ struct aa_profile *profile = labels_profile(label); | |
+ | |
+ ssize_t ret = simple_read_from_buffer(buf, size, ppos, profile->rawdata->data, profile->rawdata->size); | |
+ aa_put_label(label); | |
+ | |
+ return ret; | |
+} | |
+ | |
+static int rawdata_open(struct inode *inode, struct file *file) | |
+{ | |
+ if (!policy_view_capable()) | |
+ return -EACCES; | |
+ | |
+ file->private_data = aa_get_proxy(inode->i_private); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static const struct file_operations aa_fs_rawdata_fops = { | |
+ .open = rawdata_open, | |
+ .read = rawdata_read, | |
+ .llseek = generic_file_llseek, | |
+ .release = rawdata_release, | |
+}; | |
+ | |
/** fns to setup dynamic per profile/namespace files **/ | |
/** | |
@@ -774,6 +876,29 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) | |
profile->dents[AAFS_PROF_HASH] = dent; | |
} | |
+ if (profile->rawdata) { | |
+ dent = create_profile_file(dir, "raw_hash", profile, | |
+ &aa_fs_seq_raw_hash_fops); | |
+ if (IS_ERR(dent)) | |
+ goto fail; | |
+ profile->dents[AAFS_PROF_RAW_HASH] = dent; | |
+ | |
+ dent = create_profile_file(dir, "raw_abi", profile, | |
+ &aa_fs_seq_raw_abi_fops); | |
+ if (IS_ERR(dent)) | |
+ goto fail; | |
+ profile->dents[AAFS_PROF_RAW_ABI] = dent; | |
+ | |
+ dent = securityfs_create_file("raw_data", S_IFREG | 0444, dir, | |
+ profile->label.proxy, | |
+ &aa_fs_rawdata_fops); | |
+ if (IS_ERR(dent)) | |
+ goto fail; | |
+ profile->dents[AAFS_PROF_RAW_DATA] = dent; | |
+ d_inode(dent)->i_size = profile->rawdata->size; | |
+ aa_get_proxy(profile->label.proxy); | |
+ } | |
+ | |
list_for_each_entry(child, &profile->base.profiles, base.list) { | |
error = __aa_fs_profile_mkdir(child, prof_child_dir(profile)); | |
if (error) | |
@@ -848,6 +973,11 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) | |
goto fail; | |
ns_subprofs_dir(ns) = dent; | |
+ dent = securityfs_create_dir("raw_data", dir); | |
+ if (IS_ERR(dent)) | |
+ goto fail; | |
+ ns_subdata_dir(ns) = dent; | |
+ | |
dent = securityfs_create_dir("namespaces", dir); | |
if (IS_ERR(dent)) | |
goto fail; | |
diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c | |
index b75dab0..5db5fe6 100644 | |
--- a/security/apparmor/crypto.c | |
+++ b/security/apparmor/crypto.c | |
@@ -29,6 +29,43 @@ unsigned int aa_hash_size(void) | |
return apparmor_hash_size; | |
} | |
+char *aa_calc_hash(void *data, size_t len) | |
+{ | |
+ struct { | |
+ struct shash_desc shash; | |
+ char ctx[crypto_shash_descsize(apparmor_tfm)]; | |
+ } desc; | |
+ char *hash = NULL; | |
+ int error = -ENOMEM; | |
+ | |
+ if (!apparmor_tfm) | |
+ return NULL; | |
+ | |
+ hash = kzalloc(apparmor_hash_size, GFP_KERNEL); | |
+ if (!hash) | |
+ goto fail; | |
+ | |
+ desc.shash.tfm = apparmor_tfm; | |
+ desc.shash.flags = 0; | |
+ | |
+ error = crypto_shash_init(&desc.shash); | |
+ if (error) | |
+ goto fail; | |
+ error = crypto_shash_update(&desc.shash, (u8 *) data, len); | |
+ if (error) | |
+ goto fail; | |
+ error = crypto_shash_final(&desc.shash, hash); | |
+ if (error) | |
+ goto fail; | |
+ | |
+ return hash; | |
+ | |
+fail: | |
+ kfree(hash); | |
+ | |
+ return ERR_PTR(error); | |
+} | |
+ | |
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, | |
size_t len) | |
{ | |
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h | |
index c6f84dc..1524241 100644 | |
--- a/security/apparmor/include/apparmorfs.h | |
+++ b/security/apparmor/include/apparmorfs.h | |
@@ -70,6 +70,7 @@ enum aafs_ns_type { | |
AAFS_NS_DIR, | |
AAFS_NS_PROFS, | |
AAFS_NS_NS, | |
+ AAFS_NS_RAW_DATA, | |
AAFS_NS_COUNT, | |
AAFS_NS_MAX_COUNT, | |
AAFS_NS_SIZE, | |
@@ -85,12 +86,16 @@ enum aafs_prof_type { | |
AAFS_PROF_MODE, | |
AAFS_PROF_ATTACH, | |
AAFS_PROF_HASH, | |
+ AAFS_PROF_RAW_DATA, | |
+ AAFS_PROF_RAW_HASH, | |
+ AAFS_PROF_RAW_ABI, | |
AAFS_PROF_SIZEOF, | |
}; | |
#define ns_dir(X) ((X)->dents[AAFS_NS_DIR]) | |
#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS]) | |
#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS]) | |
+#define ns_subdata_dir(X) ((X)->dents[AAFS_NS_RAW_DATA]) | |
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR]) | |
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS]) | |
diff --git a/security/apparmor/include/crypto.h b/security/apparmor/include/crypto.h | |
index dc418e5..c1469f8 100644 | |
--- a/security/apparmor/include/crypto.h | |
+++ b/security/apparmor/include/crypto.h | |
@@ -18,9 +18,14 @@ | |
#ifdef CONFIG_SECURITY_APPARMOR_HASH | |
unsigned int aa_hash_size(void); | |
+char *aa_calc_hash(void *data, size_t len); | |
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, | |
size_t len); | |
#else | |
+static inline char *aa_calc_hash(void *data, size_t len) | |
+{ | |
+ return NULL; | |
+} | |
static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version, | |
void *start, size_t len) | |
{ | |
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h | |
index 0686712..657d1a5 100644 | |
--- a/security/apparmor/include/policy.h | |
+++ b/security/apparmor/include/policy.h | |
@@ -149,6 +149,7 @@ struct aa_profile { | |
struct aa_net net; | |
struct aa_rlimit rlimits; | |
+ struct aa_loaddata *rawdata; | |
unsigned char *hash; | |
char *dirname; | |
struct dentry *dents[AAFS_PROF_SIZEOF]; | |
@@ -184,8 +185,8 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, | |
const char *fqname, size_t n); | |
struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name); | |
-ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, | |
- size_t size); | |
+ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, | |
+ struct aa_loaddata *udata); | |
ssize_t aa_remove_profiles(struct aa_label *label, char *name, size_t size); | |
void __aa_profile_list_release(struct list_head *head); | |
diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h | |
index c2f7a96..4c1319e 100644 | |
--- a/security/apparmor/include/policy_unpack.h | |
+++ b/security/apparmor/include/policy_unpack.h | |
@@ -16,6 +16,7 @@ | |
#define __POLICY_INTERFACE_H | |
#include <linux/list.h> | |
+#include <linux/kref.h> | |
struct aa_load_ent { | |
struct list_head list; | |
@@ -35,6 +36,30 @@ struct aa_load_ent { | |
#define PACKED_MODE_KILL 2 | |
#define PACKED_MODE_UNCONFINED 3 | |
-int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns); | |
+/* struct aa_loaddata - buffer of policy load data set */ | |
+struct aa_loaddata { | |
+ struct kref count; | |
+ size_t size; | |
+ int abi; | |
+ unsigned char *hash; | |
+ char data[]; | |
+}; | |
+ | |
+int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns); | |
+ | |
+static inline struct aa_loaddata * | |
+aa_get_loaddata(struct aa_loaddata *data) | |
+{ | |
+ if (data) | |
+ kref_get(&(data->count)); | |
+ return data; | |
+} | |
+ | |
+void aa_loaddata_kref(struct kref *kref); | |
+static inline void aa_put_loaddata(struct aa_loaddata *data) | |
+{ | |
+ if (data) | |
+ kref_put(&data->count, aa_loaddata_kref); | |
+} | |
#endif /* __POLICY_INTERFACE_H */ | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index 4d860d8..fa85b74 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -243,6 +243,8 @@ void aa_free_profile(struct aa_profile *profile) | |
} | |
kzfree(profile->hash); | |
+ aa_put_loaddata(profile->rawdata); | |
+ | |
kzfree(profile); | |
} | |
@@ -840,7 +842,6 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) | |
* @label: label that is attempting to load/replace policy | |
* @mask: permission mask | |
* @udata: serialized data stream (NOT NULL) | |
- * @size: size of the serialized data stream | |
* | |
* unpack and replace a profile on the profile list and uses of that profile | |
* by any aa_task_ctx. If the profile does not exist on the profile list | |
@@ -848,8 +849,8 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) | |
* | |
* Returns: size of data consumed else error code on failure. | |
*/ | |
-ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, | |
- size_t size) | |
+ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, | |
+ struct aa_loaddata *udata) | |
{ | |
const char *ns_name, *info = NULL; | |
struct aa_ns *ns = NULL; | |
@@ -860,7 +861,7 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, | |
LIST_HEAD(lh); | |
/* released below */ | |
- error = aa_unpack(udata, size, &lh, &ns_name); | |
+ error = aa_unpack(udata, &lh, &ns_name); | |
if (error) | |
goto out; | |
@@ -903,6 +904,7 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, | |
list_for_each_entry(ent, &lh, list) { | |
struct aa_policy *policy; | |
+ ent->new->rawdata = aa_get_loaddata(udata); | |
error = __lookup_replace(ns, ent->new->base.hname, | |
!(mask & AA_MAY_REPLACE_POLICY), | |
&ent->old, &info); | |
@@ -995,7 +997,7 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, | |
if (error) | |
return error; | |
- return size; | |
+ return udata->size; | |
fail_lock: | |
mutex_unlock(&ns->lock); | |
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c | |
index 029ae14..da7b81f 100644 | |
--- a/security/apparmor/policy_unpack.c | |
+++ b/security/apparmor/policy_unpack.c | |
@@ -125,6 +125,15 @@ static int audit_iface(struct aa_profile *new, const char *ns_name, | |
return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb); | |
} | |
+void aa_loaddata_kref(struct kref *kref) | |
+{ | |
+ struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count); | |
+ if (d) { | |
+ kzfree(d->hash); | |
+ kvfree(d); | |
+ } | |
+} | |
+ | |
/* test if read will be in packed data bounds */ | |
static bool inbounds(struct aa_ext *e, size_t size) | |
{ | |
@@ -894,7 +903,6 @@ struct aa_load_ent *aa_load_ent_alloc(void) | |
/** | |
* aa_unpack - unpack packed binary profile(s) data loaded from user space | |
* @udata: user data copied to kmem (NOT NULL) | |
- * @size: the size of the user data | |
* @lh: list to place unpacked profiles in a aa_repl_ws | |
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL) | |
* | |
@@ -904,15 +912,15 @@ struct aa_load_ent *aa_load_ent_alloc(void) | |
* | |
* Returns: profile(s) on @lh else error pointer if fails to unpack | |
*/ | |
-int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) | |
+int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns) | |
{ | |
struct aa_load_ent *tmp, *ent; | |
struct aa_profile *profile = NULL; | |
int error; | |
struct aa_ext e = { | |
- .start = udata, | |
- .end = udata + size, | |
- .pos = udata, | |
+ .start = udata->data, | |
+ .end = udata->data + udata->size, | |
+ .pos = udata->data, | |
}; | |
*ns = NULL; | |
@@ -922,7 +930,6 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) | |
error = verify_header(&e, e.pos == e.start, ns); | |
if (error) | |
goto fail; | |
- | |
start = e.pos; | |
profile = unpack_profile(&e, &ns_name); | |
if (IS_ERR(profile)) { | |
@@ -950,7 +957,15 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) | |
ent->ns_name = ns_name; | |
list_add_tail(&ent->list, lh); | |
} | |
- | |
+ udata->abi = e.version & K_ABI_MASK; | |
+ if (aa_g_hash_policy) { | |
+ udata->hash = aa_calc_hash(udata->data, udata->size); | |
+ if (IS_ERR(udata->hash)) { | |
+ error = PTR_ERR(udata->hash); | |
+ udata->hash = NULL; | |
+ goto fail; | |
+ } | |
+ } | |
return 0; | |
fail_profile: | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From d7b980149e224332437e31200583502df6dbacd9 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Sun, 24 Jul 2016 16:06:14 -0700 | |
Subject: [PATCH 43/76] securityfs: update interface to allow inode_ops, and | |
setup from vfs fns | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
include/linux/security.h | 32 ++++++++++++++- | |
security/inode.c | 103 +++++++++++++++++++++++++++++++++++------------ | |
2 files changed, 109 insertions(+), 26 deletions(-) | |
diff --git a/include/linux/security.h b/include/linux/security.h | |
index c2125e9..f257e72 100644 | |
--- a/include/linux/security.h | |
+++ b/include/linux/security.h | |
@@ -1633,7 +1633,15 @@ static inline void security_audit_rule_free(void *lsmrule) | |
#endif /* CONFIG_AUDIT */ | |
#ifdef CONFIG_SECURITYFS | |
- | |
+extern int securityfs_pin_fs(void); | |
+extern int __securityfs_setup_d_inode(struct inode *dir, struct dentry *dentry, | |
+ umode_t mode, void *data, | |
+ const struct file_operations *fops, | |
+ const struct inode_operations *iops); | |
+extern struct dentry *securityfs_create_dentry(const char *name, umode_t mode, | |
+ struct dentry *parent, void *data, | |
+ const struct file_operations *fops, | |
+ const struct inode_operations *iops); | |
extern struct dentry *securityfs_create_file(const char *name, umode_t mode, | |
struct dentry *parent, void *data, | |
const struct file_operations *fops); | |
@@ -1641,6 +1649,28 @@ extern struct dentry *securityfs_create_file(const char *name, umode_t mode, | |
extern void securityfs_remove(struct dentry *dentry); | |
#else /* CONFIG_SECURITYFS */ | |
+static inline int securityfs_pin_fs(void) | |
+{ | |
+ return -ENODEV; | |
+} | |
+ | |
+static inline int __securityfs_setup_d_inode(struct inode *dir, | |
+ struct dentry *dentry, | |
+ umode_t mode, void *data, | |
+ const struct file_operations *fops, | |
+ const struct inode_operations *iops)) | |
+{ | |
+ return -ENODEV; | |
+} | |
+ | |
+static inline struct dentry *securityfs_create_dentry(const char *name, | |
+ umode_t mode, | |
+ struct dentry *parent, void *data, | |
+ const struct file_operations *fops, | |
+ const struct inode_operations *iops) | |
+{ | |
+ return ERR_PTR(-ENODEV); | |
+} | |
static inline struct dentry *securityfs_create_dir(const char *name, | |
struct dentry *parent) | |
diff --git a/security/inode.c b/security/inode.c | |
index c83db05..7a8793d 100644 | |
--- a/security/inode.c | |
+++ b/security/inode.c | |
@@ -46,8 +46,42 @@ static struct dentry *get_sb(struct file_system_type *fs_type, | |
.kill_sb = kill_litter_super, | |
}; | |
+int securityfs_pin_fs(void) | |
+{ | |
+ return simple_pin_fs(&fs_type, &mount, &mount_count); | |
+} | |
+ | |
+int __securityfs_setup_d_inode(struct inode *dir, struct dentry *dentry, | |
+ umode_t mode, void *data, | |
+ const struct file_operations *fops, | |
+ const struct inode_operations *iops) | |
+{ | |
+ bool is_dir = S_ISDIR(mode); | |
+ struct inode *inode = new_inode(dir->i_sb); | |
+ if (!inode) | |
+ return -ENOMEM; | |
+ | |
+ inode->i_ino = get_next_ino(); | |
+ inode->i_mode = mode; | |
+ inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; | |
+ inode->i_private = data; | |
+ if (is_dir) { | |
+ inode->i_op = iops ? iops : &simple_dir_inode_operations; | |
+ inode->i_fop = &simple_dir_operations; | |
+ inc_nlink(inode); | |
+ inc_nlink(dir); | |
+ } else { | |
+ inode->i_fop = fops; | |
+ } | |
+ d_instantiate(dentry, inode); | |
+ dget(dentry); | |
+ | |
+ return 0; | |
+} | |
+EXPORT_SYMBOL_GPL(__securityfs_setup_d_inode); | |
+ | |
/** | |
- * securityfs_create_file - create a file in the securityfs filesystem | |
+ * securityfs_create_dentry - create a file/dir in the securityfs filesystem | |
* | |
* @name: a pointer to a string containing the name of the file to create. | |
* @mode: the permission that the file should have | |
@@ -59,8 +93,10 @@ static struct dentry *get_sb(struct file_system_type *fs_type, | |
* the open() call. | |
* @fops: a pointer to a struct file_operations that should be used for | |
* this file. | |
+ * @iops: a point to a struct of inode_operations that should be used for | |
+ * this file/dir | |
* | |
- * This is the basic "create a file" function for securityfs. It allows for a | |
+ * This is the basic "create a xxx" function for securityfs. It allows for a | |
* wide range of flexibility in creating a file, or a directory (if you | |
* want to create a directory, the securityfs_create_dir() function is | |
* recommended to be used instead). | |
@@ -74,13 +110,14 @@ static struct dentry *get_sb(struct file_system_type *fs_type, | |
* If securityfs is not enabled in the kernel, the value %-ENODEV is | |
* returned. | |
*/ | |
-struct dentry *securityfs_create_file(const char *name, umode_t mode, | |
- struct dentry *parent, void *data, | |
- const struct file_operations *fops) | |
+struct dentry *securityfs_create_dentry(const char *name, umode_t mode, | |
+ struct dentry *parent, void *data, | |
+ const struct file_operations *fops, | |
+ const struct inode_operations *iops) | |
{ | |
struct dentry *dentry; | |
int is_dir = S_ISDIR(mode); | |
- struct inode *dir, *inode; | |
+ struct inode *dir; | |
int error; | |
if (!is_dir) { | |
@@ -109,26 +146,9 @@ struct dentry *securityfs_create_file(const char *name, umode_t mode, | |
goto out1; | |
} | |
- inode = new_inode(dir->i_sb); | |
- if (!inode) { | |
- error = -ENOMEM; | |
+ error = __securityfs_setup_d_inode(dir, dentry, mode, data, fops, iops); | |
+ if (error) | |
goto out1; | |
- } | |
- | |
- inode->i_ino = get_next_ino(); | |
- inode->i_mode = mode; | |
- inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); | |
- inode->i_private = data; | |
- if (is_dir) { | |
- inode->i_op = &simple_dir_inode_operations; | |
- inode->i_fop = &simple_dir_operations; | |
- inc_nlink(inode); | |
- inc_nlink(dir); | |
- } else { | |
- inode->i_fop = fops; | |
- } | |
- d_instantiate(dentry, inode); | |
- dget(dentry); | |
inode_unlock(dir); | |
return dentry; | |
@@ -140,6 +160,39 @@ struct dentry *securityfs_create_file(const char *name, umode_t mode, | |
simple_release_fs(&mount, &mount_count); | |
return dentry; | |
} | |
+EXPORT_SYMBOL_GPL(securityfs_create_dentry); | |
+ | |
+/** | |
+ * securityfs_create_file - create a file in the securityfs filesystem | |
+ * | |
+ * @name: a pointer to a string containing the name of the file to create. | |
+ * @mode: the permission that the file should have | |
+ * @parent: a pointer to the parent dentry for this file. This should be a | |
+ * directory dentry if set. If this parameter is %NULL, then the | |
+ * file will be created in the root of the securityfs filesystem. | |
+ * @data: a pointer to something that the caller will want to get to later | |
+ * on. The inode.i_private pointer will point to this value on | |
+ * the open() call. | |
+ * @fops: a pointer to a struct file_operations that should be used for | |
+ * this file. | |
+ * | |
+ * This function creates a file in securityfs with the given @name. | |
+ * | |
+ * This function returns a pointer to a dentry if it succeeds. This | |
+ * pointer must be passed to the securityfs_remove() function when the file is | |
+ * to be removed (no automatic cleanup happens if your module is unloaded, | |
+ * you are responsible here). If an error occurs, the function will return | |
+ * the error value (via ERR_PTR). | |
+ * | |
+ * If securityfs is not enabled in the kernel, the value %-ENODEV is | |
+ * returned. | |
+ */ | |
+struct dentry *securityfs_create_file(const char *name, umode_t mode, | |
+ struct dentry *parent, void *data, | |
+ const struct file_operations *fops) | |
+{ | |
+ return securityfs_create_dentry(name, mode, parent, data, fops, NULL); | |
+} | |
EXPORT_SYMBOL_GPL(securityfs_create_file); | |
/** | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 286f623280ccbf276771de016b3a1877bac92096 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Thu, 22 Sep 2016 12:51:11 -0700 | |
Subject: [PATCH 44/76] apparmor: refactor aa_prepare_ns into prepare_ns and | |
create_ns routines | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/include/policy_ns.h | 1 + | |
security/apparmor/policy_ns.c | 84 ++++++++++++++++++++++++----------- | |
2 files changed, 58 insertions(+), 27 deletions(-) | |
diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h | |
index 65b1e1e..0325118 100644 | |
--- a/security/apparmor/include/policy_ns.h | |
+++ b/security/apparmor/include/policy_ns.h | |
@@ -88,6 +88,7 @@ struct aa_ns { | |
struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name); | |
struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n); | |
+struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name); | |
struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name); | |
void __aa_remove_ns(struct aa_ns *ns); | |
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c | |
index 19adb24..cbef816 100644 | |
--- a/security/apparmor/policy_ns.c | |
+++ b/security/apparmor/policy_ns.c | |
@@ -199,43 +199,73 @@ struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name) | |
return aa_findn_ns(root, name, strlen(name)); | |
} | |
+static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name) | |
+{ | |
+ struct aa_ns *ns; | |
+ | |
+ AA_BUG(!mutex_is_locked(&parent->lock)); | |
+ | |
+ ns = alloc_ns(parent->base.hname, name); | |
+ if (!ns) | |
+ return NULL; | |
+ mutex_lock(&ns->lock); | |
+ if (__aa_fs_ns_mkdir(ns, ns_subns_dir(parent), name)) { | |
+ AA_ERROR("Failed to create interface for ns %s\n", | |
+ ns->base.name); | |
+ mutex_unlock(&ns->lock); | |
+ aa_free_ns(ns); | |
+ return ERR_PTR(-ENOMEM); | |
+ } else { | |
+ ns->parent = aa_get_ns(parent); | |
+ ns->level = parent->level + 1; | |
+ list_add_rcu(&ns->base.list, &parent->sub_ns); | |
+ /* add list ref */ | |
+ aa_get_ns(ns); | |
+ } | |
+ mutex_unlock(&ns->lock); | |
+ | |
+ return ns; | |
+} | |
+ | |
+struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name) | |
+{ | |
+ struct aa_ns *ns; | |
+ | |
+ mutex_lock(&parent->lock); | |
+ /* try and find the specified ns */ | |
+ /* released by caller */ | |
+ ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name)); | |
+ if (!ns) | |
+{ | |
+printk("apparmor creating ns %s\n", name); | |
+ ns = __aa_create_ns(parent, name); | |
+} | |
+ else | |
+ ns = ERR_PTR(-EEXIST); | |
+ mutex_unlock(&parent->lock); | |
+printk("apparmor unlocking parent ns\n"); | |
+ /* return ref */ | |
+ return ns; | |
+} | |
+ | |
/** | |
* aa_prepare_ns - find an existing or create a new namespace of @name | |
- * @root: ns to treat as root | |
+ * @parent: ns to treat as parent | |
* @name: the namespace to find or add (NOT NULL) | |
* | |
- * Returns: refcounted namespace or NULL if failed to create one | |
+ * Returns: refcounted namespace or PTR_ERR if failed to create one | |
*/ | |
-struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name) | |
+struct aa_ns *aa_prepare_ns(struct aa_ns *parent, const char *name) | |
{ | |
struct aa_ns *ns; | |
- mutex_lock(&root->lock); | |
+ mutex_lock(&parent->lock); | |
/* try and find the specified ns and if it doesn't exist create it */ | |
/* released by caller */ | |
- ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, strlen(name))); | |
- if (!ns) { | |
- ns = alloc_ns(root->base.hname, name); | |
- if (!ns) | |
- goto out; | |
- mutex_lock(&ns->lock); | |
- if (__aa_fs_ns_mkdir(ns, ns_subns_dir(root), name)) { | |
- AA_ERROR("Failed to create interface for ns %s\n", | |
- ns->base.name); | |
- mutex_unlock(&ns->lock); | |
- aa_free_ns(ns); | |
- ns = NULL; | |
- goto out; | |
- } | |
- ns->parent = aa_get_ns(root); | |
- ns->level = root->level + 1; | |
- list_add_rcu(&ns->base.list, &root->sub_ns); | |
- /* add list ref */ | |
- aa_get_ns(ns); | |
- mutex_unlock(&ns->lock); | |
- } | |
-out: | |
- mutex_unlock(&root->lock); | |
+ ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name)); | |
+ if (!ns) | |
+ ns = __aa_create_ns(parent, name); | |
+ mutex_unlock(&parent->lock); | |
/* return ref */ | |
return ns; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From e172eee60738ff872ba643df773deaf3462b9017 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Thu, 22 Sep 2016 14:53:40 -0700 | |
Subject: [PATCH 45/76] apparmor: add __aa_find_ns fn | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/policy_ns.c | 5 +++++ | |
1 file changed, 5 insertions(+) | |
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c | |
index cbef816..88092b4 100644 | |
--- a/security/apparmor/policy_ns.c | |
+++ b/security/apparmor/policy_ns.c | |
@@ -162,6 +162,11 @@ static struct aa_ns *__aa_findn_ns(struct list_head *head, const char *name, | |
return (struct aa_ns *)__policy_strn_find(head, name, n); | |
} | |
+static struct aa_ns *__aa_find_ns(struct list_head *head, const char *name) | |
+{ | |
+ return __aa_findn_ns(head, name, strlen(name)); | |
+} | |
+ | |
/** | |
* aa_find_ns - look up a profile namespace on the namespace list | |
* @root: namespace to search in (NOT NULL) | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 97b230e455b5b3b1b0bb5f4c56aea08ba53b40be Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Thu, 22 Sep 2016 10:50:42 -0700 | |
Subject: [PATCH 46/76] apparmor: add mkdir/rmdir interface to manage policy | |
namespaces | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 157 ++++++++++++++++++++++++++++----- | |
security/apparmor/include/apparmorfs.h | 3 +- | |
security/apparmor/include/policy_ns.h | 24 ++++- | |
security/apparmor/policy.c | 5 +- | |
security/apparmor/policy_ns.c | 51 +++++------ | |
5 files changed, 184 insertions(+), 56 deletions(-) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index b11ce28..f727e87 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -23,6 +23,7 @@ | |
#include <linux/capability.h> | |
#include <linux/rcupdate.h> | |
#include <uapi/linux/major.h> | |
+#include <linux/fs.h> | |
#include "include/apparmor.h" | |
#include "include/apparmorfs.h" | |
@@ -916,6 +917,88 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) | |
return error; | |
} | |
+static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode) | |
+{ | |
+ struct aa_ns *ns, *parent; | |
+ /* TODO: improve permission check */ | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ int error = aa_may_manage_policy(label, AA_MAY_LOAD_POLICY); | |
+ aa_end_current_label(label); | |
+ if (error) | |
+ return error; | |
+ | |
+ parent = aa_get_ns(dir->i_private); | |
+ AA_BUG(d_inode(ns_subns_dir(parent)) != dir); | |
+ | |
+ /* we have to unlock and then relock to get locking order right | |
+ * for pin_fs | |
+ */ | |
+ inode_unlock(dir); | |
+ securityfs_pin_fs(); | |
+ inode_lock_nested(dir, I_MUTEX_PARENT); | |
+ | |
+ error = __securityfs_setup_d_inode(dir, dentry, mode | S_IFDIR, NULL, | |
+ NULL, NULL); | |
+ if (error) | |
+ return error; | |
+ | |
+ ns = aa_create_ns(parent, ACCESS_ONCE(dentry->d_name.name), dentry); | |
+ if (IS_ERR(ns)) { | |
+ error = PTR_ERR(ns); | |
+ ns = NULL; | |
+ } | |
+ | |
+ aa_put_ns(ns); /* list ref remains */ | |
+ aa_put_ns(parent); | |
+ | |
+ return error; | |
+} | |
+ | |
+static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) | |
+{ | |
+ struct aa_ns *ns, *parent; | |
+ /* TODO: improve permission check */ | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ int error = aa_may_manage_policy(label, AA_MAY_LOAD_POLICY); | |
+ aa_end_current_label(label); | |
+ if (error) | |
+ return error; | |
+ | |
+ parent = aa_get_ns(dir->i_private); | |
+ /* rmdir calls the generic securityfs functions to remove files | |
+ * from the apparmor dir. It is up to the apparmor ns locking | |
+ * to avoid races. | |
+ */ | |
+ inode_unlock(dir); | |
+ inode_unlock(dentry->d_inode); | |
+ | |
+ mutex_lock(&parent->lock); | |
+ ns = aa_get_ns(__aa_findn_ns(&parent->sub_ns, dentry->d_name.name, | |
+ dentry->d_name.len)); | |
+ if (!ns) { | |
+ error = -ENOENT; | |
+ goto out; | |
+ } | |
+ AA_BUG(ns_dir(ns) != dentry); | |
+ | |
+ __aa_remove_ns(ns); | |
+ aa_put_ns(ns); | |
+ | |
+out: | |
+ mutex_unlock(&parent->lock); | |
+ inode_lock_nested(dir, I_MUTEX_PARENT); | |
+ inode_lock(dentry->d_inode); | |
+ aa_put_ns(parent); | |
+ | |
+ return error; | |
+} | |
+ | |
+static const struct inode_operations ns_dir_inode_operations = { | |
+ .lookup = simple_lookup, | |
+ .mkdir = ns_mkdir_op, | |
+ .rmdir = ns_rmdir_op, | |
+}; | |
+ | |
/** | |
* | |
* Requires: @ns->lock held | |
@@ -939,21 +1022,57 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns) | |
mutex_unlock(&sub->lock); | |
} | |
+ if (ns_subns_dir(ns)) { | |
+ sub = d_inode(ns_subns_dir(ns))->i_private; | |
+ aa_put_ns(sub); | |
+ } | |
for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) { | |
securityfs_remove(ns->dents[i]); | |
ns->dents[i] = NULL; | |
} | |
} | |
+/* assumes cleanup in caller */ | |
+static int __aa_fs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) | |
+{ | |
+ struct dentry *dent; | |
+ | |
+ AA_BUG(!ns); | |
+ AA_BUG(!dir); | |
+ | |
+ dent = securityfs_create_dir("profiles", dir); | |
+ if (IS_ERR(dent)) | |
+ return PTR_ERR(dent); | |
+ ns_subprofs_dir(ns) = dent; | |
+ | |
+ dent = securityfs_create_dir("raw_data", dir); | |
+ if (IS_ERR(dent)) | |
+ return PTR_ERR(dent); | |
+ ns_subdata_dir(ns) = dent; | |
+ | |
+ /* use create_dentry so we can supply private data */ | |
+ dent = securityfs_create_dentry("namespaces", | |
+ S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO, | |
+ dir, ns, NULL, | |
+ &ns_dir_inode_operations); | |
+ if (IS_ERR(dent)) | |
+ return PTR_ERR(dent); | |
+ aa_get_ns(ns); | |
+ ns_subns_dir(ns) = dent; | |
+ | |
+ return 0; | |
+} | |
+ | |
/** | |
* | |
* Requires: @ns->lock held | |
*/ | |
-int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) | |
+int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name, | |
+ struct dentry *dent) | |
{ | |
struct aa_ns *sub; | |
struct aa_profile *child; | |
- struct dentry *dent, *dir; | |
+ struct dentry *dir; | |
int error; | |
AA_BUG(!ns); | |
@@ -963,35 +1082,29 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) | |
if (!name) | |
name = ns->base.name; | |
- dent = securityfs_create_dir(name, parent); | |
- if (IS_ERR(dent)) | |
- goto fail; | |
+ if (!dent) { | |
+ /* create ns dir if it doesn't already exist */ | |
+ dent = securityfs_create_dir(name, parent); | |
+ if (IS_ERR(dent)) | |
+ goto fail; | |
+ } else | |
+ dget(dent); | |
ns_dir(ns) = dir = dent; | |
+ error = __aa_fs_ns_mkdir_entries(ns, dir); | |
+ if (error) | |
+ goto fail2; | |
- dent = securityfs_create_dir("profiles", dir); | |
- if (IS_ERR(dent)) | |
- goto fail; | |
- ns_subprofs_dir(ns) = dent; | |
- | |
- dent = securityfs_create_dir("raw_data", dir); | |
- if (IS_ERR(dent)) | |
- goto fail; | |
- ns_subdata_dir(ns) = dent; | |
- | |
- dent = securityfs_create_dir("namespaces", dir); | |
- if (IS_ERR(dent)) | |
- goto fail; | |
- ns_subns_dir(ns) = dent; | |
- | |
+ /* profiles */ | |
list_for_each_entry(child, &ns->base.profiles, base.list) { | |
error = __aa_fs_profile_mkdir(child, ns_subprofs_dir(ns)); | |
if (error) | |
goto fail2; | |
} | |
+ /* subnamespaces */ | |
list_for_each_entry(sub, &ns->sub_ns, base.list) { | |
mutex_lock(&sub->lock); | |
- error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL); | |
+ error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL, NULL); | |
mutex_unlock(&sub->lock); | |
if (error) | |
goto fail2; | |
@@ -1503,7 +1616,7 @@ static int __init aa_create_aafs(void) | |
goto error; | |
mutex_lock(&root_ns->lock); | |
- error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy"); | |
+ error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy", NULL); | |
mutex_unlock(&root_ns->lock); | |
if (error) | |
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h | |
index 1524241..077f5e2 100644 | |
--- a/security/apparmor/include/apparmorfs.h | |
+++ b/security/apparmor/include/apparmorfs.h | |
@@ -105,6 +105,7 @@ void __aa_fs_profile_migrate_dents(struct aa_profile *old, | |
struct aa_profile *new); | |
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); | |
void __aa_fs_ns_rmdir(struct aa_ns *ns); | |
-int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name); | |
+int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name, | |
+ struct dentry *dent); | |
#endif /* __AA_APPARMORFS_H */ | |
diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h | |
index 0325118..4c16c9a 100644 | |
--- a/security/apparmor/include/policy_ns.h | |
+++ b/security/apparmor/include/policy_ns.h | |
@@ -88,7 +88,8 @@ struct aa_ns { | |
struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name); | |
struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n); | |
-struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name); | |
+struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name, | |
+ struct dentry *dir); | |
struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name); | |
void __aa_remove_ns(struct aa_ns *ns); | |
@@ -125,4 +126,25 @@ static inline void aa_put_ns(struct aa_ns *ns) | |
aa_put_profile(ns->unconfined); | |
} | |
+/** | |
+ * __aa_findn_ns - find a namespace on a list by @name | |
+ * @head: list to search for namespace on (NOT NULL) | |
+ * @name: name of namespace to look for (NOT NULL) | |
+ * @n: length of @name | |
+ * Returns: unrefcounted namespace | |
+ * | |
+ * Requires: rcu_read_lock be held | |
+ */ | |
+static inline struct aa_ns *__aa_findn_ns(struct list_head *head, | |
+ const char *name, size_t n) | |
+{ | |
+ return (struct aa_ns *)__policy_strn_find(head, name, n); | |
+} | |
+ | |
+static inline struct aa_ns *__aa_find_ns(struct list_head *head, | |
+ const char *name) | |
+{ | |
+ return __aa_findn_ns(head, name, strlen(name)); | |
+} | |
+ | |
#endif /* AA_NAMESPACE_H */ | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index fa85b74..3fd0bb4 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -891,9 +891,10 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, | |
} | |
if (ns_name) { | |
ns = aa_prepare_ns(labels_ns(label), ns_name); | |
- if (!ns) { | |
+ if (IS_ERR(ns)) { | |
info = "failed to prepare namespace"; | |
- error = -ENOMEM; | |
+ error = PTR_ERR(ns); | |
+ ns = NULL; | |
goto fail; | |
} | |
} else | |
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c | |
index 88092b4..88043fc 100644 | |
--- a/security/apparmor/policy_ns.c | |
+++ b/security/apparmor/policy_ns.c | |
@@ -148,26 +148,6 @@ void aa_free_ns(struct aa_ns *ns) | |
} | |
/** | |
- * __aa_findn_ns - find a namespace on a list by @name | |
- * @head: list to search for namespace on (NOT NULL) | |
- * @name: name of namespace to look for (NOT NULL) | |
- * @n: length of @name | |
- * Returns: unrefcounted namespace | |
- * | |
- * Requires: rcu_read_lock be held | |
- */ | |
-static struct aa_ns *__aa_findn_ns(struct list_head *head, const char *name, | |
- size_t n) | |
-{ | |
- return (struct aa_ns *)__policy_strn_find(head, name, n); | |
-} | |
- | |
-static struct aa_ns *__aa_find_ns(struct list_head *head, const char *name) | |
-{ | |
- return __aa_findn_ns(head, name, strlen(name)); | |
-} | |
- | |
-/** | |
* aa_find_ns - look up a profile namespace on the namespace list | |
* @root: namespace to search in (NOT NULL) | |
* @name: name of namespace to find (NOT NULL) | |
@@ -204,22 +184,27 @@ struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name) | |
return aa_findn_ns(root, name, strlen(name)); | |
} | |
-static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name) | |
+static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, | |
+ struct dentry *dir) | |
{ | |
struct aa_ns *ns; | |
+ int error; | |
+ AA_BUG(!parent); | |
+ AA_BUG(!name); | |
AA_BUG(!mutex_is_locked(&parent->lock)); | |
ns = alloc_ns(parent->base.hname, name); | |
if (!ns) | |
return NULL; | |
mutex_lock(&ns->lock); | |
- if (__aa_fs_ns_mkdir(ns, ns_subns_dir(parent), name)) { | |
+ error = __aa_fs_ns_mkdir(ns, ns_subns_dir(parent), name, dir); | |
+ if (error) { | |
AA_ERROR("Failed to create interface for ns %s\n", | |
ns->base.name); | |
mutex_unlock(&ns->lock); | |
aa_free_ns(ns); | |
- return ERR_PTR(-ENOMEM); | |
+ return ERR_PTR(error); | |
} else { | |
ns->parent = aa_get_ns(parent); | |
ns->level = parent->level + 1; | |
@@ -232,7 +217,16 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name) | |
return ns; | |
} | |
-struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name) | |
+/** | |
+ * aa_create_ns - create an ns, fail if it already exists | |
+ * @parent: the parent of the namespace being created | |
+ * @name: the name of the namespace | |
+ * @dir: if not null the dir to put the ns entries in | |
+ * | |
+ * Returns: the a refcounted ns that has been add or an ERR_PTR | |
+ */ | |
+struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name, | |
+ struct dentry *dir) | |
{ | |
struct aa_ns *ns; | |
@@ -241,14 +235,11 @@ struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name) | |
/* released by caller */ | |
ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name)); | |
if (!ns) | |
-{ | |
-printk("apparmor creating ns %s\n", name); | |
- ns = __aa_create_ns(parent, name); | |
-} | |
+ ns = __aa_create_ns(parent, name, dir); | |
else | |
ns = ERR_PTR(-EEXIST); | |
mutex_unlock(&parent->lock); | |
-printk("apparmor unlocking parent ns\n"); | |
+ | |
/* return ref */ | |
return ns; | |
} | |
@@ -269,7 +260,7 @@ struct aa_ns *aa_prepare_ns(struct aa_ns *parent, const char *name) | |
/* released by caller */ | |
ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name)); | |
if (!ns) | |
- ns = __aa_create_ns(parent, name); | |
+ ns = __aa_create_ns(parent, name, NULL); | |
mutex_unlock(&parent->lock); | |
/* return ref */ | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 94c26adf387ab1f0191d46201401f3e3140d1efa Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Tue, 27 Sep 2016 22:14:12 -0700 | |
Subject: [PATCH 47/76] apparmor: fix oops in pivot_root mediation | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/mount.c | 5 ++++- | |
1 file changed, 4 insertions(+), 1 deletion(-) | |
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c | |
index 5ca4ccf..5342062 100644 | |
--- a/security/apparmor/mount.c | |
+++ b/security/apparmor/mount.c | |
@@ -612,6 +612,9 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, | |
AA_BUG(!new_path); | |
AA_BUG(!old_path); | |
+ if (profile_unconfined(profile)) | |
+ return aa_get_newest_label(&profile->label); | |
+ | |
error = aa_path_name(old_path, path_flags(profile, old_path), | |
old_buffer, &old_name, &info, | |
profile->disconnected); | |
@@ -651,7 +654,7 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile, | |
} else if (target) | |
return target; | |
- return aa_get_label(&profile->label); | |
+ return aa_get_newest_label(&profile->label); | |
} | |
int aa_pivotroot(struct aa_label *label, const struct path *old_path, | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From ff5da7e8d5ab3494a3c7420eebfebcab8d516173 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Wed, 28 Sep 2016 02:23:56 -0700 | |
Subject: [PATCH 48/76] apparmor: fix warning that fn build_pivotroot discards | |
const | |
MIME-Version: 1.0 | |
Content-Type: text/plain; charset=UTF-8 | |
Content-Transfer-Encoding: 8bit | |
fix mount.c warnings: | |
warning: passing argument 2 of ‘build_pivotroot’ discards ‘const’ qualifier fro\ | |
m pointer target type [-Wdiscarded-qualifiers] | |
warning: passing argument 4 of ‘build_pivotroot’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/mount.c | 4 ++-- | |
1 file changed, 2 insertions(+), 2 deletions(-) | |
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c | |
index 5342062..907d3f0 100644 | |
--- a/security/apparmor/mount.c | |
+++ b/security/apparmor/mount.c | |
@@ -596,9 +596,9 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags) | |
* Returns: label for transition or ERR_PTR. Does not return NULL | |
*/ | |
static struct aa_label *build_pivotroot(struct aa_profile *profile, | |
- struct path *new_path, | |
+ const struct path *new_path, | |
char *new_buffer, | |
- struct path *old_path, | |
+ const struct path *old_path, | |
char *old_buffer) | |
{ | |
const char *old_name, *new_name = NULL, *info = NULL; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 64f23a01316ae8c7b581ed4b7ec1f141368084db Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Tue, 27 Sep 2016 15:14:48 -0700 | |
Subject: [PATCH 49/76] apparmor: add interface to advertise status of current | |
task stacking | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 105 +++++++++++++++++++++++++++++++++++++++++ | |
1 file changed, 105 insertions(+) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index f727e87..031e4f8 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -35,6 +35,7 @@ | |
#include "include/label.h" | |
#include "include/policy.h" | |
#include "include/resource.h" | |
+#include "include/label.h" | |
#include "include/lib.h" | |
#include "include/policy_unpack.h" | |
@@ -650,6 +651,106 @@ static int aa_fs_seq_hash_open(struct inode *inode, struct file *file) | |
.release = single_release, | |
}; | |
+static int aa_fs_seq_show_stacked(struct seq_file *seq, void *v) | |
+{ | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ seq_printf(seq, "%s\n", label->size > 1 ? "yes" : "no"); | |
+ aa_end_current_label(label); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int aa_fs_seq_open_stacked(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, aa_fs_seq_show_stacked, inode->i_private); | |
+} | |
+ | |
+static const struct file_operations aa_fs_stacked = { | |
+ .owner = THIS_MODULE, | |
+ .open = aa_fs_seq_open_stacked, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+}; | |
+ | |
+static int aa_fs_seq_show_ns_stacked(struct seq_file *seq, void *v) | |
+{ | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ struct aa_profile *profile; | |
+ struct label_it it; | |
+ int count = 1; | |
+ | |
+ if (label->size > 1) { | |
+ label_for_each(it, label, profile) | |
+ if (profile->ns != labels_ns(label)) { | |
+ count++; | |
+ break; | |
+ } | |
+ } | |
+ | |
+ seq_printf(seq, "%s\n", count > 1 ? "yes" : "no"); | |
+ aa_end_current_label(label); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int aa_fs_seq_open_ns_stacked(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, aa_fs_seq_show_ns_stacked, inode->i_private); | |
+} | |
+ | |
+static const struct file_operations aa_fs_ns_stacked = { | |
+ .owner = THIS_MODULE, | |
+ .open = aa_fs_seq_open_ns_stacked, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+}; | |
+ | |
+static int aa_fs_seq_show_ns_level(struct seq_file *seq, void *v) | |
+{ | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ seq_printf(seq, "%d\n", labels_ns(label)->level); | |
+ aa_end_current_label(label); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int aa_fs_seq_open_ns_level(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, aa_fs_seq_show_ns_level, inode->i_private); | |
+} | |
+ | |
+static const struct file_operations aa_fs_ns_level = { | |
+ .owner = THIS_MODULE, | |
+ .open = aa_fs_seq_open_ns_level, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+}; | |
+ | |
+static int aa_fs_seq_show_ns_name(struct seq_file *seq, void *v) | |
+{ | |
+ struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
+ seq_printf(seq, "%s\n", labels_ns(label)->base.name); | |
+ aa_end_current_label(label); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static int aa_fs_seq_open_ns_name(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, aa_fs_seq_show_ns_name, inode->i_private); | |
+} | |
+ | |
+static const struct file_operations aa_fs_ns_name = { | |
+ .owner = THIS_MODULE, | |
+ .open = aa_fs_seq_open_ns_name, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+}; | |
+ | |
static int rawdata_release(struct inode *inode, struct file *file) | |
{ | |
/* TODO: switch to loaddata when profile switched to symlink */ | |
@@ -1437,6 +1538,10 @@ static int profiles_release(struct inode *inode, struct file *file) | |
AA_FS_FILE_FOPS(".replace", 0666, &aa_fs_profile_replace), | |
AA_FS_FILE_FOPS(".remove", 0666, &aa_fs_profile_remove), | |
AA_FS_FILE_FOPS(".access", 0666, &aa_fs_access), | |
+ AA_FS_FILE_FOPS(".stacked", 0666, &aa_fs_stacked), | |
+ AA_FS_FILE_FOPS(".ns_stacked", 0666, &aa_fs_ns_stacked), | |
+ AA_FS_FILE_FOPS(".ns_level", 0666, &aa_fs_ns_level), | |
+ AA_FS_FILE_FOPS(".ns_name", 0666, &aa_fs_ns_name), | |
AA_FS_FILE_FOPS("profiles", 0444, &aa_fs_profiles_fops), | |
AA_FS_DIR("features", aa_fs_entry_features), | |
{ } | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 3589771722b9242f3177295eaabaf356b6cc5047 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Mon, 26 Sep 2016 19:06:51 -0700 | |
Subject: [PATCH 50/76] apparmor: update policy permissions to consider ns | |
being viewed/managed | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 12 ++++++------ | |
security/apparmor/include/policy.h | 6 +++--- | |
security/apparmor/lsm.c | 22 +++++++++++----------- | |
security/apparmor/policy.c | 28 +++++++++++++++++++--------- | |
4 files changed, 39 insertions(+), 29 deletions(-) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index 031e4f8..0150d06 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -130,7 +130,7 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, | |
/* high level check about policy management - fine grained in | |
* below after unpack | |
*/ | |
- error = aa_may_manage_policy(label, mask); | |
+ error = aa_may_manage_policy(label, NULL, mask); | |
if (error) | |
return error; | |
@@ -182,7 +182,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, | |
/* high level check about policy management - fine grained in | |
* below after unpack | |
*/ | |
- error = aa_may_manage_policy(label, AA_MAY_REMOVE_POLICY); | |
+ error = aa_may_manage_policy(label, NULL, AA_MAY_REMOVE_POLICY); | |
if (error) | |
return error; | |
@@ -832,7 +832,7 @@ static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, | |
static int rawdata_open(struct inode *inode, struct file *file) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_view_capable(NULL)) | |
return -EACCES; | |
file->private_data = aa_get_proxy(inode->i_private); | |
@@ -1023,7 +1023,7 @@ static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode) | |
struct aa_ns *ns, *parent; | |
/* TODO: improve permission check */ | |
struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
- int error = aa_may_manage_policy(label, AA_MAY_LOAD_POLICY); | |
+ int error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY); | |
aa_end_current_label(label); | |
if (error) | |
return error; | |
@@ -1060,7 +1060,7 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) | |
struct aa_ns *ns, *parent; | |
/* TODO: improve permission check */ | |
struct aa_label *label = aa_begin_current_label(DO_UPDATE); | |
- int error = aa_may_manage_policy(label, AA_MAY_LOAD_POLICY); | |
+ int error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY); | |
aa_end_current_label(label); | |
if (error) | |
return error; | |
@@ -1442,7 +1442,7 @@ static int seq_show_profile(struct seq_file *f, void *p) | |
static int profiles_open(struct inode *inode, struct file *file) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_view_capable(NULL)) | |
return -EACCES; | |
return seq_open(file, &aa_fs_profiles_op); | |
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h | |
index 657d1a5..27cab34 100644 | |
--- a/security/apparmor/include/policy.h | |
+++ b/security/apparmor/include/policy.h | |
@@ -296,9 +296,9 @@ static inline int AUDIT_MODE(struct aa_profile *profile) | |
return profile->audit; | |
} | |
-bool policy_view_capable(void); | |
-bool policy_admin_capable(void); | |
+bool policy_view_capable(struct aa_ns *ns); | |
+bool policy_admin_capable(struct aa_ns *ns); | |
bool aa_may_open_profiles(void); | |
-int aa_may_manage_policy(struct aa_label *label, u32 mask); | |
+int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask); | |
#endif /* __AA_POLICY_H */ | |
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c | |
index 88db25c..330437c 100644 | |
--- a/security/apparmor/lsm.c | |
+++ b/security/apparmor/lsm.c | |
@@ -1334,14 +1334,14 @@ static int __init apparmor_enabled_setup(char *str) | |
/* set global flag turning off the ability to load policy */ | |
static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp) | |
{ | |
- if (!policy_admin_capable()) | |
+ if (!policy_admin_capable(NULL)) | |
return -EPERM; | |
return param_set_bool(val, kp); | |
} | |
static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_view_capable(NULL)) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1350,7 +1350,7 @@ static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp) | |
static int param_set_aabool(const char *val, const struct kernel_param *kp) | |
{ | |
- if (!policy_admin_capable()) | |
+ if (!policy_admin_capable(NULL)) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1359,7 +1359,7 @@ static int param_set_aabool(const char *val, const struct kernel_param *kp) | |
static int param_get_aabool(char *buffer, const struct kernel_param *kp) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_view_capable(NULL)) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1368,7 +1368,7 @@ static int param_get_aabool(char *buffer, const struct kernel_param *kp) | |
static int param_set_aauint(const char *val, const struct kernel_param *kp) | |
{ | |
- if (!policy_admin_capable()) | |
+ if (!policy_admin_capable(NULL)) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1377,7 +1377,7 @@ static int param_set_aauint(const char *val, const struct kernel_param *kp) | |
static int param_get_aauint(char *buffer, const struct kernel_param *kp) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_view_capable(NULL)) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1386,7 +1386,7 @@ static int param_get_aauint(char *buffer, const struct kernel_param *kp) | |
static int param_get_audit(char *buffer, struct kernel_param *kp) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_view_capable(NULL)) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1396,7 +1396,7 @@ static int param_get_audit(char *buffer, struct kernel_param *kp) | |
static int param_set_audit(const char *val, struct kernel_param *kp) | |
{ | |
int i; | |
- if (!policy_admin_capable()) | |
+ if (!policy_admin_capable(NULL)) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1415,7 +1415,7 @@ static int param_set_audit(const char *val, struct kernel_param *kp) | |
static int param_get_mode(char *buffer, struct kernel_param *kp) | |
{ | |
- if (!policy_view_capable()) | |
+ if (!policy_view_capable(NULL)) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1426,7 +1426,7 @@ static int param_get_mode(char *buffer, struct kernel_param *kp) | |
static int param_set_mode(const char *val, struct kernel_param *kp) | |
{ | |
int i; | |
- if (!policy_admin_capable()) | |
+ if (!policy_admin_capable(NULL)) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
@@ -1513,7 +1513,7 @@ static int __init alloc_buffers(void) | |
static int apparmor_dointvec(struct ctl_table *table, int write, | |
void __user *buffer, size_t *lenp, loff_t *ppos) | |
{ | |
- if (!policy_admin_capable()) | |
+ if (!policy_admin_capable(NULL)) | |
return -EPERM; | |
if (!apparmor_enabled) | |
return -EINVAL; | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index 3fd0bb4..13d7104 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -641,25 +641,35 @@ static int audit_policy(struct aa_label *label, const char *op, | |
return error; | |
} | |
-bool policy_view_capable(void) | |
+/** | |
+ * policy_view_capable - check if viewing policy in at @ns is allowed | |
+ * ns: namespace being viewed by current task (may be NULL) | |
+ * Returns: true if viewing policy is allowed | |
+ * | |
+ * If @ns is NULL then the namespace being viewed is assumed to be the | |
+ * tasks current namespace. | |
+ */ | |
+bool policy_view_capable(struct aa_ns *ns) | |
{ | |
struct user_namespace *user_ns = current_user_ns(); | |
- struct aa_ns *ns = aa_get_current_ns(); | |
+ struct aa_ns *view_ns = aa_get_current_ns(); | |
bool root_in_user_ns = uid_eq(current_euid(), make_kuid(user_ns, 0)) || | |
in_egroup_p(make_kgid(user_ns, 0)); | |
bool response = false; | |
+ if (!ns) | |
+ ns = view_ns; | |
- if (root_in_user_ns && | |
+ if (root_in_user_ns && aa_ns_visible(view_ns, ns, true) && | |
(user_ns == &init_user_ns || | |
(unprivileged_userns_apparmor_policy != 0 && | |
- user_ns->level == ns->level))) | |
+ user_ns->level == view_ns->level))) | |
response = true; | |
- aa_put_ns(ns); | |
+ aa_put_ns(view_ns); | |
return response; | |
} | |
-bool policy_admin_capable(void) | |
+bool policy_admin_capable(struct aa_ns *ns) | |
{ | |
struct user_namespace *user_ns = current_user_ns(); | |
bool capable = ns_capable(user_ns, CAP_MAC_ADMIN); | |
@@ -667,7 +677,7 @@ bool policy_admin_capable(void) | |
AA_DEBUG("cap_mac_admin? %d\n", capable); | |
AA_DEBUG("policy locked? %d\n", aa_g_lock_policy); | |
- return policy_view_capable() && capable && !aa_g_lock_policy; | |
+ return policy_view_capable(ns) && capable && !aa_g_lock_policy; | |
} | |
/** | |
@@ -677,7 +687,7 @@ bool policy_admin_capable(void) | |
* | |
* Returns: 0 if the task is allowed to manipulate policy else error | |
*/ | |
-int aa_may_manage_policy(struct aa_label *label, u32 mask) | |
+int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask) | |
{ | |
const char *op; | |
@@ -693,7 +703,7 @@ int aa_may_manage_policy(struct aa_label *label, u32 mask) | |
return audit_policy(label, op, NULL, NULL, "policy_locked", | |
-EACCES); | |
- if (!policy_admin_capable()) | |
+ if (!policy_admin_capable(ns)) | |
return audit_policy(label, op, NULL, NULL, "not policy admin", | |
-EACCES); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 39b4c0b51084bd6bf967182ab359713a3c1aef88 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Mon, 26 Sep 2016 17:05:45 -0700 | |
Subject: [PATCH 51/76] apparmor: add per ns policy management interface | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 96 +++++++++++++++++++++++++++++----- | |
security/apparmor/include/apparmorfs.h | 6 +++ | |
security/apparmor/include/policy.h | 7 +-- | |
security/apparmor/policy.c | 16 +++--- | |
4 files changed, 102 insertions(+), 23 deletions(-) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index 0150d06..b27720e 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -119,7 +119,7 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, | |
} | |
static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, | |
- loff_t *pos) | |
+ loff_t *pos, struct aa_ns *ns) | |
{ | |
struct aa_label *label; | |
ssize_t error; | |
@@ -130,14 +130,15 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, | |
/* high level check about policy management - fine grained in | |
* below after unpack | |
*/ | |
- error = aa_may_manage_policy(label, NULL, mask); | |
+ error = aa_may_manage_policy(label, ns, mask); | |
if (error) | |
return error; | |
data = aa_simple_write_to_buffer(buf, size, size, pos); | |
error = PTR_ERR(data); | |
if (!IS_ERR(data)) { | |
- error = aa_replace_profiles(label, mask, data); | |
+ error = aa_replace_profiles(ns ? ns : labels_ns(label), label, | |
+ mask, data); | |
aa_put_loaddata(data); | |
} | |
aa_end_current_label(label); | |
@@ -149,7 +150,11 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, | |
static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, | |
loff_t *pos) | |
{ | |
- return policy_update(AA_MAY_LOAD_POLICY, buf, size, pos); | |
+ struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); | |
+ int error = policy_update(AA_MAY_LOAD_POLICY, buf, size, pos, ns); | |
+ aa_put_ns(ns); | |
+ | |
+ return error; | |
} | |
static const struct file_operations aa_fs_profile_load = { | |
@@ -161,8 +166,12 @@ static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, | |
static ssize_t profile_replace(struct file *f, const char __user *buf, | |
size_t size, loff_t *pos) | |
{ | |
- return policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY, | |
- buf, size, pos); | |
+ struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); | |
+ int error = policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY, | |
+ buf, size, pos, ns); | |
+ aa_put_ns(ns); | |
+ | |
+ return error; | |
} | |
static const struct file_operations aa_fs_profile_replace = { | |
@@ -177,14 +186,15 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, | |
struct aa_loaddata *data; | |
struct aa_label *label; | |
ssize_t error; | |
+ struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); | |
label = aa_begin_current_label(DO_UPDATE); | |
/* high level check about policy management - fine grained in | |
* below after unpack | |
*/ | |
- error = aa_may_manage_policy(label, NULL, AA_MAY_REMOVE_POLICY); | |
+ error = aa_may_manage_policy(label, ns, AA_MAY_REMOVE_POLICY); | |
if (error) | |
- return error; | |
+ goto out; | |
/* | |
* aa_remove_profile needs a null terminated string so 1 extra | |
@@ -195,11 +205,13 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, | |
error = PTR_ERR(data); | |
if (!IS_ERR(data)) { | |
data->data[size] = 0; | |
- error = aa_remove_profiles(label, data->data, size); | |
+ error = aa_remove_profiles(ns ? ns : labels_ns(label), label, | |
+ data->data, size); | |
aa_put_loaddata(data); | |
} | |
+ out: | |
aa_end_current_label(label); | |
- | |
+ aa_put_ns(ns); | |
return error; | |
} | |
@@ -1127,6 +1139,19 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns) | |
sub = d_inode(ns_subns_dir(ns))->i_private; | |
aa_put_ns(sub); | |
} | |
+ if (ns_subload(ns)) { | |
+ sub = d_inode(ns_subload(ns))->i_private; | |
+ aa_put_ns(sub); | |
+ } | |
+ if (ns_subreplace(ns)) { | |
+ sub = d_inode(ns_subreplace(ns))->i_private; | |
+ aa_put_ns(sub); | |
+ } | |
+ if (ns_subremove(ns)) { | |
+ sub = d_inode(ns_subremove(ns))->i_private; | |
+ aa_put_ns(sub); | |
+ } | |
+ | |
for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) { | |
securityfs_remove(ns->dents[i]); | |
ns->dents[i] = NULL; | |
@@ -1151,7 +1176,28 @@ static int __aa_fs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) | |
return PTR_ERR(dent); | |
ns_subdata_dir(ns) = dent; | |
- /* use create_dentry so we can supply private data */ | |
+ dent = securityfs_create_file(".load", 0666, dir, ns, | |
+ &aa_fs_profile_load); | |
+ if (IS_ERR(dent)) | |
+ return PTR_ERR(dent); | |
+ aa_get_ns(ns); | |
+ ns_subload(ns) = dent; | |
+ | |
+ dent = securityfs_create_file(".replace", 0666, dir, ns, | |
+ &aa_fs_profile_replace); | |
+ if (IS_ERR(dent)) | |
+ return PTR_ERR(dent); | |
+ aa_get_ns(ns); | |
+ ns_subreplace(ns) = dent; | |
+ | |
+ dent = securityfs_create_file(".remove", 0666, dir, ns, | |
+ &aa_fs_profile_remove); | |
+ if (IS_ERR(dent)) | |
+ return PTR_ERR(dent); | |
+ aa_get_ns(ns); | |
+ ns_subremove(ns) = dent; | |
+ | |
+ /* use create_dentry so we can supply private data */ | |
dent = securityfs_create_dentry("namespaces", | |
S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO, | |
dir, ns, NULL, | |
@@ -1534,9 +1580,6 @@ static int profiles_release(struct inode *inode, struct file *file) | |
}; | |
static struct aa_fs_entry aa_fs_entry_apparmor[] = { | |
- AA_FS_FILE_FOPS(".load", 0666, &aa_fs_profile_load), | |
- AA_FS_FILE_FOPS(".replace", 0666, &aa_fs_profile_replace), | |
- AA_FS_FILE_FOPS(".remove", 0666, &aa_fs_profile_remove), | |
AA_FS_FILE_FOPS(".access", 0666, &aa_fs_access), | |
AA_FS_FILE_FOPS(".stacked", 0666, &aa_fs_stacked), | |
AA_FS_FILE_FOPS(".ns_stacked", 0666, &aa_fs_ns_stacked), | |
@@ -1705,6 +1748,7 @@ static int aa_mk_null_file(struct dentry *parent) | |
*/ | |
static int __init aa_create_aafs(void) | |
{ | |
+ struct dentry *dent; | |
int error; | |
if (!apparmor_initialized) | |
@@ -1720,6 +1764,30 @@ static int __init aa_create_aafs(void) | |
if (error) | |
goto error; | |
+ dent = securityfs_create_file(".load", 0666, aa_fs_entry.dentry, | |
+ NULL, &aa_fs_profile_load); | |
+ if (IS_ERR(dent)) { | |
+ error = PTR_ERR(dent); | |
+ goto error; | |
+ } | |
+ ns_subload(root_ns) = dent; | |
+ | |
+ dent = securityfs_create_file(".replace", 0666, aa_fs_entry.dentry, | |
+ NULL, &aa_fs_profile_replace); | |
+ if (IS_ERR(dent)) { | |
+ error = PTR_ERR(dent); | |
+ goto error; | |
+ } | |
+ ns_subreplace(root_ns) = dent; | |
+ | |
+ dent = securityfs_create_file(".remove", 0666, aa_fs_entry.dentry, | |
+ NULL, &aa_fs_profile_remove); | |
+ if (IS_ERR(dent)) { | |
+ error = PTR_ERR(dent); | |
+ goto error; | |
+ } | |
+ ns_subremove(root_ns) = dent; | |
+ | |
mutex_lock(&root_ns->lock); | |
error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy", NULL); | |
mutex_unlock(&root_ns->lock); | |
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h | |
index 077f5e2..bec1cc4 100644 | |
--- a/security/apparmor/include/apparmorfs.h | |
+++ b/security/apparmor/include/apparmorfs.h | |
@@ -71,6 +71,9 @@ enum aafs_ns_type { | |
AAFS_NS_PROFS, | |
AAFS_NS_NS, | |
AAFS_NS_RAW_DATA, | |
+ AAFS_NS_LOAD, | |
+ AAFS_NS_REPLACE, | |
+ AAFS_NS_REMOVE, | |
AAFS_NS_COUNT, | |
AAFS_NS_MAX_COUNT, | |
AAFS_NS_SIZE, | |
@@ -96,6 +99,9 @@ enum aafs_prof_type { | |
#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS]) | |
#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS]) | |
#define ns_subdata_dir(X) ((X)->dents[AAFS_NS_RAW_DATA]) | |
+#define ns_subload(X) ((X)->dents[AAFS_NS_LOAD]) | |
+#define ns_subreplace(X) ((X)->dents[AAFS_NS_REPLACE]) | |
+#define ns_subremove(X) ((X)->dents[AAFS_NS_REMOVE]) | |
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR]) | |
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS]) | |
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h | |
index 27cab34..b6b10a6 100644 | |
--- a/security/apparmor/include/policy.h | |
+++ b/security/apparmor/include/policy.h | |
@@ -185,9 +185,10 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, | |
const char *fqname, size_t n); | |
struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name); | |
-ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, | |
- struct aa_loaddata *udata); | |
-ssize_t aa_remove_profiles(struct aa_label *label, char *name, size_t size); | |
+ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label, | |
+ u32 mask, struct aa_loaddata *udata); | |
+ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label, | |
+ char *name, size_t size); | |
void __aa_profile_list_release(struct list_head *head); | |
#define PROF_ADD 1 | |
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c | |
index 13d7104..afa5984 100644 | |
--- a/security/apparmor/policy.c | |
+++ b/security/apparmor/policy.c | |
@@ -87,6 +87,7 @@ | |
#include "include/match.h" | |
#include "include/path.h" | |
#include "include/policy.h" | |
+#include "include/policy_ns.h" | |
#include "include/policy_unpack.h" | |
#include "include/resource.h" | |
@@ -849,6 +850,7 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) | |
/** | |
* aa_replace_profiles - replace profile(s) on the profile list | |
+ * @view: namespace load is viewed from | |
* @label: label that is attempting to load/replace policy | |
* @mask: permission mask | |
* @udata: serialized data stream (NOT NULL) | |
@@ -859,8 +861,8 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) | |
* | |
* Returns: size of data consumed else error code on failure. | |
*/ | |
-ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, | |
- struct aa_loaddata *udata) | |
+ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label, | |
+ u32 mask, struct aa_loaddata *udata) | |
{ | |
const char *ns_name, *info = NULL; | |
struct aa_ns *ns = NULL; | |
@@ -900,7 +902,7 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, | |
count++; | |
} | |
if (ns_name) { | |
- ns = aa_prepare_ns(labels_ns(label), ns_name); | |
+ ns = aa_prepare_ns(view, ns_name); | |
if (IS_ERR(ns)) { | |
info = "failed to prepare namespace"; | |
error = PTR_ERR(ns); | |
@@ -908,7 +910,7 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, | |
goto fail; | |
} | |
} else | |
- ns = aa_get_ns(labels_ns(label)); | |
+ ns = aa_get_ns(view); | |
mutex_lock(&ns->lock); | |
/* setup parent and ns info */ | |
@@ -1038,6 +1040,7 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, | |
/** | |
* aa_remove_profiles - remove profile(s) from the system | |
+ * @view: namespace the remove is being done from | |
* @label: label attempting to remove policy | |
* @fqname: name of the profile or namespace to remove (NOT NULL) | |
* @size: size of the name | |
@@ -1049,7 +1052,8 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, | |
* | |
* Returns: size of data consume else error code if fails | |
*/ | |
-ssize_t aa_remove_profiles(struct aa_label *label, char *fqname, size_t size) | |
+ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label, | |
+ char *fqname, size_t size) | |
{ | |
struct aa_ns *root = NULL, *ns = NULL; | |
struct aa_profile *profile = NULL; | |
@@ -1063,7 +1067,7 @@ ssize_t aa_remove_profiles(struct aa_label *label, char *fqname, size_t size) | |
goto fail; | |
} | |
- root = labels_ns(label); | |
+ root = view; | |
if (fqname[0] == ':') { | |
name = aa_split_fqname(fqname, &ns_name); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 9a34dd6bfe7caa3097824a46a06b14f6770b45c7 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Tue, 27 Sep 2016 20:11:29 -0700 | |
Subject: [PATCH 52/76] apparmor: bump domain stacking version to 1.2 | |
BugLink: http://bugs.launchpad.net/bugs/1611078 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 2 +- | |
1 file changed, 1 insertion(+), 1 deletion(-) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index b27720e..ff3d613 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -1530,7 +1530,7 @@ static int profiles_release(struct inode *inode, struct file *file) | |
AA_FS_FILE_BOOLEAN("change_onexec", 1), | |
AA_FS_FILE_BOOLEAN("change_profile", 1), | |
AA_FS_FILE_BOOLEAN("stack", 1), | |
- AA_FS_FILE_STRING("version", "1.1"), | |
+ AA_FS_FILE_STRING("version", "1.2"), | |
{ } | |
}; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From dd703702b2ec2113f631f358f146f8051450c475 Mon Sep 17 00:00:00 2001 | |
From: John Johansen <john.johansen@canonical.com> | |
Date: Mon, 3 Oct 2016 17:27:09 -0700 | |
Subject: [PATCH 53/76] UBUNTU: SAUCE: apparmor: add flag to detect semantic | |
change, to binfmt_elf mmap | |
commit 9f834ec18defc369d73ccf9e87a2790bfa05bf46 changed when the creds | |
are installed by the binfmt_elf handler. This affects which creds | |
are used to mmap the executable into the address space. Which can have | |
an affect on apparmor policy. | |
Add a flag to apparmor at | |
/sys/kernel/security/apparmor/features/domain/fix_binfmt_elf_mmap | |
to make it possible to detect this semantic change so that the userspace | |
tools and the regression test suite can correctly deal with the change. | |
Note: since 9f834ec1 is a potential information leak fix for prof | |
events and tracing, it is expected that it could be picked up by | |
kernels earlier kernels than 4.8 so that detecting the kernel version | |
is not sufficient. | |
BugLink: http://bugs.launchpad.net/bugs/1630069 | |
Signed-off-by: John Johansen <john.johansen@canonical.com> | |
Acked-by: Brad Figg <brad.figg@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
security/apparmor/apparmorfs.c | 1 + | |
1 file changed, 1 insertion(+) | |
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c | |
index ff3d613..0904483 100644 | |
--- a/security/apparmor/apparmorfs.c | |
+++ b/security/apparmor/apparmorfs.c | |
@@ -1530,6 +1530,7 @@ static int profiles_release(struct inode *inode, struct file *file) | |
AA_FS_FILE_BOOLEAN("change_onexec", 1), | |
AA_FS_FILE_BOOLEAN("change_profile", 1), | |
AA_FS_FILE_BOOLEAN("stack", 1), | |
+ AA_FS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1), | |
AA_FS_FILE_STRING("version", "1.2"), | |
{ } | |
}; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From c1e3c4342c0dedcc266df8e156d9388e051eece9 Mon Sep 17 00:00:00 2001 | |
From: Andy Whitcroft <apw@canonical.com> | |
Date: Thu, 6 Oct 2016 14:22:12 +0100 | |
Subject: [PATCH 54/76] UBUNTU: SAUCE: (no-up) include/linux/security.h -- fix | |
syntax error with CONFIG_SECURITYFS=n | |
commit c2ac27f7a443 ("securityfs: update interface to allow | |
inode_ops, and setup from vfs") introduced a syntax error | |
in include/linux/security.h when CONFIG_SECURITYFS is not set. | |
This is exercised by the zfcpdump-kernel for s390x. | |
BugLink: http://bugs.launchpad.net/bugs/1630990 | |
Signed-off-by: Andy Whitcroft <apw@canonical.com> | |
Acked-by: Colin King <colin.king@canonical.com> | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
include/linux/security.h | 2 +- | |
1 file changed, 1 insertion(+), 1 deletion(-) | |
diff --git a/include/linux/security.h b/include/linux/security.h | |
index f257e72..7864d10 100644 | |
--- a/include/linux/security.h | |
+++ b/include/linux/security.h | |
@@ -1658,7 +1658,7 @@ static inline int __securityfs_setup_d_inode(struct inode *dir, | |
struct dentry *dentry, | |
umode_t mode, void *data, | |
const struct file_operations *fops, | |
- const struct inode_operations *iops)) | |
+ const struct inode_operations *iops) | |
{ | |
return -ENODEV; | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From a6d029f2affc2032c418ad904dede1ff1d8e53d0 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Tue, 26 Jul 2016 15:19:28 -0500 | |
Subject: [PATCH 55/76] UBUNTU: SAUCE: (namespace) security/integrity: Harden | |
against malformed xattrs | |
In general the handling of IMA/EVM xattrs is good, but I found | |
a few locations where either the xattr size or the value of the | |
type field in the xattr are not checked. Add a few simple checks | |
to these locations to prevent malformed or malicious xattrs from | |
causing problems. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
security/integrity/digsig.c | 2 +- | |
security/integrity/evm/evm_main.c | 4 ++++ | |
security/integrity/ima/ima_appraise.c | 5 ++++- | |
3 files changed, 9 insertions(+), 2 deletions(-) | |
diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c | |
index 4304372..106e855 100644 | |
--- a/security/integrity/digsig.c | |
+++ b/security/integrity/digsig.c | |
@@ -51,7 +51,7 @@ | |
int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, | |
const char *digest, int digestlen) | |
{ | |
- if (id >= INTEGRITY_KEYRING_MAX) | |
+ if (id >= INTEGRITY_KEYRING_MAX || siglen < 2) | |
return -EINVAL; | |
if (!keyring[id]) { | |
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c | |
index ba86155..e2ed498 100644 | |
--- a/security/integrity/evm/evm_main.c | |
+++ b/security/integrity/evm/evm_main.c | |
@@ -145,6 +145,10 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, | |
/* check value type */ | |
switch (xattr_data->type) { | |
case EVM_XATTR_HMAC: | |
+ if (xattr_len != sizeof(struct evm_ima_xattr_data)) { | |
+ evm_status = INTEGRITY_FAIL; | |
+ goto out; | |
+ } | |
rc = evm_calc_hmac(dentry, xattr_name, xattr_value, | |
xattr_value_len, calc.digest); | |
if (rc) | |
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c | |
index 389325a..4c0526d 100644 | |
--- a/security/integrity/ima/ima_appraise.c | |
+++ b/security/integrity/ima/ima_appraise.c | |
@@ -130,6 +130,7 @@ enum hash_algo ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, | |
int xattr_len) | |
{ | |
struct signature_v2_hdr *sig; | |
+ enum hash_algo ret; | |
if (!xattr_value || xattr_len < 2) | |
/* return default hash algo */ | |
@@ -143,7 +144,9 @@ enum hash_algo ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, | |
return sig->hash_algo; | |
break; | |
case IMA_XATTR_DIGEST_NG: | |
- return xattr_value->digest[0]; | |
+ ret = xattr_value->digest[0]; | |
+ if (ret < HASH_ALGO__LAST) | |
+ return ret; | |
break; | |
case IMA_XATTR_DIGEST: | |
/* this is for backward compatibility */ | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 93b8c0ecaf420863f12522e0a572c8278666a4c7 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Fri, 31 Jul 2015 12:58:34 -0500 | |
Subject: [PATCH 56/76] UBUNTU: SAUCE: (namespace) block_dev: Support checking | |
inode permissions in lookup_bdev() | |
When looking up a block device by path no permission check is | |
done to verify that the user has access to the block device inode | |
at the specified path. In some cases it may be necessary to | |
check permissions towards the inode, such as allowing | |
unprivileged users to mount block devices in user namespaces. | |
Add an argument to lookup_bdev() to optionally perform this | |
permission check. A value of 0 skips the permission check and | |
behaves the same as before. A non-zero value specifies the mask | |
of access rights required towards the inode at the specified | |
path. The check is always skipped if the user has CAP_SYS_ADMIN. | |
All callers of lookup_bdev() currently pass a mask of 0, so this | |
patch results in no functional change. Subsequent patches will | |
add permission checks where appropriate. | |
Acked-by: Serge Hallyn <serge.hallyn@canonical.com> | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
drivers/md/bcache/super.c | 2 +- | |
drivers/md/dm-table.c | 2 +- | |
drivers/mtd/mtdsuper.c | 2 +- | |
fs/block_dev.c | 13 ++++++++++--- | |
fs/quota/quota.c | 2 +- | |
include/linux/fs.h | 2 +- | |
6 files changed, 15 insertions(+), 8 deletions(-) | |
diff --git a/drivers/md/bcache/super.c b/drivers/md/bcache/super.c | |
index 66669c8..ebb0c21 100644 | |
--- a/drivers/md/bcache/super.c | |
+++ b/drivers/md/bcache/super.c | |
@@ -1960,7 +1960,7 @@ static ssize_t register_bcache(struct kobject *k, struct kobj_attribute *attr, | |
sb); | |
if (IS_ERR(bdev)) { | |
if (bdev == ERR_PTR(-EBUSY)) { | |
- bdev = lookup_bdev(strim(path)); | |
+ bdev = lookup_bdev(strim(path), 0); | |
mutex_lock(&bch_register_lock); | |
if (!IS_ERR(bdev) && bch_is_open(bdev)) | |
err = "device already registered"; | |
diff --git a/drivers/md/dm-table.c b/drivers/md/dm-table.c | |
index 5ac239d..808905a 100644 | |
--- a/drivers/md/dm-table.c | |
+++ b/drivers/md/dm-table.c | |
@@ -375,7 +375,7 @@ dev_t dm_get_dev_t(const char *path) | |
dev_t uninitialized_var(dev); | |
struct block_device *bdev; | |
- bdev = lookup_bdev(path); | |
+ bdev = lookup_bdev(path, 0); | |
if (IS_ERR(bdev)) | |
dev = name_to_dev_t(path); | |
else { | |
diff --git a/drivers/mtd/mtdsuper.c b/drivers/mtd/mtdsuper.c | |
index 20c02a3..b5b60e1 100644 | |
--- a/drivers/mtd/mtdsuper.c | |
+++ b/drivers/mtd/mtdsuper.c | |
@@ -176,7 +176,7 @@ struct dentry *mount_mtd(struct file_system_type *fs_type, int flags, | |
/* try the old way - the hack where we allowed users to mount | |
* /dev/mtdblock$(n) but didn't actually _use_ the blockdev | |
*/ | |
- bdev = lookup_bdev(dev_name); | |
+ bdev = lookup_bdev(dev_name, 0); | |
if (IS_ERR(bdev)) { | |
ret = PTR_ERR(bdev); | |
pr_debug("MTDSB: lookup_bdev() returned %d\n", ret); | |
diff --git a/fs/block_dev.c b/fs/block_dev.c | |
index 092a2ee..58c509a 100644 | |
--- a/fs/block_dev.c | |
+++ b/fs/block_dev.c | |
@@ -1487,7 +1487,7 @@ struct block_device *blkdev_get_by_path(const char *path, fmode_t mode, | |
struct block_device *bdev; | |
int err; | |
- bdev = lookup_bdev(path); | |
+ bdev = lookup_bdev(path, 0); | |
if (IS_ERR(bdev)) | |
return bdev; | |
@@ -1883,12 +1883,14 @@ int ioctl_by_bdev(struct block_device *bdev, unsigned cmd, unsigned long arg) | |
/** | |
* lookup_bdev - lookup a struct block_device by name | |
* @pathname: special file representing the block device | |
+ * @mask: rights to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC) | |
* | |
* Get a reference to the blockdevice at @pathname in the current | |
* namespace if possible and return it. Return ERR_PTR(error) | |
- * otherwise. | |
+ * otherwise. If @mask is non-zero, check for access rights to the | |
+ * inode at @pathname. | |
*/ | |
-struct block_device *lookup_bdev(const char *pathname) | |
+struct block_device *lookup_bdev(const char *pathname, int mask) | |
{ | |
struct block_device *bdev; | |
struct inode *inode; | |
@@ -1903,6 +1905,11 @@ struct block_device *lookup_bdev(const char *pathname) | |
return ERR_PTR(error); | |
inode = d_backing_inode(path.dentry); | |
+ if (mask != 0 && !capable(CAP_SYS_ADMIN)) { | |
+ error = __inode_permission(inode, mask); | |
+ if (error) | |
+ goto fail; | |
+ } | |
error = -ENOTBLK; | |
if (!S_ISBLK(inode->i_mode)) | |
goto fail; | |
diff --git a/fs/quota/quota.c b/fs/quota/quota.c | |
index 2d44542..09de0c4 100644 | |
--- a/fs/quota/quota.c | |
+++ b/fs/quota/quota.c | |
@@ -805,7 +805,7 @@ static struct super_block *quotactl_block(const char __user *special, int cmd) | |
if (IS_ERR(tmp)) | |
return ERR_CAST(tmp); | |
- bdev = lookup_bdev(tmp->name); | |
+ bdev = lookup_bdev(tmp->name, 0); | |
putname(tmp); | |
if (IS_ERR(bdev)) | |
return ERR_CAST(bdev); | |
diff --git a/include/linux/fs.h b/include/linux/fs.h | |
index cdec4d0..fc7ffe2 100644 | |
--- a/include/linux/fs.h | |
+++ b/include/linux/fs.h | |
@@ -2486,7 +2486,7 @@ static inline void unregister_chrdev(unsigned int major, const char *name) | |
#define BLKDEV_MAJOR_HASH_SIZE 255 | |
extern const char *__bdevname(dev_t, char *buffer); | |
extern const char *bdevname(struct block_device *bdev, char *buffer); | |
-extern struct block_device *lookup_bdev(const char *); | |
+extern struct block_device *lookup_bdev(const char *, int mask); | |
extern void blkdev_show(struct seq_file *,off_t); | |
#else | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From b7fb63b8036d8cfcb66afee43d597d3698056801 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Wed, 7 Oct 2015 14:49:47 -0500 | |
Subject: [PATCH 57/76] UBUNTU: SAUCE: (namespace) block_dev: Check permissions | |
towards block device inode when mounting | |
Unprivileged users should not be able to mount block devices when | |
they lack sufficient privileges towards the block device inode. | |
Update blkdev_get_by_path() to validate that the user has the | |
required access to the inode at the specified path. The check | |
will be skipped for CAP_SYS_ADMIN, so privileged mounts will | |
continue working as before. | |
Acked-by: Serge Hallyn <serge.hallyn@canonical.com> | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
fs/block_dev.c | 7 ++++++- | |
1 file changed, 6 insertions(+), 1 deletion(-) | |
diff --git a/fs/block_dev.c b/fs/block_dev.c | |
index 58c509a..8d9fac2 100644 | |
--- a/fs/block_dev.c | |
+++ b/fs/block_dev.c | |
@@ -1485,9 +1485,14 @@ struct block_device *blkdev_get_by_path(const char *path, fmode_t mode, | |
void *holder) | |
{ | |
struct block_device *bdev; | |
+ int perm = 0; | |
int err; | |
- bdev = lookup_bdev(path, 0); | |
+ if (mode & FMODE_READ) | |
+ perm |= MAY_READ; | |
+ if (mode & FMODE_WRITE) | |
+ perm |= MAY_WRITE; | |
+ bdev = lookup_bdev(path, perm); | |
if (IS_ERR(bdev)) | |
return bdev; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From db2d1973424c0440e1be88d8d508121c5c747a2b Mon Sep 17 00:00:00 2001 | |
From: "Eric W. Biederman" <ebiederm@xmission.com> | |
Date: Sat, 2 Jul 2016 09:54:25 -0500 | |
Subject: [PATCH 58/76] UBUNTU: SAUCE: (namespace) fs: Allow superblock owner | |
to change ownership of inodes | |
Allow users with CAP_SYS_CHOWN over the superblock of a filesystem to | |
chown files. Ordinarily the capable_wrt_inode_uidgid check is | |
sufficient to allow access to files but when the underlying filesystem | |
has uids or gids that don't map to the current user namespace it is | |
not enough, so the chown permission checks need to be extended to | |
allow this case. | |
Calling chown on filesystem nodes whose uid or gid don't map is | |
necessary if those nodes are going to be modified as writing back | |
inodes which contain uids or gids that don't map is likely to cause | |
filesystem corruption of the uid or gid fields. | |
Once chown has been called the existing capable_wrt_inode_uidgid | |
checks are sufficient, to allow the owner of a superblock to do anything | |
the global root user can do with an appropriate set of capabilities. | |
For the proc filesystem this relaxation of permissions is not safe, as | |
some files are owned by users (particularly GLOBAL_ROOT_UID) outside | |
of the control of the mounter of the proc and that would be unsafe to | |
grant chown access to. So update setattr on proc to disallow changing | |
files whose uids or gids are outside of proc's s_user_ns. | |
The original version of this patch was written by: Seth Forshee. I | |
have rewritten and rethought this patch enough so it's really not the | |
same thing (certainly it needs a different description), but he | |
deserves credit for getting out there and getting the conversation | |
started, and finding the potential gotcha's and putting up with my | |
semi-paranoid feedback. | |
Inspired-by: Seth Forshee <seth.forshee@canonical.com> | |
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com> | |
[ saf: Context adjustments in proc ] | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
fs/attr.c | 34 ++++++++++++++++++++++++++-------- | |
fs/proc/base.c | 7 +++++++ | |
fs/proc/generic.c | 7 +++++++ | |
fs/proc/proc_sysctl.c | 7 +++++++ | |
4 files changed, 47 insertions(+), 8 deletions(-) | |
diff --git a/fs/attr.c b/fs/attr.c | |
index c902b3d..540543b 100644 | |
--- a/fs/attr.c | |
+++ b/fs/attr.c | |
@@ -16,6 +16,30 @@ | |
#include <linux/evm.h> | |
#include <linux/ima.h> | |
+static bool chown_ok(const struct inode *inode, kuid_t uid) | |
+{ | |
+ if (uid_eq(current_fsuid(), inode->i_uid) && | |
+ uid_eq(uid, inode->i_uid)) | |
+ return true; | |
+ if (capable_wrt_inode_uidgid(inode, CAP_CHOWN)) | |
+ return true; | |
+ if (ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN)) | |
+ return true; | |
+ return false; | |
+} | |
+ | |
+static bool chgrp_ok(const struct inode *inode, kgid_t gid) | |
+{ | |
+ if (uid_eq(current_fsuid(), inode->i_uid) && | |
+ (in_group_p(gid) || gid_eq(gid, inode->i_gid))) | |
+ return true; | |
+ if (capable_wrt_inode_uidgid(inode, CAP_CHOWN)) | |
+ return true; | |
+ if (ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN)) | |
+ return true; | |
+ return false; | |
+} | |
+ | |
/** | |
* setattr_prepare - check if attribute changes to a dentry are allowed | |
* @dentry: dentry to check | |
@@ -50,17 +74,11 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr) | |
goto kill_priv; | |
/* Make sure a caller can chown. */ | |
- if ((ia_valid & ATTR_UID) && | |
- (!uid_eq(current_fsuid(), inode->i_uid) || | |
- !uid_eq(attr->ia_uid, inode->i_uid)) && | |
- !capable_wrt_inode_uidgid(inode, CAP_CHOWN)) | |
+ if ((ia_valid & ATTR_UID) && !chown_ok(inode, attr->ia_uid)) | |
return -EPERM; | |
/* Make sure caller can chgrp. */ | |
- if ((ia_valid & ATTR_GID) && | |
- (!uid_eq(current_fsuid(), inode->i_uid) || | |
- (!in_group_p(attr->ia_gid) && !gid_eq(attr->ia_gid, inode->i_gid))) && | |
- !capable_wrt_inode_uidgid(inode, CAP_CHOWN)) | |
+ if ((ia_valid & ATTR_GID) && !chgrp_ok(inode, attr->ia_gid)) | |
return -EPERM; | |
/* Make sure a caller can chmod. */ | |
diff --git a/fs/proc/base.c b/fs/proc/base.c | |
index ca651ac..01c99ed 100644 | |
--- a/fs/proc/base.c | |
+++ b/fs/proc/base.c | |
@@ -688,10 +688,17 @@ int proc_setattr(struct dentry *dentry, struct iattr *attr) | |
{ | |
int error; | |
struct inode *inode = d_inode(dentry); | |
+ struct user_namespace *s_user_ns; | |
if (attr->ia_valid & ATTR_MODE) | |
return -EPERM; | |
+ /* Don't let anyone mess with weird proc files */ | |
+ s_user_ns = inode->i_sb->s_user_ns; | |
+ if (!kuid_has_mapping(s_user_ns, inode->i_uid) || | |
+ !kgid_has_mapping(s_user_ns, inode->i_gid)) | |
+ return -EPERM; | |
+ | |
error = setattr_prepare(dentry, attr); | |
if (error) | |
return error; | |
diff --git a/fs/proc/generic.c b/fs/proc/generic.c | |
index 7eb3cef..d2baeac 100644 | |
--- a/fs/proc/generic.c | |
+++ b/fs/proc/generic.c | |
@@ -103,8 +103,15 @@ static int proc_notify_change(struct dentry *dentry, struct iattr *iattr) | |
{ | |
struct inode *inode = d_inode(dentry); | |
struct proc_dir_entry *de = PDE(inode); | |
+ struct user_namespace *s_user_ns; | |
int error; | |
+ /* Don't let anyone mess with weird proc files */ | |
+ s_user_ns = inode->i_sb->s_user_ns; | |
+ if (!kuid_has_mapping(s_user_ns, inode->i_uid) || | |
+ !kgid_has_mapping(s_user_ns, inode->i_gid)) | |
+ return -EPERM; | |
+ | |
error = setattr_prepare(dentry, iattr); | |
if (error) | |
return error; | |
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c | |
index d4e37ac..231d295 100644 | |
--- a/fs/proc/proc_sysctl.c | |
+++ b/fs/proc/proc_sysctl.c | |
@@ -755,11 +755,18 @@ static int proc_sys_permission(struct inode *inode, int mask) | |
static int proc_sys_setattr(struct dentry *dentry, struct iattr *attr) | |
{ | |
struct inode *inode = d_inode(dentry); | |
+ struct user_namespace *s_user_ns; | |
int error; | |
if (attr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)) | |
return -EPERM; | |
+ /* Don't let anyone mess with weird proc files */ | |
+ s_user_ns = inode->i_sb->s_user_ns; | |
+ if (!kuid_has_mapping(s_user_ns, inode->i_uid) || | |
+ !kgid_has_mapping(s_user_ns, inode->i_gid)) | |
+ return -EPERM; | |
+ | |
error = setattr_prepare(dentry, attr); | |
if (error) | |
return error; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From ab673a7f970ad0bf6451f5a18ebba9ce13db328e Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Tue, 26 Apr 2016 14:36:28 -0500 | |
Subject: [PATCH 59/76] UBUNTU: SAUCE: (namespace) fs: Don't remove suid for | |
CAP_FSETID for userns root | |
Expand the check in should_remove_suid() to keep privileges for | |
CAP_FSETID in s_user_ns rather than init_user_ns. | |
--EWB Changed from ns_capable(sb->s_user_ns, ) to capable_wrt_inode_uidgid | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
Acked-by: Serge Hallyn <serge.hallyn@canonical.com> | |
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com> | |
--- | |
fs/inode.c | 6 ++++-- | |
1 file changed, 4 insertions(+), 2 deletions(-) | |
diff --git a/fs/inode.c b/fs/inode.c | |
index 88110fd..046cc93 100644 | |
--- a/fs/inode.c | |
+++ b/fs/inode.c | |
@@ -1736,7 +1736,8 @@ void touch_atime(const struct path *path) | |
*/ | |
int should_remove_suid(struct dentry *dentry) | |
{ | |
- umode_t mode = d_inode(dentry)->i_mode; | |
+ struct inode *inode = d_inode(dentry); | |
+ umode_t mode = inode->i_mode; | |
int kill = 0; | |
/* suid always must be killed */ | |
@@ -1750,7 +1751,8 @@ int should_remove_suid(struct dentry *dentry) | |
if (unlikely((mode & S_ISGID) && (mode & S_IXGRP))) | |
kill |= ATTR_KILL_SGID; | |
- if (unlikely(kill && !capable(CAP_FSETID) && S_ISREG(mode))) | |
+ if (unlikely(kill && !capable_wrt_inode_uidgid(inode, CAP_FSETID) && | |
+ S_ISREG(mode))) | |
return kill; | |
return 0; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 12c2411d2e4674d6befa3a045354c74f363aa183 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Tue, 26 Apr 2016 14:36:29 -0500 | |
Subject: [PATCH 60/76] UBUNTU: SAUCE: (namespace) fs: Allow superblock owner | |
to access do_remount_sb() | |
Superblock level remounts are currently restricted to global | |
CAP_SYS_ADMIN, as is the path for changing the root mount to | |
read only on umount. Loosen both of these permission checks to | |
also allow CAP_SYS_ADMIN in any namespace which is privileged | |
towards the userns which originally mounted the filesystem. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
Acked-by: "Eric W. Biederman" <ebiederm@xmission.com> | |
Acked-by: Serge Hallyn <serge.hallyn@canonical.com> | |
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com> | |
--- | |
fs/namespace.c | 4 ++-- | |
1 file changed, 2 insertions(+), 2 deletions(-) | |
diff --git a/fs/namespace.c b/fs/namespace.c | |
index 7cea503..40a1d11 100644 | |
--- a/fs/namespace.c | |
+++ b/fs/namespace.c | |
@@ -1545,7 +1545,7 @@ static int do_umount(struct mount *mnt, int flags) | |
* Special case for "unmounting" root ... | |
* we just try to remount it readonly. | |
*/ | |
- if (!capable(CAP_SYS_ADMIN)) | |
+ if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) | |
return -EPERM; | |
down_write(&sb->s_umount); | |
if (!(sb->s_flags & MS_RDONLY)) | |
@@ -2277,7 +2277,7 @@ static int do_remount(struct path *path, int flags, int mnt_flags, | |
down_write(&sb->s_umount); | |
if (flags & MS_BIND) | |
err = change_mount_flags(path->mnt, flags); | |
- else if (!capable(CAP_SYS_ADMIN)) | |
+ else if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) | |
err = -EPERM; | |
else | |
err = do_remount_sb(sb, flags, data, 0); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From e67d227702962ed0f317876f3f56eda9fa2cefa0 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Tue, 26 Apr 2016 14:36:30 -0500 | |
Subject: [PATCH 61/76] UBUNTU: SAUCE: (namespace) capabilities: Allow | |
privileged user in s_user_ns to set security.* xattrs | |
A privileged user in s_user_ns will generally have the ability to | |
manipulate the backing store and insert security.* xattrs into | |
the filesystem directly. Therefore the kernel must be prepared to | |
handle these xattrs from unprivileged mounts, and it makes little | |
sense for commoncap to prevent writing these xattrs to the | |
filesystem. The capability and LSM code have already been updated | |
to appropriately handle xattrs from unprivileged mounts, so it | |
is safe to loosen this restriction on setting xattrs. | |
The exception to this logic is that writing xattrs to a mounted | |
filesystem may also cause the LSM inode_post_setxattr or | |
inode_setsecurity callbacks to be invoked. SELinux will deny the | |
xattr update by virtue of applying mountpoint labeling to | |
unprivileged userns mounts, and Smack will deny the writes for | |
any user without global CAP_MAC_ADMIN, so loosening the | |
capability check in commoncap is safe in this respect as well. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
Acked-by: Serge Hallyn <serge.hallyn@canonical.com> | |
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com> | |
--- | |
security/commoncap.c | 12 ++++++++---- | |
1 file changed, 8 insertions(+), 4 deletions(-) | |
diff --git a/security/commoncap.c b/security/commoncap.c | |
index ab5af4c..1407a3c 100644 | |
--- a/security/commoncap.c | |
+++ b/security/commoncap.c | |
@@ -659,15 +659,17 @@ int cap_bprm_secureexec(struct linux_binprm *bprm) | |
int cap_inode_setxattr(struct dentry *dentry, const char *name, | |
const void *value, size_t size, int flags) | |
{ | |
+ struct user_namespace *user_ns = dentry->d_sb->s_user_ns; | |
+ | |
if (!strcmp(name, XATTR_NAME_CAPS)) { | |
- if (!capable(CAP_SETFCAP)) | |
+ if (!ns_capable(user_ns, CAP_SETFCAP)) | |
return -EPERM; | |
return 0; | |
} | |
if (!strncmp(name, XATTR_SECURITY_PREFIX, | |
sizeof(XATTR_SECURITY_PREFIX) - 1) && | |
- !capable(CAP_SYS_ADMIN)) | |
+ !ns_capable(user_ns, CAP_SYS_ADMIN)) | |
return -EPERM; | |
return 0; | |
} | |
@@ -685,15 +687,17 @@ int cap_inode_setxattr(struct dentry *dentry, const char *name, | |
*/ | |
int cap_inode_removexattr(struct dentry *dentry, const char *name) | |
{ | |
+ struct user_namespace *user_ns = dentry->d_sb->s_user_ns; | |
+ | |
if (!strcmp(name, XATTR_NAME_CAPS)) { | |
- if (!capable(CAP_SETFCAP)) | |
+ if (!ns_capable(user_ns, CAP_SETFCAP)) | |
return -EPERM; | |
return 0; | |
} | |
if (!strncmp(name, XATTR_SECURITY_PREFIX, | |
sizeof(XATTR_SECURITY_PREFIX) - 1) && | |
- !capable(CAP_SYS_ADMIN)) | |
+ !ns_capable(user_ns, CAP_SYS_ADMIN)) | |
return -EPERM; | |
return 0; | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 6dbf24f09af3cfb7154e03bbcc81eede05eafe86 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Sun, 15 Feb 2015 14:35:35 -0600 | |
Subject: [PATCH 62/76] UBUNTU: SAUCE: (namespace) fs: Allow CAP_SYS_ADMIN in | |
s_user_ns to freeze and thaw filesystems | |
The user in control of a super block should be allowed to freeze | |
and thaw it. Relax the restrictions on the FIFREEZE and FITHAW | |
ioctls to require CAP_SYS_ADMIN in s_user_ns. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com> | |
--- | |
fs/ioctl.c | 4 ++-- | |
1 file changed, 2 insertions(+), 2 deletions(-) | |
diff --git a/fs/ioctl.c b/fs/ioctl.c | |
index c415668..f750d2c 100644 | |
--- a/fs/ioctl.c | |
+++ b/fs/ioctl.c | |
@@ -542,7 +542,7 @@ static int ioctl_fsfreeze(struct file *filp) | |
{ | |
struct super_block *sb = file_inode(filp)->i_sb; | |
- if (!capable(CAP_SYS_ADMIN)) | |
+ if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) | |
return -EPERM; | |
/* If filesystem doesn't support freeze feature, return. */ | |
@@ -559,7 +559,7 @@ static int ioctl_fsthaw(struct file *filp) | |
{ | |
struct super_block *sb = file_inode(filp)->i_sb; | |
- if (!capable(CAP_SYS_ADMIN)) | |
+ if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) | |
return -EPERM; | |
/* Thaw */ | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 9281e54e044e8f00f26fd8a931440458c4b65954 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Fri, 8 Jul 2016 15:57:51 -0500 | |
Subject: [PATCH 63/76] UBUNTU: SAUCE: (namespace) posix_acl: Export | |
posix_acl_fix_xattr_userns() to modules | |
Fuse will make use of this function to provide backwards- | |
compatible acl support when proper posix acl support is added. | |
Add a check to return immediately if the to and from namespaces | |
are the same, and remove equivalent checks from its callers. | |
Also return an error code to indicate to callers whether or not | |
the conversion of the id between the user namespaces was | |
successful. For a valid xattr the id will continue to be changed | |
regardless to maintain the current behaviour for existing | |
callers, so they do not require updates to handle failed | |
conversions. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
fs/posix_acl.c | 42 +++++++++++++++++++++++++---------------- | |
include/linux/posix_acl_xattr.h | 9 +++++++++ | |
2 files changed, 35 insertions(+), 16 deletions(-) | |
diff --git a/fs/posix_acl.c b/fs/posix_acl.c | |
index c9d48dc..f1d7d2a 100644 | |
--- a/fs/posix_acl.c | |
+++ b/fs/posix_acl.c | |
@@ -661,58 +661,68 @@ int posix_acl_update_mode(struct inode *inode, umode_t *mode_p, | |
/* | |
* Fix up the uids and gids in posix acl extended attributes in place. | |
*/ | |
-static void posix_acl_fix_xattr_userns( | |
+int posix_acl_fix_xattr_userns( | |
struct user_namespace *to, struct user_namespace *from, | |
void *value, size_t size) | |
{ | |
struct posix_acl_xattr_header *header = value; | |
struct posix_acl_xattr_entry *entry = (void *)(header + 1), *end; | |
int count; | |
- kuid_t uid; | |
- kgid_t gid; | |
+ kuid_t kuid; | |
+ kgid_t kgid; | |
+ uid_t uid; | |
+ gid_t gid; | |
+ int ret = 0; | |
+ if (to == from) | |
+ return 0; | |
if (!value) | |
- return; | |
+ return 0; | |
if (size < sizeof(struct posix_acl_xattr_header)) | |
- return; | |
+ return -EINVAL; | |
if (header->a_version != cpu_to_le32(POSIX_ACL_XATTR_VERSION)) | |
- return; | |
+ return -EINVAL; | |
count = posix_acl_xattr_count(size); | |
if (count < 0) | |
- return; | |
+ return -EINVAL; | |
if (count == 0) | |
- return; | |
+ return 0; | |
for (end = entry + count; entry != end; entry++) { | |
switch(le16_to_cpu(entry->e_tag)) { | |
case ACL_USER: | |
- uid = make_kuid(from, le32_to_cpu(entry->e_id)); | |
- entry->e_id = cpu_to_le32(from_kuid(to, uid)); | |
+ kuid = make_kuid(from, le32_to_cpu(entry->e_id)); | |
+ uid = from_kuid(to, kuid); | |
+ entry->e_id = cpu_to_le32(uid); | |
+ if (uid == (uid_t)-1) | |
+ ret = -EOVERFLOW; | |
break; | |
case ACL_GROUP: | |
- gid = make_kgid(from, le32_to_cpu(entry->e_id)); | |
- entry->e_id = cpu_to_le32(from_kgid(to, gid)); | |
+ kgid = make_kgid(from, le32_to_cpu(entry->e_id)); | |
+ gid = from_kgid(to, kgid); | |
+ entry->e_id = cpu_to_le32(gid); | |
+ if (gid == (gid_t)-1) | |
+ ret = -EOVERFLOW; | |
break; | |
default: | |
break; | |
} | |
} | |
+ | |
+ return ret; | |
} | |
+EXPORT_SYMBOL(posix_acl_fix_xattr_userns); | |
void posix_acl_fix_xattr_from_user(void *value, size_t size) | |
{ | |
struct user_namespace *user_ns = current_user_ns(); | |
- if (user_ns == &init_user_ns) | |
- return; | |
posix_acl_fix_xattr_userns(&init_user_ns, user_ns, value, size); | |
} | |
void posix_acl_fix_xattr_to_user(void *value, size_t size) | |
{ | |
struct user_namespace *user_ns = current_user_ns(); | |
- if (user_ns == &init_user_ns) | |
- return; | |
posix_acl_fix_xattr_userns(user_ns, &init_user_ns, value, size); | |
} | |
diff --git a/include/linux/posix_acl_xattr.h b/include/linux/posix_acl_xattr.h | |
index 8b867e3..67e148d 100644 | |
--- a/include/linux/posix_acl_xattr.h | |
+++ b/include/linux/posix_acl_xattr.h | |
@@ -32,9 +32,18 @@ | |
} | |
#ifdef CONFIG_FS_POSIX_ACL | |
+int posix_acl_fix_xattr_userns(struct user_namespace *to, | |
+ struct user_namespace *from, | |
+ void *value, size_t size); | |
void posix_acl_fix_xattr_from_user(void *value, size_t size); | |
void posix_acl_fix_xattr_to_user(void *value, size_t size); | |
#else | |
+static inline int posix_acl_fix_xattr_userns(struct user_namespace *to, | |
+ struct user_namespace *from, | |
+ void *value, size_t size) | |
+{ | |
+ return 0; | |
+} | |
static inline void posix_acl_fix_xattr_from_user(void *value, size_t size) | |
{ | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 56431d88ee18fd2f50f5cd57e74e32555b6e7e26 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Wed, 2 Jul 2014 16:29:19 -0500 | |
Subject: [PATCH 64/76] UBUNTU: SAUCE: (namespace) fuse: Add support for pid | |
namespaces | |
When the userspace process servicing fuse requests is running in | |
a pid namespace then pids passed via the fuse fd are not being | |
translated into that process' namespace. Translation is necessary | |
for the pid to be useful to that process. | |
Since no use case currently exists for changing namespaces all | |
translations can be done relative to the pid namespace in use | |
when fuse_conn_init() is called. For fuse this translates to | |
mount time, and for cuse this is when /dev/cuse is opened. IO for | |
this connection from another namespace will return errors. | |
Requests from processes whose pid cannot be translated into the | |
target namespace will have a value of 0 for in.h.pid. | |
File locking changes based on previous work done by Eric | |
Biederman. | |
Acked-by: Miklos Szeredi <mszeredi@redhat.com> | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
fs/fuse/dev.c | 15 +++++++++++---- | |
fs/fuse/file.c | 22 +++++++++++++++++----- | |
fs/fuse/fuse_i.h | 4 ++++ | |
fs/fuse/inode.c | 3 +++ | |
4 files changed, 35 insertions(+), 9 deletions(-) | |
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c | |
index f117926..e0a6a4f 100644 | |
--- a/fs/fuse/dev.c | |
+++ b/fs/fuse/dev.c | |
@@ -19,6 +19,7 @@ | |
#include <linux/pipe_fs_i.h> | |
#include <linux/swap.h> | |
#include <linux/splice.h> | |
+#include <linux/sched.h> | |
MODULE_ALIAS_MISCDEV(FUSE_MINOR); | |
MODULE_ALIAS("devname:fuse"); | |
@@ -111,11 +112,11 @@ static void __fuse_put_request(struct fuse_req *req) | |
atomic_dec(&req->count); | |
} | |
-static void fuse_req_init_context(struct fuse_req *req) | |
+static void fuse_req_init_context(struct fuse_conn *fc, struct fuse_req *req) | |
{ | |
req->in.h.uid = from_kuid_munged(&init_user_ns, current_fsuid()); | |
req->in.h.gid = from_kgid_munged(&init_user_ns, current_fsgid()); | |
- req->in.h.pid = current->pid; | |
+ req->in.h.pid = pid_nr_ns(task_pid(current), fc->pid_ns); | |
} | |
void fuse_set_initialized(struct fuse_conn *fc) | |
@@ -162,7 +163,7 @@ static struct fuse_req *__fuse_get_req(struct fuse_conn *fc, unsigned npages, | |
goto out; | |
} | |
- fuse_req_init_context(req); | |
+ fuse_req_init_context(fc, req); | |
__set_bit(FR_WAITING, &req->flags); | |
if (for_background) | |
__set_bit(FR_BACKGROUND, &req->flags); | |
@@ -255,7 +256,7 @@ struct fuse_req *fuse_get_req_nofail_nopages(struct fuse_conn *fc, | |
if (!req) | |
req = get_reserved_req(fc, file); | |
- fuse_req_init_context(req); | |
+ fuse_req_init_context(fc, req); | |
__set_bit(FR_WAITING, &req->flags); | |
__clear_bit(FR_BACKGROUND, &req->flags); | |
return req; | |
@@ -1222,6 +1223,9 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, | |
struct fuse_in *in; | |
unsigned reqsize; | |
+ if (task_active_pid_ns(current) != fc->pid_ns) | |
+ return -EIO; | |
+ | |
restart: | |
spin_lock(&fiq->waitq.lock); | |
err = -EAGAIN; | |
@@ -1820,6 +1824,9 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, | |
struct fuse_req *req; | |
struct fuse_out_header oh; | |
+ if (task_active_pid_ns(current) != fc->pid_ns) | |
+ return -EIO; | |
+ | |
if (nbytes < sizeof(struct fuse_out_header)) | |
return -EINVAL; | |
diff --git a/fs/fuse/file.c b/fs/fuse/file.c | |
index 2401c5d..c25bef3 100644 | |
--- a/fs/fuse/file.c | |
+++ b/fs/fuse/file.c | |
@@ -2087,7 +2087,8 @@ static int fuse_direct_mmap(struct file *file, struct vm_area_struct *vma) | |
return generic_file_mmap(file, vma); | |
} | |
-static int convert_fuse_file_lock(const struct fuse_file_lock *ffl, | |
+static int convert_fuse_file_lock(struct fuse_conn *fc, | |
+ const struct fuse_file_lock *ffl, | |
struct file_lock *fl) | |
{ | |
switch (ffl->type) { | |
@@ -2102,7 +2103,14 @@ static int convert_fuse_file_lock(const struct fuse_file_lock *ffl, | |
fl->fl_start = ffl->start; | |
fl->fl_end = ffl->end; | |
- fl->fl_pid = ffl->pid; | |
+ | |
+ /* | |
+ * Convert pid into the caller's pid namespace. If the pid | |
+ * does not map into the namespace fl_pid will get set to 0. | |
+ */ | |
+ rcu_read_lock(); | |
+ fl->fl_pid = pid_vnr(find_pid_ns(ffl->pid, fc->pid_ns)); | |
+ rcu_read_unlock(); | |
break; | |
default: | |
@@ -2151,7 +2159,7 @@ static int fuse_getlk(struct file *file, struct file_lock *fl) | |
args.out.args[0].value = &outarg; | |
err = fuse_simple_request(fc, &args); | |
if (!err) | |
- err = convert_fuse_file_lock(&outarg.lk, fl); | |
+ err = convert_fuse_file_lock(fc, &outarg.lk, fl); | |
return err; | |
} | |
@@ -2163,7 +2171,8 @@ static int fuse_setlk(struct file *file, struct file_lock *fl, int flock) | |
FUSE_ARGS(args); | |
struct fuse_lk_in inarg; | |
int opcode = (fl->fl_flags & FL_SLEEP) ? FUSE_SETLKW : FUSE_SETLK; | |
- pid_t pid = fl->fl_type != F_UNLCK ? current->tgid : 0; | |
+ struct pid *pid = fl->fl_type != F_UNLCK ? task_tgid(current) : NULL; | |
+ pid_t pid_nr = pid_nr_ns(pid, fc->pid_ns); | |
int err; | |
if (fl->fl_lmops && fl->fl_lmops->lm_grant) { | |
@@ -2175,7 +2184,10 @@ static int fuse_setlk(struct file *file, struct file_lock *fl, int flock) | |
if (fl->fl_flags & FL_CLOSE) | |
return 0; | |
- fuse_lk_fill(&args, file, fl, opcode, pid, flock, &inarg); | |
+ if (pid && pid_nr == 0) | |
+ return -EOVERFLOW; | |
+ | |
+ fuse_lk_fill(&args, file, fl, opcode, pid_nr, flock, &inarg); | |
err = fuse_simple_request(fc, &args); | |
/* locking is restartable */ | |
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h | |
index 9130794..2760cc9 100644 | |
--- a/fs/fuse/fuse_i.h | |
+++ b/fs/fuse/fuse_i.h | |
@@ -24,6 +24,7 @@ | |
#include <linux/workqueue.h> | |
#include <linux/kref.h> | |
#include <linux/xattr.h> | |
+#include <linux/pid_namespace.h> | |
/** Max number of pages that can be used in a single read request */ | |
#define FUSE_MAX_PAGES_PER_REQ 32 | |
@@ -461,6 +462,9 @@ struct fuse_conn { | |
/** The group id for this mount */ | |
kgid_t group_id; | |
+ /** The pid namespace for this mount */ | |
+ struct pid_namespace *pid_ns; | |
+ | |
/** Maximum read size */ | |
unsigned max_read; | |
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c | |
index 6fe6a88..5b97ad5 100644 | |
--- a/fs/fuse/inode.c | |
+++ b/fs/fuse/inode.c | |
@@ -21,6 +21,7 @@ | |
#include <linux/sched.h> | |
#include <linux/exportfs.h> | |
#include <linux/posix_acl.h> | |
+#include <linux/pid_namespace.h> | |
MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>"); | |
MODULE_DESCRIPTION("Filesystem in Userspace"); | |
@@ -626,6 +627,7 @@ void fuse_conn_init(struct fuse_conn *fc) | |
fc->connected = 1; | |
fc->attr_version = 1; | |
get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key)); | |
+ fc->pid_ns = get_pid_ns(task_active_pid_ns(current)); | |
} | |
EXPORT_SYMBOL_GPL(fuse_conn_init); | |
@@ -634,6 +636,7 @@ void fuse_conn_put(struct fuse_conn *fc) | |
if (atomic_dec_and_test(&fc->count)) { | |
if (fc->destroy_req) | |
fuse_request_free(fc->destroy_req); | |
+ put_pid_ns(fc->pid_ns); | |
fc->release(fc); | |
} | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 3615bd9044186b582af2e5c70d235de7d112ee3d Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Thu, 26 Jun 2014 11:58:11 -0500 | |
Subject: [PATCH 65/76] UBUNTU: SAUCE: (namespace) fuse: Support fuse | |
filesystems outside of init_user_ns | |
In order to support mounts from namespaces other than | |
init_user_ns, fuse must translate uids and gids to/from the | |
userns of the process servicing requests on /dev/fuse. This | |
patch does that, with a couple of restrictions on the namespace: | |
- The userns for the fuse connection is fixed to the namespace | |
from which /dev/fuse is opened. | |
- The namespace must be the same as s_user_ns. | |
These restrictions simplify the implementation by avoiding the | |
need to pass around userns references and by allowing fuse to | |
rely on the checks in inode_change_ok for ownership changes. | |
Either restriction could be relaxed in the future if needed. | |
For cuse the namespace used for the connection is also simply | |
current_user_ns() at the time /dev/cuse is opened. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
fs/fuse/cuse.c | 3 ++- | |
fs/fuse/dev.c | 14 ++++++++++---- | |
fs/fuse/dir.c | 14 +++++++------- | |
fs/fuse/fuse_i.h | 6 +++++- | |
fs/fuse/inode.c | 31 +++++++++++++++++++------------ | |
5 files changed, 43 insertions(+), 25 deletions(-) | |
diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c | |
index c5b6b71..98ebd0f 100644 | |
--- a/fs/fuse/cuse.c | |
+++ b/fs/fuse/cuse.c | |
@@ -48,6 +48,7 @@ | |
#include <linux/stat.h> | |
#include <linux/module.h> | |
#include <linux/uio.h> | |
+#include <linux/user_namespace.h> | |
#include "fuse_i.h" | |
@@ -498,7 +499,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file) | |
if (!cc) | |
return -ENOMEM; | |
- fuse_conn_init(&cc->fc); | |
+ fuse_conn_init(&cc->fc, current_user_ns()); | |
fud = fuse_dev_alloc(&cc->fc); | |
if (!fud) { | |
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c | |
index e0a6a4f..616a880 100644 | |
--- a/fs/fuse/dev.c | |
+++ b/fs/fuse/dev.c | |
@@ -114,8 +114,8 @@ static void __fuse_put_request(struct fuse_req *req) | |
static void fuse_req_init_context(struct fuse_conn *fc, struct fuse_req *req) | |
{ | |
- req->in.h.uid = from_kuid_munged(&init_user_ns, current_fsuid()); | |
- req->in.h.gid = from_kgid_munged(&init_user_ns, current_fsgid()); | |
+ req->in.h.uid = from_kuid(fc->user_ns, current_fsuid()); | |
+ req->in.h.gid = from_kgid(fc->user_ns, current_fsgid()); | |
req->in.h.pid = pid_nr_ns(task_pid(current), fc->pid_ns); | |
} | |
@@ -167,6 +167,10 @@ static struct fuse_req *__fuse_get_req(struct fuse_conn *fc, unsigned npages, | |
__set_bit(FR_WAITING, &req->flags); | |
if (for_background) | |
__set_bit(FR_BACKGROUND, &req->flags); | |
+ if (req->in.h.uid == (uid_t)-1 || req->in.h.gid == (gid_t)-1) { | |
+ fuse_put_request(fc, req); | |
+ return ERR_PTR(-EOVERFLOW); | |
+ } | |
return req; | |
@@ -1223,7 +1227,8 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, | |
struct fuse_in *in; | |
unsigned reqsize; | |
- if (task_active_pid_ns(current) != fc->pid_ns) | |
+ if (task_active_pid_ns(current) != fc->pid_ns || | |
+ current_user_ns() != fc->user_ns) | |
return -EIO; | |
restart: | |
@@ -1824,7 +1829,8 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, | |
struct fuse_req *req; | |
struct fuse_out_header oh; | |
- if (task_active_pid_ns(current) != fc->pid_ns) | |
+ if (task_active_pid_ns(current) != fc->pid_ns || | |
+ current_user_ns() != fc->user_ns) | |
return -EIO; | |
if (nbytes < sizeof(struct fuse_out_header)) | |
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c | |
index 642c57b..981cd823 100644 | |
--- a/fs/fuse/dir.c | |
+++ b/fs/fuse/dir.c | |
@@ -858,8 +858,8 @@ static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr, | |
stat->ino = attr->ino; | |
stat->mode = (inode->i_mode & S_IFMT) | (attr->mode & 07777); | |
stat->nlink = attr->nlink; | |
- stat->uid = make_kuid(&init_user_ns, attr->uid); | |
- stat->gid = make_kgid(&init_user_ns, attr->gid); | |
+ stat->uid = make_kuid(fc->user_ns, attr->uid); | |
+ stat->gid = make_kgid(fc->user_ns, attr->gid); | |
stat->rdev = inode->i_rdev; | |
stat->atime.tv_sec = attr->atime; | |
stat->atime.tv_nsec = attr->atimensec; | |
@@ -1478,17 +1478,17 @@ static bool update_mtime(unsigned ivalid, bool trust_local_mtime) | |
return true; | |
} | |
-static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg, | |
- bool trust_local_cmtime) | |
+static void iattr_to_fattr(struct fuse_conn *fc, struct iattr *iattr, | |
+ struct fuse_setattr_in *arg, bool trust_local_cmtime) | |
{ | |
unsigned ivalid = iattr->ia_valid; | |
if (ivalid & ATTR_MODE) | |
arg->valid |= FATTR_MODE, arg->mode = iattr->ia_mode; | |
if (ivalid & ATTR_UID) | |
- arg->valid |= FATTR_UID, arg->uid = from_kuid(&init_user_ns, iattr->ia_uid); | |
+ arg->valid |= FATTR_UID, arg->uid = from_kuid(fc->user_ns, iattr->ia_uid); | |
if (ivalid & ATTR_GID) | |
- arg->valid |= FATTR_GID, arg->gid = from_kgid(&init_user_ns, iattr->ia_gid); | |
+ arg->valid |= FATTR_GID, arg->gid = from_kgid(fc->user_ns, iattr->ia_gid); | |
if (ivalid & ATTR_SIZE) | |
arg->valid |= FATTR_SIZE, arg->size = iattr->ia_size; | |
if (ivalid & ATTR_ATIME) { | |
@@ -1649,7 +1649,7 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr, | |
memset(&inarg, 0, sizeof(inarg)); | |
memset(&outarg, 0, sizeof(outarg)); | |
- iattr_to_fattr(attr, &inarg, trust_local_cmtime); | |
+ iattr_to_fattr(fc, attr, &inarg, trust_local_cmtime); | |
if (file) { | |
struct fuse_file *ff = file->private_data; | |
inarg.valid |= FATTR_FH; | |
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h | |
index 2760cc9..5ff0dc4 100644 | |
--- a/fs/fuse/fuse_i.h | |
+++ b/fs/fuse/fuse_i.h | |
@@ -25,6 +25,7 @@ | |
#include <linux/kref.h> | |
#include <linux/xattr.h> | |
#include <linux/pid_namespace.h> | |
+#include <linux/user_namespace.h> | |
/** Max number of pages that can be used in a single read request */ | |
#define FUSE_MAX_PAGES_PER_REQ 32 | |
@@ -465,6 +466,9 @@ struct fuse_conn { | |
/** The pid namespace for this mount */ | |
struct pid_namespace *pid_ns; | |
+ /** The user namespace for this mount */ | |
+ struct user_namespace *user_ns; | |
+ | |
/** Maximum read size */ | |
unsigned max_read; | |
@@ -876,7 +880,7 @@ void fuse_request_send_background_locked(struct fuse_conn *fc, | |
/** | |
* Initialize fuse_conn | |
*/ | |
-void fuse_conn_init(struct fuse_conn *fc); | |
+void fuse_conn_init(struct fuse_conn *fc, struct user_namespace *user_ns); | |
/** | |
* Release reference to fuse_conn | |
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c | |
index 5b97ad5..29a8fbe 100644 | |
--- a/fs/fuse/inode.c | |
+++ b/fs/fuse/inode.c | |
@@ -171,8 +171,8 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr, | |
inode->i_ino = fuse_squash_ino(attr->ino); | |
inode->i_mode = (inode->i_mode & S_IFMT) | (attr->mode & 07777); | |
set_nlink(inode, attr->nlink); | |
- inode->i_uid = make_kuid(&init_user_ns, attr->uid); | |
- inode->i_gid = make_kgid(&init_user_ns, attr->gid); | |
+ inode->i_uid = make_kuid(fc->user_ns, attr->uid); | |
+ inode->i_gid = make_kgid(fc->user_ns, attr->gid); | |
inode->i_blocks = attr->blocks; | |
inode->i_atime.tv_sec = attr->atime; | |
inode->i_atime.tv_nsec = attr->atimensec; | |
@@ -484,7 +484,8 @@ static int fuse_match_uint(substring_t *s, unsigned int *res) | |
return err; | |
} | |
-static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev) | |
+static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev, | |
+ struct user_namespace *user_ns) | |
{ | |
char *p; | |
memset(d, 0, sizeof(struct fuse_mount_data)); | |
@@ -520,7 +521,7 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev) | |
case OPT_USER_ID: | |
if (fuse_match_uint(&args[0], &uv)) | |
return 0; | |
- d->user_id = make_kuid(current_user_ns(), uv); | |
+ d->user_id = make_kuid(user_ns, uv); | |
if (!uid_valid(d->user_id)) | |
return 0; | |
d->user_id_present = 1; | |
@@ -529,7 +530,7 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev) | |
case OPT_GROUP_ID: | |
if (fuse_match_uint(&args[0], &uv)) | |
return 0; | |
- d->group_id = make_kgid(current_user_ns(), uv); | |
+ d->group_id = make_kgid(user_ns, uv); | |
if (!gid_valid(d->group_id)) | |
return 0; | |
d->group_id_present = 1; | |
@@ -572,8 +573,8 @@ static int fuse_show_options(struct seq_file *m, struct dentry *root) | |
struct super_block *sb = root->d_sb; | |
struct fuse_conn *fc = get_fuse_conn_super(sb); | |
- seq_printf(m, ",user_id=%u", from_kuid_munged(&init_user_ns, fc->user_id)); | |
- seq_printf(m, ",group_id=%u", from_kgid_munged(&init_user_ns, fc->group_id)); | |
+ seq_printf(m, ",user_id=%u", from_kuid_munged(fc->user_ns, fc->user_id)); | |
+ seq_printf(m, ",group_id=%u", from_kgid_munged(fc->user_ns, fc->group_id)); | |
if (fc->default_permissions) | |
seq_puts(m, ",default_permissions"); | |
if (fc->allow_other) | |
@@ -604,7 +605,7 @@ static void fuse_pqueue_init(struct fuse_pqueue *fpq) | |
fpq->connected = 1; | |
} | |
-void fuse_conn_init(struct fuse_conn *fc) | |
+void fuse_conn_init(struct fuse_conn *fc, struct user_namespace *user_ns) | |
{ | |
memset(fc, 0, sizeof(*fc)); | |
spin_lock_init(&fc->lock); | |
@@ -628,6 +629,7 @@ void fuse_conn_init(struct fuse_conn *fc) | |
fc->attr_version = 1; | |
get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key)); | |
fc->pid_ns = get_pid_ns(task_active_pid_ns(current)); | |
+ fc->user_ns = get_user_ns(user_ns); | |
} | |
EXPORT_SYMBOL_GPL(fuse_conn_init); | |
@@ -637,6 +639,7 @@ void fuse_conn_put(struct fuse_conn *fc) | |
if (fc->destroy_req) | |
fuse_request_free(fc->destroy_req); | |
put_pid_ns(fc->pid_ns); | |
+ put_user_ns(fc->user_ns); | |
fc->release(fc); | |
} | |
} | |
@@ -1069,7 +1072,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) | |
sb->s_flags &= ~(MS_NOSEC | MS_I_VERSION); | |
- if (!parse_fuse_opt(data, &d, is_bdev)) | |
+ if (!parse_fuse_opt(data, &d, is_bdev, sb->s_user_ns)) | |
goto err; | |
if (is_bdev) { | |
@@ -1094,8 +1097,12 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) | |
if (!file) | |
goto err; | |
- if ((file->f_op != &fuse_dev_operations) || | |
- (file->f_cred->user_ns != &init_user_ns)) | |
+ /* | |
+ * Require mount to happen from the same user namespace which | |
+ * opened /dev/fuse to prevent potential attacks. | |
+ */ | |
+ if (file->f_op != &fuse_dev_operations || | |
+ file->f_cred->user_ns != sb->s_user_ns) | |
goto err_fput; | |
fc = kmalloc(sizeof(*fc), GFP_KERNEL); | |
@@ -1103,7 +1110,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) | |
if (!fc) | |
goto err_fput; | |
- fuse_conn_init(fc); | |
+ fuse_conn_init(fc, sb->s_user_ns); | |
fc->release = fuse_free_conn; | |
fud = fuse_dev_alloc(fc); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 7cafb5953438882910b99316c8170af37adfd65c Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Wed, 24 Aug 2016 11:47:05 -0500 | |
Subject: [PATCH 66/76] UBUNTU: SAUCE: (namespace) fuse: Translate ids in posix | |
acl xattrs | |
Fuse currently lacks comprehensive support for posix ACLs, but | |
some fuse filesystems process the acl xattrs internally. For this | |
to continue to work the ids within the xattrs need to be mapped | |
into s_user_ns when written to the filesystem and mapped from | |
s_user_ns when read. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
fs/fuse/xattr.c | 27 ++++++++++++++++++++++++++- | |
1 file changed, 26 insertions(+), 1 deletion(-) | |
diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c | |
index 3caac46..5126301 100644 | |
--- a/fs/fuse/xattr.c | |
+++ b/fs/fuse/xattr.c | |
@@ -16,12 +16,24 @@ int fuse_setxattr(struct inode *inode, const char *name, const void *value, | |
{ | |
struct fuse_conn *fc = get_fuse_conn(inode); | |
FUSE_ARGS(args); | |
+ void *buf = NULL; | |
struct fuse_setxattr_in inarg; | |
int err; | |
if (fc->no_setxattr) | |
return -EOPNOTSUPP; | |
+ if (!strcmp(name, XATTR_NAME_POSIX_ACL_ACCESS) || | |
+ !strcmp(name, XATTR_NAME_POSIX_ACL_DEFAULT)) { | |
+ buf = kmemdup(value, size, GFP_KERNEL); | |
+ if (!buf) | |
+ return -ENOMEM; | |
+ err = posix_acl_fix_xattr_userns(inode->i_sb->s_user_ns, | |
+ &init_user_ns, buf, size); | |
+ if (err) | |
+ goto out; | |
+ } | |
+ | |
memset(&inarg, 0, sizeof(inarg)); | |
inarg.size = size; | |
inarg.flags = flags; | |
@@ -33,7 +45,7 @@ int fuse_setxattr(struct inode *inode, const char *name, const void *value, | |
args.in.args[1].size = strlen(name) + 1; | |
args.in.args[1].value = name; | |
args.in.args[2].size = size; | |
- args.in.args[2].value = value; | |
+ args.in.args[2].value = buf ? buf : value; | |
err = fuse_simple_request(fc, &args); | |
if (err == -ENOSYS) { | |
fc->no_setxattr = 1; | |
@@ -43,6 +55,9 @@ int fuse_setxattr(struct inode *inode, const char *name, const void *value, | |
fuse_invalidate_attr(inode); | |
fuse_update_ctime(inode); | |
} | |
+ | |
+out: | |
+ kfree(buf); | |
return err; | |
} | |
@@ -80,6 +95,16 @@ ssize_t fuse_getxattr(struct inode *inode, const char *name, void *value, | |
ret = fuse_simple_request(fc, &args); | |
if (!ret && !size) | |
ret = min_t(ssize_t, outarg.size, XATTR_SIZE_MAX); | |
+ if (!ret) { | |
+ if (!size) { | |
+ ret = outarg.size; | |
+ } else if (!strcmp(name, XATTR_NAME_POSIX_ACL_ACCESS) || | |
+ !strcmp(name, XATTR_NAME_POSIX_ACL_DEFAULT)) { | |
+ ret = posix_acl_fix_xattr_userns(&init_user_ns, | |
+ inode->i_sb->s_user_ns, | |
+ value, size); | |
+ } | |
+ } | |
if (ret == -ENOSYS) { | |
fc->no_getxattr = 1; | |
ret = -EOPNOTSUPP; | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From fe8d497942815be1eeb2fb61acbb6e051c2b18f2 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Thu, 2 Oct 2014 15:34:45 -0500 | |
Subject: [PATCH 67/76] UBUNTU: SAUCE: (namespace) fuse: Restrict allow_other | |
to the superblock's namespace or a descendant | |
Unprivileged users are normally restricted from mounting with the | |
allow_other option by system policy, but this could be bypassed | |
for a mount done with user namespace root permissions. In such | |
cases allow_other should not allow users outside the userns | |
to access the mount as doing so would give the unprivileged user | |
the ability to manipulate processes it would otherwise be unable | |
to manipulate. Restrict allow_other to apply to users in the same | |
userns used at mount or a descendant of that namespace. Also | |
export current_in_userns() for use by fuse when built as a | |
module. | |
Acked-by: Serge Hallyn <serge.hallyn@canonical.com> | |
Acked-by: Miklos Szeredi <mszeredi@redhat.com> | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
fs/fuse/dir.c | 2 +- | |
kernel/user_namespace.c | 1 + | |
2 files changed, 2 insertions(+), 1 deletion(-) | |
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c | |
index 981cd823..850bd4f 100644 | |
--- a/fs/fuse/dir.c | |
+++ b/fs/fuse/dir.c | |
@@ -1034,7 +1034,7 @@ int fuse_allow_current_process(struct fuse_conn *fc) | |
const struct cred *cred; | |
if (fc->allow_other) | |
- return 1; | |
+ return current_in_userns(fc->user_ns); | |
cred = current_cred(); | |
if (uid_eq(cred->euid, fc->user_id) && | |
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c | |
index 86b7854..96d5f95 100644 | |
--- a/kernel/user_namespace.c | |
+++ b/kernel/user_namespace.c | |
@@ -997,6 +997,7 @@ bool current_in_userns(const struct user_namespace *target_ns) | |
} | |
return false; | |
} | |
+EXPORT_SYMBOL(current_in_userns); | |
static inline struct user_namespace *to_user_ns(struct ns_common *ns) | |
{ | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 31d91f00b35e23e2d8a5881657940743079b55bc Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Thu, 2 Oct 2014 15:51:41 -0500 | |
Subject: [PATCH 68/76] UBUNTU: SAUCE: (namespace) fuse: Allow user namespace | |
mounts | |
Acked-by: Miklos Szeredi <mszeredi@redhat.com> | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
fs/fuse/inode.c | 4 ++-- | |
1 file changed, 2 insertions(+), 2 deletions(-) | |
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c | |
index 29a8fbe..60a95aba 100644 | |
--- a/fs/fuse/inode.c | |
+++ b/fs/fuse/inode.c | |
@@ -1223,7 +1223,7 @@ static void fuse_kill_sb_anon(struct super_block *sb) | |
static struct file_system_type fuse_fs_type = { | |
.owner = THIS_MODULE, | |
.name = "fuse", | |
- .fs_flags = FS_HAS_SUBTYPE, | |
+ .fs_flags = FS_HAS_SUBTYPE | FS_USERNS_MOUNT, | |
.mount = fuse_mount, | |
.kill_sb = fuse_kill_sb_anon, | |
}; | |
@@ -1255,7 +1255,7 @@ static void fuse_kill_sb_blk(struct super_block *sb) | |
.name = "fuseblk", | |
.mount = fuse_mount_blk, | |
.kill_sb = fuse_kill_sb_blk, | |
- .fs_flags = FS_REQUIRES_DEV | FS_HAS_SUBTYPE, | |
+ .fs_flags = FS_REQUIRES_DEV | FS_HAS_SUBTYPE | FS_USERNS_MOUNT, | |
}; | |
MODULE_ALIAS_FS("fuseblk"); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 5a4cb21efc67165b9402ac1ad3141c05001e5e49 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Sat, 18 Oct 2014 13:02:09 +0200 | |
Subject: [PATCH 69/76] UBUNTU: SAUCE: (namespace) ext4: Add support for | |
unprivileged mounts from user namespaces | |
Support unprivileged mounting of ext4 volumes from user | |
namespaces. This requires the following changes: | |
- Perform all uid, gid, and projid conversions to/from disk | |
relative to s_user_ns. In many cases this will already be | |
handled by the vfs helper functions. This also requires | |
updates to handle cases where ids may not map into s_user_ns. | |
A new helper, projid_valid_eq(), is added to help with this. | |
- Update most capability checks to check for capabilities in | |
s_user_ns rather than init_user_ns. These mostly reflect | |
changes to the filesystem that a user in s_user_ns could | |
already make externally by virtue of having write access to | |
the backing device. | |
- Restrict unsafe options in either the mount options or the | |
ext4 superblock. Currently the only concerning option is | |
errors=panic, and this is made to require CAP_SYS_ADMIN in | |
init_user_ns. | |
- Verify that unprivileged users have the required access to the | |
journal device at the path passed via the journal_path mount | |
option. | |
Note that for the journal_path and the journal_dev mount | |
options, and for external journal devices specified in the | |
ext4 superblock, devcgroup restrictions will be enforced by | |
__blkdev_get(), (via blkdev_get_by_dev()), ensuring that the | |
user has been granted appropriate access to the block device. | |
- Set the FS_USERNS_MOUNT flag on the filesystem types supported | |
by ext4. | |
sysfs attributes for ext4 mounts remain writable only by real | |
root. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
fs/ext4/acl.c | 31 ++++++++++++++++++--------- | |
fs/ext4/balloc.c | 4 ++-- | |
fs/ext4/ialloc.c | 6 +++++- | |
fs/ext4/inode.c | 16 +++++++------- | |
fs/ext4/ioctl.c | 10 +++++---- | |
fs/ext4/namei.c | 16 +++++++------- | |
fs/ext4/resize.c | 2 +- | |
fs/ext4/super.c | 57 +++++++++++++++++++++++++++++++++++++------------- | |
include/linux/projid.h | 5 +++++ | |
9 files changed, 100 insertions(+), 47 deletions(-) | |
diff --git a/fs/ext4/acl.c b/fs/ext4/acl.c | |
index dfa5199..1696e5f 100644 | |
--- a/fs/ext4/acl.c | |
+++ b/fs/ext4/acl.c | |
@@ -13,7 +13,7 @@ | |
* Convert from filesystem to in-memory representation. | |
*/ | |
static struct posix_acl * | |
-ext4_acl_from_disk(const void *value, size_t size) | |
+ext4_acl_from_disk(struct super_block *sb, const void *value, size_t size) | |
{ | |
const char *end = (char *)value + size; | |
int n, count; | |
@@ -57,16 +57,20 @@ | |
if ((char *)value > end) | |
goto fail; | |
acl->a_entries[n].e_uid = | |
- make_kuid(&init_user_ns, | |
+ make_kuid(sb->s_user_ns, | |
le32_to_cpu(entry->e_id)); | |
+ if (!uid_valid(acl->a_entries[n].e_uid)) | |
+ goto fail; | |
break; | |
case ACL_GROUP: | |
value = (char *)value + sizeof(ext4_acl_entry); | |
if ((char *)value > end) | |
goto fail; | |
acl->a_entries[n].e_gid = | |
- make_kgid(&init_user_ns, | |
+ make_kgid(sb->s_user_ns, | |
le32_to_cpu(entry->e_id)); | |
+ if (!gid_valid(acl->a_entries[n].e_gid)) | |
+ goto fail; | |
break; | |
default: | |
@@ -86,11 +90,14 @@ | |
* Convert from in-memory to filesystem representation. | |
*/ | |
static void * | |
-ext4_acl_to_disk(const struct posix_acl *acl, size_t *size) | |
+ext4_acl_to_disk(struct super_block *sb, const struct posix_acl *acl, | |
+ size_t *size) | |
{ | |
ext4_acl_header *ext_acl; | |
char *e; | |
size_t n; | |
+ uid_t uid; | |
+ gid_t gid; | |
*size = ext4_acl_size(acl->a_count); | |
ext_acl = kmalloc(sizeof(ext4_acl_header) + acl->a_count * | |
@@ -106,13 +113,17 @@ | |
entry->e_perm = cpu_to_le16(acl_e->e_perm); | |
switch (acl_e->e_tag) { | |
case ACL_USER: | |
- entry->e_id = cpu_to_le32( | |
- from_kuid(&init_user_ns, acl_e->e_uid)); | |
+ uid = from_kuid(sb->s_user_ns, acl_e->e_uid); | |
+ if (uid == (uid_t)-1) | |
+ goto fail; | |
+ entry->e_id = cpu_to_le32(uid); | |
e += sizeof(ext4_acl_entry); | |
break; | |
case ACL_GROUP: | |
- entry->e_id = cpu_to_le32( | |
- from_kgid(&init_user_ns, acl_e->e_gid)); | |
+ gid = from_kgid(sb->s_user_ns, acl_e->e_gid); | |
+ if (gid == (gid_t)-1) | |
+ goto fail; | |
+ entry->e_id = cpu_to_le32(gid); | |
e += sizeof(ext4_acl_entry); | |
break; | |
@@ -165,7 +176,7 @@ struct posix_acl * | |
retval = ext4_xattr_get(inode, name_index, "", value, retval); | |
} | |
if (retval > 0) | |
- acl = ext4_acl_from_disk(value, retval); | |
+ acl = ext4_acl_from_disk(inode->i_sb, value, retval); | |
else if (retval == -ENODATA || retval == -ENOSYS) | |
acl = NULL; | |
else | |
@@ -211,7 +222,7 @@ struct posix_acl * | |
return -EINVAL; | |
} | |
if (acl) { | |
- value = ext4_acl_to_disk(acl, &size); | |
+ value = ext4_acl_to_disk(inode->i_sb, acl, &size); | |
if (IS_ERR(value)) | |
return (int)PTR_ERR(value); | |
} | |
diff --git a/fs/ext4/balloc.c b/fs/ext4/balloc.c | |
index e04ec86..f22fdd4e 100644 | |
--- a/fs/ext4/balloc.c | |
+++ b/fs/ext4/balloc.c | |
@@ -565,8 +565,8 @@ static int ext4_has_free_clusters(struct ext4_sb_info *sbi, | |
/* Hm, nope. Are (enough) root reserved clusters available? */ | |
if (uid_eq(sbi->s_resuid, current_fsuid()) || | |
- (!gid_eq(sbi->s_resgid, GLOBAL_ROOT_GID) && in_group_p(sbi->s_resgid)) || | |
- capable(CAP_SYS_RESOURCE) || | |
+ (!gid_eq(sbi->s_resgid, make_kgid(sbi->s_sb->s_user_ns, 0)) && in_group_p(sbi->s_resgid)) || | |
+ ns_capable(sbi->s_sb->s_user_ns, CAP_SYS_RESOURCE) || | |
(flags & EXT4_MB_USE_ROOT_BLOCKS)) { | |
if (free_clusters >= (nclusters + dirty_clusters + | |
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c | |
index 170421e..1452d72 100644 | |
--- a/fs/ext4/ialloc.c | |
+++ b/fs/ext4/ialloc.c | |
@@ -764,6 +764,10 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir, | |
if (!dir || !dir->i_nlink) | |
return ERR_PTR(-EPERM); | |
+ /* Supplied owner must be valid */ | |
+ if (owner && (owner[0] == (uid_t)-1 || owner[1] == (uid_t)-1)) | |
+ return ERR_PTR(-EOVERFLOW); | |
+ | |
if ((ext4_encrypted_inode(dir) || | |
DUMMY_ENCRYPTION_ENABLED(EXT4_SB(dir->i_sb))) && | |
(S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) { | |
@@ -806,7 +810,7 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir, | |
ext4_test_inode_flag(dir, EXT4_INODE_PROJINHERIT)) | |
ei->i_projid = EXT4_I(dir)->i_projid; | |
else | |
- ei->i_projid = make_kprojid(&init_user_ns, EXT4_DEF_PROJID); | |
+ ei->i_projid = make_kprojid(sb->s_user_ns, EXT4_DEF_PROJID); | |
err = dquot_initialize(inode); | |
if (err) | |
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c | |
index 33a509c..42e4f43 100644 | |
--- a/fs/ext4/inode.c | |
+++ b/fs/ext4/inode.c | |
@@ -4501,7 +4501,7 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino) | |
} | |
i_uid_write(inode, i_uid); | |
i_gid_write(inode, i_gid); | |
- ei->i_projid = make_kprojid(&init_user_ns, i_projid); | |
+ ei->i_projid = make_kprojid(sb->s_user_ns, i_projid); | |
set_nlink(inode, le16_to_cpu(raw_inode->i_links_count)); | |
ext4_clear_state_flags(ei); /* Only relevant on 32-bit archs */ | |
@@ -4818,7 +4818,7 @@ static int ext4_do_update_inode(handle_t *handle, | |
raw_inode->i_mode = cpu_to_le16(inode->i_mode); | |
i_uid = i_uid_read(inode); | |
i_gid = i_gid_read(inode); | |
- i_projid = from_kprojid(&init_user_ns, ei->i_projid); | |
+ i_projid = from_kprojid(sb->s_user_ns, ei->i_projid); | |
if (!(test_opt(inode->i_sb, NO_UID32))) { | |
raw_inode->i_uid_low = cpu_to_le16(low_16_bits(i_uid)); | |
raw_inode->i_gid_low = cpu_to_le16(low_16_bits(i_gid)); | |
@@ -4897,12 +4897,14 @@ static int ext4_do_update_inode(handle_t *handle, | |
} | |
} | |
- BUG_ON(!ext4_has_feature_project(inode->i_sb) && | |
- i_projid != EXT4_DEF_PROJID); | |
+ if (i_projid != (projid_t)-1) { | |
+ BUG_ON(!ext4_has_feature_project(inode->i_sb) && | |
+ i_projid != EXT4_DEF_PROJID); | |
- if (EXT4_INODE_SIZE(inode->i_sb) > EXT4_GOOD_OLD_INODE_SIZE && | |
- EXT4_FITS_IN_INODE(raw_inode, ei, i_projid)) | |
- raw_inode->i_projid = cpu_to_le32(i_projid); | |
+ if (EXT4_INODE_SIZE(inode->i_sb) > EXT4_GOOD_OLD_INODE_SIZE && | |
+ EXT4_FITS_IN_INODE(raw_inode, ei, i_projid)) | |
+ raw_inode->i_projid = cpu_to_le32(i_projid); | |
+ } | |
ext4_inode_csum_set(inode, raw_inode, ei); | |
spin_unlock(&ei->i_raw_lock); | |
diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c | |
index bf5ae8e..febb2ae 100644 | |
--- a/fs/ext4/ioctl.c | |
+++ b/fs/ext4/ioctl.c | |
@@ -236,7 +236,7 @@ static int ext4_ioctl_setflags(struct inode *inode, | |
* the relevant capability. | |
*/ | |
if ((jflag ^ oldflags) & (EXT4_JOURNAL_DATA_FL)) { | |
- if (!capable(CAP_SYS_RESOURCE)) | |
+ if (!ns_capable(inode->i_sb->s_user_ns, CAP_SYS_RESOURCE)) | |
goto flags_out; | |
} | |
if ((flags ^ oldflags) & EXT4_EXTENTS_FL) | |
@@ -318,8 +318,10 @@ static int ext4_ioctl_setproject(struct file *filp, __u32 projid) | |
if (EXT4_INODE_SIZE(sb) <= EXT4_GOOD_OLD_INODE_SIZE) | |
return -EOPNOTSUPP; | |
- kprojid = make_kprojid(&init_user_ns, (projid_t)projid); | |
+ kprojid = make_kprojid(sb->s_user_ns, (projid_t)projid); | |
+ if (!projid_valid(kprojid)) | |
+ return -EOVERFLOW; | |
if (projid_eq(kprojid, EXT4_I(inode)->i_projid)) | |
return 0; | |
@@ -741,7 +743,7 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | |
struct fstrim_range range; | |
int ret = 0; | |
- if (!capable(CAP_SYS_ADMIN)) | |
+ if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) | |
return -EPERM; | |
if (!blk_queue_discard(q)) | |
@@ -843,7 +845,7 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | |
fa.fsx_xflags = ext4_iflags_to_xflags(ei->i_flags & EXT4_FL_USER_VISIBLE); | |
if (ext4_has_feature_project(inode->i_sb)) { | |
- fa.fsx_projid = (__u32)from_kprojid(&init_user_ns, | |
+ fa.fsx_projid = (__u32)from_kprojid_munged(sb->s_user_ns, | |
EXT4_I(inode)->i_projid); | |
} | |
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c | |
index 104f8bf..d20b9b3 100644 | |
--- a/fs/ext4/namei.c | |
+++ b/fs/ext4/namei.c | |
@@ -3236,8 +3236,8 @@ static int ext4_link(struct dentry *old_dentry, | |
return -EPERM; | |
if ((ext4_test_inode_flag(dir, EXT4_INODE_PROJINHERIT)) && | |
- (!projid_eq(EXT4_I(dir)->i_projid, | |
- EXT4_I(old_dentry->d_inode)->i_projid))) | |
+ (!projid_valid_eq(EXT4_I(dir)->i_projid, | |
+ EXT4_I(old_dentry->d_inode)->i_projid))) | |
return -EXDEV; | |
err = dquot_initialize(dir); | |
@@ -3521,8 +3521,8 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, | |
u8 old_file_type; | |
if ((ext4_test_inode_flag(new_dir, EXT4_INODE_PROJINHERIT)) && | |
- (!projid_eq(EXT4_I(new_dir)->i_projid, | |
- EXT4_I(old_dentry->d_inode)->i_projid))) | |
+ (!projid_valid_eq(EXT4_I(new_dir)->i_projid, | |
+ EXT4_I(old_dentry->d_inode)->i_projid))) | |
return -EXDEV; | |
retval = dquot_initialize(old.dir); | |
@@ -3732,11 +3732,11 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry, | |
return -EPERM; | |
if ((ext4_test_inode_flag(new_dir, EXT4_INODE_PROJINHERIT) && | |
- !projid_eq(EXT4_I(new_dir)->i_projid, | |
- EXT4_I(old_dentry->d_inode)->i_projid)) || | |
+ !projid_valid_eq(EXT4_I(new_dir)->i_projid, | |
+ EXT4_I(old_dentry->d_inode)->i_projid)) || | |
(ext4_test_inode_flag(old_dir, EXT4_INODE_PROJINHERIT) && | |
- !projid_eq(EXT4_I(old_dir)->i_projid, | |
- EXT4_I(new_dentry->d_inode)->i_projid))) | |
+ !projid_valid_eq(EXT4_I(old_dir)->i_projid, | |
+ EXT4_I(new_dentry->d_inode)->i_projid))) | |
return -EXDEV; | |
retval = dquot_initialize(old.dir); | |
diff --git a/fs/ext4/resize.c b/fs/ext4/resize.c | |
index cf68100..a5e2ed2 100644 | |
--- a/fs/ext4/resize.c | |
+++ b/fs/ext4/resize.c | |
@@ -20,7 +20,7 @@ int ext4_resize_begin(struct super_block *sb) | |
{ | |
int ret = 0; | |
- if (!capable(CAP_SYS_RESOURCE)) | |
+ if (!ns_capable(sb->s_user_ns, CAP_SYS_RESOURCE)) | |
return -EPERM; | |
/* | |
diff --git a/fs/ext4/super.c b/fs/ext4/super.c | |
index bbc316d..5314794 100644 | |
--- a/fs/ext4/super.c | |
+++ b/fs/ext4/super.c | |
@@ -38,6 +38,7 @@ | |
#include <linux/log2.h> | |
#include <linux/crc16.h> | |
#include <linux/cleancache.h> | |
+#include <linux/user_namespace.h> | |
#include <asm/uaccess.h> | |
#include <linux/kthread.h> | |
@@ -117,7 +118,7 @@ static struct inode *ext4_get_journal_inode(struct super_block *sb, | |
.name = "ext2", | |
.mount = ext4_mount, | |
.kill_sb = kill_block_super, | |
- .fs_flags = FS_REQUIRES_DEV, | |
+ .fs_flags = FS_REQUIRES_DEV | FS_USERNS_MOUNT, | |
}; | |
MODULE_ALIAS_FS("ext2"); | |
MODULE_ALIAS("ext2"); | |
@@ -132,7 +133,7 @@ static struct inode *ext4_get_journal_inode(struct super_block *sb, | |
.name = "ext3", | |
.mount = ext4_mount, | |
.kill_sb = kill_block_super, | |
- .fs_flags = FS_REQUIRES_DEV, | |
+ .fs_flags = FS_REQUIRES_DEV | FS_USERNS_MOUNT, | |
}; | |
MODULE_ALIAS_FS("ext3"); | |
MODULE_ALIAS("ext3"); | |
@@ -1632,6 +1633,13 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token, | |
return -1; | |
} | |
+ if (token == Opt_err_panic && !capable(CAP_SYS_ADMIN)) { | |
+ ext4_msg(sb, KERN_ERR, | |
+ "Mount option \"%s\" not allowed for unprivileged mounts", | |
+ opt); | |
+ return -1; | |
+ } | |
+ | |
if (args->from && !(m->flags & MOPT_STRING) && match_int(args, &arg)) | |
return -1; | |
if (args->from && (m->flags & MOPT_GTE0) && (arg < 0)) | |
@@ -1680,14 +1688,14 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token, | |
} else if (token == Opt_stripe) { | |
sbi->s_stripe = arg; | |
} else if (token == Opt_resuid) { | |
- uid = make_kuid(current_user_ns(), arg); | |
+ uid = make_kuid(sb->s_user_ns, arg); | |
if (!uid_valid(uid)) { | |
ext4_msg(sb, KERN_ERR, "Invalid uid value %d", arg); | |
return -1; | |
} | |
sbi->s_resuid = uid; | |
} else if (token == Opt_resgid) { | |
- gid = make_kgid(current_user_ns(), arg); | |
+ gid = make_kgid(sb->s_user_ns, arg); | |
if (!gid_valid(gid)) { | |
ext4_msg(sb, KERN_ERR, "Invalid gid value %d", arg); | |
return -1; | |
@@ -1726,6 +1734,19 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token, | |
return -1; | |
} | |
+ /* | |
+ * Refuse access for unprivileged mounts if the user does | |
+ * not have rw access to the journal device via the supplied | |
+ * path. | |
+ */ | |
+ if (!capable(CAP_SYS_ADMIN) && | |
+ inode_permission(d_inode(path.dentry), MAY_READ|MAY_WRITE)) { | |
+ ext4_msg(sb, KERN_ERR, | |
+ "error: Insufficient access to journal path %s", | |
+ journal_path); | |
+ return -1; | |
+ } | |
+ | |
journal_inode = d_inode(path.dentry); | |
if (!S_ISBLK(journal_inode->i_mode)) { | |
ext4_msg(sb, KERN_ERR, "error: journal path %s " | |
@@ -1967,14 +1988,14 @@ static int _ext4_show_options(struct seq_file *seq, struct super_block *sb, | |
SEQ_OPTS_PRINT("%s", token2str(m->token)); | |
} | |
- if (nodefs || !uid_eq(sbi->s_resuid, make_kuid(&init_user_ns, EXT4_DEF_RESUID)) || | |
+ if (nodefs || !uid_eq(sbi->s_resuid, make_kuid(sb->s_user_ns, EXT4_DEF_RESUID)) || | |
le16_to_cpu(es->s_def_resuid) != EXT4_DEF_RESUID) | |
SEQ_OPTS_PRINT("resuid=%u", | |
- from_kuid_munged(&init_user_ns, sbi->s_resuid)); | |
- if (nodefs || !gid_eq(sbi->s_resgid, make_kgid(&init_user_ns, EXT4_DEF_RESGID)) || | |
+ from_kuid_munged(sb->s_user_ns, sbi->s_resuid)); | |
+ if (nodefs || !gid_eq(sbi->s_resgid, make_kgid(sb->s_user_ns, EXT4_DEF_RESGID)) || | |
le16_to_cpu(es->s_def_resgid) != EXT4_DEF_RESGID) | |
SEQ_OPTS_PRINT("resgid=%u", | |
- from_kgid_munged(&init_user_ns, sbi->s_resgid)); | |
+ from_kgid_munged(sb->s_user_ns, sbi->s_resgid)); | |
def_errors = nodefs ? -1 : le16_to_cpu(es->s_errors); | |
if (test_opt(sb, ERRORS_RO) && def_errors != EXT4_ERRORS_RO) | |
SEQ_OPTS_PUTS("errors=remount-ro"); | |
@@ -3444,19 +3465,26 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) | |
else if ((def_mount_opts & EXT4_DEFM_JMODE) == EXT4_DEFM_JMODE_WBACK) | |
set_opt(sb, WRITEBACK_DATA); | |
- if (le16_to_cpu(sbi->s_es->s_errors) == EXT4_ERRORS_PANIC) | |
+ if (le16_to_cpu(sbi->s_es->s_errors) == EXT4_ERRORS_PANIC) { | |
+ if (!capable(CAP_SYS_ADMIN)) | |
+ goto failed_mount; | |
set_opt(sb, ERRORS_PANIC); | |
- else if (le16_to_cpu(sbi->s_es->s_errors) == EXT4_ERRORS_CONTINUE) | |
+ } else if (le16_to_cpu(sbi->s_es->s_errors) == EXT4_ERRORS_CONTINUE) { | |
set_opt(sb, ERRORS_CONT); | |
- else | |
+ } else { | |
set_opt(sb, ERRORS_RO); | |
+ } | |
/* block_validity enabled by default; disable with noblock_validity */ | |
set_opt(sb, BLOCK_VALIDITY); | |
if (def_mount_opts & EXT4_DEFM_DISCARD) | |
set_opt(sb, DISCARD); | |
- sbi->s_resuid = make_kuid(&init_user_ns, le16_to_cpu(es->s_def_resuid)); | |
- sbi->s_resgid = make_kgid(&init_user_ns, le16_to_cpu(es->s_def_resgid)); | |
+ sbi->s_resuid = make_kuid(sb->s_user_ns, le16_to_cpu(es->s_def_resuid)); | |
+ if (!uid_valid(sbi->s_resuid)) | |
+ sbi->s_resuid = make_kuid(sb->s_user_ns, EXT4_DEF_RESUID); | |
+ sbi->s_resgid = make_kgid(sb->s_user_ns, le16_to_cpu(es->s_def_resgid)); | |
+ if (!gid_valid(sbi->s_resgid)) | |
+ sbi->s_resgid = make_kgid(sb->s_user_ns, EXT4_DEF_RESGID); | |
sbi->s_commit_interval = JBD2_DEFAULT_MAX_COMMIT_AGE * HZ; | |
sbi->s_min_batch_time = EXT4_DEF_MIN_BATCH_TIME; | |
sbi->s_max_batch_time = EXT4_DEF_MAX_BATCH_TIME; | |
@@ -4261,6 +4289,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) | |
ext4_blkdev_remove(sbi); | |
brelse(bh); | |
out_fail: | |
+ /* sb->s_user_ns will be put when sb is destroyed */ | |
sb->s_fs_info = NULL; | |
kfree(sbi->s_blockgroup_lock); | |
out_free_base: | |
@@ -5578,7 +5607,7 @@ static inline int ext3_feature_set_ok(struct super_block *sb) | |
.name = "ext4", | |
.mount = ext4_mount, | |
.kill_sb = kill_block_super, | |
- .fs_flags = FS_REQUIRES_DEV, | |
+ .fs_flags = FS_REQUIRES_DEV | FS_USERNS_MOUNT, | |
}; | |
MODULE_ALIAS_FS("ext4"); | |
diff --git a/include/linux/projid.h b/include/linux/projid.h | |
index 8c1f2c5..344e807 100644 | |
--- a/include/linux/projid.h | |
+++ b/include/linux/projid.h | |
@@ -47,6 +47,11 @@ static inline bool projid_valid(kprojid_t projid) | |
return !projid_eq(projid, INVALID_PROJID); | |
} | |
+static inline bool projid_valid_eq(kprojid_t left, kprojid_t right) | |
+{ | |
+ return projid_eq(left, right) && projid_valid(left); | |
+} | |
+ | |
#ifdef CONFIG_USER_NS | |
extern kprojid_t make_kprojid(struct user_namespace *from, projid_t projid); | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 16d9a726206bbdb8f5b79f22ea5146515d6a337e Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Tue, 9 Feb 2016 13:26:34 -0600 | |
Subject: [PATCH 70/76] UBUNTU: SAUCE: (namespace) ext4: Add module parameter | |
to enable user namespace mounts | |
This is still an experimental feature, so disable it by default | |
and allow it only when the system administrator supplies the | |
userns_mounts=true module parameter. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
Conflicts: | |
fs/ext4/super.c | |
--- | |
fs/ext4/super.c | 12 ++++++++++-- | |
1 file changed, 10 insertions(+), 2 deletions(-) | |
diff --git a/fs/ext4/super.c b/fs/ext4/super.c | |
index 5314794..ffbc9fe 100644 | |
--- a/fs/ext4/super.c | |
+++ b/fs/ext4/super.c | |
@@ -112,6 +112,10 @@ static struct inode *ext4_get_journal_inode(struct super_block *sb, | |
* transaction start -> page lock(s) -> i_data_sem (rw) | |
*/ | |
+static bool userns_mounts = false; | |
+module_param(userns_mounts, bool, 0644); | |
+MODULE_PARM_DESC(userns_mounts, "Allow mounts from unprivileged user namespaces"); | |
+ | |
#if !defined(CONFIG_EXT2_FS) && !defined(CONFIG_EXT2_FS_MODULE) && defined(CONFIG_EXT4_USE_FOR_EXT2) | |
static struct file_system_type ext2_fs_type = { | |
.owner = THIS_MODULE, | |
@@ -3327,7 +3331,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) | |
char *orig_data = kstrdup(data, GFP_KERNEL); | |
struct buffer_head *bh; | |
struct ext4_super_block *es = NULL; | |
- struct ext4_sb_info *sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); | |
+ struct ext4_sb_info *sbi; | |
ext4_fsblk_t block; | |
ext4_fsblk_t sb_block = get_sb_block(&data); | |
ext4_fsblk_t logical_sb_block; | |
@@ -3346,7 +3350,11 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) | |
unsigned int journal_ioprio = DEFAULT_JOURNAL_IOPRIO; | |
ext4_group_t first_not_zeroed; | |
- if ((data && !orig_data) || !sbi) | |
+ if (!userns_mounts && !capable(CAP_SYS_ADMIN)) | |
+ return -EPERM; | |
+ | |
+ sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); | |
+ if (!sbi) | |
goto out_free_base; | |
sbi->s_blockgroup_lock = | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From a0e6f4549bda646184af45c0ead927b0fa0a0087 Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Mon, 19 Sep 2016 15:46:26 -0500 | |
Subject: [PATCH 71/76] UBUNTU: SAUCE: (namespace) block_dev: Forbid | |
unprivileged mounting when device is opened for writing | |
For unprivileged mounts to be safe the user must not be able to | |
make changes to the backing store while it is mounted. This patch | |
takes a step towards preventing this by refusing to mount in a | |
user namepspace if the block device is open for writing and | |
refusing attempts to open the block device for writing by non- | |
root while it is mounted in a user namespace. | |
To prevent this from happening we use i_writecount in the inodes | |
of the bdev filesystem similarly to how it is used for regular | |
files. Whenever the device is opened for writing i_writecount | |
is checked; if it is negative the open returns -EBUSY, otherwise | |
i_writecount is incremented. On mount, a positive i_writecount | |
results in mount_bdev returning -EBUSY, otherwise i_writecount | |
is decremented. Opens by root and mounts from init_user_ns do not | |
check nor modify i_writecount. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
fs/block_dev.c | 17 +++++++++++++++++ | |
fs/super.c | 32 ++++++++++++++++++++++++++++++-- | |
2 files changed, 47 insertions(+), 2 deletions(-) | |
diff --git a/fs/block_dev.c b/fs/block_dev.c | |
index 8d9fac2..cd1f807 100644 | |
--- a/fs/block_dev.c | |
+++ b/fs/block_dev.c | |
@@ -1571,6 +1571,20 @@ static int blkdev_open(struct inode * inode, struct file * filp) | |
if (bdev == NULL) | |
return -ENOMEM; | |
+ /* | |
+ * A negative i_writecount for bdev->bd_inode means that the bdev | |
+ * or one of its paritions is mounted in a user namespace. Deny | |
+ * writing for non-root in this case, otherwise an unprivileged | |
+ * user can attack the kernel by modifying the backing store of a | |
+ * mounted filesystem. | |
+ */ | |
+ if ((filp->f_mode & FMODE_WRITE) && | |
+ !file_ns_capable(filp, &init_user_ns, CAP_SYS_ADMIN) && | |
+ !atomic_inc_unless_negative(&bdev->bd_inode->i_writecount)) { | |
+ bdput(bdev); | |
+ return -EBUSY; | |
+ } | |
+ | |
filp->f_mapping = bdev->bd_inode->i_mapping; | |
return blkdev_get(bdev, filp->f_mode, filp); | |
@@ -1672,6 +1686,9 @@ void blkdev_put(struct block_device *bdev, fmode_t mode) | |
static int blkdev_close(struct inode * inode, struct file * filp) | |
{ | |
struct block_device *bdev = I_BDEV(bdev_file_inode(filp)); | |
+ if (filp->f_mode & FMODE_WRITE && | |
+ !file_ns_capable(filp, &init_user_ns, CAP_SYS_ADMIN)) | |
+ atomic_dec(&bdev->bd_inode->i_writecount); | |
blkdev_put(bdev, filp->f_mode); | |
return 0; | |
} | |
diff --git a/fs/super.c b/fs/super.c | |
index c183835..45517b4 100644 | |
--- a/fs/super.c | |
+++ b/fs/super.c | |
@@ -1033,6 +1033,23 @@ struct dentry *mount_bdev(struct file_system_type *fs_type, | |
if (IS_ERR(bdev)) | |
return ERR_CAST(bdev); | |
+ if (current_user_ns() != &init_user_ns) { | |
+ /* | |
+ * For userns mounts, disallow mounting if bdev is open for | |
+ * writing | |
+ */ | |
+ if (!atomic_dec_unless_positive(&bdev->bd_inode->i_writecount)) { | |
+ error = -EBUSY; | |
+ goto error_bdev; | |
+ } | |
+ if (bdev->bd_contains != bdev && | |
+ !atomic_dec_unless_positive(&bdev->bd_contains->bd_inode->i_writecount)) { | |
+ atomic_inc(&bdev->bd_inode->i_writecount); | |
+ error = -EBUSY; | |
+ goto error_bdev; | |
+ } | |
+ } | |
+ | |
/* | |
* once the super is inserted into the list by sget, s_umount | |
* will protect the lockfs code from trying to start a snapshot | |
@@ -1042,7 +1059,7 @@ struct dentry *mount_bdev(struct file_system_type *fs_type, | |
if (bdev->bd_fsfreeze_count > 0) { | |
mutex_unlock(&bdev->bd_fsfreeze_mutex); | |
error = -EBUSY; | |
- goto error_bdev; | |
+ goto error_inc; | |
} | |
s = sget(fs_type, test_bdev_super, set_bdev_super, flags | MS_NOSEC, | |
bdev); | |
@@ -1054,7 +1071,7 @@ struct dentry *mount_bdev(struct file_system_type *fs_type, | |
if ((flags ^ s->s_flags) & MS_RDONLY) { | |
deactivate_locked_super(s); | |
error = -EBUSY; | |
- goto error_bdev; | |
+ goto error_inc; | |
} | |
/* | |
@@ -1085,6 +1102,12 @@ struct dentry *mount_bdev(struct file_system_type *fs_type, | |
error_s: | |
error = PTR_ERR(s); | |
+error_inc: | |
+ if (current_user_ns() != &init_user_ns) { | |
+ atomic_inc(&bdev->bd_inode->i_writecount); | |
+ if (bdev->bd_contains != bdev) | |
+ atomic_inc(&bdev->bd_contains->bd_inode->i_writecount); | |
+ } | |
error_bdev: | |
blkdev_put(bdev, mode); | |
error: | |
@@ -1101,6 +1124,11 @@ void kill_block_super(struct super_block *sb) | |
generic_shutdown_super(sb); | |
sync_blockdev(bdev); | |
WARN_ON_ONCE(!(mode & FMODE_EXCL)); | |
+ if (sb->s_user_ns != &init_user_ns) { | |
+ atomic_inc(&bdev->bd_inode->i_writecount); | |
+ if (bdev->bd_contains != bdev) | |
+ atomic_inc(&bdev->bd_contains->bd_inode->i_writecount); | |
+ } | |
blkdev_put(bdev, mode | FMODE_EXCL); | |
} | |
-- | |
1.9.3 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 8a0ba787b39ba81a1d8d0cfaa8b53033fccf375e Mon Sep 17 00:00:00 2001 | |
From: Tim Gardner <tim.gardner@canonical.com> | |
Date: Wed, 23 Nov 2016 09:13:25 -0700 | |
Subject: [PATCH 72/76] Revert "UBUNTU: SAUCE: overlayfs: Skip permission | |
checking for trusted.overlayfs.* xattrs" | |
This reverts commit acb0bcdbf914e0786352fd5c547c91b19327b225. | |
Causes FTBS | |
Signed-off-by: Tim Gardner <tim.gardner@canonical.com> | |
--- | |
fs/overlayfs/overlayfs.h | 16 ++-------------- | |
fs/xattr.c | 33 +-------------------------------- | |
include/linux/xattr.h | 1 - | |
3 files changed, 3 insertions(+), 47 deletions(-) | |
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h | |
index 099cb8c..e218e74 100644 | |
--- a/fs/overlayfs/overlayfs.h | |
+++ b/fs/overlayfs/overlayfs.h | |
@@ -95,13 +95,7 @@ static inline int ovl_do_symlink(struct inode *dir, struct dentry *dentry, | |
static inline int ovl_do_setxattr(struct dentry *dentry, const char *name, | |
const void *value, size_t size, int flags) | |
{ | |
- struct inode *inode = dentry->d_inode; | |
- int err; | |
- | |
- inode_lock(inode); | |
- err = __vfs_setxattr_noperm(dentry, name, value, size, flags); | |
- inode_unlock(inode); | |
- | |
+ int err = vfs_setxattr(dentry, name, value, size, flags); | |
pr_debug("setxattr(%pd2, \"%s\", \"%*s\", 0x%x) = %i\n", | |
dentry, name, (int) size, (char *) value, flags, err); | |
return err; | |
@@ -109,13 +103,7 @@ static inline int ovl_do_setxattr(struct dentry *dentry, const char *name, | |
static inline int ovl_do_removexattr(struct dentry *dentry, const char *name) | |
{ | |
- struct inode *inode = dentry->d_inode; | |
- int err; | |
- | |
- inode_lock(inode); | |
- err = __vfs_removexattr_noperm(dentry, name); | |
- inode_unlock(inode); | |
- | |
+ int err = vfs_removexattr(dentry, name); | |
pr_debug("removexattr(%pd2, \"%s\") = %i\n", dentry, name, err); | |
return err; | |
} | |
diff --git a/fs/xattr.c b/fs/xattr.c | |
index afd9343..2d13b4e 100644 | |
--- a/fs/xattr.c | |
+++ b/fs/xattr.c | |
@@ -202,7 +202,6 @@ int __vfs_setxattr_noperm(struct dentry *dentry, const char *name, | |
return error; | |
} | |
-EXPORT_SYMBOL_GPL(__vfs_setxattr_noperm); | |
int | |
@@ -365,36 +364,6 @@ int __vfs_setxattr_noperm(struct dentry *dentry, const char *name, | |
} | |
EXPORT_SYMBOL_GPL(vfs_listxattr); | |
-/** | |
- * __vfs_removexattr_noperm - perform removexattr operation without | |
- * performing permission checks. | |
- * | |
- * @dentry - object to perform setxattr on | |
- * @name - xattr name to set | |
- * | |
- * returns the result of the internal setxattr or setsecurity operations. | |
- * | |
- * This function requires the caller to lock the inode's i_mutex before it | |
- * is executed. It also assumes that the caller will make the appropriate | |
- * permission checks. | |
- */ | |
-int __vfs_removexattr_noperm(struct dentry *dentry, const char *name) | |
-{ | |
- struct inode *inode = dentry->d_inode; | |
- int error = -EOPNOTSUPP; | |
- | |
- if (inode->i_op->removexattr) { | |
- error = inode->i_op->removexattr(dentry, name); | |
- if (!error) { | |
- fsnotify_xattr(dentry); | |
- evm_inode_post_removexattr(dentry, name); | |
- } | |
- } | |
- | |
- return error; | |
-} | |
-EXPORT_SYMBOL_GPL(__vfs_removexattr_noperm); | |
- | |
int | |
__vfs_removexattr(struct dentry *dentry, const char *name) | |
{ | |
@@ -425,7 +394,7 @@ int __vfs_removexattr_noperm(struct dentry *dentry, const char *name) | |
if (error) | |
goto out; | |
- error = __vfs_removexattr_noperm(dentry, name); | |
+ error = __vfs_removexattr(dentry, name); | |
if (!error) { | |
fsnotify_xattr(dentry); | |
diff --git a/include/linux/xattr.h b/include/linux/xattr.h | |
index 6fb2dfc..e77605a 100644 | |
--- a/include/linux/xattr.h | |
+++ b/include/linux/xattr.h | |
@@ -53,7 +53,6 @@ struct xattr { | |
int __vfs_setxattr_noperm(struct dentry *, const char *, const void *, size_t, int); | |
int vfs_setxattr(struct dentry *, const char *, const void *, size_t, int); | |
int __vfs_removexattr(struct dentry *, const char *); | |
-int __vfs_removexattr_noperm(struct dentry *dentry, const char *name); | |
int vfs_removexattr(struct dentry *, const char *); | |
ssize_t generic_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size); | |
-- | |
1.9.3 | |
This file has been truncated, but you can view the full file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 477fa81a0e97b293b1b8ec813e80a5e410ef474e Mon Sep 17 00:00:00 2001 | |
From: Seth Forshee <seth.forshee@canonical.com> | |
Date: Wed, 7 Dec 2016 15:20:37 -0600 | |
Subject: [PATCH 73/76] UBUNTU: SAUCE: Import aufs driver | |
Import aufs4.x-rcN 20161010 from https://github.com/sfjro/aufs4-standalone | |
commit f858de65a9b2d7a6cebd4d90a65aa23799dca9e8. | |
Signed-off-by: Seth Forshee <seth.forshee@canonical.com> | |
--- | |
Documentation/ABI/testing/debugfs-aufs | 50 + | |
Documentation/ABI/testing/sysfs-aufs | 31 + | |
Documentation/filesystems/aufs/README | 392 +++++ | |
Documentation/filesystems/aufs/design/01intro.txt | 170 ++ | |
Documentation/filesystems/aufs/design/02struct.txt | 258 +++ | |
.../filesystems/aufs/design/03atomic_open.txt | 85 + | |
Documentation/filesystems/aufs/design/03lookup.txt | 113 ++ | |
Documentation/filesystems/aufs/design/04branch.txt | 74 + | |
.../filesystems/aufs/design/05wbr_policy.txt | 64 + | |
Documentation/filesystems/aufs/design/06fhsm.txt | 120 ++ | |
Documentation/filesystems/aufs/design/06mmap.txt | 72 + | |
Documentation/filesystems/aufs/design/06xattr.txt | 96 + | |
Documentation/filesystems/aufs/design/07export.txt | 58 + | |
Documentation/filesystems/aufs/design/08shwh.txt | 52 + | |
Documentation/filesystems/aufs/design/10dynop.txt | 47 + | |
MAINTAINERS | 13 + | |
drivers/block/loop.c | 18 + | |
fs/Kconfig | 1 + | |
fs/Makefile | 1 + | |
fs/aufs/Kconfig | 185 ++ | |
fs/aufs/Makefile | 44 + | |
fs/aufs/aufs.h | 59 + | |
fs/aufs/branch.c | 1412 +++++++++++++++ | |
fs/aufs/branch.h | 309 ++++ | |
fs/aufs/conf.mk | 38 + | |
fs/aufs/cpup.c | 1391 +++++++++++++++ | |
fs/aufs/cpup.h | 94 + | |
fs/aufs/dbgaufs.c | 438 +++++ | |
fs/aufs/dbgaufs.h | 48 + | |
fs/aufs/dcsub.c | 225 +++ | |
fs/aufs/dcsub.h | 136 ++ | |
fs/aufs/debug.c | 440 +++++ | |
fs/aufs/debug.h | 225 +++ | |
fs/aufs/dentry.c | 1130 ++++++++++++ | |
fs/aufs/dentry.h | 255 +++ | |
fs/aufs/dinfo.c | 553 ++++++ | |
fs/aufs/dir.c | 762 ++++++++ | |
fs/aufs/dir.h | 137 ++ | |
fs/aufs/dynop.c | 371 ++++ | |
fs/aufs/dynop.h | 74 + | |
fs/aufs/export.c | 837 +++++++++ | |
fs/aufs/f_op.c | 772 ++++++++ | |
fs/aufs/fhsm.c | 426 +++++ | |
fs/aufs/file.c | 857 +++++++++ | |
fs/aufs/file.h | 294 ++++ | |
fs/aufs/finfo.c | 151 ++ | |
fs/aufs/fstype.h | 400 +++++ | |
fs/aufs/hfsnotify.c | 287 +++ | |
fs/aufs/hfsplus.c | 56 + | |
fs/aufs/hnotify.c | 723 ++++++++ | |
fs/aufs/i_op.c | 1451 +++++++++++++++ | |
fs/aufs/i_op_add.c | 924 ++++++++++ | |
fs/aufs/i_op_del.c | 511 ++++++ | |
fs/aufs/i_op_ren.c | 1015 +++++++++++ | |
fs/aufs/iinfo.c | 285 +++ | |
fs/aufs/inode.c | 519 ++++++ | |
fs/aufs/inode.h | 700 ++++++++ | |
fs/aufs/ioctl.c | 219 +++ | |
fs/aufs/loop.c | 147 ++ | |
fs/aufs/loop.h | 52 + | |
fs/aufs/magic.mk | 30 + | |
fs/aufs/module.c | 333 ++++ | |
fs/aufs/module.h | 156 ++ | |
fs/aufs/mvdown.c | 704 ++++++++ | |
fs/aufs/opts.c | 1860 ++++++++++++++++++++ | |
fs/aufs/opts.h | 211 +++ | |
fs/aufs/plink.c | 514 ++++++ | |
fs/aufs/poll.c | 52 + | |
fs/aufs/posix_acl.c | 98 ++ | |
fs/aufs/procfs.c | 169 ++ | |
fs/aufs/rdu.c | 381 ++++ | |
fs/aufs/rwsem.h | 198 +++ | |
fs/aufs/sbinfo.c | 355 ++++ | |
fs/aufs/spl.h | 113 ++ | |
fs/aufs/super.c | 1038 +++++++++++ | |
fs/aufs/super.h | 638 +++++++ | |
fs/aufs/sysaufs.c | 104 ++ | |
fs/aufs/sysaufs.h | 101 ++ | |
fs/aufs/sysfs.c | 376 ++++ | |
fs/aufs/sysrq.c | 157 ++ | |
fs/aufs/vdir.c | 900 ++++++++++ | |
fs/aufs/vfsub.c | 884 ++++++++++ | |
fs/aufs/vfsub.h | 316 ++++ | |
fs/aufs/wbr_policy.c | 765 ++++++++ | |
fs/aufs/whout.c | 1060 +++++++++++ | |
fs/aufs/whout.h | 85 + | |
fs/aufs/wkq.c | 213 +++ | |
fs/aufs/wkq.h | 93 + | |
fs/aufs/xattr.c | 347 ++++ | |
fs/aufs/xino.c | 1318 ++++++++++++++ | |
fs/dcache.c | 3 +- | |
fs/exec.c | 1 + | |
fs/fcntl.c | 5 +- | |
fs/file_table.c | 4 + | |
fs/inode.c | 3 +- | |
fs/namespace.c | 2 + | |
fs/notify/group.c | 4 + | |
fs/notify/mark.c | 4 + | |
fs/open.c | 2 + | |
fs/proc/base.c | 2 +- | |
fs/proc/nommu.c | 5 +- | |
fs/proc/task_mmu.c | 7 +- | |
fs/proc/task_nommu.c | 5 +- | |
fs/read_write.c | 24 + | |
fs/splice.c | 12 +- | |
fs/xattr.c | 1 + | |
include/linux/file.h | 1 + | |
include/linux/fs.h | 9 + | |
include/linux/mm.h | 22 + | |
include/linux/mm_types.h | 2 + | |
include/linux/splice.h | 6 + | |
include/uapi/linux/Kbuild | 1 + | |
include/uapi/linux/aufs_type.h | 419 +++++ | |
kernel/fork.c | 2 +- | |
kernel/task_work.c | 1 + | |
mm/Makefile | 2 +- | |
mm/filemap.c | 2 +- | |
mm/memory.c | 2 +- | |
mm/mmap.c | 33 +- | |
mm/nommu.c | 10 +- | |
mm/prfile.c | 86 + | |
security/commoncap.c | 2 + | |
security/device_cgroup.c | 2 + | |
security/security.c | 10 + | |
124 files changed, 34902 insertions(+), 30 deletions(-) | |
create mode 100644 Documentation/ABI/testing/debugfs-aufs | |
create mode 100644 Documentation/ABI/testing/sysfs-aufs | |
create mode 100644 Documentation/filesystems/aufs/README | |
create mode 100644 Documentation/filesystems/aufs/design/01intro.txt | |
create mode 100644 Documentation/filesystems/aufs/design/02struct.txt | |
create mode 100644 Documentation/filesystems/aufs/design/03atomic_open.txt | |
create mode 100644 Documentation/filesystems/aufs/design/03lookup.txt | |
create mode 100644 Documentation/filesystems/aufs/design/04branch.txt | |
create mode 100644 Documentation/filesystems/aufs/design/05wbr_policy.txt | |
create mode 100644 Documentation/filesystems/aufs/design/06fhsm.txt | |
create mode 100644 Documentation/filesystems/aufs/design/06mmap.txt | |
create mode 100644 Documentation/filesystems/aufs/design/06xattr.txt | |
create mode 100644 Documentation/filesystems/aufs/design/07export.txt | |
create mode 100644 Documentation/filesystems/aufs/design/08shwh.txt | |
create mode 100644 Documentation/filesystems/aufs/design/10dynop.txt | |
create mode 100644 fs/aufs/Kconfig | |
create mode 100644 fs/aufs/Makefile | |
create mode 100644 fs/aufs/aufs.h | |
create mode 100644 fs/aufs/branch.c | |
create mode 100644 fs/aufs/branch.h | |
create mode 100644 fs/aufs/conf.mk | |
create mode 100644 fs/aufs/cpup.c | |
create mode 100644 fs/aufs/cpup.h | |
create mode 100644 fs/aufs/dbgaufs.c | |
create mode 100644 fs/aufs/dbgaufs.h | |
create mode 100644 fs/aufs/dcsub.c | |
create mode 100644 fs/aufs/dcsub.h | |
create mode 100644 fs/aufs/debug.c | |
create mode 100644 fs/aufs/debug.h | |
create mode 100644 fs/aufs/dentry.c | |
create mode 100644 fs/aufs/dentry.h | |
create mode 100644 fs/aufs/dinfo.c | |
create mode 100644 fs/aufs/dir.c | |
create mode 100644 fs/aufs/dir.h | |
create mode 100644 fs/aufs/dynop.c | |
create mode 100644 fs/aufs/dynop.h | |
create mode 100644 fs/aufs/export.c | |
create mode 100644 fs/aufs/f_op.c | |
create mode 100644 fs/aufs/fhsm.c | |
create mode 100644 fs/aufs/file.c | |
create mode 100644 fs/aufs/file.h | |
create mode 100644 fs/aufs/finfo.c | |
create mode 100644 fs/aufs/fstype.h | |
create mode 100644 fs/aufs/hfsnotify.c | |
create mode 100644 fs/aufs/hfsplus.c | |
create mode 100644 fs/aufs/hnotify.c | |
create mode 100644 fs/aufs/i_op.c | |
create mode 100644 fs/aufs/i_op_add.c | |
create mode 100644 fs/aufs/i_op_del.c | |
create mode 100644 fs/aufs/i_op_ren.c | |
create mode 100644 fs/aufs/iinfo.c | |
create mode 100644 fs/aufs/inode.c | |
create mode 100644 fs/aufs/inode.h | |
create mode 100644 fs/aufs/ioctl.c | |
create mode 100644 fs/aufs/loop.c | |
create mode 100644 fs/aufs/loop.h | |
create mode 100644 fs/aufs/magic.mk | |
create mode 100644 fs/aufs/module.c | |
create mode 100644 fs/aufs/module.h | |
create mode 100644 fs/aufs/mvdown.c | |
create mode 100644 fs/aufs/opts.c | |
create mode 100644 fs/aufs/opts.h | |
create mode 100644 fs/aufs/plink.c | |
create mode 100644 fs/aufs/poll.c | |
create mode 100644 fs/aufs/posix_acl.c | |
create mode 100644 fs/aufs/procfs.c | |
create mode 100644 fs/aufs/rdu.c | |
create mode 100644 fs/aufs/rwsem.h | |
create mode 100644 fs/aufs/sbinfo.c | |
create mode 100644 fs/aufs/spl.h | |
create mode 100644 fs/aufs/super.c | |
create mode 100644 fs/aufs/super.h | |
create mode 100644 fs/aufs/sysaufs.c | |
create mode 100644 fs/aufs/sysaufs.h | |
create mode 100644 fs/aufs/sysfs.c | |
create mode 100644 fs/aufs/sysrq.c | |
create mode 100644 fs/aufs/vdir.c | |
create mode 100644 fs/aufs/vfsub.c | |
create mode 100644 fs/aufs/vfsub.h | |
create mode 100644 fs/aufs/wbr_policy.c | |
create mode 100644 fs/aufs/whout.c | |
create mode 100644 fs/aufs/whout.h | |
create mode 100644 fs/aufs/wkq.c | |
create mode 100644 fs/aufs/wkq.h | |
create mode 100644 fs/aufs/xattr.c | |
create mode 100644 fs/aufs/xino.c | |
create mode 100644 include/uapi/linux/aufs_type.h | |
create mode 100644 mm/prfile.c | |
diff --git a/Documentation/ABI/testing/debugfs-aufs b/Documentation/ABI/testing/debugfs-aufs | |
new file mode 100644 | |
index 0000000..99642d1 | |
--- /dev/null | |
+++ b/Documentation/ABI/testing/debugfs-aufs | |
@@ -0,0 +1,50 @@ | |
+What: /debug/aufs/si_<id>/ | |
+Date: March 2009 | |
+Contact: J. R. Okajima <hooanon05g@gmail.com> | |
+Description: | |
+ Under /debug/aufs, a directory named si_<id> is created | |
+ per aufs mount, where <id> is a unique id generated | |
+ internally. | |
+ | |
+What: /debug/aufs/si_<id>/plink | |
+Date: Apr 2013 | |
+Contact: J. R. Okajima <hooanon05g@gmail.com> | |
+Description: | |
+ It has three lines and shows the information about the | |
+ pseudo-link. The first line is a single number | |
+ representing a number of buckets. The second line is a | |
+ number of pseudo-links per buckets (separated by a | |
+ blank). The last line is a single number representing a | |
+ total number of psedo-links. | |
+ When the aufs mount option 'noplink' is specified, it | |
+ will show "1\n0\n0\n". | |
+ | |
+What: /debug/aufs/si_<id>/xib | |
+Date: March 2009 | |
+Contact: J. R. Okajima <hooanon05g@gmail.com> | |
+Description: | |
+ It shows the consumed blocks by xib (External Inode Number | |
+ Bitmap), its block size and file size. | |
+ When the aufs mount option 'noxino' is specified, it | |
+ will be empty. About XINO files, see the aufs manual. | |
+ | |
+What: /debug/aufs/si_<id>/xino0, xino1 ... xinoN | |
+Date: March 2009 | |
+Contact: J. R. Okajima <hooanon05g@gmail.com> | |
+Description: | |
+ It shows the consumed blocks by xino (External Inode Number | |
+ Translation Table), its link count, block size and file | |
+ size. | |
+ When the aufs mount option 'noxino' is specified, it | |
+ will be empty. About XINO files, see the aufs manual. | |
+ | |
+What: /debug/aufs/si_<id>/xigen | |
+Date: March 2009 | |
+Contact: J. R. Okajima <hooanon05g@gmail.com> | |
+Description: | |
+ It shows the consumed blocks by xigen (External Inode | |
+ Generation Table), its block size and file size. | |
+ If CONFIG_AUFS_EXPORT is disabled, this entry will not | |
+ be created. | |
+ When the aufs mount option 'noxino' is specified, it | |
+ will be empty. About XINO files, see the aufs manual. | |
diff --git a/Documentation/ABI/testing/sysfs-aufs b/Documentation/ABI/testing/sysfs-aufs | |
new file mode 100644 | |
index 0000000..82f9518 | |
--- /dev/null | |
+++ b/Documentation/ABI/testing/sysfs-aufs | |
@@ -0,0 +1,31 @@ | |
+What: /sys/fs/aufs/si_<id>/ | |
+Date: March 2009 | |
+Contact: J. R. Okajima <hooanon05g@gmail.com> | |
+Description: | |
+ Under /sys/fs/aufs, a directory named si_<id> is created | |
+ per aufs mount, where <id> is a unique id generated | |
+ internally. | |
+ | |
+What: /sys/fs/aufs/si_<id>/br0, br1 ... brN | |
+Date: March 2009 | |
+Contact: J. R. Okajima <hooanon05g@gmail.com> | |
+Description: | |
+ It shows the abolute path of a member directory (which | |
+ is called branch) in aufs, and its permission. | |
+ | |
+What: /sys/fs/aufs/si_<id>/brid0, brid1 ... bridN | |
+Date: July 2013 | |
+Contact: J. R. Okajima <hooanon05g@gmail.com> | |
+Description: | |
+ It shows the id of a member directory (which is called | |
+ branch) in aufs. | |
+ | |
+What: /sys/fs/aufs/si_<id>/xi_path | |
+Date: March 2009 | |
+Contact: J. R. Okajima <hooanon05g@gmail.com> | |
+Description: | |
+ It shows the abolute path of XINO (External Inode Number | |
+ Bitmap, Translation Table and Generation Table) file | |
+ even if it is the default path. | |
+ When the aufs mount option 'noxino' is specified, it | |
+ will be empty. About XINO files, see the aufs manual. | |
diff --git a/Documentation/filesystems/aufs/README b/Documentation/filesystems/aufs/README | |
new file mode 100644 | |
index 0000000..36df674 | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/README | |
@@ -0,0 +1,392 @@ | |
+ | |
+Aufs4 -- advanced multi layered unification filesystem version 4.x | |
+http://aufs.sf.net | |
+Junjiro R. Okajima | |
+ | |
+ | |
+0. Introduction | |
+---------------------------------------- | |
+In the early days, aufs was entirely re-designed and re-implemented | |
+Unionfs Version 1.x series. Adding many original ideas, approaches, | |
+improvements and implementations, it becomes totally different from | |
+Unionfs while keeping the basic features. | |
+Recently, Unionfs Version 2.x series begin taking some of the same | |
+approaches to aufs1's. | |
+Unionfs is being developed by Professor Erez Zadok at Stony Brook | |
+University and his team. | |
+ | |
+Aufs4 supports linux-4.0 and later, and for linux-3.x series try aufs3. | |
+If you want older kernel version support, try aufs2-2.6.git or | |
+aufs2-standalone.git repository, aufs1 from CVS on SourceForge. | |
+ | |
+Note: it becomes clear that "Aufs was rejected. Let's give it up." | |
+ According to Christoph Hellwig, linux rejects all union-type | |
+ filesystems but UnionMount. | |
+<http://marc.info/?l=linux-kernel&m=123938533724484&w=2> | |
+ | |
+PS. Al Viro seems have a plan to merge aufs as well as overlayfs and | |
+ UnionMount, and he pointed out an issue around a directory mutex | |
+ lock and aufs addressed it. But it is still unsure whether aufs will | |
+ be merged (or any other union solution). | |
+<http://marc.info/?l=linux-kernel&m=136312705029295&w=1> | |
+ | |
+ | |
+1. Features | |
+---------------------------------------- | |
+- unite several directories into a single virtual filesystem. The member | |
+ directory is called as a branch. | |
+- you can specify the permission flags to the branch, which are 'readonly', | |
+ 'readwrite' and 'whiteout-able.' | |
+- by upper writable branch, internal copyup and whiteout, files/dirs on | |
+ readonly branch are modifiable logically. | |
+- dynamic branch manipulation, add, del. | |
+- etc... | |
+ | |
+Also there are many enhancements in aufs, such as: | |
+- test only the highest one for the directory permission (dirperm1) | |
+- copyup on open (coo=) | |
+- 'move' policy for copy-up between two writable branches, after | |
+ checking free space. | |
+- xattr, acl | |
+- readdir(3) in userspace. | |
+- keep inode number by external inode number table | |
+- keep the timestamps of file/dir in internal copyup operation | |
+- seekable directory, supporting NFS readdir. | |
+- whiteout is hardlinked in order to reduce the consumption of inodes | |
+ on branch | |
+- do not copyup, nor create a whiteout when it is unnecessary | |
+- revert a single systemcall when an error occurs in aufs | |
+- remount interface instead of ioctl | |
+- maintain /etc/mtab by an external command, /sbin/mount.aufs. | |
+- loopback mounted filesystem as a branch | |
+- kernel thread for removing the dir who has a plenty of whiteouts | |
+- support copyup sparse file (a file which has a 'hole' in it) | |
+- default permission flags for branches | |
+- selectable permission flags for ro branch, whether whiteout can | |
+ exist or not | |
+- export via NFS. | |
+- support <sysfs>/fs/aufs and <debugfs>/aufs. | |
+- support multiple writable branches, some policies to select one | |
+ among multiple writable branches. | |
+- a new semantics for link(2) and rename(2) to support multiple | |
+ writable branches. | |
+- no glibc changes are required. | |
+- pseudo hardlink (hardlink over branches) | |
+- allow a direct access manually to a file on branch, e.g. bypassing aufs. | |
+ including NFS or remote filesystem branch. | |
+- userspace wrapper for pathconf(3)/fpathconf(3) with _PC_LINK_MAX. | |
+- and more... | |
+ | |
+Currently these features are dropped temporary from aufs4. | |
+See design/08plan.txt in detail. | |
+- nested mount, i.e. aufs as readonly no-whiteout branch of another aufs | |
+ (robr) | |
+- statistics of aufs thread (/sys/fs/aufs/stat) | |
+ | |
+Features or just an idea in the future (see also design/*.txt), | |
+- reorder the branch index without del/re-add. | |
+- permanent xino files for NFSD | |
+- an option for refreshing the opened files after add/del branches | |
+- light version, without branch manipulation. (unnecessary?) | |
+- copyup in userspace | |
+- inotify in userspace | |
+- readv/writev | |
+ | |
+ | |
+2. Download | |
+---------------------------------------- | |
+There are three GIT trees for aufs4, aufs4-linux.git, | |
+aufs4-standalone.git, and aufs-util.git. Note that there is no "4" in | |
+"aufs-util.git." | |
+While the aufs-util is always necessary, you need either of aufs4-linux | |
+or aufs4-standalone. | |
+ | |
+The aufs4-linux tree includes the whole linux mainline GIT tree, | |
+git://git.kernel.org/.../torvalds/linux.git. | |
+And you cannot select CONFIG_AUFS_FS=m for this version, eg. you cannot | |
+build aufs4 as an external kernel module. | |
+Several extra patches are not included in this tree. Only | |
+aufs4-standalone tree contains them. They are described in the later | |
+section "Configuration and Compilation." | |
+ | |
+On the other hand, the aufs4-standalone tree has only aufs source files | |
+and necessary patches, and you can select CONFIG_AUFS_FS=m. | |
+But you need to apply all aufs patches manually. | |
+ | |
+You will find GIT branches whose name is in form of "aufs4.x" where "x" | |
+represents the linux kernel version, "linux-4.x". For instance, | |
+"aufs4.0" is for linux-4.0. For latest "linux-4.x-rcN", use | |
+"aufs4.x-rcN" branch. | |
+ | |
+o aufs4-linux tree | |
+$ git clone --reference /your/linux/git/tree \ | |
+ git://github.com/sfjro/aufs4-linux.git aufs4-linux.git | |
+- if you don't have linux GIT tree, then remove "--reference ..." | |
+$ cd aufs4-linux.git | |
+$ git checkout origin/aufs4.0 | |
+ | |
+Or You may want to directly git-pull aufs into your linux GIT tree, and | |
+leave the patch-work to GIT. | |
+$ cd /your/linux/git/tree | |
+$ git remote add aufs4 git://github.com/sfjro/aufs4-linux.git | |
+$ git fetch aufs4 | |
+$ git checkout -b my4.0 v4.0 | |
+$ (add your local change...) | |
+$ git pull aufs4 aufs4.0 | |
+- now you have v4.0 + your_changes + aufs4.0 in you my4.0 branch. | |
+- you may need to solve some conflicts between your_changes and | |
+ aufs4.0. in this case, git-rerere is recommended so that you can | |
+ solve the similar conflicts automatically when you upgrade to 4.1 or | |
+ later in the future. | |
+ | |
+o aufs4-standalone tree | |
+$ git clone git://github.com/sfjro/aufs4-standalone.git aufs4-standalone.git | |
+$ cd aufs4-standalone.git | |
+$ git checkout origin/aufs4.0 | |
+ | |
+o aufs-util tree | |
+$ git clone git://git.code.sf.net/p/aufs/aufs-util aufs-util.git | |
+- note that the public aufs-util.git is on SourceForge instead of | |
+ GitHUB. | |
+$ cd aufs-util.git | |
+$ git checkout origin/aufs4.0 | |
+ | |
+Note: The 4.x-rcN branch is to be used with `rc' kernel versions ONLY. | |
+The minor version number, 'x' in '4.x', of aufs may not always | |
+follow the minor version number of the kernel. | |
+Because changes in the kernel that cause the use of a new | |
+minor version number do not always require changes to aufs-util. | |
+ | |
+Since aufs-util has its own minor version number, you may not be | |
+able to find a GIT branch in aufs-util for your kernel's | |
+exact minor version number. | |
+In this case, you should git-checkout the branch for the | |
+nearest lower number. | |
+ | |
+For (an unreleased) example: | |
+If you are using "linux-4.10" and the "aufs4.10" branch | |
+does not exist in aufs-util repository, then "aufs4.9", "aufs4.8" | |
+or something numerically smaller is the branch for your kernel. | |
+ | |
+Also you can view all branches by | |
+ $ git branch -a | |
+ | |
+ | |
+3. Configuration and Compilation | |
+---------------------------------------- | |
+Make sure you have git-checkout'ed the correct branch. | |
+ | |
+For aufs4-linux tree, | |
+- enable CONFIG_AUFS_FS. | |
+- set other aufs configurations if necessary. | |
+ | |
+For aufs4-standalone tree, | |
+There are several ways to build. | |
+ | |
+1. | |
+- apply ./aufs4-kbuild.patch to your kernel source files. | |
+- apply ./aufs4-base.patch too. | |
+- apply ./aufs4-mmap.patch too. | |
+- apply ./aufs4-standalone.patch too, if you have a plan to set | |
+ CONFIG_AUFS_FS=m. otherwise you don't need ./aufs4-standalone.patch. | |
+- copy ./{Documentation,fs,include/uapi/linux/aufs_type.h} files to your | |
+ kernel source tree. Never copy $PWD/include/uapi/linux/Kbuild. | |
+- enable CONFIG_AUFS_FS, you can select either | |
+ =m or =y. | |
+- and build your kernel as usual. | |
+- install the built kernel. | |
+ Note: Since linux-3.9, every filesystem module requires an alias | |
+ "fs-<fsname>". You should make sure that "fs-aufs" is listed in your | |
+ modules.aliases file if you set CONFIG_AUFS_FS=m. | |
+- install the header files too by "make headers_install" to the | |
+ directory where you specify. By default, it is $PWD/usr. | |
+ "make help" shows a brief note for headers_install. | |
+- and reboot your system. | |
+ | |
+2. | |
+- module only (CONFIG_AUFS_FS=m). | |
+- apply ./aufs4-base.patch to your kernel source files. | |
+- apply ./aufs4-mmap.patch too. | |
+- apply ./aufs4-standalone.patch too. | |
+- build your kernel, don't forget "make headers_install", and reboot. | |
+- edit ./config.mk and set other aufs configurations if necessary. | |
+ Note: You should read $PWD/fs/aufs/Kconfig carefully which describes | |
+ every aufs configurations. | |
+- build the module by simple "make". | |
+ Note: Since linux-3.9, every filesystem module requires an alias | |
+ "fs-<fsname>". You should make sure that "fs-aufs" is listed in your | |
+ modules.aliases file. | |
+- you can specify ${KDIR} make variable which points to your kernel | |
+ source tree. | |
+- install the files | |
+ + run "make install" to install the aufs module, or copy the built | |
+ $PWD/aufs.ko to /lib/modules/... and run depmod -a (or reboot simply). | |
+ + run "make install_headers" (instead of headers_install) to install | |
+ the modified aufs header file (you can specify DESTDIR which is | |
+ available in aufs standalone version's Makefile only), or copy | |
+ $PWD/usr/include/linux/aufs_type.h to /usr/include/linux or wherever | |
+ you like manually. By default, the target directory is $PWD/usr. | |
+- no need to apply aufs4-kbuild.patch, nor copying source files to your | |
+ kernel source tree. | |
+ | |
+Note: The header file aufs_type.h is necessary to build aufs-util | |
+ as well as "make headers_install" in the kernel source tree. | |
+ headers_install is subject to be forgotten, but it is essentially | |
+ necessary, not only for building aufs-util. | |
+ You may not meet problems without headers_install in some older | |
+ version though. | |
+ | |
+And then, | |
+- read README in aufs-util, build and install it | |
+- note that your distribution may contain an obsoleted version of | |
+ aufs_type.h in /usr/include/linux or something. When you build aufs | |
+ utilities, make sure that your compiler refers the correct aufs header | |
+ file which is built by "make headers_install." | |
+- if you want to use readdir(3) in userspace or pathconf(3) wrapper, | |
+ then run "make install_ulib" too. And refer to the aufs manual in | |
+ detail. | |
+ | |
+There several other patches in aufs4-standalone.git. They are all | |
+optional. When you meet some problems, they will help you. | |
+- aufs4-loopback.patch | |
+ Supports a nested loopback mount in a branch-fs. This patch is | |
+ unnecessary until aufs produces a message like "you may want to try | |
+ another patch for loopback file". | |
+- vfs-ino.patch | |
+ Modifies a system global kernel internal function get_next_ino() in | |
+ order to stop assigning 0 for an inode-number. Not directly related to | |
+ aufs, but recommended generally. | |
+- tmpfs-idr.patch | |
+ Keeps the tmpfs inode number as the lowest value. Effective to reduce | |
+ the size of aufs XINO files for tmpfs branch. Also it prevents the | |
+ duplication of inode number, which is important for backup tools and | |
+ other utilities. When you find aufs XINO files for tmpfs branch | |
+ growing too much, try this patch. | |
+- lockdep-debug.patch | |
+ Because aufs is not only an ordinary filesystem (callee of VFS), but | |
+ also a caller of VFS functions for branch filesystems, subclassing of | |
+ the internal locks for LOCKDEP is necessary. LOCKDEP is a debugging | |
+ feature of linux kernel. If you enable CONFIG_LOCKDEP, then you will | |
+ need to apply this debug patch to expand several constant values. | |
+ If don't know what LOCKDEP, then you don't have apply this patch. | |
+ | |
+ | |
+4. Usage | |
+---------------------------------------- | |
+At first, make sure aufs-util are installed, and please read the aufs | |
+manual, aufs.5 in aufs-util.git tree. | |
+$ man -l aufs.5 | |
+ | |
+And then, | |
+$ mkdir /tmp/rw /tmp/aufs | |
+# mount -t aufs -o br=/tmp/rw:${HOME} none /tmp/aufs | |
+ | |
+Here is another example. The result is equivalent. | |
+# mount -t aufs -o br=/tmp/rw=rw:${HOME}=ro none /tmp/aufs | |
+ Or | |
+# mount -t aufs -o br:/tmp/rw none /tmp/aufs | |
+# mount -o remount,append:${HOME} /tmp/aufs | |
+ | |
+Then, you can see whole tree of your home dir through /tmp/aufs. If | |
+you modify a file under /tmp/aufs, the one on your home directory is | |
+not affected, instead the same named file will be newly created under | |
+/tmp/rw. And all of your modification to a file will be applied to | |
+the one under /tmp/rw. This is called the file based Copy on Write | |
+(COW) method. | |
+Aufs mount options are described in aufs.5. | |
+If you run chroot or something and make your aufs as a root directory, | |
+then you need to customize the shutdown script. See the aufs manual in | |
+detail. | |
+ | |
+Additionally, there are some sample usages of aufs which are a | |
+diskless system with network booting, and LiveCD over NFS. | |
+See sample dir in CVS tree on SourceForge. | |
+ | |
+ | |
+5. Contact | |
+---------------------------------------- | |
+When you have any problems or strange behaviour in aufs, please let me | |
+know with: | |
+- /proc/mounts (instead of the output of mount(8)) | |
+- /sys/module/aufs/* | |
+- /sys/fs/aufs/* (if you have them) | |
+- /debug/aufs/* (if you have them) | |
+- linux kernel version | |
+ if your kernel is not plain, for example modified by distributor, | |
+ the url where i can download its source is necessary too. | |
+- aufs version which was printed at loading the module or booting the | |
+ system, instead of the date you downloaded. | |
+- configuration (define/undefine CONFIG_AUFS_xxx) | |
+- kernel configuration or /proc/config.gz (if you have it) | |
+- behaviour which you think to be incorrect | |
+- actual operation, reproducible one is better | |
+- mailto: aufs-users at lists.sourceforge.net | |
+ | |
+Usually, I don't watch the Public Areas(Bugs, Support Requests, Patches, | |
+and Feature Requests) on SourceForge. Please join and write to | |
+aufs-users ML. | |
+ | |
+ | |
+6. Acknowledgements | |
+---------------------------------------- | |
+Thanks to everyone who have tried and are using aufs, whoever | |
+have reported a bug or any feedback. | |
+ | |
+Especially donators: | |
+Tomas Matejicek(slax.org) made a donation (much more than once). | |
+ Since Apr 2010, Tomas M (the author of Slax and Linux Live | |
+ scripts) is making "doubling" donations. | |
+ Unfortunately I cannot list all of the donators, but I really | |
+ appreciate. | |
+ It ends Aug 2010, but the ordinary donation URL is still available. | |
+ <http://sourceforge.net/donate/index.php?group_id=167503> | |
+Dai Itasaka made a donation (2007/8). | |
+Chuck Smith made a donation (2008/4, 10 and 12). | |
+Henk Schoneveld made a donation (2008/9). | |
+Chih-Wei Huang, ASUS, CTC donated Eee PC 4G (2008/10). | |
+Francois Dupoux made a donation (2008/11). | |
+Bruno Cesar Ribas and Luis Carlos Erpen de Bona, C3SL serves public | |
+ aufs2 GIT tree (2009/2). | |
+William Grant made a donation (2009/3). | |
+Patrick Lane made a donation (2009/4). | |
+The Mail Archive (mail-archive.com) made donations (2009/5). | |
+Nippy Networks (Ed Wildgoose) made a donation (2009/7). | |
+New Dream Network, LLC (www.dreamhost.com) made a donation (2009/11). | |
+Pavel Pronskiy made a donation (2011/2). | |
+Iridium and Inmarsat satellite phone retailer (www.mailasail.com), Nippy | |
+ Networks (Ed Wildgoose) made a donation for hardware (2011/3). | |
+Max Lekomcev (DOM-TV project) made a donation (2011/7, 12, 2012/3, 6 and | |
+11). | |
+Sam Liddicott made a donation (2011/9). | |
+Era Scarecrow made a donation (2013/4). | |
+Bor Ratajc made a donation (2013/4). | |
+Alessandro Gorreta made a donation (2013/4). | |
+POIRETTE Marc made a donation (2013/4). | |
+Alessandro Gorreta made a donation (2013/4). | |
+lauri kasvandik made a donation (2013/5). | |
+"pemasu from Finland" made a donation (2013/7). | |
+The Parted Magic Project made a donation (2013/9 and 11). | |
+Pavel Barta made a donation (2013/10). | |
+Nikolay Pertsev made a donation (2014/5). | |
+James B made a donation (2014/7 and 2015/7). | |
+Stefano Di Biase made a donation (2014/8). | |
+Daniel Epellei made a donation (2015/1). | |
+OmegaPhil made a donation (2016/1). | |
+Tomasz Szewczyk made a donation (2016/4). | |
+ | |
+Thank you very much. | |
+Donations are always, including future donations, very important and | |
+helpful for me to keep on developing aufs. | |
+ | |
+ | |
+7. | |
+---------------------------------------- | |
+If you are an experienced user, no explanation is needed. Aufs is | |
+just a linux filesystem. | |
+ | |
+ | |
+Enjoy! | |
+ | |
+# Local variables: ; | |
+# mode: text; | |
+# End: ; | |
diff --git a/Documentation/filesystems/aufs/design/01intro.txt b/Documentation/filesystems/aufs/design/01intro.txt | |
new file mode 100644 | |
index 0000000..2799da9 | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/01intro.txt | |
@@ -0,0 +1,170 @@ | |
+ | |
+# Copyright (C) 2005-2016 Junjiro R. Okajima | |
+# | |
+# 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+Introduction | |
+---------------------------------------- | |
+ | |
+aufs [ei ju: ef es] | [a u f s] | |
+1. abbrev. for "advanced multi-layered unification filesystem". | |
+2. abbrev. for "another unionfs". | |
+3. abbrev. for "auf das" in German which means "on the" in English. | |
+ Ex. "Butter aufs Brot"(G) means "butter onto bread"(E). | |
+ But "Filesystem aufs Filesystem" is hard to understand. | |
+ | |
+AUFS is a filesystem with features: | |
+- multi layered stackable unification filesystem, the member directory | |
+ is called as a branch. | |
+- branch permission and attribute, 'readonly', 'real-readonly', | |
+ 'readwrite', 'whiteout-able', 'link-able whiteout', etc. and their | |
+ combination. | |
+- internal "file copy-on-write". | |
+- logical deletion, whiteout. | |
+- dynamic branch manipulation, adding, deleting and changing permission. | |
+- allow bypassing aufs, user's direct branch access. | |
+- external inode number translation table and bitmap which maintains the | |
+ persistent aufs inode number. | |
+- seekable directory, including NFS readdir. | |
+- file mapping, mmap and sharing pages. | |
+- pseudo-link, hardlink over branches. | |
+- loopback mounted filesystem as a branch. | |
+- several policies to select one among multiple writable branches. | |
+- revert a single systemcall when an error occurs in aufs. | |
+- and more... | |
+ | |
+ | |
+Multi Layered Stackable Unification Filesystem | |
+---------------------------------------------------------------------- | |
+Most people already knows what it is. | |
+It is a filesystem which unifies several directories and provides a | |
+merged single directory. When users access a file, the access will be | |
+passed/re-directed/converted (sorry, I am not sure which English word is | |
+correct) to the real file on the member filesystem. The member | |
+filesystem is called 'lower filesystem' or 'branch' and has a mode | |
+'readonly' and 'readwrite.' And the deletion for a file on the lower | |
+readonly branch is handled by creating 'whiteout' on the upper writable | |
+branch. | |
+ | |
+On LKML, there have been discussions about UnionMount (Jan Blunck, | |
+Bharata B Rao and Valerie Aurora) and Unionfs (Erez Zadok). They took | |
+different approaches to implement the merged-view. | |
+The former tries putting it into VFS, and the latter implements as a | |
+separate filesystem. | |
+(If I misunderstand about these implementations, please let me know and | |
+I shall correct it. Because it is a long time ago when I read their | |
+source files last time). | |
+ | |
+UnionMount's approach will be able to small, but may be hard to share | |
+branches between several UnionMount since the whiteout in it is | |
+implemented in the inode on branch filesystem and always | |
+shared. According to Bharata's post, readdir does not seems to be | |
+finished yet. | |
+There are several missing features known in this implementations such as | |
+- for users, the inode number may change silently. eg. copy-up. | |
+- link(2) may break by copy-up. | |
+- read(2) may get an obsoleted filedata (fstat(2) too). | |
+- fcntl(F_SETLK) may be broken by copy-up. | |
+- unnecessary copy-up may happen, for example mmap(MAP_PRIVATE) after | |
+ open(O_RDWR). | |
+ | |
+In linux-3.18, "overlay" filesystem (formerly known as "overlayfs") was | |
+merged into mainline. This is another implementation of UnionMount as a | |
+separated filesystem. All the limitations and known problems which | |
+UnionMount are equally inherited to "overlay" filesystem. | |
+ | |
+Unionfs has a longer history. When I started implementing a stackable | |
+filesystem (Aug 2005), it already existed. It has virtual super_block, | |
+inode, dentry and file objects and they have an array pointing lower | |
+same kind objects. After contributing many patches for Unionfs, I | |
+re-started my project AUFS (Jun 2006). | |
+ | |
+In AUFS, the structure of filesystem resembles to Unionfs, but I | |
+implemented my own ideas, approaches and enhancements and it became | |
+totally different one. | |
+ | |
+Comparing DM snapshot and fs based implementation | |
+- the number of bytes to be copied between devices is much smaller. | |
+- the type of filesystem must be one and only. | |
+- the fs must be writable, no readonly fs, even for the lower original | |
+ device. so the compression fs will not be usable. but if we use | |
+ loopback mount, we may address this issue. | |
+ for instance, | |
+ mount /cdrom/squashfs.img /sq | |
+ losetup /sq/ext2.img | |
+ losetup /somewhere/cow | |
+ dmsetup "snapshot /dev/loop0 /dev/loop1 ..." | |
+- it will be difficult (or needs more operations) to extract the | |
+ difference between the original device and COW. | |
+- DM snapshot-merge may help a lot when users try merging. in the | |
+ fs-layer union, users will use rsync(1). | |
+ | |
+You may want to read my old paper "Filesystems in LiveCD" | |
+(http://aufs.sourceforge.net/aufs2/report/sq/sq.pdf). | |
+ | |
+ | |
+Several characters/aspects/persona of aufs | |
+---------------------------------------------------------------------- | |
+ | |
+Aufs has several characters, aspects or persona. | |
+1. a filesystem, callee of VFS helper | |
+2. sub-VFS, caller of VFS helper for branches | |
+3. a virtual filesystem which maintains persistent inode number | |
+4. reader/writer of files on branches such like an application | |
+ | |
+1. Callee of VFS Helper | |
+As an ordinary linux filesystem, aufs is a callee of VFS. For instance, | |
+unlink(2) from an application reaches sys_unlink() kernel function and | |
+then vfs_unlink() is called. vfs_unlink() is one of VFS helper and it | |
+calls filesystem specific unlink operation. Actually aufs implements the | |
+unlink operation but it behaves like a redirector. | |
+ | |
+2. Caller of VFS Helper for Branches | |
+aufs_unlink() passes the unlink request to the branch filesystem as if | |
+it were called from VFS. So the called unlink operation of the branch | |
+filesystem acts as usual. As a caller of VFS helper, aufs should handle | |
+every necessary pre/post operation for the branch filesystem. | |
+- acquire the lock for the parent dir on a branch | |
+- lookup in a branch | |
+- revalidate dentry on a branch | |
+- mnt_want_write() for a branch | |
+- vfs_unlink() for a branch | |
+- mnt_drop_write() for a branch | |
+- release the lock on a branch | |
+ | |
+3. Persistent Inode Number | |
+One of the most important issue for a filesystem is to maintain inode | |
+numbers. This is particularly important to support exporting a | |
+filesystem via NFS. Aufs is a virtual filesystem which doesn't have a | |
+backend block device for its own. But some storage is necessary to | |
+keep and maintain the inode numbers. It may be a large space and may not | |
+suit to keep in memory. Aufs rents some space from its first writable | |
+branch filesystem (by default) and creates file(s) on it. These files | |
+are created by aufs internally and removed soon (currently) keeping | |
+opened. | |
+Note: Because these files are removed, they are totally gone after | |
+ unmounting aufs. It means the inode numbers are not persistent | |
+ across unmount or reboot. I have a plan to make them really | |
+ persistent which will be important for aufs on NFS server. | |
+ | |
+4. Read/Write Files Internally (copy-on-write) | |
+Because a branch can be readonly, when you write a file on it, aufs will | |
+"copy-up" it to the upper writable branch internally. And then write the | |
+originally requested thing to the file. Generally kernel doesn't | |
+open/read/write file actively. In aufs, even a single write may cause a | |
+internal "file copy". This behaviour is very similar to cp(1) command. | |
+ | |
+Some people may think it is better to pass such work to user space | |
+helper, instead of doing in kernel space. Actually I am still thinking | |
+about it. But currently I have implemented it in kernel space. | |
diff --git a/Documentation/filesystems/aufs/design/02struct.txt b/Documentation/filesystems/aufs/design/02struct.txt | |
new file mode 100644 | |
index 0000000..6ad4a5b | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/02struct.txt | |
@@ -0,0 +1,258 @@ | |
+ | |
+# Copyright (C) 2005-2016 Junjiro R. Okajima | |
+# | |
+# 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+Basic Aufs Internal Structure | |
+ | |
+Superblock/Inode/Dentry/File Objects | |
+---------------------------------------------------------------------- | |
+As like an ordinary filesystem, aufs has its own | |
+superblock/inode/dentry/file objects. All these objects have a | |
+dynamically allocated array and store the same kind of pointers to the | |
+lower filesystem, branch. | |
+For example, when you build a union with one readwrite branch and one | |
+readonly, mounted /au, /rw and /ro respectively. | |
+- /au = /rw + /ro | |
+- /ro/fileA exists but /rw/fileA | |
+ | |
+Aufs lookup operation finds /ro/fileA and gets dentry for that. These | |
+pointers are stored in a aufs dentry. The array in aufs dentry will be, | |
+- [0] = NULL (because /rw/fileA doesn't exist) | |
+- [1] = /ro/fileA | |
+ | |
+This style of an array is essentially same to the aufs | |
+superblock/inode/dentry/file objects. | |
+ | |
+Because aufs supports manipulating branches, ie. add/delete/change | |
+branches dynamically, these objects has its own generation. When | |
+branches are changed, the generation in aufs superblock is | |
+incremented. And a generation in other object are compared when it is | |
+accessed. When a generation in other objects are obsoleted, aufs | |
+refreshes the internal array. | |
+ | |
+ | |
+Superblock | |
+---------------------------------------------------------------------- | |
+Additionally aufs superblock has some data for policies to select one | |
+among multiple writable branches, XIB files, pseudo-links and kobject. | |
+See below in detail. | |
+About the policies which supports copy-down a directory, see | |
+wbr_policy.txt too. | |
+ | |
+ | |
+Branch and XINO(External Inode Number Translation Table) | |
+---------------------------------------------------------------------- | |
+Every branch has its own xino (external inode number translation table) | |
+file. The xino file is created and unlinked by aufs internally. When two | |
+members of a union exist on the same filesystem, they share the single | |
+xino file. | |
+The struct of a xino file is simple, just a sequence of aufs inode | |
+numbers which is indexed by the lower inode number. | |
+In the above sample, assume the inode number of /ro/fileA is i111 and | |
+aufs assigns the inode number i999 for fileA. Then aufs writes 999 as | |
+4(8) bytes at 111 * 4(8) bytes offset in the xino file. | |
+ | |
+When the inode numbers are not contiguous, the xino file will be sparse | |
+which has a hole in it and doesn't consume as much disk space as it | |
+might appear. If your branch filesystem consumes disk space for such | |
+holes, then you should specify 'xino=' option at mounting aufs. | |
+ | |
+Aufs has a mount option to free the disk blocks for such holes in XINO | |
+files on tmpfs or ramdisk. But it is not so effective actually. If you | |
+meet a problem of disk shortage due to XINO files, then you should try | |
+"tmpfs-ino.patch" (and "vfs-ino.patch" too) in aufs4-standalone.git. | |
+The patch localizes the assignment inumbers per tmpfs-mount and avoid | |
+the holes in XINO files. | |
+ | |
+Also a writable branch has three kinds of "whiteout bases". All these | |
+are existed when the branch is joined to aufs, and their names are | |
+whiteout-ed doubly, so that users will never see their names in aufs | |
+hierarchy. | |
+1. a regular file which will be hardlinked to all whiteouts. | |
+2. a directory to store a pseudo-link. | |
+3. a directory to store an "orphan"-ed file temporary. | |
+ | |
+1. Whiteout Base | |
+ When you remove a file on a readonly branch, aufs handles it as a | |
+ logical deletion and creates a whiteout on the upper writable branch | |
+ as a hardlink of this file in order not to consume inode on the | |
+ writable branch. | |
+2. Pseudo-link Dir | |
+ See below, Pseudo-link. | |
+3. Step-Parent Dir | |
+ When "fileC" exists on the lower readonly branch only and it is | |
+ opened and removed with its parent dir, and then user writes | |
+ something into it, then aufs copies-up fileC to this | |
+ directory. Because there is no other dir to store fileC. After | |
+ creating a file under this dir, the file is unlinked. | |
+ | |
+Because aufs supports manipulating branches, ie. add/delete/change | |
+dynamically, a branch has its own id. When the branch order changes, | |
+aufs finds the new index by searching the branch id. | |
+ | |
+ | |
+Pseudo-link | |
+---------------------------------------------------------------------- | |
+Assume "fileA" exists on the lower readonly branch only and it is | |
+hardlinked to "fileB" on the branch. When you write something to fileA, | |
+aufs copies-up it to the upper writable branch. Additionally aufs | |
+creates a hardlink under the Pseudo-link Directory of the writable | |
+branch. The inode of a pseudo-link is kept in aufs super_block as a | |
+simple list. If fileB is read after unlinking fileA, aufs returns | |
+filedata from the pseudo-link instead of the lower readonly | |
+branch. Because the pseudo-link is based upon the inode, to keep the | |
+inode number by xino (see above) is essentially necessary. | |
+ | |
+All the hardlinks under the Pseudo-link Directory of the writable branch | |
+should be restored in a proper location later. Aufs provides a utility | |
+to do this. The userspace helpers executed at remounting and unmounting | |
+aufs by default. | |
+During this utility is running, it puts aufs into the pseudo-link | |
+maintenance mode. In this mode, only the process which began the | |
+maintenance mode (and its child processes) is allowed to operate in | |
+aufs. Some other processes which are not related to the pseudo-link will | |
+be allowed to run too, but the rest have to return an error or wait | |
+until the maintenance mode ends. If a process already acquires an inode | |
+mutex (in VFS), it has to return an error. | |
+ | |
+ | |
+XIB(external inode number bitmap) | |
+---------------------------------------------------------------------- | |
+Addition to the xino file per a branch, aufs has an external inode number | |
+bitmap in a superblock object. It is also an internal file such like a | |
+xino file. | |
+It is a simple bitmap to mark whether the aufs inode number is in-use or | |
+not. | |
+To reduce the file I/O, aufs prepares a single memory page to cache xib. | |
+ | |
+As well as XINO files, aufs has a feature to truncate/refresh XIB to | |
+reduce the number of consumed disk blocks for these files. | |
+ | |
+ | |
+Virtual or Vertical Dir, and Readdir in Userspace | |
+---------------------------------------------------------------------- | |
+In order to support multiple layers (branches), aufs readdir operation | |
+constructs a virtual dir block on memory. For readdir, aufs calls | |
+vfs_readdir() internally for each dir on branches, merges their entries | |
+with eliminating the whiteout-ed ones, and sets it to file (dir) | |
+object. So the file object has its entry list until it is closed. The | |
+entry list will be updated when the file position is zero and becomes | |
+obsoleted. This decision is made in aufs automatically. | |
+ | |
+The dynamically allocated memory block for the name of entries has a | |
+unit of 512 bytes (by default) and stores the names contiguously (no | |
+padding). Another block for each entry is handled by kmem_cache too. | |
+During building dir blocks, aufs creates hash list and judging whether | |
+the entry is whiteouted by its upper branch or already listed. | |
+The merged result is cached in the corresponding inode object and | |
+maintained by a customizable life-time option. | |
+ | |
+Some people may call it can be a security hole or invite DoS attack | |
+since the opened and once readdir-ed dir (file object) holds its entry | |
+list and becomes a pressure for system memory. But I'd say it is similar | |
+to files under /proc or /sys. The virtual files in them also holds a | |
+memory page (generally) while they are opened. When an idea to reduce | |
+memory for them is introduced, it will be applied to aufs too. | |
+For those who really hate this situation, I've developed readdir(3) | |
+library which operates this merging in userspace. You just need to set | |
+LD_PRELOAD environment variable, and aufs will not consume no memory in | |
+kernel space for readdir(3). | |
+ | |
+ | |
+Workqueue | |
+---------------------------------------------------------------------- | |
+Aufs sometimes requires privilege access to a branch. For instance, | |
+in copy-up/down operation. When a user process is going to make changes | |
+to a file which exists in the lower readonly branch only, and the mode | |
+of one of ancestor directories may not be writable by a user | |
+process. Here aufs copy-up the file with its ancestors and they may | |
+require privilege to set its owner/group/mode/etc. | |
+This is a typical case of a application character of aufs (see | |
+Introduction). | |
+ | |
+Aufs uses workqueue synchronously for this case. It creates its own | |
+workqueue. The workqueue is a kernel thread and has privilege. Aufs | |
+passes the request to call mkdir or write (for example), and wait for | |
+its completion. This approach solves a problem of a signal handler | |
+simply. | |
+If aufs didn't adopt the workqueue and changed the privilege of the | |
+process, then the process may receive the unexpected SIGXFSZ or other | |
+signals. | |
+ | |
+Also aufs uses the system global workqueue ("events" kernel thread) too | |
+for asynchronous tasks, such like handling inotify/fsnotify, re-creating a | |
+whiteout base and etc. This is unrelated to a privilege. | |
+Most of aufs operation tries acquiring a rw_semaphore for aufs | |
+superblock at the beginning, at the same time waits for the completion | |
+of all queued asynchronous tasks. | |
+ | |
+ | |
+Whiteout | |
+---------------------------------------------------------------------- | |
+The whiteout in aufs is very similar to Unionfs's. That is represented | |
+by its filename. UnionMount takes an approach of a file mode, but I am | |
+afraid several utilities (find(1) or something) will have to support it. | |
+ | |
+Basically the whiteout represents "logical deletion" which stops aufs to | |
+lookup further, but also it represents "dir is opaque" which also stop | |
+further lookup. | |
+ | |
+In aufs, rmdir(2) and rename(2) for dir uses whiteout alternatively. | |
+In order to make several functions in a single systemcall to be | |
+revertible, aufs adopts an approach to rename a directory to a temporary | |
+unique whiteouted name. | |
+For example, in rename(2) dir where the target dir already existed, aufs | |
+renames the target dir to a temporary unique whiteouted name before the | |
+actual rename on a branch, and then handles other actions (make it opaque, | |
+update the attributes, etc). If an error happens in these actions, aufs | |
+simply renames the whiteouted name back and returns an error. If all are | |
+succeeded, aufs registers a function to remove the whiteouted unique | |
+temporary name completely and asynchronously to the system global | |
+workqueue. | |
+ | |
+ | |
+Copy-up | |
+---------------------------------------------------------------------- | |
+It is a well-known feature or concept. | |
+When user modifies a file on a readonly branch, aufs operate "copy-up" | |
+internally and makes change to the new file on the upper writable branch. | |
+When the trigger systemcall does not update the timestamps of the parent | |
+dir, aufs reverts it after copy-up. | |
+ | |
+ | |
+Move-down (aufs3.9 and later) | |
+---------------------------------------------------------------------- | |
+"Copy-up" is one of the essential feature in aufs. It copies a file from | |
+the lower readonly branch to the upper writable branch when a user | |
+changes something about the file. | |
+"Move-down" is an opposite action of copy-up. Basically this action is | |
+ran manually instead of automatically and internally. | |
+For desgin and implementation, aufs has to consider these issues. | |
+- whiteout for the file may exist on the lower branch. | |
+- ancestor directories may not exist on the lower branch. | |
+- diropq for the ancestor directories may exist on the upper branch. | |
+- free space on the lower branch will reduce. | |
+- another access to the file may happen during moving-down, including | |
+ UDBA (see "Revalidate Dentry and UDBA"). | |
+- the file should not be hard-linked nor pseudo-linked. they should be | |
+ handled by auplink utility later. | |
+ | |
+Sometimes users want to move-down a file from the upper writable branch | |
+to the lower readonly or writable branch. For instance, | |
+- the free space of the upper writable branch is going to run out. | |
+- create a new intermediate branch between the upper and lower branch. | |
+- etc. | |
+ | |
+For this purpose, use "aumvdown" command in aufs-util.git. | |
diff --git a/Documentation/filesystems/aufs/design/03atomic_open.txt b/Documentation/filesystems/aufs/design/03atomic_open.txt | |
new file mode 100644 | |
index 0000000..9cfd1da | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/03atomic_open.txt | |
@@ -0,0 +1,85 @@ | |
+ | |
+# Copyright (C) 2015-2016 Junjiro R. Okajima | |
+# | |
+# 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+Support for a branch who has its ->atomic_open() | |
+---------------------------------------------------------------------- | |
+The filesystems who implement its ->atomic_open() are not majority. For | |
+example NFSv4 does, and aufs should call NFSv4 ->atomic_open, | |
+particularly for open(O_CREAT|O_EXCL, 0400) case. Other than | |
+->atomic_open(), NFSv4 returns an error for this open(2). While I am not | |
+sure whether all filesystems who have ->atomic_open() behave like this, | |
+but NFSv4 surely returns the error. | |
+ | |
+In order to support ->atomic_open() for aufs, there are a few | |
+approaches. | |
+ | |
+A. Introduce aufs_atomic_open() | |
+ - calls one of VFS:do_last(), lookup_open() or atomic_open() for | |
+ branch fs. | |
+B. Introduce aufs_atomic_open() calling create, open and chmod. this is | |
+ an aufs user Pip Cet's approach | |
+ - calls aufs_create(), VFS finish_open() and notify_change(). | |
+ - pass fake-mode to finish_open(), and then correct the mode by | |
+ notify_change(). | |
+C. Extend aufs_open() to call branch fs's ->atomic_open() | |
+ - no aufs_atomic_open(). | |
+ - aufs_lookup() registers the TID to an aufs internal object. | |
+ - aufs_create() does nothing when the matching TID is registered, but | |
+ registers the mode. | |
+ - aufs_open() calls branch fs's ->atomic_open() when the matching | |
+ TID is registered. | |
+D. Extend aufs_open() to re-try branch fs's ->open() with superuser's | |
+ credential | |
+ - no aufs_atomic_open(). | |
+ - aufs_create() registers the TID to an internal object. this info | |
+ represents "this process created this file just now." | |
+ - when aufs gets EACCES from branch fs's ->open(), then confirm the | |
+ registered TID and re-try open() with superuser's credential. | |
+ | |
+Pros and cons for each approach. | |
+ | |
+A. | |
+ - straightforward but highly depends upon VFS internal. | |
+ - the atomic behavaiour is kept. | |
+ - some of parameters such as nameidata are hard to reproduce for | |
+ branch fs. | |
+ - large overhead. | |
+B. | |
+ - easy to implement. | |
+ - the atomic behavaiour is lost. | |
+C. | |
+ - the atomic behavaiour is kept. | |
+ - dirty and tricky. | |
+ - VFS checks whether the file is created correctly after calling | |
+ ->create(), which means this approach doesn't work. | |
+D. | |
+ - easy to implement. | |
+ - the atomic behavaiour is lost. | |
+ - to open a file with superuser's credential and give it to a user | |
+ process is a bad idea, since the file object keeps the credential | |
+ in it. It may affect LSM or something. This approach doesn't work | |
+ either. | |
+ | |
+The approach A is ideal, but it hard to implement. So here is a | |
+variation of A, which is to be implemented. | |
+ | |
+A-1. Introduce aufs_atomic_open() | |
+ - calls branch fs ->atomic_open() if exists. otherwise calls | |
+ vfs_create() and finish_open(). | |
+ - the demerit is that the several checks after branch fs | |
+ ->atomic_open() are lost. in the ordinary case, the checks are | |
+ done by VFS:do_last(), lookup_open() and atomic_open(). some can | |
+ be implemented in aufs, but not all I am afraid. | |
diff --git a/Documentation/filesystems/aufs/design/03lookup.txt b/Documentation/filesystems/aufs/design/03lookup.txt | |
new file mode 100644 | |
index 0000000..a75b663 | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/03lookup.txt | |
@@ -0,0 +1,113 @@ | |
+ | |
+# Copyright (C) 2005-2016 Junjiro R. Okajima | |
+# | |
+# 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+Lookup in a Branch | |
+---------------------------------------------------------------------- | |
+Since aufs has a character of sub-VFS (see Introduction), it operates | |
+lookup for branches as VFS does. It may be a heavy work. But almost all | |
+lookup operation in aufs is the simplest case, ie. lookup only an entry | |
+directly connected to its parent. Digging down the directory hierarchy | |
+is unnecessary. VFS has a function lookup_one_len() for that use, and | |
+aufs calls it. | |
+ | |
+When a branch is a remote filesystem, aufs basically relies upon its | |
+->d_revalidate(), also aufs forces the hardest revalidate tests for | |
+them. | |
+For d_revalidate, aufs implements three levels of revalidate tests. See | |
+"Revalidate Dentry and UDBA" in detail. | |
+ | |
+ | |
+Test Only the Highest One for the Directory Permission (dirperm1 option) | |
+---------------------------------------------------------------------- | |
+Let's try case study. | |
+- aufs has two branches, upper readwrite and lower readonly. | |
+ /au = /rw + /ro | |
+- "dirA" exists under /ro, but /rw. and its mode is 0700. | |
+- user invoked "chmod a+rx /au/dirA" | |
+- the internal copy-up is activated and "/rw/dirA" is created and its | |
+ permission bits are set to world readable. | |
+- then "/au/dirA" becomes world readable? | |
+ | |
+In this case, /ro/dirA is still 0700 since it exists in readonly branch, | |
+or it may be a natively readonly filesystem. If aufs respects the lower | |
+branch, it should not respond readdir request from other users. But user | |
+allowed it by chmod. Should really aufs rejects showing the entries | |
+under /ro/dirA? | |
+ | |
+To be honest, I don't have a good solution for this case. So aufs | |
+implements 'dirperm1' and 'nodirperm1' mount options, and leave it to | |
+users. | |
+When dirperm1 is specified, aufs checks only the highest one for the | |
+directory permission, and shows the entries. Otherwise, as usual, checks | |
+every dir existing on all branches and rejects the request. | |
+ | |
+As a side effect, dirperm1 option improves the performance of aufs | |
+because the number of permission check is reduced when the number of | |
+branch is many. | |
+ | |
+ | |
+Revalidate Dentry and UDBA (User's Direct Branch Access) | |
+---------------------------------------------------------------------- | |
+Generally VFS helpers re-validate a dentry as a part of lookup. | |
+0. digging down the directory hierarchy. | |
+1. lock the parent dir by its i_mutex. | |
+2. lookup the final (child) entry. | |
+3. revalidate it. | |
+4. call the actual operation (create, unlink, etc.) | |
+5. unlock the parent dir | |
+ | |
+If the filesystem implements its ->d_revalidate() (step 3), then it is | |
+called. Actually aufs implements it and checks the dentry on a branch is | |
+still valid. | |
+But it is not enough. Because aufs has to release the lock for the | |
+parent dir on a branch at the end of ->lookup() (step 2) and | |
+->d_revalidate() (step 3) while the i_mutex of the aufs dir is still | |
+held by VFS. | |
+If the file on a branch is changed directly, eg. bypassing aufs, after | |
+aufs released the lock, then the subsequent operation may cause | |
+something unpleasant result. | |
+ | |
+This situation is a result of VFS architecture, ->lookup() and | |
+->d_revalidate() is separated. But I never say it is wrong. It is a good | |
+design from VFS's point of view. It is just not suitable for sub-VFS | |
+character in aufs. | |
+ | |
+Aufs supports such case by three level of revalidation which is | |
+selectable by user. | |
+1. Simple Revalidate | |
+ Addition to the native flow in VFS's, confirm the child-parent | |
+ relationship on the branch just after locking the parent dir on the | |
+ branch in the "actual operation" (step 4). When this validation | |
+ fails, aufs returns EBUSY. ->d_revalidate() (step 3) in aufs still | |
+ checks the validation of the dentry on branches. | |
+2. Monitor Changes Internally by Inotify/Fsnotify | |
+ Addition to above, in the "actual operation" (step 4) aufs re-lookup | |
+ the dentry on the branch, and returns EBUSY if it finds different | |
+ dentry. | |
+ Additionally, aufs sets the inotify/fsnotify watch for every dir on branches | |
+ during it is in cache. When the event is notified, aufs registers a | |
+ function to kernel 'events' thread by schedule_work(). And the | |
+ function sets some special status to the cached aufs dentry and inode | |
+ private data. If they are not cached, then aufs has nothing to | |
+ do. When the same file is accessed through aufs (step 0-3) later, | |
+ aufs will detect the status and refresh all necessary data. | |
+ In this mode, aufs has to ignore the event which is fired by aufs | |
+ itself. | |
+3. No Extra Validation | |
+ This is the simplest test and doesn't add any additional revalidation | |
+ test, and skip the revalidation in step 4. It is useful and improves | |
+ aufs performance when system surely hide the aufs branches from user, | |
+ by over-mounting something (or another method). | |
diff --git a/Documentation/filesystems/aufs/design/04branch.txt b/Documentation/filesystems/aufs/design/04branch.txt | |
new file mode 100644 | |
index 0000000..6203ac4 | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/04branch.txt | |
@@ -0,0 +1,74 @@ | |
+ | |
+# Copyright (C) 2005-2016 Junjiro R. Okajima | |
+# | |
+# 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+Branch Manipulation | |
+ | |
+Since aufs supports dynamic branch manipulation, ie. add/remove a branch | |
+and changing its permission/attribute, there are a lot of works to do. | |
+ | |
+ | |
+Add a Branch | |
+---------------------------------------------------------------------- | |
+o Confirm the adding dir exists outside of aufs, including loopback | |
+ mount, and its various attributes. | |
+o Initialize the xino file and whiteout bases if necessary. | |
+ See struct.txt. | |
+ | |
+o Check the owner/group/mode of the directory | |
+ When the owner/group/mode of the adding directory differs from the | |
+ existing branch, aufs issues a warning because it may impose a | |
+ security risk. | |
+ For example, when a upper writable branch has a world writable empty | |
+ top directory, a malicious user can create any files on the writable | |
+ branch directly, like copy-up and modify manually. If something like | |
+ /etc/{passwd,shadow} exists on the lower readonly branch but the upper | |
+ writable branch, and the writable branch is world-writable, then a | |
+ malicious guy may create /etc/passwd on the writable branch directly | |
+ and the infected file will be valid in aufs. | |
+ I am afraid it can be a security issue, but aufs can do nothing except | |
+ producing a warning. | |
+ | |
+ | |
+Delete a Branch | |
+---------------------------------------------------------------------- | |
+o Confirm the deleting branch is not busy | |
+ To be general, there is one merit to adopt "remount" interface to | |
+ manipulate branches. It is to discard caches. At deleting a branch, | |
+ aufs checks the still cached (and connected) dentries and inodes. If | |
+ there are any, then they are all in-use. An inode without its | |
+ corresponding dentry can be alive alone (for example, inotify/fsnotify case). | |
+ | |
+ For the cached one, aufs checks whether the same named entry exists on | |
+ other branches. | |
+ If the cached one is a directory, because aufs provides a merged view | |
+ to users, as long as one dir is left on any branch aufs can show the | |
+ dir to users. In this case, the branch can be removed from aufs. | |
+ Otherwise aufs rejects deleting the branch. | |
+ | |
+ If any file on the deleting branch is opened by aufs, then aufs | |
+ rejects deleting. | |
+ | |
+ | |
+Modify the Permission of a Branch | |
+---------------------------------------------------------------------- | |
+o Re-initialize or remove the xino file and whiteout bases if necessary. | |
+ See struct.txt. | |
+ | |
+o rw --> ro: Confirm the modifying branch is not busy | |
+ Aufs rejects the request if any of these conditions are true. | |
+ - a file on the branch is mmap-ed. | |
+ - a regular file on the branch is opened for write and there is no | |
+ same named entry on the upper branch. | |
diff --git a/Documentation/filesystems/aufs/design/05wbr_policy.txt b/Documentation/filesystems/aufs/design/05wbr_policy.txt | |
new file mode 100644 | |
index 0000000..539cc14 | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/05wbr_policy.txt | |
@@ -0,0 +1,64 @@ | |
+ | |
+# Copyright (C) 2005-2016 Junjiro R. Okajima | |
+# | |
+# 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+Policies to Select One among Multiple Writable Branches | |
+---------------------------------------------------------------------- | |
+When the number of writable branch is more than one, aufs has to decide | |
+the target branch for file creation or copy-up. By default, the highest | |
+writable branch which has the parent (or ancestor) dir of the target | |
+file is chosen (top-down-parent policy). | |
+By user's request, aufs implements some other policies to select the | |
+writable branch, for file creation several policies, round-robin, | |
+most-free-space, and other policies. For copy-up, top-down-parent, | |
+bottom-up-parent, bottom-up and others. | |
+ | |
+As expected, the round-robin policy selects the branch in circular. When | |
+you have two writable branches and creates 10 new files, 5 files will be | |
+created for each branch. mkdir(2) systemcall is an exception. When you | |
+create 10 new directories, all will be created on the same branch. | |
+And the most-free-space policy selects the one which has most free | |
+space among the writable branches. The amount of free space will be | |
+checked by aufs internally, and users can specify its time interval. | |
+ | |
+The policies for copy-up is more simple, | |
+top-down-parent is equivalent to the same named on in create policy, | |
+bottom-up-parent selects the writable branch where the parent dir | |
+exists and the nearest upper one from the copyup-source, | |
+bottom-up selects the nearest upper writable branch from the | |
+copyup-source, regardless the existence of the parent dir. | |
+ | |
+There are some rules or exceptions to apply these policies. | |
+- If there is a readonly branch above the policy-selected branch and | |
+ the parent dir is marked as opaque (a variation of whiteout), or the | |
+ target (creating) file is whiteout-ed on the upper readonly branch, | |
+ then the result of the policy is ignored and the target file will be | |
+ created on the nearest upper writable branch than the readonly branch. | |
+- If there is a writable branch above the policy-selected branch and | |
+ the parent dir is marked as opaque or the target file is whiteouted | |
+ on the branch, then the result of the policy is ignored and the target | |
+ file will be created on the highest one among the upper writable | |
+ branches who has diropq or whiteout. In case of whiteout, aufs removes | |
+ it as usual. | |
+- link(2) and rename(2) systemcalls are exceptions in every policy. | |
+ They try selecting the branch where the source exists as possible | |
+ since copyup a large file will take long time. If it can't be, | |
+ ie. the branch where the source exists is readonly, then they will | |
+ follow the copyup policy. | |
+- There is an exception for rename(2) when the target exists. | |
+ If the rename target exists, aufs compares the index of the branches | |
+ where the source and the target exists and selects the higher | |
+ one. If the selected branch is readonly, then aufs follows the | |
+ copyup policy. | |
diff --git a/Documentation/filesystems/aufs/design/06fhsm.txt b/Documentation/filesystems/aufs/design/06fhsm.txt | |
new file mode 100644 | |
index 0000000..aee7779 | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/06fhsm.txt | |
@@ -0,0 +1,120 @@ | |
+ | |
+# Copyright (C) 2011-2016 Junjiro R. Okajima | |
+# | |
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA | |
+ | |
+ | |
+File-based Hierarchical Storage Management (FHSM) | |
+---------------------------------------------------------------------- | |
+Hierarchical Storage Management (or HSM) is a well-known feature in the | |
+storage world. Aufs provides this feature as file-based with multiple | |
+writable branches, based upon the principle of "Colder, the Lower". | |
+Here the word "colder" means that the less used files, and "lower" means | |
+that the position in the order of the stacked branches vertically. | |
+These multiple writable branches are prioritized, ie. the topmost one | |
+should be the fastest drive and be used heavily. | |
+ | |
+o Characters in aufs FHSM story | |
+- aufs itself and a new branch attribute. | |
+- a new ioctl interface to move-down and to establish a connection with | |
+ the daemon ("move-down" is a converse of "copy-up"). | |
+- userspace tool and daemon. | |
+ | |
+The userspace daemon establishes a connection with aufs and waits for | |
+the notification. The notified information is very similar to struct | |
+statfs containing the number of consumed blocks and inodes. | |
+When the consumed blocks/inodes of a branch exceeds the user-specified | |
+upper watermark, the daemon activates its move-down process until the | |
+consumed blocks/inodes reaches the user-specified lower watermark. | |
+ | |
+The actual move-down is done by aufs based upon the request from | |
+user-space since we need to maintain the inode number and the internal | |
+pointer arrays in aufs. | |
+ | |
+Currently aufs FHSM handles the regular files only. Additionally they | |
+must not be hard-linked nor pseudo-linked. | |
+ | |
+ | |
+o Cowork of aufs and the user-space daemon | |
+ During the userspace daemon established the connection, aufs sends a | |
+ small notification to it whenever aufs writes something into the | |
+ writable branch. But it may cost high since aufs issues statfs(2) | |
+ internally. So user can specify a new option to cache the | |
+ info. Actually the notification is controlled by these factors. | |
+ + the specified cache time. | |
+ + classified as "force" by aufs internally. | |
+ Until the specified time expires, aufs doesn't send the info | |
+ except the forced cases. When aufs decide forcing, the info is always | |
+ notified to userspace. | |
+ For example, the number of free inodes is generally large enough and | |
+ the shortage of it happens rarely. So aufs doesn't force the | |
+ notification when creating a new file, directory and others. This is | |
+ the typical case which aufs doesn't force. | |
+ When aufs writes the actual filedata and the files consumes any of new | |
+ blocks, the aufs forces notifying. | |
+ | |
+ | |
+o Interfaces in aufs | |
+- New branch attribute. | |
+ + fhsm | |
+ Specifies that the branch is managed by FHSM feature. In other word, | |
+ participant in the FHSM. | |
+ When nofhsm is set to the branch, it will not be the source/target | |
+ branch of the move-down operation. This attribute is set | |
+ independently from coo and moo attributes, and if you want full | |
+ FHSM, you should specify them as well. | |
+- New mount option. | |
+ + fhsm_sec | |
+ Specifies a second to suppress many less important info to be | |
+ notified. | |
+- New ioctl. | |
+ + AUFS_CTL_FHSM_FD | |
+ create a new file descriptor which userspace can read the notification | |
+ (a subset of struct statfs) from aufs. | |
+- Module parameter 'brs' | |
+ It has to be set to 1. Otherwise the new mount option 'fhsm' will not | |
+ be set. | |
+- mount helpers /sbin/mount.aufs and /sbin/umount.aufs | |
+ When there are two or more branches with fhsm attributes, | |
+ /sbin/mount.aufs invokes the user-space daemon and /sbin/umount.aufs | |
+ terminates it. As a result of remounting and branch-manipulation, the | |
+ number of branches with fhsm attribute can be one. In this case, | |
+ /sbin/mount.aufs will terminate the user-space daemon. | |
+ | |
+ | |
+Finally the operation is done as these steps in kernel-space. | |
+- make sure that, | |
+ + no one else is using the file. | |
+ + the file is not hard-linked. | |
+ + the file is not pseudo-linked. | |
+ + the file is a regular file. | |
+ + the parent dir is not opaqued. | |
+- find the target writable branch. | |
+- make sure the file is not whiteout-ed by the upper (than the target) | |
+ branch. | |
+- make the parent dir on the target branch. | |
+- mutex lock the inode on the branch. | |
+- unlink the whiteout on the target branch (if exists). | |
+- lookup and create the whiteout-ed temporary name on the target branch. | |
+- copy the file as the whiteout-ed temporary name on the target branch. | |
+- rename the whiteout-ed temporary name to the original name. | |
+- unlink the file on the source branch. | |
+- maintain the internal pointer array and the external inode number | |
+ table (XINO). | |
+- maintain the timestamps and other attributes of the parent dir and the | |
+ file. | |
+ | |
+And of course, in every step, an error may happen. So the operation | |
+should restore the original file state after an error happens. | |
diff --git a/Documentation/filesystems/aufs/design/06mmap.txt b/Documentation/filesystems/aufs/design/06mmap.txt | |
new file mode 100644 | |
index 0000000..676f3af | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/06mmap.txt | |
@@ -0,0 +1,72 @@ | |
+ | |
+# Copyright (C) 2005-2016 Junjiro R. Okajima | |
+# | |
+# 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+mmap(2) -- File Memory Mapping | |
+---------------------------------------------------------------------- | |
+In aufs, the file-mapped pages are handled by a branch fs directly, no | |
+interaction with aufs. It means aufs_mmap() calls the branch fs's | |
+->mmap(). | |
+This approach is simple and good, but there is one problem. | |
+Under /proc, several entries show the mmapped files by its path (with | |
+device and inode number), and the printed path will be the path on the | |
+branch fs's instead of virtual aufs's. | |
+This is not a problem in most cases, but some utilities lsof(1) (and its | |
+user) may expect the path on aufs. | |
+ | |
+To address this issue, aufs adds a new member called vm_prfile in struct | |
+vm_area_struct (and struct vm_region). The original vm_file points to | |
+the file on the branch fs in order to handle everything correctly as | |
+usual. The new vm_prfile points to a virtual file in aufs, and the | |
+show-functions in procfs refers to vm_prfile if it is set. | |
+Also we need to maintain several other places where touching vm_file | |
+such like | |
+- fork()/clone() copies vma and the reference count of vm_file is | |
+ incremented. | |
+- merging vma maintains the ref count too. | |
+ | |
+This is not a good approach. It just fakes the printed path. But it | |
+leaves all behaviour around f_mapping unchanged. This is surely an | |
+advantage. | |
+Actually aufs had adopted another complicated approach which calls | |
+generic_file_mmap() and handles struct vm_operations_struct. In this | |
+approach, aufs met a hard problem and I could not solve it without | |
+switching the approach. | |
+ | |
+There may be one more another approach which is | |
+- bind-mount the branch-root onto the aufs-root internally | |
+- grab the new vfsmount (ie. struct mount) | |
+- lazy-umount the branch-root internally | |
+- in open(2) the aufs-file, open the branch-file with the hidden | |
+ vfsmount (instead of the original branch's vfsmount) | |
+- ideally this "bind-mount and lazy-umount" should be done atomically, | |
+ but it may be possible from userspace by the mount helper. | |
+ | |
+Adding the internal hidden vfsmount and using it in opening a file, the | |
+file path under /proc will be printed correctly. This approach looks | |
+smarter, but is not possible I am afraid. | |
+- aufs-root may be bind-mount later. when it happens, another hidden | |
+ vfsmount will be required. | |
+- it is hard to get the chance to bind-mount and lazy-umount | |
+ + in kernel-space, FS can have vfsmount in open(2) via | |
+ file->f_path, and aufs can know its vfsmount. But several locks are | |
+ already acquired, and if aufs tries to bind-mount and lazy-umount | |
+ here, then it may cause a deadlock. | |
+ + in user-space, bind-mount doesn't invoke the mount helper. | |
+- since /proc shows dev and ino, aufs has to give vma these info. it | |
+ means a new member vm_prinode will be necessary. this is essentially | |
+ equivalent to vm_prfile described above. | |
+ | |
+I have to give up this "looks-smater" approach. | |
diff --git a/Documentation/filesystems/aufs/design/06xattr.txt b/Documentation/filesystems/aufs/design/06xattr.txt | |
new file mode 100644 | |
index 0000000..4242fd8 | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/06xattr.txt | |
@@ -0,0 +1,96 @@ | |
+ | |
+# Copyright (C) 2014-2016 Junjiro R. Okajima | |
+# | |
+# 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 St, Fifth Floor, Boston, MA 02110-1301 USA | |
+ | |
+ | |
+Listing XATTR/EA and getting the value | |
+---------------------------------------------------------------------- | |
+For the inode standard attributes (owner, group, timestamps, etc.), aufs | |
+shows the values from the topmost existing file. This behaviour is good | |
+for the non-dir entries since the bahaviour exactly matches the shown | |
+information. But for the directories, aufs considers all the same named | |
+entries on the lower branches. Which means, if one of the lower entry | |
+rejects readdir call, then aufs returns an error even if the topmost | |
+entry allows it. This behaviour is necessary to respect the branch fs's | |
+security, but can make users confused since the user-visible standard | |
+attributes don't match the behaviour. | |
+To address this issue, aufs has a mount option called dirperm1 which | |
+checks the permission for the topmost entry only, and ignores the lower | |
+entry's permission. | |
+ | |
+A similar issue can happen around XATTR. | |
+getxattr(2) and listxattr(2) families behave as if dirperm1 option is | |
+always set. Otherwise these very unpleasant situation would happen. | |
+- listxattr(2) may return the duplicated entries. | |
+- users may not be able to remove or reset the XATTR forever, | |
+ | |
+ | |
+XATTR/EA support in the internal (copy,move)-(up,down) | |
+---------------------------------------------------------------------- | |
+Generally the extended attributes of inode are categorized as these. | |
+- "security" for LSM and capability. | |
+- "system" for posix ACL, 'acl' mount option is required for the branch | |
+ fs generally. | |
+- "trusted" for userspace, CAP_SYS_ADMIN is required. | |
+- "user" for userspace, 'user_xattr' mount option is required for the | |
+ branch fs generally. | |
+ | |
+Moreover there are some other categories. Aufs handles these rather | |
+unpopular categories as the ordinary ones, ie. there is no special | |
+condition nor exception. | |
+ | |
+In copy-up, the support for XATTR on the dst branch may differ from the | |
+src branch. In this case, the copy-up operation will get an error and | |
+the original user operation which triggered the copy-up will fail. It | |
+can happen that even all copy-up will fail. | |
+When both of src and dst branches support XATTR and if an error occurs | |
+during copying XATTR, then the copy-up should fail obviously. That is a | |
+good reason and aufs should return an error to userspace. But when only | |
+the src branch support that XATTR, aufs should not return an error. | |
+For example, the src branch supports ACL but the dst branch doesn't | |
+because the dst branch may natively un-support it or temporary | |
+un-support it due to "noacl" mount option. Of course, the dst branch fs | |
+may NOT return an error even if the XATTR is not supported. It is | |
+totally up to the branch fs. | |
+ | |
+Anyway when the aufs internal copy-up gets an error from the dst branch | |
+fs, then aufs tries removing the just copied entry and returns the error | |
+to the userspace. The worst case of this situation will be all copy-up | |
+will fail. | |
+ | |
+For the copy-up operation, there two basic approaches. | |
+- copy the specified XATTR only (by category above), and return the | |
+ error unconditionally if it happens. | |
+- copy all XATTR, and ignore the error on the specified category only. | |
+ | |
+In order to support XATTR and to implement the correct behaviour, aufs | |
+chooses the latter approach and introduces some new branch attributes, | |
+"icexsec", "icexsys", "icextr", "icexusr", and "icexoth". | |
+They correspond to the XATTR namespaces (see above). Additionally, to be | |
+convenient, "icex" is also provided which means all "icex*" attributes | |
+are set (here the word "icex" stands for "ignore copy-error on XATTR"). | |
+ | |
+The meaning of these attributes is to ignore the error from setting | |
+XATTR on that branch. | |
+Note that aufs tries copying all XATTR unconditionally, and ignores the | |
+error from the dst branch according to the specified attributes. | |
+ | |
+Some XATTR may have its default value. The default value may come from | |
+the parent dir or the environment. If the default value is set at the | |
+file creating-time, it will be overwritten by copy-up. | |
+Some contradiction may happen I am afraid. | |
+Do we need another attribute to stop copying XATTR? I am unsure. For | |
+now, aufs implements the branch attributes to ignore the error. | |
diff --git a/Documentation/filesystems/aufs/design/07export.txt b/Documentation/filesystems/aufs/design/07export.txt | |
new file mode 100644 | |
index 0000000..9a4f31f | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/07export.txt | |
@@ -0,0 +1,58 @@ | |
+ | |
+# Copyright (C) 2005-2016 Junjiro R. Okajima | |
+# | |
+# 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+Export Aufs via NFS | |
+---------------------------------------------------------------------- | |
+Here is an approach. | |
+- like xino/xib, add a new file 'xigen' which stores aufs inode | |
+ generation. | |
+- iget_locked(): initialize aufs inode generation for a new inode, and | |
+ store it in xigen file. | |
+- destroy_inode(): increment aufs inode generation and store it in xigen | |
+ file. it is necessary even if it is not unlinked, because any data of | |
+ inode may be changed by UDBA. | |
+- encode_fh(): for a root dir, simply return FILEID_ROOT. otherwise | |
+ build file handle by | |
+ + branch id (4 bytes) | |
+ + superblock generation (4 bytes) | |
+ + inode number (4 or 8 bytes) | |
+ + parent dir inode number (4 or 8 bytes) | |
+ + inode generation (4 bytes)) | |
+ + return value of exportfs_encode_fh() for the parent on a branch (4 | |
+ bytes) | |
+ + file handle for a branch (by exportfs_encode_fh()) | |
+- fh_to_dentry(): | |
+ + find the index of a branch from its id in handle, and check it is | |
+ still exist in aufs. | |
+ + 1st level: get the inode number from handle and search it in cache. | |
+ + 2nd level: if not found in cache, get the parent inode number from | |
+ the handle and search it in cache. and then open the found parent | |
+ dir, find the matching inode number by vfs_readdir() and get its | |
+ name, and call lookup_one_len() for the target dentry. | |
+ + 3rd level: if the parent dir is not cached, call | |
+ exportfs_decode_fh() for a branch and get the parent on a branch, | |
+ build a pathname of it, convert it a pathname in aufs, call | |
+ path_lookup(). now aufs gets a parent dir dentry, then handle it as | |
+ the 2nd level. | |
+ + to open the dir, aufs needs struct vfsmount. aufs keeps vfsmount | |
+ for every branch, but not itself. to get this, (currently) aufs | |
+ searches in current->nsproxy->mnt_ns list. it may not be a good | |
+ idea, but I didn't get other approach. | |
+ + test the generation of the gotten inode. | |
+- every inode operation: they may get EBUSY due to UDBA. in this case, | |
+ convert it into ESTALE for NFSD. | |
+- readdir(): call lockdep_on/off() because filldir in NFSD calls | |
+ lookup_one_len(), vfs_getattr(), encode_fh() and others. | |
diff --git a/Documentation/filesystems/aufs/design/08shwh.txt b/Documentation/filesystems/aufs/design/08shwh.txt | |
new file mode 100644 | |
index 0000000..0a22906 | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/08shwh.txt | |
@@ -0,0 +1,52 @@ | |
+ | |
+# Copyright (C) 2005-2016 Junjiro R. Okajima | |
+# | |
+# 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+Show Whiteout Mode (shwh) | |
+---------------------------------------------------------------------- | |
+Generally aufs hides the name of whiteouts. But in some cases, to show | |
+them is very useful for users. For instance, creating a new middle layer | |
+(branch) by merging existing layers. | |
+ | |
+(borrowing aufs1 HOW-TO from a user, Michael Towers) | |
+When you have three branches, | |
+- Bottom: 'system', squashfs (underlying base system), read-only | |
+- Middle: 'mods', squashfs, read-only | |
+- Top: 'overlay', ram (tmpfs), read-write | |
+ | |
+The top layer is loaded at boot time and saved at shutdown, to preserve | |
+the changes made to the system during the session. | |
+When larger changes have been made, or smaller changes have accumulated, | |
+the size of the saved top layer data grows. At this point, it would be | |
+nice to be able to merge the two overlay branches ('mods' and 'overlay') | |
+and rewrite the 'mods' squashfs, clearing the top layer and thus | |
+restoring save and load speed. | |
+ | |
+This merging is simplified by the use of another aufs mount, of just the | |
+two overlay branches using the 'shwh' option. | |
+# mount -t aufs -o ro,shwh,br:/livesys/overlay=ro+wh:/livesys/mods=rr+wh \ | |
+ aufs /livesys/merge_union | |
+ | |
+A merged view of these two branches is then available at | |
+/livesys/merge_union, and the new feature is that the whiteouts are | |
+visible! | |
+Note that in 'shwh' mode the aufs mount must be 'ro', which will disable | |
+writing to all branches. Also the default mode for all branches is 'ro'. | |
+It is now possible to save the combined contents of the two overlay | |
+branches to a new squashfs, e.g.: | |
+# mksquashfs /livesys/merge_union /path/to/newmods.squash | |
+ | |
+This new squashfs archive can be stored on the boot device and the | |
+initramfs will use it to replace the old one at the next boot. | |
diff --git a/Documentation/filesystems/aufs/design/10dynop.txt b/Documentation/filesystems/aufs/design/10dynop.txt | |
new file mode 100644 | |
index 0000000..4ab46ff | |
--- /dev/null | |
+++ b/Documentation/filesystems/aufs/design/10dynop.txt | |
@@ -0,0 +1,47 @@ | |
+ | |
+# Copyright (C) 2010-2016 Junjiro R. Okajima | |
+# | |
+# 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, see <http://www.gnu.org/licenses/>. | |
+ | |
+Dynamically customizable FS operations | |
+---------------------------------------------------------------------- | |
+Generally FS operations (struct inode_operations, struct | |
+address_space_operations, struct file_operations, etc.) are defined as | |
+"static const", but it never means that FS have only one set of | |
+operation. Some FS have multiple sets of them. For instance, ext2 has | |
+three sets, one for XIP, for NOBH, and for normal. | |
+Since aufs overrides and redirects these operations, sometimes aufs has | |
+to change its behaviour according to the branch FS type. More importantly | |
+VFS acts differently if a function (member in the struct) is set or | |
+not. It means aufs should have several sets of operations and select one | |
+among them according to the branch FS definition. | |
+ | |
+In order to solve this problem and not to affect the behaviour of VFS, | |
+aufs defines these operations dynamically. For instance, aufs defines | |
+dummy direct_IO function for struct address_space_operations, but it may | |
+not be set to the address_space_operations actually. When the branch FS | |
+doesn't have it, aufs doesn't set it to its address_space_operations | |
+while the function definition itself is still alive. So the behaviour | |
+itself will not change, and it will return an error when direct_IO is | |
+not set. | |
+ | |
+The lifetime of these dynamically generated operation object is | |
+maintained by aufs branch object. When the branch is removed from aufs, | |
+the reference counter of the object is decremented. When it reaches | |
+zero, the dynamically generated operation object will be freed. | |
+ | |
+This approach is designed to support AIO (io_submit), Direct I/O and | |
+XIP (DAX) mainly. | |
+Currently this approach is applied to address_space_operations for | |
+regular files only. | |
diff --git a/MAINTAINERS b/MAINTAINERS | |
index 63cefa6..d78b954 100644 | |
--- a/MAINTAINERS | |
+++ b/MAINTAINERS | |
@@ -2293,6 +2293,19 @@ F: include/linux/audit.h | |
F: include/uapi/linux/audit.h | |
F: kernel/audit* | |
+AUFS (advanced multi layered unification filesystem) FILESYSTEM | |
+M: "J. R. Okajima" <hooanon05g@gmail.com> | |
+L: linux-unionfs@vger.kernel.org | |
+L: aufs-users@lists.sourceforge.net (members only) | |
+W: http://aufs.sourceforge.net | |
+T: git://github.com/sfjro/aufs4-linux.git | |
+S: Supported | |
+F: Documentation/filesystems/aufs/ | |
+F: Documentation/ABI/testing/debugfs-aufs | |
+F: Documentation/ABI/testing/sysfs-aufs | |
+F: fs/aufs/ | |
+F: include/uapi/linux/aufs_type.h | |
+ | |
AUXILIARY DISPLAY DRIVERS | |
M: Miguel Ojeda Sandonis <miguel.ojeda.sandonis@gmail.com> | |
W: http://miguelojeda.es/auxdisplay.htm | |
diff --git a/drivers/block/loop.c b/drivers/block/loop.c | |
index 4af8187..c27854b 100644 | |
--- a/drivers/block/loop.c | |
+++ b/drivers/block/loop.c | |
@@ -701,6 +701,24 @@ static inline int is_loop_device(struct file *file) | |
return i && S_ISBLK(i->i_mode) && MAJOR(i->i_rdev) == LOOP_MAJOR; | |
} | |
+/* | |
+ * for AUFS | |
+ * no get/put for file. | |
+ */ | |
+struct file *loop_backing_file(struct super_block *sb) | |
+{ | |
+ struct file *ret; | |
+ struct loop_device *l; | |
+ | |
+ ret = NULL; | |
+ if (MAJOR(sb->s_dev) == LOOP_MAJOR) { | |
+ l = sb->s_bdev->bd_disk->private_data; | |
+ ret = l->lo_backing_file; | |
+ } | |
+ return ret; | |
+} | |
+EXPORT_SYMBOL_GPL(loop_backing_file); | |
+ | |
/* loop sysfs attributes */ | |
static ssize_t loop_attr_show(struct device *dev, char *page, | |
diff --git a/fs/Kconfig b/fs/Kconfig | |
index 4bd03a2..620e01b 100644 | |
--- a/fs/Kconfig | |
+++ b/fs/Kconfig | |
@@ -249,6 +249,7 @@ source "fs/pstore/Kconfig" | |
source "fs/sysv/Kconfig" | |
source "fs/ufs/Kconfig" | |
source "fs/exofs/Kconfig" | |
+source "fs/aufs/Kconfig" | |
endif # MISC_FILESYSTEMS | |
diff --git a/fs/Makefile b/fs/Makefile | |
index ed2b632..aa6d14b 100644 | |
--- a/fs/Makefile | |
+++ b/fs/Makefile | |
@@ -129,3 +129,4 @@ obj-y += exofs/ # Multiple modules | |
obj-$(CONFIG_CEPH_FS) += ceph/ | |
obj-$(CONFIG_PSTORE) += pstore/ | |
obj-$(CONFIG_EFIVAR_FS) += efivarfs/ | |
+obj-$(CONFIG_AUFS_FS) += aufs/ | |
diff --git a/fs/aufs/Kconfig b/fs/aufs/Kconfig | |
new file mode 100644 | |
index 0000000..63560ce | |
--- /dev/null | |
+++ b/fs/aufs/Kconfig | |
@@ -0,0 +1,185 @@ | |
+config AUFS_FS | |
+ tristate "Aufs (Advanced multi layered unification filesystem) support" | |
+ help | |
+ Aufs is a stackable unification filesystem such as Unionfs, | |
+ which unifies several directories and provides a merged single | |
+ directory. | |
+ In the early days, aufs was entirely re-designed and | |
+ re-implemented Unionfs Version 1.x series. Introducing many | |
+ original ideas, approaches and improvements, it becomes totally | |
+ different from Unionfs while keeping the basic features. | |
+ | |
+if AUFS_FS | |
+choice | |
+ prompt "Maximum number of branches" | |
+ default AUFS_BRANCH_MAX_127 | |
+ help | |
+ Specifies the maximum number of branches (or member directories) | |
+ in a single aufs. The larger value consumes more system | |
+ resources and has a minor impact to performance. | |
+config AUFS_BRANCH_MAX_127 | |
+ bool "127" | |
+ help | |
+ Specifies the maximum number of branches (or member directories) | |
+ in a single aufs. The larger value consumes more system | |
+ resources and has a minor impact to performance. | |
+config AUFS_BRANCH_MAX_511 | |
+ bool "511" | |
+ help | |
+ Specifies the maximum number of branches (or member directories) | |
+ in a single aufs. The larger value consumes more system | |
+ resources and has a minor impact to performance. | |
+config AUFS_BRANCH_MAX_1023 | |
+ bool "1023" | |
+ help | |
+ Specifies the maximum number of branches (or member directories) | |
+ in a single aufs. The larger value consumes more system | |
+ resources and has a minor impact to performance. | |
+config AUFS_BRANCH_MAX_32767 | |
+ bool "32767" | |
+ help | |
+ Specifies the maximum number of branches (or member directories) | |
+ in a single aufs. The larger value consumes more system | |
+ resources and has a minor impact to performance. | |
+endchoice | |
+ | |
+config AUFS_SBILIST | |
+ bool | |
+ depends on AUFS_MAGIC_SYSRQ || PROC_FS | |
+ default y | |
+ help | |
+ Automatic configuration for internal use. | |
+ When aufs supports Magic SysRq or /proc, enabled automatically. | |
+ | |
+config AUFS_HNOTIFY | |
+ bool "Detect direct branch access (bypassing aufs)" | |
+ help | |
+ If you want to modify files on branches directly, eg. bypassing aufs, | |
+ and want aufs to detect the changes of them fully, then enable this | |
+ option and use 'udba=notify' mount option. | |
+ Currently there is only one available configuration, "fsnotify". | |
+ It will have a negative impact to the performance. | |
+ See detail in aufs.5. | |
+ | |
+choice | |
+ prompt "method" if AUFS_HNOTIFY | |
+ default AUFS_HFSNOTIFY | |
+config AUFS_HFSNOTIFY | |
+ bool "fsnotify" | |
+ select FSNOTIFY | |
+endchoice | |
+ | |
+config AUFS_EXPORT | |
+ bool "NFS-exportable aufs" | |
+ depends on EXPORTFS | |
+ help | |
+ If you want to export your mounted aufs via NFS, then enable this | |
+ option. There are several requirements for this configuration. | |
+ See detail in aufs.5. | |
+ | |
+config AUFS_INO_T_64 | |
+ bool | |
+ depends on AUFS_EXPORT | |
+ depends on 64BIT && !(ALPHA || S390) | |
+ default y | |
+ help | |
+ Automatic configuration for internal use. | |
+ /* typedef unsigned long/int __kernel_ino_t */ | |
+ /* alpha and s390x are int */ | |
+ | |
+config AUFS_XATTR | |
+ bool "support for XATTR/EA (including Security Labels)" | |
+ help | |
+ If your branch fs supports XATTR/EA and you want to make them | |
+ available in aufs too, then enable this opsion and specify the | |
+ branch attributes for EA. | |
+ See detail in aufs.5. | |
+ | |
+config AUFS_FHSM | |
+ bool "File-based Hierarchical Storage Management" | |
+ help | |
+ Hierarchical Storage Management (or HSM) is a well-known feature | |
+ in the storage world. Aufs provides this feature as file-based. | |
+ with multiple branches. | |
+ These multiple branches are prioritized, ie. the topmost one | |
+ should be the fastest drive and be used heavily. | |
+ | |
+config AUFS_RDU | |
+ bool "Readdir in userspace" | |
+ help | |
+ Aufs has two methods to provide a merged view for a directory, | |
+ by a user-space library and by kernel-space natively. The latter | |
+ is always enabled but sometimes large and slow. | |
+ If you enable this option, install the library in aufs2-util | |
+ package, and set some environment variables for your readdir(3), | |
+ then the work will be handled in user-space which generally | |
+ shows better performance in most cases. | |
+ See detail in aufs.5. | |
+ | |
+config AUFS_SHWH | |
+ bool "Show whiteouts" | |
+ help | |
+ If you want to make the whiteouts in aufs visible, then enable | |
+ this option and specify 'shwh' mount option. Although it may | |
+ sounds like philosophy or something, but in technically it | |
+ simply shows the name of whiteout with keeping its behaviour. | |
+ | |
+config AUFS_BR_RAMFS | |
+ bool "Ramfs (initramfs/rootfs) as an aufs branch" | |
+ help | |
+ If you want to use ramfs as an aufs branch fs, then enable this | |
+ option. Generally tmpfs is recommended. | |
+ Aufs prohibited them to be a branch fs by default, because | |
+ initramfs becomes unusable after switch_root or something | |
+ generally. If you sets initramfs as an aufs branch and boot your | |
+ system by switch_root, you will meet a problem easily since the | |
+ files in initramfs may be inaccessible. | |
+ Unless you are going to use ramfs as an aufs branch fs without | |
+ switch_root or something, leave it N. | |
+ | |
+config AUFS_BR_FUSE | |
+ bool "Fuse fs as an aufs branch" | |
+ depends on FUSE_FS | |
+ select AUFS_POLL | |
+ help | |
+ If you want to use fuse-based userspace filesystem as an aufs | |
+ branch fs, then enable this option. | |
+ It implements the internal poll(2) operation which is | |
+ implemented by fuse only (curretnly). | |
+ | |
+config AUFS_POLL | |
+ bool | |
+ help | |
+ Automatic configuration for internal use. | |
+ | |
+config AUFS_BR_HFSPLUS | |
+ bool "Hfsplus as an aufs branch" | |
+ depends on HFSPLUS_FS | |
+ default y | |
+ help | |
+ If you want to use hfsplus fs as an aufs branch fs, then enable | |
+ this option. This option introduces a small overhead at | |
+ copying-up a file on hfsplus. | |
+ | |
+config AUFS_BDEV_LOOP | |
+ bool | |
+ depends on BLK_DEV_LOOP | |
+ default y | |
+ help | |
+ Automatic configuration for internal use. | |
+ Convert =[ym] into =y. | |
+ | |
+config AUFS_DEBUG | |
+ bool "Debug aufs" | |
+ help | |
+ Enable this to compile aufs internal debug code. | |
+ It will have a negative impact to the performance. | |
+ | |
+config AUFS_MAGIC_SYSRQ | |
+ bool | |
+ depends on AUFS_DEBUG && MAGIC_SYSRQ | |
+ default y | |
+ help | |
+ Automatic configuration for internal use. | |
+ When aufs supports Magic SysRq, enabled automatically. | |
+endif | |
diff --git a/fs/aufs/Makefile b/fs/aufs/Makefile | |
new file mode 100644 | |
index 0000000..c7a501e | |
--- /dev/null | |
+++ b/fs/aufs/Makefile | |
@@ -0,0 +1,44 @@ | |
+ | |
+include ${src}/magic.mk | |
+ifeq (${CONFIG_AUFS_FS},m) | |
+include ${src}/conf.mk | |
+endif | |
+-include ${src}/priv_def.mk | |
+ | |
+# cf. include/linux/kernel.h | |
+# enable pr_debug | |
+ccflags-y += -DDEBUG | |
+# sparse requires the full pathname | |
+ifdef M | |
+ccflags-y += -include ${M}/../../include/uapi/linux/aufs_type.h | |
+else | |
+ccflags-y += -include ${srctree}/include/uapi/linux/aufs_type.h | |
+endif | |
+ | |
+obj-$(CONFIG_AUFS_FS) += aufs.o | |
+aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \ | |
+ wkq.o vfsub.o dcsub.o \ | |
+ cpup.o whout.o wbr_policy.o \ | |
+ dinfo.o dentry.o \ | |
+ dynop.o \ | |
+ finfo.o file.o f_op.o \ | |
+ dir.o vdir.o \ | |
+ iinfo.o inode.o i_op.o i_op_add.o i_op_del.o i_op_ren.o \ | |
+ mvdown.o ioctl.o | |
+ | |
+# all are boolean | |
+aufs-$(CONFIG_PROC_FS) += procfs.o plink.o | |
+aufs-$(CONFIG_SYSFS) += sysfs.o | |
+aufs-$(CONFIG_DEBUG_FS) += dbgaufs.o | |
+aufs-$(CONFIG_AUFS_BDEV_LOOP) += loop.o | |
+aufs-$(CONFIG_AUFS_HNOTIFY) += hnotify.o | |
+aufs-$(CONFIG_AUFS_HFSNOTIFY) += hfsnotify.o | |
+aufs-$(CONFIG_AUFS_EXPORT) += export.o | |
+aufs-$(CONFIG_AUFS_XATTR) += xattr.o | |
+aufs-$(CONFIG_FS_POSIX_ACL) += posix_acl.o | |
+aufs-$(CONFIG_AUFS_FHSM) += fhsm.o | |
+aufs-$(CONFIG_AUFS_POLL) += poll.o | |
+aufs-$(CONFIG_AUFS_RDU) += rdu.o | |
+aufs-$(CONFIG_AUFS_BR_HFSPLUS) += hfsplus.o | |
+aufs-$(CONFIG_AUFS_DEBUG) += debug.o | |
+aufs-$(CONFIG_AUFS_MAGIC_SYSRQ) += sysrq.o | |
diff --git a/fs/aufs/aufs.h b/fs/aufs/aufs.h | |
new file mode 100644 | |
index 0000000..e48d268 | |
--- /dev/null | |
+++ b/fs/aufs/aufs.h | |
@@ -0,0 +1,59 @@ | |
+/* | |
+ * Copyright (C) 2005-2016 Junjiro R. Okajima | |
+ * | |
+ * This program, aufs 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, see <http://www.gnu.org/licenses/>. | |
+ */ | |
+ | |
+/* | |
+ * all header files | |
+ */ | |
+ | |
+#ifndef __AUFS_H__ | |
+#define __AUFS_H__ | |
+ | |
+#ifdef __KERNEL__ | |
+ | |
+#define AuStub(type, name, body, ...) \ | |
+ static inline type name(__VA_ARGS__) { body; } | |
+ | |
+#define AuStubVoid(name, ...) \ | |
+ AuStub(void, name, , __VA_ARGS__) | |
+#define AuStubInt0(name, ...) \ | |
+ AuStub(int, name, return 0, __VA_ARGS__) | |
+ | |
+#include "debug.h" | |
+ | |
+#include "branch.h" | |
+#include "cpup.h" | |
+#include "dcsub.h" | |
+#include "dbgaufs.h" | |
+#include "dentry.h" | |
+#include "dir.h" | |
+#include "dynop.h" | |
+#include "file.h" | |
+#include "fstype.h" | |
+#include "inode.h" | |
+#include "loop.h" | |
+#include "module.h" | |
+#include "opts.h" | |
+#include "rwsem.h" | |
+#include "spl.h" | |
+#include "super.h" | |
+#include "sysaufs.h" | |
+#include "vfsub.h" | |
+#include "whout.h" | |
+#include "wkq.h" | |
+ | |
+#endif /* __KERNEL__ */ | |
+#endif /* __AUFS_H__ */ | |
diff --git a/fs/aufs/branch.c b/fs/aufs/branch.c | |
new file mode 100644 | |
index 0000000..66495d2 | |
--- /dev/null | |
+++ b/fs/aufs/branch.c | |
@@ -0,0 +1,1412 @@ | |
+/* | |
+ * Copyright (C) 2005-2016 Junjiro R. Okajima | |
+ * | |
+ * This program, aufs 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, see <http://www.gnu.org/licenses/>. | |
+ */ | |
+ | |
+/* | |
+ * branch management | |
+ */ | |
+ | |
+#include <linux/compat.h> | |
+#include <linux/statfs.h> | |
+#include "aufs.h" | |
+ | |
+/* | |
+ * free a single branch | |
+ */ | |
+static void au_br_do_free(struct au_branch *br) | |
+{ | |
+ int i; | |
+ struct au_wbr *wbr; | |
+ struct au_dykey **key; | |
+ | |
+ au_hnotify_fin_br(br); | |
+ | |
+ if (br->br_xino.xi_file) | |
+ fput(br->br_xino.xi_file); | |
+ mutex_destroy(&br->br_xino.xi_nondir_mtx); | |
+ | |
+ AuDebugOn(au_br_count(br)); | |
+ au_br_count_fin(br); | |
+ | |
+ wbr = br->br_wbr; | |
+ if (wbr) { | |
+ for (i = 0; i < AuBrWh_Last; i++) | |
+ dput(wbr->wbr_wh[i]); | |
+ AuDebugOn(atomic_read(&wbr->wbr_wh_running)); | |
+ AuRwDestroy(&wbr->wbr_wh_rwsem); | |
+ } | |
+ | |
+ if (br->br_fhsm) { | |
+ au_br_fhsm_fin(br->br_fhsm); | |
+ au_delayed_kfree(br->br_fhsm); | |
+ } | |
+ | |
+ key = br->br_dykey; | |
+ for (i = 0; i < AuBrDynOp; i++, key++) | |
+ if (*key) | |
+ au_dy_put(*key); | |
+ else | |
+ break; | |
+ | |
+ /* recursive lock, s_umount of branch's */ | |
+ lockdep_off(); | |
+ path_put(&br->br_path); | |
+ lockdep_on(); | |
+ if (wbr) | |
+ au_delayed_kfree(wbr); | |
+ au_delayed_kfree(br); | |
+} | |
+ | |
+/* | |
+ * frees all branches | |
+ */ | |
+void au_br_free(struct au_sbinfo *sbinfo) | |
+{ | |
+ aufs_bindex_t bmax; | |
+ struct au_branch **br; | |
+ | |
+ AuRwMustWriteLock(&sbinfo->si_rwsem); | |
+ | |
+ bmax = sbinfo->si_bbot + 1; | |
+ br = sbinfo->si_branch; | |
+ while (bmax--) | |
+ au_br_do_free(*br++); | |
+} | |
+ | |
+/* | |
+ * find the index of a branch which is specified by @br_id. | |
+ */ | |
+int au_br_index(struct super_block *sb, aufs_bindex_t br_id) | |
+{ | |
+ aufs_bindex_t bindex, bbot; | |
+ | |
+ bbot = au_sbbot(sb); | |
+ for (bindex = 0; bindex <= bbot; bindex++) | |
+ if (au_sbr_id(sb, bindex) == br_id) | |
+ return bindex; | |
+ return -1; | |
+} | |
+ | |
+/* ---------------------------------------------------------------------- */ | |
+ | |
+/* | |
+ * add a branch | |
+ */ | |
+ | |
+static int test_overlap(struct super_block *sb, struct dentry *h_adding, | |
+ struct dentry *h_root) | |
+{ | |
+ if (unlikely(h_adding == h_root | |
+ || au_test_loopback_overlap(sb, h_adding))) | |
+ return 1; | |
+ if (h_adding->d_sb != h_root->d_sb) | |
+ return 0; | |
+ return au_test_subdir(h_adding, h_root) | |
+ || au_test_subdir(h_root, h_adding); | |
+} | |
+ | |
+/* | |
+ * returns a newly allocated branch. @new_nbranch is a number of branches | |
+ * after adding a branch. | |
+ */ | |
+static struct au_branch *au_br_alloc(struct super_block *sb, int new_nbranch, | |
+ int perm) | |
+{ | |
+ struct au_branch *add_branch; | |
+ struct dentry *root; | |
+ struct inode *inode; | |
+ int err; | |
+ | |
+ err = -ENOMEM; | |
+ root = sb->s_root; | |
+ add_branch = kzalloc(sizeof(*add_branch), GFP_NOFS); | |
+ if (unlikely(!add_branch)) | |
+ goto out; | |
+ | |
+ err = au_hnotify_init_br(add_branch, perm); | |
+ if (unlikely(err)) | |
+ goto out_br; | |
+ | |
+ if (au_br_writable(perm)) { | |
+ /* may be freed separately at changing the branch permission */ | |
+ add_branch->br_wbr = kzalloc(sizeof(*add_branch->br_wbr), | |
+ GFP_NOFS); | |
+ if (unlikely(!add_branch->br_wbr)) | |
+ goto out_hnotify; | |
+ } | |
+ | |
+ if (au_br_fhsm(perm)) { | |
+ err = au_fhsm_br_alloc(add_branch); | |
+ if (unlikely(err)) | |
+ goto out_wbr; | |
+ } | |
+ | |
+ err = au_sbr_realloc(au_sbi(sb), new_nbranch, /*may_shrink*/0); | |
+ if (!err) | |
+ err = au_di_realloc(au_di(root), new_nbranch, /*may_shrink*/0); | |
+ if (!err) { | |
+ inode = d_inode(root); | |
+ err = au_hinode_realloc(au_ii(inode), new_nbranch, /*may_shrink*/0); | |
+ } | |
+ if (!err) | |
+ return add_branch; /* success */ | |
+ | |
+out_wbr: | |
+ if (add_branch->br_wbr) | |
+ au_delayed_kfree(add_branch->br_wbr); | |
+out_hnotify: | |
+ au_hnotify_fin_br(add_branch); | |
+out_br: | |
+ au_delayed_kfree(add_branch); | |
+out: | |
+ return ERR_PTR(err); | |
+} | |
+ | |
+/* | |
+ * test if the branch permission is legal or not. | |
+ */ | |
+static int test_br(struct inode *inode, int brperm, char *path) | |
+{ | |
+ int err; | |
+ | |
+ err = (au_br_writable(brperm) && IS_RDONLY(inode)); | |
+ if (!err) | |
+ goto out; | |
+ | |
+ err = -EINVAL; | |
+ pr_err("write permission for readonly mount or inode, %s\n", path); | |
+ | |
+out: | |
+ return err; | |
+} | |
+ | |
+/* | |
+ * returns: | |
+ * 0: success, the caller will add it | |
+ * plus: success, it is already unified, the caller should ignore it | |
+ * minus: error | |
+ */ | |
+static int test_add(struct super_block *sb, struct au_opt_add *add, int remount) | |
+{ | |
+ int err; | |
+ aufs_bindex_t bbot, bindex; | |
+ struct dentry *root, *h_dentry; | |
+ struct inode *inode, *h_inode; | |
+ | |
+ root = sb->s_root; | |
+ bbot = au_sbbot(sb); | |
+ if (unlikely(bbot >= 0 | |
+ && au_find_dbindex(root, add->path.dentry) >= 0)) { | |
+ err = 1; | |
+ if (!remount) { | |
+ err = -EINVAL; | |
+ pr_err("%s duplicated\n", add->pathname); | |
+ } | |
+ goto out; | |
+ } | |
+ | |
+ err = -ENOSPC; /* -E2BIG; */ | |
+ if (unlikely(AUFS_BRANCH_MAX <= add->bindex | |
+ || AUFS_BRANCH_MAX - 1 <= bbot)) { | |
+ pr_err("number of branches exceeded %s\n", add->pathname); | |
+ goto out; | |
+ } | |
+ | |
+ err = -EDOM; | |
+ if (unlikely(add->bindex < 0 || bbot + 1 < add->bindex)) { | |
+ pr_err("bad index %d\n", add->bindex); | |
+ goto out; | |
+ } | |
+ | |
+ inode = d_inode(add->path.dentry); | |
+ err = -ENOENT; | |
+ if (unlikely(!inode->i_nlink)) { | |
+ pr_err("no existence %s\n", add->pathname); | |
+ goto out; | |
+ } | |
+ | |
+ err = -EINVAL; | |
+ if (unlikely(inode->i_sb == sb)) { | |
+ pr_err("%s must be outside\n", add->pathname); | |
+ goto out; | |
+ } | |
+ | |
+ if (unlikely(au_test_fs_unsuppoted(inode->i_sb))) { | |
+ pr_err("unsupported filesystem, %s (%s)\n", | |
+ add->pathname, au_sbtype(inode->i_sb)); | |
+ goto out; | |
+ } | |
+ | |
+ if (unlikely(inode->i_sb->s_stack_depth)) { | |
+ pr_err("already stacked, %s (%s)\n", | |
+ add->pathname, au_sbtype(inode->i_sb)); | |
+ goto out; | |
+ } | |
+ | |
+ err = test_br(d_inode(add->path.dentry), add->perm, add->pathname); | |
+ if (unlikely(err)) | |
+ goto out; | |
+ | |
+ if (bbot < 0) | |
+ return 0; /* success */ | |
+ | |
+ err = -EINVAL; | |
+ for (bindex = 0; bindex <= bbot; bindex++) | |
+ if (unlikely(test_overlap(sb, add->path.dentry, | |
+ au_h_dptr(root, bindex)))) { | |
+ pr_err("%s is overlapped\n", add->pathname); | |
+ goto out; | |
+ } | |
+ | |
+ err = 0; | |
+ if (au_opt_test(au_mntflags(sb), WARN_PERM)) { | |
+ h_dentry = au_h_dptr(root, 0); | |
+ h_inode = d_inode(h_dentry); | |
+ if ((h_inode->i_mode & S_IALLUGO) != (inode->i_mode & S_IALLUGO) | |
+ || !uid_eq(h_inode->i_uid, inode->i_uid) | |
+ || !gid_eq(h_inode->i_gid, inode->i_gid)) | |
+ pr_warn("uid/gid/perm %s %u/%u/0%o, %u/%u/0%o\n", | |
+ add->pathname, | |
+ i_uid_read(inode), i_gid_read(inode), | |
+ (inode->i_mode & S_IALLUGO), | |
+ i_uid_read(h_inode), i_gid_read(h_inode), | |
+ (h_inode->i_mode & S_IALLUGO)); | |
+ } | |
+ | |
+out: | |
+ return err; | |
+} | |
+ | |
+/* | |
+ * initialize or clean the whiteouts for an adding branch | |
+ */ | |
+static int au_br_init_wh(struct super_block *sb, struct au_branch *br, | |
+ int new_perm) | |
+{ | |
+ int err, old_perm; | |
+ aufs_bindex_t bindex; | |
+ struct inode *h_inode; | |
+ struct au_wbr *wbr; | |
+ struct au_hinode *hdir; | |
+ struct dentry *h_dentry; | |
+ | |
+ err = vfsub_mnt_want_write(au_br_mnt(br)); | |
+ if (unlikely(err)) | |
+ goto out; | |
+ | |
+ wbr = br->br_wbr; | |
+ old_perm = br->br_perm; | |
+ br->br_perm = new_perm; | |
+ hdir = NULL; | |
+ h_inode = NULL; | |
+ bindex = au_br_index(sb, br->br_id); | |
+ if (0 <= bindex) { | |
+ hdir = au_hi(d_inode(sb->s_root), bindex); | |
+ au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT); | |
+ } else { | |
+ h_dentry = au_br_dentry(br); | |
+ h_inode = d_inode(h_dentry); | |
+ inode_lock_nested(h_inode, AuLsc_I_PARENT); | |
+ } | |
+ if (!wbr) | |
+ err = au_wh_init(br, sb); | |
+ else { | |
+ wbr_wh_write_lock(wbr); | |
+ err = au_wh_init(br, sb); | |
+ wbr_wh_write_unlock(wbr); | |
+ } | |
+ if (hdir) | |
+ au_hn_inode_unlock(hdir); | |
+ else | |
+ inode_unlock(h_inode); | |
+ vfsub_mnt_drop_write(au_br_mnt(br)); | |
+ br->br_perm = old_perm; | |
+ | |
+ if (!err && wbr && !au_br_writable(new_perm)) { | |
+ au_delayed_kfree(wbr); | |
+ br->br_wbr = NULL; | |
+ } | |
+ | |
+out: | |
+ return err; | |
+} | |
+ | |
+static int au_wbr_init(struct au_branch *br, struct super_block *sb, | |
+ int perm) | |
+{ | |
+ int err; | |
+ struct kstatfs kst; | |
+ struct au_wbr *wbr; | |
+ | |
+ wbr = br->br_wbr; | |
+ au_rw_init(&wbr->wbr_wh_rwsem); | |
+ atomic_set(&wbr->wbr_wh_running, 0); | |
+ | |
+ /* | |
+ * a limit for rmdir/rename a dir | |
+ * cf. AUFS_MAX_NAMELEN in include/uapi/linux/aufs_type.h | |
+ */ | |
+ err = vfs_statfs(&br->br_path, &kst); | |
+ if (unlikely(err)) | |
+ goto out; | |
+ err = -EINVAL; | |
+ if (kst.f_namelen >= NAME_MAX) | |
+ err = au_br_init_wh(sb, br, perm); | |
+ else | |
+ pr_err("%pd(%s), unsupported namelen %ld\n", | |
+ au_br_dentry(br), | |
+ au_sbtype(au_br_dentry(br)->d_sb), kst.f_namelen); | |
+ | |
+out: | |
+ return err; | |
+} | |
+ | |
+/* initialize a new branch */ | |
+static int au_br_init(struct au_branch *br, struct super_block *sb, | |
+ struct au_opt_add *add) | |
+{ | |
+ int err; | |
+ struct inode *h_inode; | |
+ | |
+ err = 0; | |
+ mutex_init(&br->br_xino.xi_nondir_mtx); | |
+ br->br_perm = add->perm; | |
+ br->br_path = add->path; /* set first, path_get() later */ | |
+ spin_lock_init(&br->br_dykey_lock); | |
+ au_br_count_init(br); | |
+ atomic_set(&br->br_xino_running, 0); | |
+ br->br_id = au_new_br_id(sb); | |
+ AuDebugOn(br->br_id < 0); | |
+ | |
+ if (au_br_writable(add->perm)) { | |
+ err = au_wbr_init(br, sb, add->perm); | |
+ if (unlikely(err)) | |
+ goto out_err; | |
+ } | |
+ | |
+ if (au_opt_test(au_mntflags(sb), XINO)) { | |
+ h_inode = d_inode(add->path.dentry); | |
+ err = au_xino_br(sb, br, h_inode->i_ino, | |
+ au_sbr(sb, 0)->br_xino.xi_file, /*do_test*/1); | |
+ if (unlikely(err)) { | |
+ AuDebugOn(br->br_xino.xi_file); | |
+ goto out_err; | |
+ } | |
+ } | |
+ | |
+ sysaufs_br_init(br); | |
+ path_get(&br->br_path); | |
+ goto out; /* success */ | |
+ | |
+out_err: | |
+ memset(&br->br_path, 0, sizeof(br->br_path)); | |
+out: | |
+ return err; | |
+} | |
+ | |
+static void au_br_do_add_brp(struct au_sbinfo *sbinfo, aufs_bindex_t bindex, | |
+ struct au_branch *br, aufs_bindex_t bbot, | |
+ aufs_bindex_t amount) | |
+{ | |
+ struct au_branch **brp; | |
+ | |
+ AuRwMustWriteLock(&sbinfo->si_rwsem); | |
+ | |
+ brp = sbinfo->si_branch + bindex; | |
+ memmove(brp + 1, brp, sizeof(*brp) * amount); | |
+ *brp = br; | |
+ sbinfo->si_bbot++; | |
+ if (unlikely(bbot < 0)) | |
+ sbinfo->si_bbot = 0; | |
+} | |
+ | |
+static void au_br_do_add_hdp(struct au_dinfo *dinfo, aufs_bindex_t bindex, | |
+ aufs_bindex_t bbot, aufs_bindex_t amount) | |
+{ | |
+ struct au_hdentry *hdp; | |
+ | |
+ AuRwMustWriteLock(&dinfo->di_rwsem); | |
+ | |
+ hdp = au_hdentry(dinfo, bindex); | |
+ memmove(hdp + 1, hdp, sizeof(*hdp) * amount); | |
+ au_h_dentry_init(hdp); | |
+ dinfo->di_bbot++; | |
+ if (unlikely(bbot < 0)) | |
+ dinfo->di_btop = 0; | |
+} | |
+ | |
+static void au_br_do_add_hip(struct au_iinfo *iinfo, aufs_bindex_t bindex, | |
+ aufs_bindex_t bbot, aufs_bindex_t amount) | |
+{ | |
+ struct au_hinode *hip; | |
+ | |
+ AuRwMustWriteLock(&iinfo->ii_rwsem); | |
+ | |
+ hip = au_hinode(iinfo, bindex); | |
+ memmove(hip + 1, hip, sizeof(*hip) * amount); | |
+ au_hinode_init(hip); | |
+ iinfo->ii_bbot++; | |
+ if (unlikely(bbot < 0)) | |
+ iinfo->ii_btop = 0; | |
+} | |
+ | |
+static void au_br_do_add(struct super_block *sb, struct au_branch *br, | |
+ aufs_bindex_t bindex) | |
+{ | |
+ struct dentry *root, *h_dentry; | |
+ struct inode *root_inode, *h_inode; | |
+ aufs_bindex_t bbot, amount; | |
+ | |
+ root = sb->s_root; | |
+ root_inode = d_inode(root); | |
+ bbot = au_sbbot(sb); | |
+ amount = bbot + 1 - bindex; | |
+ h_dentry = au_br_dentry(br); | |
+ au_sbilist_lock(); | |
+ au_br_do_add_brp(au_sbi(sb), bindex, br, bbot, amount); | |
+ au_br_do_add_hdp(au_di(root), bindex, bbot, amount); | |
+ au_br_do_add_hip(au_ii(root_inode), bindex, bbot, amount); | |
+ au_set_h_dptr(root, bindex, dget(h_dentry)); | |
+ h_inode = d_inode(h_dentry); | |
+ au_set_h_iptr(root_inode, bindex, au_igrab(h_inode), /*flags*/0); | |
+ au_sbilist_unlock(); | |
+} | |
+ | |
+int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount) | |
+{ | |
+ int err; | |
+ aufs_bindex_t bbot, add_bindex; | |
+ struct dentry *root, *h_dentry; | |
+ struct inode *root_inode; | |
+ struct au_branch *add_branch; | |
+ | |
+ root = sb->s_root; | |
+ root_inode = d_inode(root); | |
+ IMustLock(root_inode); | |
+ IiMustWriteLock(root_inode); | |
+ err = test_add(sb, add, remount); | |
+ if (unlikely(err < 0)) | |
+ goto out; | |
+ if (err) { | |
+ err = 0; | |
+ goto out; /* success */ | |
+ } | |
+ | |
+ bbot = au_sbbot(sb); | |
+ add_branch = au_br_alloc(sb, bbot + 2, add->perm); | |
+ err = PTR_ERR(add_branch); | |
+ if (IS_ERR(add_branch)) | |
+ goto out; | |
+ | |
+ err = au_br_init(add_branch, sb, add); | |
+ if (unlikely(err)) { | |
+ au_br_do_free(add_branch); | |
+ goto out; | |
+ } | |
+ | |
+ add_bindex = add->bindex; | |
+ if (!remount) | |
+ au_br_do_add(sb, add_branch, add_bindex); | |
+ else { | |
+ sysaufs_brs_del(sb, add_bindex); | |
+ au_br_do_add(sb, add_branch, add_bindex); | |
+ sysaufs_brs_add(sb, add_bindex); | |
+ } | |
+ | |
+ h_dentry = add->path.dentry; | |
+ if (!add_bindex) { | |
+ au_cpup_attr_all(root_inode, /*force*/1); | |
+ sb->s_maxbytes = h_dentry->d_sb->s_maxbytes; | |
+ } else | |
+ au_add_nlink(root_inode, d_inode(h_dentry)); | |
+ | |
+ /* | |
+ * this test/set prevents aufs from handling unnecesary notify events | |
+ * of xino files, in case of re-adding a writable branch which was | |
+ * once detached from aufs. | |
+ */ | |
+ if (au_xino_brid(sb) < 0 | |
+ && au_br_writable(add_branch->br_perm) | |
+ && !au_test_fs_bad_xino(h_dentry->d_sb) | |
+ && add_branch->br_xino.xi_file | |
+ && add_branch->br_xino.xi_file->f_path.dentry->d_parent == h_dentry) | |
+ au_xino_brid_set(sb, add_branch->br_id); | |
+ | |
+out: | |
+ return err; | |
+} | |
+ | |
+/* ---------------------------------------------------------------------- */ | |
+ | |
+static unsigned long long au_farray_cb(struct super_block *sb, void *a, | |
+ unsigned long long max __maybe_unused, | |
+ void *arg) | |
+{ | |
+ unsigned long long n; | |
+ struct file **p, *f; | |
+ struct au_sphlhead *files; | |
+ struct au_finfo *finfo; | |
+ | |
+ n = 0; | |
+ p = a; | |
+ files = &au_sbi(sb)->si_files; | |
+ spin_lock(&files->spin); | |
+ hlist_for_each_entry(finfo, &files->head, fi_hlist) { | |
+ f = finfo->fi_file; | |
+ if (file_count(f) | |
+ && !special_file(file_inode(f)->i_mode)) { | |
+ get_file(f); | |
+ *p++ = f; | |
+ n++; | |
+ AuDebugOn(n > max); | |
+ } | |
+ } | |
+ spin_unlock(&files->spin); | |
+ | |
+ return n; | |
+} | |
+ | |
+static struct file **au_farray_alloc(struct super_block *sb, | |
+ unsigned long long *max) | |
+{ | |
+ *max = au_nfiles(sb); | |
+ return au_array_alloc(max, au_farray_cb, sb, /*arg*/NULL); | |
+} | |
+ | |
+static void au_farray_free(struct file **a, unsigned long long max) | |
+{ | |
+ unsigned long long ull; | |
+ | |
+ for (ull = 0; ull < max; ull++) | |
+ if (a[ull]) | |
+ fput(a[ull]); | |
+ kvfree(a); | |
+} | |
+ | |
+/* ---------------------------------------------------------------------- */ | |
+ | |
+/* | |
+ * delete a branch | |
+ */ | |
+ | |
+/* to show the line number, do not make it inlined function */ | |
+#define AuVerbose(do_info, fmt, ...) do { \ | |
+ if (do_info) \ | |
+ pr_info(fmt, ##__VA_ARGS__); \ | |
+} while (0) | |
+ | |
+static int au_test_ibusy(struct inode *inode, aufs_bindex_t btop, | |
+ aufs_bindex_t bbot) | |
+{ | |
+ return (inode && !S_ISDIR(inode->i_mode)) || btop == bbot; | |
+} | |
+ | |
+static int au_test_dbusy(struct dentry *dentry, aufs_bindex_t btop, | |
+ aufs_bindex_t bbot) | |
+{ | |
+ return au_test_ibusy(d_inode(dentry), btop, bbot); | |
+} | |
+ | |
+/* | |
+ * test if the branch is deletable or not. | |
+ */ | |
+static int test_dentry_busy(struct dentry *root, aufs_bindex_t bindex, | |
+ unsigned int sigen, const unsigned int verbose) | |
+{ | |
+ int err, i, j, ndentry; | |
+ aufs_bindex_t btop, bbot; | |
+ struct au_dcsub_pages dpages; | |
+ struct au_dpage *dpage; | |
+ struct dentry *d; | |
+ | |
+ err = au_dpages_init(&dpages, GFP_NOFS); | |
+ if (unlikely(err)) | |
+ goto out; | |
+ err = au_dcsub_pages(&dpages, root, NULL, NULL); | |
+ if (unlikely(err)) | |
+ goto out_dpages; | |
+ | |
+ for (i = 0; !err && i < dpages.ndpage; i++) { | |
+ dpage = dpages.dpages + i; | |
+ ndentry = dpage->ndentry; | |
+ for (j = 0; !err && j < ndentry; j++) { | |
+ d = dpage->dentries[j]; | |
+ AuDebugOn(au_dcount(d) <= 0); | |
+ if (!au_digen_test(d, sigen)) { | |
+ di_read_lock_child(d, AuLock_IR); | |
+ if (unlikely(au_dbrange_test(d))) { | |
+ di_read_unlock(d, AuLock_IR); | |
+ continue; | |
+ } | |
+ } else { | |
+ di_write_lock_child(d); | |
+ if (unlikely(au_dbrange_test(d))) { | |
+ di_write_unlock(d); | |
+ continue; | |
+ } | |
+ err = au_reval_dpath(d, sigen); | |
+ if (!err) | |
+ di_downgrade_lock(d, AuLock_IR); | |
+ else { | |
+ di_write_unlock(d); | |
+ break; | |
+ } | |
+ } | |
+ | |
+ /* AuDbgDentry(d); */ | |
+ btop = au_dbtop(d); | |
+ bbot = au_dbbot(d); | |
+ if (btop <= bindex | |
+ && bindex <= bbot | |
+ && au_h_dptr(d, bindex) | |
+ && au_test_dbusy(d, btop, bbot)) { | |
+ err = -EBUSY; | |
+ AuVerbose(verbose, "busy %pd\n", d); | |
+ AuDbgDentry(d); | |
+ } | |
+ di_read_unlock(d, AuLock_IR); | |
+ } | |
+ } | |
+ | |
+out_dpages: | |
+ au_dpages_free(&dpages); | |
+out: | |
+ return err; | |
+} | |
+ | |
+static int test_inode_busy(struct super_block *sb, aufs_bindex_t bindex, | |
+ unsigned int sigen, const unsigned int verbose) | |
+{ | |
+ int err; | |
+ unsigned long long max, ull; | |
+ struct inode *i, **array; | |
+ aufs_bindex_t btop, bbot; | |
+ | |
+ array = au_iarray_alloc(sb, &max); | |
+ err = PTR_ERR(array); | |
+ if (IS_ERR(array)) | |
+ goto out; | |
+ | |
+ err = 0; | |
+ AuDbg("b%d\n", bindex); | |
+ for (ull = 0; !err && ull < max; ull++) { | |
+ i = array[ull]; | |
+ if (unlikely(!i)) | |
+ break; | |
+ if (i->i_ino == AUFS_ROOT_INO) | |
+ continue; | |
+ | |
+ /* AuDbgInode(i); */ | |
+ if (au_iigen(i, NULL) == sigen) | |
+ ii_read_lock_child(i); | |
+ else { | |
+ ii_write_lock_child(i); | |
+ err = au_refresh_hinode_self(i); | |
+ au_iigen_dec(i); | |
+ if (!err) | |
+ ii_downgrade_lock(i); | |
+ else { | |
+ ii_write_unlock(i); | |
+ break; | |
+ } | |
+ } | |
+ | |
+ btop = au_ibtop(i); | |
+ bbot = au_ibbot(i); | |
+ if (btop <= bindex | |
+ && bindex <= bbot | |
+ && au_h_iptr(i, bindex) | |
+ && au_test_ibusy(i, btop, bbot)) { | |
+ err = -EBUSY; | |
+ AuVerbose(verbose, "busy i%lu\n", i->i_ino); | |
+ AuDbgInode(i); | |
+ } | |
+ ii_read_unlock(i); | |
+ } | |
+ au_iarray_free(array, max); | |
+ | |
+out: | |
+ return err; | |
+} | |
+ | |
+static int test_children_busy(struct dentry *root, aufs_bindex_t bindex, | |
+ const unsigned int verbose) | |
+{ | |
+ int err; | |
+ unsigned int sigen; | |
+ | |
+ sigen = au_sigen(root->d_sb); | |
+ DiMustNoWaiters(root); | |
+ IiMustNoWaiters(d_inode(root)); | |
+ di_write_unlock(root); | |
+ err = test_dentry_busy(root, bindex, sigen, verbose); | |
+ if (!err) | |
+ err = test_inode_busy(root->d_sb, bindex, sigen, verbose); | |
+ di_write_lock_child(root); /* aufs_write_lock() calls ..._child() */ | |
+ | |
+ return err; | |
+} | |
+ | |
+static int test_dir_busy(struct file *file, aufs_bindex_t br_id, | |
+ struct file **to_free, int *idx) | |
+{ | |
+ int err; | |
+ unsigned char matched, root; | |
+ aufs_bindex_t bindex, bbot; | |
+ struct au_fidir *fidir; | |
+ struct au_hfile *hfile; | |
+ | |
+ err = 0; | |
+ root = IS_ROOT(file->f_path.dentry); | |
+ if (root) { | |
+ get_file(file); | |
+ to_free[*idx] = file; | |
+ (*idx)++; | |
+ goto out; | |
+ } | |
+ | |
+ matched = 0; | |
+ fidir = au_fi(file)->fi_hdir; | |
+ AuDebugOn(!fidir); | |
+ bbot = au_fbbot_dir(file); | |
+ for (bindex = au_fbtop(file); bindex <= bbot; bindex++) { | |
+ hfile = fidir->fd_hfile + bindex; | |
+ if (!hfile->hf_file) | |
+ continue; | |
+ | |
+ if (hfile->hf_br->br_id == br_id) { | |
+ matched = 1; | |
+ break; | |
+ } | |
+ } | |
+ if (matched) | |
+ err = -EBUSY; | |
+ | |
+out: | |
+ return err; | |
+} | |
+ | |
+static int test_file_busy(struct super_block *sb, aufs_bindex_t br_id, | |
+ struct file **to_free, int opened) | |
+{ | |
+ int err, idx; | |
+ unsigned long long ull, max; | |
+ aufs_bindex_t btop; | |
+ struct file *file, **array; | |
+ struct dentry *root; | |
+ struct au_hfile *hfile; | |
+ | |
+ array = au_farray_alloc(sb, &max); | |
+ err = PTR_ERR(array); | |
+ if (IS_ERR(array)) | |
+ goto out; | |
+ | |
+ err = 0; | |
+ idx = 0; | |
+ root = sb->s_root; | |
+ di_write_unlock(root); | |
+ for (ull = 0; ull < max; ull++) { | |
+ file = array[ull]; | |
+ if (unlikely(!file)) | |
+ break; | |
+ | |
+ /* AuDbg("%pD\n", file); */ | |
+ fi_read_lock(file); | |
+ btop = au_fbtop(file); | |
+ if (!d_is_dir(file->f_path.dentry)) { | |
+ hfile = &au_fi(file)->fi_htop; | |
+ if (hfile->hf_br->br_id == br_id) | |
+ err = -EBUSY; | |
+ } else | |
+ err = test_dir_busy(file, br_id, to_free, &idx); | |
+ fi_read_unlock(file); | |
+ if (unlikely(err)) | |
+ break; | |
+ } | |
+ di_write_lock_child(root); | |
+ au_farray_free(array, max); | |
+ AuDebugOn(idx > opened); | |
+ | |
+out: | |
+ return err; | |
+} | |
+ | |
+static void br_del_file(struct file **to_free, unsigned long long opened, | |
+ aufs_bindex_t br_id) | |
+{ | |
+ unsigned long long ull; | |
+ aufs_bindex_t bindex, btop, bbot, bfound; | |
+ struct file *file; | |
+ struct au_fidir *fidir; | |
+ struct au_hfile *hfile; | |
+ | |
+ for (ull = 0; ull < opened; ull++) { | |
+ file = to_free[ull]; | |
+ if (unlikely(!file)) | |
+ break; | |
+ | |
+ /* AuDbg("%pD\n", file); */ | |
+ AuDebugOn(!d_is_dir(file->f_path.dentry)); | |
+ bfound = -1; | |
+ fidir = au_fi(file)->fi_hdir; | |
+ AuDebugOn(!fidir); | |
+ fi_write_lock(file); | |
+ btop = au_fbtop(file); | |
+ bbot = au_fbbot_dir(file); | |
+ for (bindex = btop; bindex <= bbot; bindex++) { | |
+ hfile = fidir->fd_hfile + bindex; | |
+ if (!hfile->hf_file) | |
+ continue; | |
+ | |
+ if (hfile->hf_br->br_id == br_id) { | |
+ bfound = bindex; | |
+ break; | |
+ } | |
+ } | |
+ AuDebugOn(bfound < 0); | |
+ au_set_h_fptr(file, bfound, NULL); | |
+ if (bfound == btop) { | |
+ for (btop++; btop <= bbot; btop++) | |
+ if (au_hf_dir(file, btop)) { | |
+ au_set_fbtop(file, btop); | |
+ break; | |
+ } | |
+ } | |
+ fi_write_unlock(file); | |
+ } | |
+} | |
+ | |
+static void au_br_do_del_brp(struct au_sbinfo *sbinfo, | |
+ const aufs_bindex_t bindex, | |
+ const aufs_bindex_t bbot) | |
+{ | |
+ struct au_branch **brp, **p; | |
+ | |
+ AuRwMustWriteLock(&sbinfo->si_rwsem); | |
+ | |
+ brp = sbinfo->si_branch + bindex; | |
+ if (bindex < bbot) | |
+ memmove(brp, brp + 1, sizeof(*brp) * (bbot - bindex)); | |
+ sbinfo->si_branch[0 + bbot] = NULL; | |
+ sbinfo->si_bbot--; | |
+ | |
+ p = au_krealloc(sbinfo->si_branch, sizeof(*p) * bbot, AuGFP_SBILIST, | |
+ /*may_shrink*/1); | |
+ if (p) | |
+ sbinfo->si_branch = p; | |
+ /* harmless error */ | |
+} | |
+ | |
+static void au_br_do_del_hdp(struct au_dinfo *dinfo, const aufs_bindex_t bindex, | |
+ const aufs_bindex_t bbot) | |
+{ | |
+ struct au_hdentry *hdp, *p; | |
+ | |
+ AuRwMustWriteLock(&dinfo->di_rwsem); | |
+ | |
+ hdp = au_hdentry(dinfo, bindex); | |
+ if (bindex < bbot) | |
+ memmove(hdp, hdp + 1, sizeof(*hdp) * (bbot - bindex)); | |
+ /* au_h_dentry_init(au_hdentry(dinfo, bbot); */ | |
+ dinfo->di_bbot--; | |
+ | |
+ p = au_krealloc(dinfo->di_hdentry, sizeof(*p) * bbot, AuGFP_SBILIST, | |
+ /*may_shrink*/1); | |
+ if (p) | |
+ dinfo->di_hdentry = p; | |
+ /* harmless error */ | |
+} | |
+ | |
+static void au_br_do_del_hip(struct au_iinfo *iinfo, const aufs_bindex_t bindex, | |
+ const aufs_bindex_t bbot) | |
+{ | |
+ struct au_hinode *hip, *p; | |
+ | |
+ AuRwMustWriteLock(&iinfo->ii_rwsem); | |
+ | |
+ hip = au_hinode(iinfo, bindex); | |
+ if (bindex < bbot) | |
+ memmove(hip, hip + 1, sizeof(*hip) * (bbot - bindex)); | |
+ /* au_hinode_init(au_hinode(iinfo, bbot)); */ | |
+ iinfo->ii_bbot--; | |
+ | |
+ p = au_krealloc(iinfo->ii_hinode, sizeof(*p) * bbot, AuGFP_SBILIST, | |
+ /*may_shrink*/1); | |
+ if (p) | |
+ iinfo->ii_hinode = p; | |
+ /* harmless error */ | |
+} | |
+ | |
+static void au_br_do_del(struct super_block *sb, aufs_bindex_t bindex, | |
+ struct au_branch *br) | |
+{ | |
+ aufs_bindex_t bbot; | |
+ struct au_sbinfo *sbinfo; | |
+ struct dentry *root, *h_root; | |
+ struct inode *inode, *h_inode; | |
+ struct au_hinode *hinode; | |
+ | |
+ SiMustWriteLock(sb); | |
+ | |
+ root = sb->s_root; | |
+ inode = d_inode(root); | |
+ sbinfo = a |
View raw
(Sorry about that, but we can’t show files that are this big right now.)
View raw
(Sorry about that, but we can’t show files that are this big right now.)
View raw
(Sorry about that, but we can’t show files that are this big right now.)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment