[PATCH 3/4] TOMOYO: Add symlink's target condition support.

From: Tetsuo Handa
Date: Thu Jun 10 2010 - 08:10:33 EST


Symlinks are frequently abused by attackers. TOMOYO checks permissions using
dereferenced pathname. Thus, symlinks don't affect on whether the dereferenced
file is accessible or not. But symlinks do affect on how the dereferenced file
is used if the file is accessible. For example, symlink from
/var/www/html/index.txt to /var/www/html/.htpasswd will make Apache disclose
the content of /var/www/html/.htpasswd if FollowSymLinks is specified in the
configuration files.

# echo 'demo:$apr1$Dim11...$yaZFoEyPOeO/gJe2lA0wz/' > /var/www/html/.htpasswd
# ln -s .htpasswd /var/www/html/index.txt
# curl http://127.0.0.1/index.txt
demo:$apr1$Dim11...$yaZFoEyPOeO/gJe2lA0wz/

This patch allows users to check symlink's target passed to symlink()
operation. For example,

allow_symlink /var/www/html/index.html if symlink.target="\*.html"

will allow creation of symlink /var/www/html/index.html only if the symlink's
target matches pattern "\*.html".

Signed-off-by: Tetsuo Handa <penguin-kernel@xxxxxxxxxxxxxxxxxxx>
---
security/tomoyo/common.c | 49 ++++++++++++++++++++++++++++++++++++++++----
security/tomoyo/common.h | 10 ++++++++
security/tomoyo/condition.c | 17 +++++++++++++--
security/tomoyo/file.c | 15 ++++++++++++-
security/tomoyo/tomoyo.c | 15 +++++++------
5 files changed, 91 insertions(+), 15 deletions(-)

--- security-testing-2.6.orig/security/tomoyo/common.c
+++ security-testing-2.6/security/tomoyo/common.c
@@ -20,6 +20,7 @@ static struct tomoyo_profile tomoyo_defa
.preference.learning_verbose = false,
.preference.learning_exec_realpath = true,
.preference.learning_exec_argv0 = true,
+ .preference.learning_symlink_target = true,
.preference.permissive_verbose = true
};

@@ -362,6 +363,10 @@ static int tomoyo_write_profile(struct t
profile->preference.learning_exec_argv0 = true;
else if (strstr(cp, "exec.argv0=no"))
profile->preference.learning_exec_argv0 = false;
+ if (strstr(cp, "symlink.target=yes"))
+ profile->preference.learning_symlink_target = true;
+ else if (strstr(cp, "symlink.target=no"))
+ profile->preference.learning_symlink_target = false;
return 0;
}
if (profile == &tomoyo_default_profile)
@@ -422,14 +427,17 @@ static int tomoyo_read_profile(struct to
goto body;
tomoyo_io_printf(head, "PROFILE_VERSION=%s\n", "20090903");
tomoyo_io_printf(head, "PREFERENCE::learning={ verbose=%s "
- "max_entry=%u exec.realpath=%s exec.argv0=%s }\n",
+ "max_entry=%u exec.realpath=%s exec.argv0=%s "
+ "symlink.target=%s }\n",
tomoyo_yesno(tomoyo_default_profile.preference.
learning_verbose),
tomoyo_default_profile.preference.learning_max_entry,
tomoyo_yesno(tomoyo_default_profile.preference.
learning_exec_realpath),
tomoyo_yesno(tomoyo_default_profile.preference.
- learning_exec_argv0));
+ learning_exec_argv0),
+ tomoyo_yesno(tomoyo_default_profile.preference.
+ learning_symlink_target));
tomoyo_io_printf(head, "PREFERENCE::permissive={ verbose=%s }\n",
tomoyo_yesno(tomoyo_default_profile.preference.
permissive_verbose));
@@ -474,14 +482,16 @@ static int tomoyo_read_profile(struct to
!tomoyo_io_printf(head, "%u-PREFERENCE::learning={ "
"verbose=%s max_entry=%u "
"exec.realpath=%s exec.argv0=%s "
- "}\n", index,
+ "symlink.target=%s }\n", index,
tomoyo_yesno(profile->preference.
learning_verbose),
profile->preference.learning_max_entry,
tomoyo_yesno(profile->preference.
learning_exec_realpath),
tomoyo_yesno(profile->preference.
- learning_exec_argv0)))
+ learning_exec_argv0),
+ tomoyo_yesno(profile->preference.
+ learning_symlink_target)))
goto out;
if (profile->permissive != &tomoyo_default_profile.preference
&& !tomoyo_io_printf(head, "%u-PREFERENCE::permissive={ "
@@ -1706,6 +1716,35 @@ static struct tomoyo_condition *tomoyo_g
return cond;
}

+/**
+ * tomoyo_get_symlink_condition - Get condition part for symlink requests.
+ *
+ * @r: Pointer to "struct tomoyo_request_info".
+ *
+ * Returns pointer to "struct tomoyo_condition" on success, NULL otherwise.
+ */
+static struct tomoyo_condition *tomoyo_get_symlink_condition
+(const struct tomoyo_request_info *r)
+{
+ struct tomoyo_condition *cond;
+ char *buf;
+ int len;
+ const char *symlink;
+ const struct tomoyo_preference *pref = tomoyo_profile(r->profile)->
+ learning;
+ if (!pref->learning_symlink_target)
+ return NULL;
+ symlink = r->obj->symlink_target->name;
+ len = strlen(symlink) + 32;
+ buf = kmalloc(len, GFP_NOFS);
+ if (!buf)
+ return NULL;
+ snprintf(buf, len - 1, "if symlink.target=\"%s\"", symlink);
+ cond = tomoyo_get_condition(buf);
+ kfree(buf);
+ return cond;
+}
+
/* Wait queue for tomoyo_query_list. */
static DECLARE_WAIT_QUEUE_HEAD(tomoyo_query_wait);

@@ -1770,6 +1809,8 @@ int tomoyo_supervisor(struct tomoyo_requ
tomoyo_normalize_line(buffer);
if (r->ee)
cond = tomoyo_get_execute_condition(r->ee);
+ else if (r->obj && r->obj->symlink_target)
+ cond = tomoyo_get_symlink_condition(r);
else
cond = NULL;
tomoyo_write_domain_policy2(buffer, r->domain, cond, false);
--- security-testing-2.6.orig/security/tomoyo/common.h
+++ security-testing-2.6/security/tomoyo/common.h
@@ -196,6 +196,7 @@ enum tomoyo_conditions_index {
TOMOYO_EXEC_ARGC, /* "struct linux_binprm *"->argc */
TOMOYO_EXEC_ENVC, /* "struct linux_binprm *"->envc */
TOMOYO_EXEC_REALPATH,
+ TOMOYO_SYMLINK_TARGET,
TOMOYO_MAX_CONDITION_KEYWORD,
TOMOYO_NUMBER_UNION,
TOMOYO_NAME_UNION,
@@ -213,6 +214,11 @@ struct tomoyo_page_dump {
char *data; /* Contents of "page". Size is PAGE_SIZE. */
};

+/* Structure for attribute checks in addition to pathname checks. */
+struct tomoyo_obj_info {
+ struct tomoyo_path_info *symlink_target;
+};
+
struct tomoyo_condition_element {
/*
* Left hand operand. A "struct tomoyo_argv" for TOMOYO_ARGV_ENTRY, a
@@ -264,6 +270,7 @@ struct tomoyo_execve_entry;
struct tomoyo_request_info {
struct tomoyo_domain_info *domain;
struct tomoyo_execve_entry *ee;
+ struct tomoyo_obj_info *obj;
u8 retry;
u8 profile;
u8 mode; /* One of tomoyo_mode_index . */
@@ -776,6 +783,7 @@ struct tomoyo_preference {
bool learning_verbose;
bool learning_exec_realpath;
bool learning_exec_argv0;
+ bool learning_symlink_target;
bool permissive_verbose;
};

@@ -1004,7 +1012,7 @@ int tomoyo_path_number_perm(const u8 ope
unsigned long number);
int tomoyo_path_number3_perm(const u8 operation, struct path *path,
const unsigned int mode, unsigned int dev);
-int tomoyo_path_perm(const u8 operation, struct path *path);
+int tomoyo_path_perm(const u8 operation, struct path *path, const char *target);
int tomoyo_path2_perm(const u8 operation, struct path *path1,
struct path *path2);
int tomoyo_find_next_domain(struct linux_binprm *bprm);
--- security-testing-2.6.orig/security/tomoyo/condition.c
+++ security-testing-2.6/security/tomoyo/condition.c
@@ -382,6 +382,7 @@ const char *tomoyo_condition_keyword[TOM
[TOMOYO_EXEC_ARGC] = "exec.argc",
[TOMOYO_EXEC_ENVC] = "exec.envc",
[TOMOYO_EXEC_REALPATH] = "exec.realpath",
+ [TOMOYO_SYMLINK_TARGET] = "symlink.target",
};

/* #define DEBUG_CONDITION */
@@ -473,7 +474,8 @@ struct tomoyo_condition *tomoyo_get_cond
condc++;
dprintk(KERN_WARNING "%u: <%s> left=%u\n", __LINE__,
word, left);
- if (left == TOMOYO_EXEC_REALPATH) {
+ if (left == TOMOYO_EXEC_REALPATH ||
+ left == TOMOYO_SYMLINK_TARGET) {
names_count++;
continue;
}
@@ -588,7 +590,8 @@ struct tomoyo_condition *tomoyo_get_cond
condc--;
dprintk(KERN_WARNING "%u: <%s> left=%u\n", __LINE__,
word, left);
- if (left == TOMOYO_EXEC_REALPATH) {
+ if (left == TOMOYO_EXEC_REALPATH ||
+ left == TOMOYO_SYMLINK_TARGET) {
right = TOMOYO_NAME_UNION;
if (!tomoyo_parse_name_union_quoted(word, names_p++))
goto out;
@@ -689,6 +692,7 @@ bool tomoyo_condition(struct tomoyo_requ
const struct tomoyo_name_union *names_p;
const struct tomoyo_argv_entry *argv;
const struct tomoyo_envp_entry *envp;
+ struct tomoyo_obj_info *obj;
u16 condc;
u16 argc;
u16 envc;
@@ -699,6 +703,7 @@ bool tomoyo_condition(struct tomoyo_requ
condc = cond->condc;
argc = cond->argc;
envc = cond->envc;
+ obj = r->obj;
if (r->ee)
bprm = r->ee->bprm;
if (!bprm && (argc || envc))
@@ -722,8 +727,16 @@ bool tomoyo_condition(struct tomoyo_requ
if (right == TOMOYO_NAME_UNION) {
const struct tomoyo_name_union *ptr = names_p++;
switch (left) {
+ struct tomoyo_path_info *symlink;
struct tomoyo_execve_entry *ee;
struct file *file;
+ case TOMOYO_SYMLINK_TARGET:
+ symlink = obj ? obj->symlink_target : NULL;
+ if (!symlink ||
+ tomoyo_compare_name_union(symlink, ptr)
+ != match)
+ goto out;
+ break;
case TOMOYO_EXEC_REALPATH:
ee = r->ee;
file = ee ? ee->bprm->file : NULL;
--- security-testing-2.6.orig/security/tomoyo/file.c
+++ security-testing-2.6/security/tomoyo/file.c
@@ -1407,14 +1407,17 @@ int tomoyo_check_open_permission(struct
*
* @operation: Type of operation.
* @path: Pointer to "struct path".
+ * @target: Symlink's target if @operation is TOMOYO_TYPE_SYMLINK.
*
* Returns 0 on success, negative value otherwise.
*/
-int tomoyo_path_perm(const u8 operation, struct path *path)
+int tomoyo_path_perm(const u8 operation, struct path *path, const char *target)
{
int error = -ENOMEM;
struct tomoyo_path_info buf;
struct tomoyo_request_info r;
+ struct tomoyo_obj_info obj;
+ struct tomoyo_path_info symlink_target;
int idx;

if (!path->mnt)
@@ -1438,8 +1441,18 @@ int tomoyo_path_perm(const u8 operation,
case TOMOYO_TYPE_UMOUNT:
tomoyo_add_slash(&buf);
break;
+ case TOMOYO_TYPE_SYMLINK:
+ symlink_target.name = tomoyo_encode(target);
+ if (!symlink_target.name)
+ goto out;
+ tomoyo_fill_path_info(&symlink_target);
+ obj.symlink_target = &symlink_target;
+ r.obj = &obj;
+ break;
}
error = tomoyo_path_permission(&r, operation, &buf);
+ if (operation == TOMOYO_TYPE_SYMLINK)
+ kfree(symlink_target.name);
out:
kfree(buf.name);
tomoyo_read_unlock(idx);
--- security-testing-2.6.orig/security/tomoyo/tomoyo.c
+++ security-testing-2.6/security/tomoyo/tomoyo.c
@@ -95,13 +95,13 @@ static int tomoyo_bprm_check_security(st

static int tomoyo_path_truncate(struct path *path)
{
- return tomoyo_path_perm(TOMOYO_TYPE_TRUNCATE, path);
+ return tomoyo_path_perm(TOMOYO_TYPE_TRUNCATE, path, NULL);
}

static int tomoyo_path_unlink(struct path *parent, struct dentry *dentry)
{
struct path path = { parent->mnt, dentry };
- return tomoyo_path_perm(TOMOYO_TYPE_UNLINK, &path);
+ return tomoyo_path_perm(TOMOYO_TYPE_UNLINK, &path, NULL);
}

static int tomoyo_path_mkdir(struct path *parent, struct dentry *dentry,
@@ -115,14 +115,14 @@ static int tomoyo_path_mkdir(struct path
static int tomoyo_path_rmdir(struct path *parent, struct dentry *dentry)
{
struct path path = { parent->mnt, dentry };
- return tomoyo_path_perm(TOMOYO_TYPE_RMDIR, &path);
+ return tomoyo_path_perm(TOMOYO_TYPE_RMDIR, &path, NULL);
}

static int tomoyo_path_symlink(struct path *parent, struct dentry *dentry,
const char *old_name)
{
struct path path = { parent->mnt, dentry };
- return tomoyo_path_perm(TOMOYO_TYPE_SYMLINK, &path);
+ return tomoyo_path_perm(TOMOYO_TYPE_SYMLINK, &path, old_name);
}

static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry,
@@ -177,7 +177,8 @@ static int tomoyo_file_fcntl(struct file
unsigned long arg)
{
if (cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND))
- return tomoyo_path_perm(TOMOYO_TYPE_REWRITE, &file->f_path);
+ return tomoyo_path_perm(TOMOYO_TYPE_REWRITE, &file->f_path,
+ NULL);
return 0;
}

@@ -216,7 +217,7 @@ static int tomoyo_path_chown(struct path

static int tomoyo_path_chroot(struct path *path)
{
- return tomoyo_path_perm(TOMOYO_TYPE_CHROOT, path);
+ return tomoyo_path_perm(TOMOYO_TYPE_CHROOT, path, NULL);
}

static int tomoyo_sb_mount(char *dev_name, struct path *path,
@@ -228,7 +229,7 @@ static int tomoyo_sb_mount(char *dev_nam
static int tomoyo_sb_umount(struct vfsmount *mnt, int flags)
{
struct path path = { mnt, mnt->mnt_root };
- return tomoyo_path_perm(TOMOYO_TYPE_UMOUNT, &path);
+ return tomoyo_path_perm(TOMOYO_TYPE_UMOUNT, &path, NULL);
}

static int tomoyo_sb_pivotroot(struct path *old_path, struct path *new_path)
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/