1 |
/* |
/* |
2 |
* fs/realpath.c |
* security/ccsecurity/realpath.c |
|
* |
|
|
* Get the canonicalized absolute pathnames. The basis for SAKURA and TOMOYO. |
|
3 |
* |
* |
4 |
* Copyright (C) 2005-2009 NTT DATA CORPORATION |
* Copyright (C) 2005-2009 NTT DATA CORPORATION |
5 |
* |
* |
6 |
* Version: 1.7.0-pre 2009/05/28 |
* Version: 1.7.0 2009/09/03 |
7 |
* |
* |
8 |
* This file is applicable to both 2.4.30 and 2.6.11 and later. |
* This file is applicable to both 2.4.30 and 2.6.11 and later. |
9 |
* See README.ccs for ChangeLog. |
* See README.ccs for ChangeLog. |
10 |
* |
* |
11 |
*/ |
*/ |
12 |
|
|
13 |
#include <linux/string.h> |
#include <linux/string.h> |
14 |
#include <linux/mm.h> |
#include <linux/mm.h> |
15 |
#include <linux/utime.h> |
#include <linux/utime.h> |
27 |
#else |
#else |
28 |
static const int ccs_lookup_flags = LOOKUP_FOLLOW | LOOKUP_POSITIVE; |
static const int ccs_lookup_flags = LOOKUP_FOLLOW | LOOKUP_POSITIVE; |
29 |
#endif |
#endif |
|
#include <linux/proc_fs.h> |
|
|
#include <linux/ccs_common.h> |
|
|
#include <linux/realpath.h> |
|
30 |
#include <net/sock.h> |
#include <net/sock.h> |
31 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) |
32 |
#include <linux/kthread.h> |
#include <linux/kthread.h> |
33 |
#endif |
#endif |
34 |
|
#include <linux/proc_fs.h> |
35 |
|
#include "internal.h" |
36 |
|
|
37 |
|
static int ccs_kern_path(const char *pathname, int flags, struct path *path) |
38 |
|
{ |
39 |
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28) |
40 |
|
if (!pathname || kern_path(pathname, flags, path)) |
41 |
|
return -ENOENT; |
42 |
|
#else |
43 |
|
struct nameidata nd; |
44 |
|
if (!pathname || path_lookup(pathname, flags, &nd)) |
45 |
|
return -ENOENT; |
46 |
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) |
47 |
|
*path = nd.path; |
48 |
|
#else |
49 |
|
path->dentry = nd.dentry; |
50 |
|
path->mnt = nd.mnt; |
51 |
|
#endif |
52 |
|
#endif |
53 |
|
return 0; |
54 |
|
} |
55 |
|
|
56 |
/** |
/** |
57 |
* ccs_get_absolute_path - Get the path of a dentry but ignores chroot'ed root. |
* ccs_get_absolute_path - Get the path of a dentry but ignores chroot'ed root. |
58 |
* |
* |
59 |
* @dentry: Pointer to "struct dentry". |
* @path: Pointer to "struct path". |
|
* @vfsmnt: Pointer to "struct vfsmount". |
|
60 |
* @buffer: Pointer to buffer to return value in. |
* @buffer: Pointer to buffer to return value in. |
61 |
* @buflen: Sizeof @buffer. |
* @buflen: Sizeof @buffer. |
62 |
* |
* |
63 |
* Returns 0 on success, -ENOMEM otherwise. |
* Returns the buffer on success, an error code otherwise. |
64 |
* |
* |
65 |
* Caller holds the dcache_lock and vfsmount_lock. |
* Caller holds the dcache_lock and vfsmount_lock. |
66 |
* Based on __d_path() in fs/dcache.c |
* Based on __d_path() in fs/dcache.c |
67 |
* |
* |
68 |
* If dentry is a directory, trailing '/' is appended. |
* If dentry is a directory, trailing '/' is appended. |
69 |
* Characters out of 0x20 < c < 0x7F range are converted to |
* /proc/pid is represented as /proc/self if pid is current. |
|
* \ooo style octal string. |
|
|
* Character \ is converted to \\ string. |
|
70 |
*/ |
*/ |
71 |
static int ccs_get_absolute_path(struct dentry *dentry, struct vfsmount *vfsmnt, |
static char *ccs_get_absolute_path(struct path *path, char * const buffer, |
72 |
char *buffer, int buflen) |
const int buflen) |
73 |
{ |
{ |
74 |
/***** CRITICAL SECTION START *****/ |
char *pos = buffer + buflen - 1; |
75 |
char *start = buffer; |
struct dentry *dentry = path->dentry; |
76 |
char *end = buffer + buflen; |
struct vfsmount *vfsmnt = path->mnt; |
77 |
bool is_dir = (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)); |
bool is_dir = (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)); |
78 |
|
const char *name; |
79 |
|
int len; |
80 |
|
|
81 |
if (buflen < 256) |
if (buflen < 256) |
82 |
goto out; |
goto out; |
83 |
|
|
84 |
*--end = '\0'; |
*pos = '\0'; |
|
buflen--; |
|
|
|
|
85 |
for (;;) { |
for (;;) { |
86 |
struct dentry *parent; |
struct dentry *parent; |
|
|
|
87 |
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { |
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { |
|
/* Global root? */ |
|
88 |
if (vfsmnt->mnt_parent == vfsmnt) |
if (vfsmnt->mnt_parent == vfsmnt) |
89 |
break; |
break; |
90 |
dentry = vfsmnt->mnt_mountpoint; |
dentry = vfsmnt->mnt_mountpoint; |
93 |
} |
} |
94 |
if (is_dir) { |
if (is_dir) { |
95 |
is_dir = false; |
is_dir = false; |
96 |
*--end = '/'; |
*--pos = '/'; |
|
buflen--; |
|
97 |
} |
} |
98 |
parent = dentry->d_parent; |
parent = dentry->d_parent; |
99 |
{ |
name = dentry->d_name.name; |
100 |
const char *sp = dentry->d_name.name; |
len = dentry->d_name.len; |
101 |
const char *cp = sp + dentry->d_name.len - 1; |
if (IS_ROOT(parent) && *name > '0' && *name <= '9' && |
102 |
unsigned char c; |
parent->d_sb && |
103 |
|
parent->d_sb->s_magic == PROC_SUPER_MAGIC) { |
104 |
/* |
char *ep; |
105 |
* Exception: Use /proc/self/ rather than |
const pid_t pid = (pid_t) simple_strtoul(name, &ep, |
106 |
* /proc/\$/ for current process. |
10); |
|
*/ |
|
|
if (IS_ROOT(parent) && *sp > '0' && *sp <= '9' && |
|
|
parent->d_sb && |
|
|
parent->d_sb->s_magic == PROC_SUPER_MAGIC) { |
|
|
char *ep; |
|
|
const pid_t pid |
|
|
= (pid_t) simple_strtoul(sp, &ep, 10); |
|
107 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) |
108 |
const pid_t tgid |
const pid_t tgid = task_tgid_nr_ns(current, |
109 |
= task_tgid_nr_ns(current, |
dentry->d_sb-> |
110 |
dentry->d_sb-> |
s_fs_info); |
111 |
s_fs_info); |
if (!*ep && pid == tgid && tgid) { |
112 |
if (!*ep && pid == tgid && tgid) { |
name = "self"; |
113 |
sp = "self"; |
len = 4; |
|
cp = sp + 3; |
|
|
} |
|
|
#else |
|
|
if (!*ep && pid == sys_getpid()) { |
|
|
sp = "self"; |
|
|
cp = sp + 3; |
|
|
} |
|
|
#endif |
|
114 |
} |
} |
115 |
|
#else |
116 |
while (sp <= cp) { |
if (!*ep && pid == sys_getpid()) { |
117 |
c = *(unsigned char *) cp; |
name = "self"; |
118 |
if (c == '\\') { |
len = 4; |
|
buflen -= 2; |
|
|
if (buflen < 0) |
|
|
goto out; |
|
|
*--end = '\\'; |
|
|
*--end = '\\'; |
|
|
} else if (c > ' ' && c < 127) { |
|
|
if (--buflen < 0) |
|
|
goto out; |
|
|
*--end = (char) c; |
|
|
} else { |
|
|
buflen -= 4; |
|
|
if (buflen < 0) |
|
|
goto out; |
|
|
*--end = (c & 7) + '0'; |
|
|
*--end = ((c >> 3) & 7) + '0'; |
|
|
*--end = (c >> 6) + '0'; |
|
|
*--end = '\\'; |
|
|
} |
|
|
cp--; |
|
119 |
} |
} |
120 |
if (--buflen < 0) |
#endif |
|
goto out; |
|
|
*--end = '/'; |
|
121 |
} |
} |
122 |
|
pos -= len; |
123 |
|
if (pos <= buffer) |
124 |
|
goto out; |
125 |
|
memmove(pos, name, len); |
126 |
|
*--pos = '/'; |
127 |
dentry = parent; |
dentry = parent; |
128 |
} |
} |
129 |
if (*end == '/') { |
if (*pos == '/') |
130 |
buflen++; |
pos++; |
131 |
end++; |
len = dentry->d_name.len; |
132 |
} |
pos -= len; |
133 |
{ |
if (pos < buffer) |
134 |
const char *sp = dentry->d_name.name; |
goto out; |
135 |
const char *cp = sp + dentry->d_name.len - 1; |
memmove(pos, dentry->d_name.name, len); |
136 |
unsigned char c; |
return pos; |
|
while (sp <= cp) { |
|
|
c = *(unsigned char *) cp; |
|
|
if (c == '\\') { |
|
|
buflen -= 2; |
|
|
if (buflen < 0) |
|
|
goto out; |
|
|
*--end = '\\'; |
|
|
*--end = '\\'; |
|
|
} else if (c > ' ' && c < 127) { |
|
|
if (--buflen < 0) |
|
|
goto out; |
|
|
*--end = (char) c; |
|
|
} else { |
|
|
buflen -= 4; |
|
|
if (buflen < 0) |
|
|
goto out; |
|
|
*--end = (c & 7) + '0'; |
|
|
*--end = ((c >> 3) & 7) + '0'; |
|
|
*--end = (c >> 6) + '0'; |
|
|
*--end = '\\'; |
|
|
} |
|
|
cp--; |
|
|
} |
|
|
} |
|
|
/* Move the pathname to the top of the buffer. */ |
|
|
memmove(start, end, strlen(end) + 1); |
|
|
return 0; |
|
137 |
out: |
out: |
138 |
return -ENOMEM; |
return ERR_PTR(-ENOMEM); |
|
/***** CRITICAL SECTION END *****/ |
|
139 |
} |
} |
140 |
|
|
141 |
#define SOCKFS_MAGIC 0x534F434B |
#define SOCKFS_MAGIC 0x534F434B |
142 |
|
|
143 |
/** |
/** |
144 |
* ccs_realpath_from_dentry2 - Returns realpath(3) of the given dentry but ignores chroot'ed root. |
* ccs_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root. |
145 |
* |
* |
146 |
* @dentry: Pointer to "struct dentry". |
* @path: Pointer to "struct path". |
|
* @mnt: Pointer to "struct vfsmount". |
|
|
* @newname: Pointer to buffer to return value in. |
|
|
* @newname_len: Size of @newname. |
|
147 |
* |
* |
148 |
* Returns 0 on success, negative value otherwise. |
* Returns the realpath of the given @path on success, NULL otherwise. |
149 |
|
* |
150 |
|
* This function uses kzalloc(), so caller must kfree() if this function |
151 |
|
* didn't return NULL. |
152 |
*/ |
*/ |
153 |
static int ccs_realpath_from_dentry2(struct dentry *dentry, |
char *ccs_realpath_from_path(struct path *path) |
|
struct vfsmount *mnt, |
|
|
char *newname, int newname_len) |
|
154 |
{ |
{ |
155 |
int error = -EINVAL; |
char *buf = NULL; |
156 |
struct dentry *d_dentry; |
char *name = NULL; |
157 |
struct vfsmount *d_mnt; |
unsigned int buf_len = PAGE_SIZE / 2; |
158 |
if (!dentry || !newname || newname_len <= 2048) |
struct dentry *dentry = path->dentry; |
159 |
goto out; |
if (!dentry) |
160 |
/* Get better name for socket. */ |
return NULL; |
161 |
if (dentry->d_sb && dentry->d_sb->s_magic == SOCKFS_MAGIC) { |
while (1) { |
162 |
struct inode *inode = dentry->d_inode; |
char *pos; |
163 |
struct socket *sock = inode ? SOCKET_I(inode) : NULL; |
buf_len <<= 1; |
164 |
struct sock *sk = sock ? sock->sk : NULL; |
kfree(buf); |
165 |
if (sk) { |
buf = kmalloc(buf_len, GFP_KERNEL); |
166 |
snprintf(newname, newname_len - 1, |
if (!buf) |
167 |
"socket:[family=%u:type=%u:protocol=%u]", |
break; |
168 |
sk->sk_family, sk->sk_type, sk->sk_protocol); |
/* Get better name for socket. */ |
169 |
} else { |
if (dentry->d_sb && dentry->d_sb->s_magic == SOCKFS_MAGIC) { |
170 |
snprintf(newname, newname_len - 1, "socket:[unknown]"); |
struct inode *inode = dentry->d_inode; |
171 |
} |
struct socket *sock = inode ? SOCKET_I(inode) : NULL; |
172 |
return 0; |
struct sock *sk = sock ? sock->sk : NULL; |
173 |
} |
if (sk) { |
174 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) |
snprintf(buf, buf_len - 1, "socket:[family=%u:" |
175 |
if (dentry->d_op && dentry->d_op->d_dname) { |
"type=%u:protocol=%u]", sk->sk_family, |
176 |
/* For "socket:[\$]" and "pipe:[\$]". */ |
sk->sk_type, sk->sk_protocol); |
|
static const int offset = 1536; |
|
|
char *dp = newname; |
|
|
char *sp = dentry->d_op->d_dname(dentry, newname + offset, |
|
|
newname_len - offset); |
|
|
if (IS_ERR(sp)) { |
|
|
error = PTR_ERR(sp); |
|
|
goto out; |
|
|
} |
|
|
error = -ENOMEM; |
|
|
newname += offset; |
|
|
while (1) { |
|
|
const unsigned char c = *(unsigned char *) sp++; |
|
|
if (c == '\\') { |
|
|
if (dp + 2 >= newname) |
|
|
break; |
|
|
*dp++ = '\\'; |
|
|
*dp++ = '\\'; |
|
|
} else if (c > ' ' && c < 127) { |
|
|
if (dp + 1 >= newname) |
|
|
break; |
|
|
*dp++ = (char) c; |
|
|
} else if (c) { |
|
|
if (dp + 4 >= newname) |
|
|
break; |
|
|
*dp++ = '\\'; |
|
|
*dp++ = (c >> 6) + '0'; |
|
|
*dp++ = ((c >> 3) & 7) + '0'; |
|
|
*dp++ = (c & 7) + '0'; |
|
177 |
} else { |
} else { |
178 |
*dp = '\0'; |
snprintf(buf, buf_len - 1, "socket:[unknown]"); |
|
return 0; |
|
179 |
} |
} |
180 |
|
name = ccs_encode(buf); |
181 |
|
break; |
182 |
} |
} |
183 |
goto out; |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) |
184 |
|
/* For "socket:[\$]" and "pipe:[\$]". */ |
185 |
|
if (dentry->d_op && dentry->d_op->d_dname) { |
186 |
|
pos = dentry->d_op->d_dname(dentry, buf, buf_len - 1); |
187 |
|
if (IS_ERR(pos)) |
188 |
|
continue; |
189 |
|
name = ccs_encode(pos); |
190 |
|
break; |
191 |
|
} |
192 |
|
#endif |
193 |
|
if (!path->mnt) |
194 |
|
break; |
195 |
|
path_get(path); |
196 |
|
ccs_realpath_lock(); |
197 |
|
pos = ccs_get_absolute_path(path, buf, buf_len - 1); |
198 |
|
ccs_realpath_unlock(); |
199 |
|
path_put(path); |
200 |
|
if (IS_ERR(pos)) |
201 |
|
continue; |
202 |
|
name = ccs_encode(pos); |
203 |
|
break; |
204 |
} |
} |
|
#endif |
|
|
if (!mnt) |
|
|
goto out; |
|
|
d_dentry = dget(dentry); |
|
|
d_mnt = mntget(mnt); |
|
|
/***** CRITICAL SECTION START *****/ |
|
|
ccs_realpath_lock(); |
|
|
error = ccs_get_absolute_path(d_dentry, d_mnt, newname, newname_len); |
|
|
ccs_realpath_unlock(); |
|
|
/***** CRITICAL SECTION END *****/ |
|
|
dput(d_dentry); |
|
|
mntput(d_mnt); |
|
|
out: |
|
|
if (error) |
|
|
printk(KERN_WARNING "ccs_realpath: Pathname too long. (%d)\n", |
|
|
error); |
|
|
return error; |
|
|
} |
|
|
|
|
|
/** |
|
|
* ccs_realpath_from_dentry - Returns realpath(3) of the given pathname but ignores chroot'ed root. |
|
|
* |
|
|
* @dentry: Pointer to "struct dentry". |
|
|
* @mnt: Pointer to "struct vfsmount". |
|
|
* |
|
|
* Returns the realpath of the given @dentry and @mnt on success, |
|
|
* NULL otherwise. |
|
|
* |
|
|
* These functions use kzalloc(), so caller must kfree() |
|
|
* if these functions didn't return NULL. |
|
|
*/ |
|
|
char *ccs_realpath_from_dentry(struct dentry *dentry, struct vfsmount *mnt) |
|
|
{ |
|
|
char *buf = kzalloc(CCS_MAX_PATHNAME_LEN, GFP_KERNEL); |
|
|
if (buf && ccs_realpath_from_dentry2(dentry, mnt, buf, |
|
|
CCS_MAX_PATHNAME_LEN - 2) == 0) |
|
|
return buf; |
|
205 |
kfree(buf); |
kfree(buf); |
206 |
return NULL; |
if (!name) |
207 |
} |
ccs_warn_oom(__func__); |
208 |
|
return name; |
|
/** |
|
|
* ccs_realpath - Get realpath of a pathname. |
|
|
* |
|
|
* @pathname: The pathname to solve. |
|
|
* |
|
|
* Returns the realpath of @pathname on success, NULL otherwise. |
|
|
*/ |
|
|
char *ccs_realpath(const char *pathname) |
|
|
{ |
|
|
struct nameidata nd; |
|
|
if (pathname && path_lookup(pathname, ccs_lookup_flags, &nd) == 0) { |
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) |
|
|
char *buf = ccs_realpath_from_dentry(nd.path.dentry, |
|
|
nd.path.mnt); |
|
|
path_put(&nd.path); |
|
|
#else |
|
|
char *buf = ccs_realpath_from_dentry(nd.dentry, nd.mnt); |
|
|
path_release(&nd); |
|
|
#endif |
|
|
return buf; |
|
|
} |
|
|
return NULL; |
|
209 |
} |
} |
210 |
|
|
211 |
/** |
/** |
212 |
* ccs_realpath_both - Get realpath of a pathname and symlink. |
* ccs_symlink_path - Get symlink's pathname. |
213 |
* |
* |
214 |
* @pathname: The pathname to solve. |
* @pathname: The pathname to solve. |
215 |
* @ee: Pointer to "struct ccs_execve_entry". |
* @name: Pointer to "struct ccs_path_info". |
216 |
* |
* |
217 |
* Returns 0 on success, negative value otherwise. |
* Returns 0 on success, negative value otherwise. |
218 |
|
* |
219 |
|
* This function uses kzalloc(), so caller must kfree() if this function |
220 |
|
* didn't return NULL. |
221 |
*/ |
*/ |
222 |
int ccs_realpath_both(const char *pathname, struct ccs_execve_entry *ee) |
int ccs_symlink_path(const char *pathname, struct ccs_path_info *name) |
223 |
{ |
{ |
224 |
struct nameidata nd; |
char *buf; |
225 |
int ret; |
struct path path; |
226 |
bool is_symlink; |
if (ccs_kern_path(pathname, ccs_lookup_flags ^ LOOKUP_FOLLOW, &path)) |
|
if (!pathname || |
|
|
path_lookup(pathname, ccs_lookup_flags ^ LOOKUP_FOLLOW, &nd)) |
|
227 |
return -ENOENT; |
return -ENOENT; |
228 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) |
buf = ccs_realpath_from_path(&path); |
229 |
is_symlink = nd.path.dentry->d_inode && |
path_put(&path); |
230 |
S_ISLNK(nd.path.dentry->d_inode->i_mode); |
if (buf) { |
231 |
ret = ccs_realpath_from_dentry2(nd.path.dentry, nd.path.mnt, |
name->name = buf; |
232 |
ee->tmp, CCS_EXEC_TMPSIZE - 1); |
ccs_fill_path_info(name); |
|
path_put(&nd.path); |
|
|
#else |
|
|
is_symlink = nd.dentry->d_inode && S_ISLNK(nd.dentry->d_inode->i_mode); |
|
|
ret = ccs_realpath_from_dentry2(nd.dentry, nd.mnt, ee->tmp, |
|
|
CCS_EXEC_TMPSIZE - 1); |
|
|
path_release(&nd); |
|
|
#endif |
|
|
if (ret) |
|
|
return -ENOMEM; |
|
|
if (strlen(ee->tmp) > CCS_MAX_PATHNAME_LEN - 1) |
|
|
return -ENOMEM; |
|
|
ee->program_path[CCS_MAX_PATHNAME_LEN - 1] = '\0'; |
|
|
if (!is_symlink) { |
|
|
strncpy(ee->program_path, ee->tmp, |
|
|
CCS_MAX_PATHNAME_LEN - 1); |
|
233 |
return 0; |
return 0; |
234 |
} |
} |
235 |
if (path_lookup(pathname, ccs_lookup_flags, &nd)) |
return -ENOMEM; |
|
return -ENOENT; |
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) |
|
|
ret = ccs_realpath_from_dentry2(nd.path.dentry, nd.path.mnt, |
|
|
ee->program_path, |
|
|
CCS_MAX_PATHNAME_LEN - 1); |
|
|
path_put(&nd.path); |
|
|
#else |
|
|
ret = ccs_realpath_from_dentry2(nd.dentry, nd.mnt, ee->program_path, |
|
|
CCS_MAX_PATHNAME_LEN - 1); |
|
|
path_release(&nd); |
|
|
#endif |
|
|
return ret ? -ENOMEM : 0; |
|
236 |
} |
} |
237 |
|
|
238 |
/** |
/** |
263 |
len += 4; |
len += 4; |
264 |
} |
} |
265 |
len++; |
len++; |
266 |
cp = kzalloc(len, GFP_KERNEL); |
/* Reserve space for appending "/". */ |
267 |
|
cp = kzalloc(len + 10, GFP_KERNEL); |
268 |
if (!cp) |
if (!cp) |
269 |
return NULL; |
return NULL; |
270 |
cp0 = cp; |
cp0 = cp; |
286 |
return cp0; |
return cp0; |
287 |
} |
} |
288 |
|
|
|
static atomic_t ccs_non_string_memory_size; |
|
|
static unsigned int ccs_quota_for_non_string; |
|
|
|
|
|
/** |
|
|
* ccs_memory_ok - Check memory quota. |
|
|
* |
|
|
* @ptr: Pointer to allocated memory. |
|
|
* @size: Size in byte. |
|
|
* |
|
|
* Returns true if @ptr is not NULL and quota not exceeded, false otehrwise. |
|
|
*/ |
|
|
bool ccs_memory_ok(const void *ptr, const unsigned int size) |
|
|
{ |
|
|
atomic_add(size, &ccs_non_string_memory_size); |
|
|
if (ptr && (!ccs_quota_for_non_string || |
|
|
atomic_read(&ccs_non_string_memory_size) |
|
|
<= ccs_quota_for_non_string)) |
|
|
return true; |
|
|
atomic_sub(size, &ccs_non_string_memory_size); |
|
|
printk(KERN_WARNING "ERROR: Out of memory. (%s)\n", __func__); |
|
|
if (!ccs_policy_loaded) |
|
|
panic("MAC Initialization failed.\n"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
/** |
|
|
* ccs_memory_free - Free memory for elements. |
|
|
* |
|
|
* @ptr: Pointer to allocated memory. |
|
|
* @size: Size in byte. |
|
|
*/ |
|
|
static void ccs_memory_free(const void *ptr, size_t size) |
|
|
{ |
|
|
atomic_sub(size, &ccs_non_string_memory_size); |
|
|
kfree(ptr); |
|
|
} |
|
|
|
|
|
/** |
|
|
* ccs_put_path_group - Delete memory for "struct ccs_path_group_entry". |
|
|
* |
|
|
* @group: Pointer to "struct ccs_path_group_entry". |
|
|
*/ |
|
|
void ccs_put_path_group(struct ccs_path_group_entry *group) |
|
|
{ |
|
|
struct ccs_path_group_member *member; |
|
|
struct ccs_path_group_member *next_member; |
|
|
LIST_HEAD(q); |
|
|
bool can_delete_group = false; |
|
|
if (!group) |
|
|
return; |
|
|
mutex_lock(&ccs_policy_lock); |
|
|
if (atomic_dec_and_test(&group->users)) { |
|
|
list_for_each_entry_safe(member, next_member, |
|
|
&group->path_group_member_list, |
|
|
list) { |
|
|
if (!member->is_deleted) |
|
|
break; |
|
|
list_del(&member->list); |
|
|
list_add(&member->list, &q); |
|
|
} |
|
|
if (list_empty(&group->path_group_member_list)) { |
|
|
list_del(&group->list); |
|
|
can_delete_group = true; |
|
|
} |
|
|
} |
|
|
mutex_unlock(&ccs_policy_lock); |
|
|
list_for_each_entry_safe(member, next_member, &q, list) { |
|
|
list_del(&member->list); |
|
|
ccs_put_name(member->member_name); |
|
|
ccs_memory_free(member, sizeof(*member)); |
|
|
} |
|
|
if (can_delete_group) { |
|
|
ccs_put_name(group->group_name); |
|
|
ccs_memory_free(group, sizeof(*group)); |
|
|
} |
|
|
} |
|
|
|
|
|
/** |
|
|
* ccs_put_address_group - Delete memory for "struct ccs_address_group_entry". |
|
|
* |
|
|
* @group: Pointer to "struct ccs_address_group_entry". |
|
|
*/ |
|
|
void ccs_put_address_group(struct ccs_address_group_entry *group) |
|
|
{ |
|
|
struct ccs_address_group_member *member; |
|
|
struct ccs_address_group_member *next_member; |
|
|
LIST_HEAD(q); |
|
|
bool can_delete_group = false; |
|
|
if (!group) |
|
|
return; |
|
|
mutex_lock(&ccs_policy_lock); |
|
|
if (atomic_dec_and_test(&group->users)) { |
|
|
list_for_each_entry_safe(member, next_member, |
|
|
&group->address_group_member_list, |
|
|
list) { |
|
|
if (!member->is_deleted) |
|
|
break; |
|
|
list_del(&member->list); |
|
|
list_add(&member->list, &q); |
|
|
} |
|
|
if (list_empty(&group->address_group_member_list)) { |
|
|
list_del(&group->list); |
|
|
can_delete_group = true; |
|
|
} |
|
|
} |
|
|
mutex_unlock(&ccs_policy_lock); |
|
|
list_for_each_entry_safe(member, next_member, &q, list) { |
|
|
list_del(&member->list); |
|
|
if (member->is_ipv6) { |
|
|
ccs_put_ipv6_address(member->min.ipv6); |
|
|
ccs_put_ipv6_address(member->max.ipv6); |
|
|
} |
|
|
ccs_memory_free(member, sizeof(*member)); |
|
|
} |
|
|
if (can_delete_group) { |
|
|
ccs_put_name(group->group_name); |
|
|
ccs_memory_free(group, sizeof(*group)); |
|
|
} |
|
|
} |
|
|
|
|
|
static LIST_HEAD(ccs_address_list); |
|
|
|
|
|
/** |
|
|
* ccs_get_ipv6_address - Keep the given IPv6 address on the RAM. |
|
|
* |
|
|
* @addr: Pointer to "struct in6_addr". |
|
|
* |
|
|
* Returns pointer to "struct in6_addr" on success, NULL otherwise. |
|
|
* |
|
|
* The RAM is shared, so NEVER try to modify or kfree() the returned address. |
|
|
*/ |
|
|
const struct in6_addr *ccs_get_ipv6_address(const struct in6_addr *addr) |
|
|
{ |
|
|
struct ccs_ipv6addr_entry *entry; |
|
|
struct ccs_ipv6addr_entry *ptr; |
|
|
int error = -ENOMEM; |
|
|
if (!addr) |
|
|
return NULL; |
|
|
entry = kzalloc(sizeof(*entry), GFP_KERNEL); |
|
|
mutex_lock(&ccs_policy_lock); |
|
|
list_for_each_entry(ptr, &ccs_address_list, list) { |
|
|
if (memcmp(&ptr->addr, addr, sizeof(*addr))) |
|
|
continue; |
|
|
atomic_inc(&ptr->users); |
|
|
error = 0; |
|
|
break; |
|
|
} |
|
|
if (error && ccs_memory_ok(entry, sizeof(*entry))) { |
|
|
ptr = entry; |
|
|
ptr->addr = *addr; |
|
|
atomic_set(&ptr->users, 1); |
|
|
list_add_tail(&ptr->list, &ccs_address_list); |
|
|
entry = NULL; |
|
|
} |
|
|
mutex_unlock(&ccs_policy_lock); |
|
|
kfree(entry); |
|
|
return ptr ? &ptr->addr : NULL; |
|
|
} |
|
|
|
|
|
/** |
|
|
* ccs_put_ipv6_address - Delete the given IPv6 address on the RAM. |
|
|
* |
|
|
* @addr: Pointer to "struct in6_addr". |
|
|
*/ |
|
|
void ccs_put_ipv6_address(const struct in6_addr *addr) |
|
|
{ |
|
|
struct ccs_ipv6addr_entry *ptr; |
|
|
bool can_delete = false; |
|
|
if (!addr) |
|
|
return; |
|
|
ptr = container_of(addr, struct ccs_ipv6addr_entry, addr); |
|
|
mutex_lock(&ccs_policy_lock); |
|
|
if (atomic_dec_and_test(&ptr->users)) { |
|
|
list_del(&ptr->list); |
|
|
can_delete = true; |
|
|
} |
|
|
mutex_unlock(&ccs_policy_lock); |
|
|
if (can_delete) |
|
|
ccs_memory_free(ptr, sizeof(*ptr)); |
|
|
} |
|
|
|
|
|
/** |
|
|
* ccs_put_condition - Delete memory for "struct ccs_condition". |
|
|
* |
|
|
* @cond: Pointer to "struct ccs_condition". |
|
|
*/ |
|
|
void ccs_put_condition(struct ccs_condition *cond) |
|
|
{ |
|
|
const unsigned long *ptr; |
|
|
const struct ccs_argv_entry *argv; |
|
|
const struct ccs_envp_entry *envp; |
|
|
const struct ccs_symlinkp_entry *symlinkp; |
|
|
u16 condc; |
|
|
u16 argc; |
|
|
u16 envc; |
|
|
u16 symlinkc; |
|
|
u16 i; |
|
|
bool can_delete = false; |
|
|
if (!cond) |
|
|
return; |
|
|
mutex_lock(&ccs_policy_lock); |
|
|
if (atomic_dec_and_test(&cond->users)) { |
|
|
list_del(&cond->list); |
|
|
can_delete = true; |
|
|
} |
|
|
mutex_unlock(&ccs_policy_lock); |
|
|
if (!can_delete) |
|
|
return; |
|
|
condc = cond->condc; |
|
|
argc = cond->argc; |
|
|
envc = cond->envc; |
|
|
symlinkc = cond->symlinkc; |
|
|
ptr = (const unsigned long *) (cond + 1); |
|
|
argv = (const struct ccs_argv_entry *) (ptr + condc); |
|
|
envp = (const struct ccs_envp_entry *) (argv + argc); |
|
|
symlinkp = (const struct ccs_symlinkp_entry *) (envp + envc); |
|
|
for (i = 0; i < argc; argv++, i++) |
|
|
ccs_put_name(argv->value); |
|
|
for (i = 0; i < envc; envp++, i++) { |
|
|
ccs_put_name(envp->name); |
|
|
ccs_put_name(envp->value); |
|
|
} |
|
|
for (i = 0; i < symlinkc; symlinkp++, i++) |
|
|
ccs_put_name(symlinkp->value); |
|
|
ccs_memory_free(cond, cond->size); |
|
|
} |
|
|
|
|
|
static unsigned int ccs_string_memory_size; |
|
|
static unsigned int ccs_quota_for_string; |
|
|
|
|
|
#define MAX_HASH 256 |
|
|
|
|
|
/* Structure for string data. */ |
|
|
struct ccs_name_entry { |
|
|
struct list_head list; |
|
|
atomic_t users; |
|
|
int size; |
|
|
struct ccs_path_info entry; |
|
|
}; |
|
|
|
|
|
/* The list for "struct ccs_name_entry". */ |
|
|
static struct list_head ccs_name_list[MAX_HASH]; |
|
|
static DEFINE_MUTEX(ccs_name_list_lock); |
|
|
|
|
|
/** |
|
|
* ccs_get_name - Allocate memory for string data. |
|
|
* |
|
|
* @name: The string to store into the permernent memory. |
|
|
* |
|
|
* Returns pointer to "struct ccs_path_info" on success, NULL otherwise. |
|
|
*/ |
|
|
const struct ccs_path_info *ccs_get_name(const char *name) |
|
|
{ |
|
|
struct ccs_name_entry *ptr; |
|
|
unsigned int hash; |
|
|
int len; |
|
|
int allocated_len; |
|
|
|
|
|
if (!name) |
|
|
return NULL; |
|
|
len = strlen(name) + 1; |
|
|
if (len > CCS_MAX_PATHNAME_LEN) { |
|
|
printk(KERN_WARNING "ERROR: Name too long. (%s)\n", __func__); |
|
|
return NULL; |
|
|
} |
|
|
hash = full_name_hash((const unsigned char *) name, len - 1); |
|
|
/***** EXCLUSIVE SECTION START *****/ |
|
|
mutex_lock(&ccs_name_list_lock); |
|
|
list_for_each_entry(ptr, &ccs_name_list[hash % MAX_HASH], list) { |
|
|
if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name)) |
|
|
continue; |
|
|
atomic_inc(&ptr->users); |
|
|
goto out; |
|
|
} |
|
|
ptr = kzalloc(sizeof(*ptr) + len, GFP_KERNEL); |
|
|
allocated_len = ptr ? sizeof(*ptr) + len : 0; |
|
|
ccs_string_memory_size += allocated_len; |
|
|
if (!allocated_len || |
|
|
(ccs_quota_for_string && |
|
|
ccs_string_memory_size > ccs_quota_for_string)) { |
|
|
ccs_string_memory_size -= allocated_len; |
|
|
kfree(ptr); |
|
|
ptr = NULL; |
|
|
printk(KERN_WARNING "ERROR: Out of memory. (%s)\n", __func__); |
|
|
if (!ccs_policy_loaded) |
|
|
panic("MAC Initialization failed.\n"); |
|
|
goto out; |
|
|
} |
|
|
ptr->entry.name = ((char *) ptr) + sizeof(*ptr); |
|
|
memmove((char *) ptr->entry.name, name, len); |
|
|
atomic_set(&ptr->users, 1); |
|
|
ccs_fill_path_info(&ptr->entry); |
|
|
ptr->size = allocated_len; |
|
|
list_add_tail(&ptr->list, &ccs_name_list[hash % MAX_HASH]); |
|
|
out: |
|
|
mutex_unlock(&ccs_name_list_lock); |
|
|
/***** EXCLUSIVE SECTION END *****/ |
|
|
return ptr ? &ptr->entry : NULL; |
|
|
} |
|
|
|
|
|
/** |
|
|
* ccs_put_name - Delete shared memory for string data. |
|
|
* |
|
|
* @name: Pointer to "struct ccs_path_info". |
|
|
*/ |
|
|
void ccs_put_name(const struct ccs_path_info *name) |
|
|
{ |
|
|
struct ccs_name_entry *ptr; |
|
|
bool can_delete = false; |
|
|
if (!name) |
|
|
return; |
|
|
ptr = container_of(name, struct ccs_name_entry, entry); |
|
|
/***** EXCLUSIVE SECTION START *****/ |
|
|
mutex_lock(&ccs_name_list_lock); |
|
|
if (atomic_dec_and_test(&ptr->users)) { |
|
|
list_del(&ptr->list); |
|
|
ccs_string_memory_size -= ptr->size; |
|
|
can_delete = true; |
|
|
} |
|
|
mutex_unlock(&ccs_name_list_lock); |
|
|
/***** EXCLUSIVE SECTION END *****/ |
|
|
if (can_delete) |
|
|
kfree(ptr); |
|
|
} |
|
|
|
|
|
struct srcu_struct ccs_ss; |
|
|
|
|
|
/** |
|
|
* ccs_realpath_init - Initialize realpath related code. |
|
|
* |
|
|
* Returns 0. |
|
|
*/ |
|
|
static int __init ccs_realpath_init(void) |
|
|
{ |
|
|
int i; |
|
|
/* Constraint for ccs_get_name(). */ |
|
|
if (CCS_MAX_PATHNAME_LEN > PAGE_SIZE) |
|
|
panic("Bad size."); |
|
|
/* Constraint for "struct ccs_execve_entry"->tmp users. */ |
|
|
if (CCS_MAX_PATHNAME_LEN > CCS_EXEC_TMPSIZE) |
|
|
panic("Bad size."); |
|
|
if (init_srcu_struct(&ccs_ss)) |
|
|
panic("Out of memory."); |
|
|
for (i = 0; i < MAX_HASH; i++) |
|
|
INIT_LIST_HEAD(&ccs_name_list[i]); |
|
|
INIT_LIST_HEAD(&ccs_kernel_domain.acl_info_list); |
|
|
ccs_kernel_domain.domainname = ccs_get_name(ROOT_NAME); |
|
|
list_add_tail_rcu(&ccs_kernel_domain.list, &ccs_domain_list); |
|
|
if (ccs_find_domain(ROOT_NAME) != &ccs_kernel_domain) |
|
|
panic("Can't register ccs_kernel_domain"); |
|
|
#ifdef CONFIG_CCSECURITY_BUILTIN_INITIALIZERS |
|
|
{ |
|
|
/* Load built-in policy. */ |
|
|
static char ccs_builtin_initializers[] __initdata |
|
|
= CONFIG_CCSECURITY_BUILTIN_INITIALIZERS; |
|
|
char *cp = ccs_builtin_initializers; |
|
|
ccs_normalize_line(cp); |
|
|
while (cp && *cp) { |
|
|
char *cp2 = strchr(cp, ' '); |
|
|
if (cp2) |
|
|
*cp2++ = '\0'; |
|
|
ccs_write_domain_initializer_policy(cp, false, false); |
|
|
cp = cp2; |
|
|
} |
|
|
} |
|
|
#endif |
|
|
return 0; |
|
|
} |
|
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) |
|
|
__initcall(ccs_realpath_init); |
|
|
#else |
|
|
core_initcall(ccs_realpath_init); |
|
|
#endif |
|
|
|
|
|
unsigned int ccs_audit_log_memory_size; |
|
|
unsigned int ccs_quota_for_audit_log; |
|
|
|
|
|
unsigned int ccs_query_memory_size; |
|
|
unsigned int ccs_quota_for_query; |
|
|
|
|
|
/** |
|
|
* ccs_read_memory_counter - Check for memory usage. |
|
|
* |
|
|
* @head: Pointer to "struct ccs_io_buffer". |
|
|
* |
|
|
* Returns memory usage. |
|
|
*/ |
|
|
int ccs_read_memory_counter(struct ccs_io_buffer *head) |
|
|
{ |
|
|
if (!head->read_eof) { |
|
|
const unsigned int string = ccs_string_memory_size; |
|
|
const unsigned int nonstring |
|
|
= atomic_read(&ccs_non_string_memory_size); |
|
|
const unsigned int audit_log = ccs_audit_log_memory_size; |
|
|
const unsigned int query = ccs_query_memory_size; |
|
|
char buffer[64]; |
|
|
memset(buffer, 0, sizeof(buffer)); |
|
|
if (ccs_quota_for_string) |
|
|
snprintf(buffer, sizeof(buffer) - 1, |
|
|
" (Quota: %10u)", ccs_quota_for_string); |
|
|
else |
|
|
buffer[0] = '\0'; |
|
|
ccs_io_printf(head, "Policy (string): %10u%s\n", |
|
|
string, buffer); |
|
|
if (ccs_quota_for_non_string) |
|
|
snprintf(buffer, sizeof(buffer) - 1, |
|
|
" (Quota: %10u)", ccs_quota_for_non_string); |
|
|
else |
|
|
buffer[0] = '\0'; |
|
|
ccs_io_printf(head, "Policy (non-string): %10u%s\n", |
|
|
nonstring, buffer); |
|
|
if (ccs_quota_for_audit_log) |
|
|
snprintf(buffer, sizeof(buffer) - 1, |
|
|
" (Quota: %10u)", ccs_quota_for_audit_log); |
|
|
else |
|
|
buffer[0] = '\0'; |
|
|
ccs_io_printf(head, "Audit logs: %10u%s\n", |
|
|
audit_log, buffer); |
|
|
if (ccs_quota_for_query) |
|
|
snprintf(buffer, sizeof(buffer) - 1, |
|
|
" (Quota: %10u)", ccs_quota_for_query); |
|
|
else |
|
|
buffer[0] = '\0'; |
|
|
ccs_io_printf(head, "Interactive enforcement: %10u%s\n", |
|
|
query, buffer); |
|
|
ccs_io_printf(head, "Total: %10u\n", |
|
|
string + nonstring + audit_log + query); |
|
|
head->read_eof = true; |
|
|
} |
|
|
return 0; |
|
|
} |
|
|
|
|
|
/** |
|
|
* ccs_write_memory_quota - Set memory quota. |
|
|
* |
|
|
* @head: Pointer to "struct ccs_io_buffer". |
|
|
* |
|
|
* Returns 0. |
|
|
*/ |
|
|
int ccs_write_memory_quota(struct ccs_io_buffer *head) |
|
|
{ |
|
|
char *data = head->write_buf; |
|
|
unsigned int size; |
|
|
if (sscanf(data, "Policy (string): %u", &size) == 1) |
|
|
ccs_quota_for_string = size; |
|
|
else if (sscanf(data, "Policy (non-string): %u", &size) == 1) |
|
|
ccs_quota_for_non_string = size; |
|
|
else if (sscanf(data, "Audit logs: %u", &size) == 1) |
|
|
ccs_quota_for_audit_log = size; |
|
|
else if (sscanf(data, "Interactive enforcement: %u", &size) == 1) |
|
|
ccs_quota_for_query = size; |
|
|
return 0; |
|
|
} |
|
|
|
|
|
/* Garbage collector functions */ |
|
|
|
|
|
enum ccs_gc_id { |
|
|
CCS_ID_CONDITION, |
|
|
CCS_ID_RESERVEDPORT, |
|
|
CCS_ID_ADDRESS_GROUP, |
|
|
CCS_ID_ADDRESS_GROUP_MEMBER, |
|
|
CCS_ID_PATH_GROUP, |
|
|
CCS_ID_PATH_GROUP_MEMBER, |
|
|
CCS_ID_GLOBAL_ENV, |
|
|
CCS_ID_AGGREGATOR, |
|
|
CCS_ID_DOMAIN_INITIALIZER, |
|
|
CCS_ID_DOMAIN_KEEPER, |
|
|
CCS_ID_ALIAS, |
|
|
CCS_ID_GLOBALLY_READABLE, |
|
|
CCS_ID_PATTERN, |
|
|
CCS_ID_NO_REWRITE, |
|
|
CCS_ID_MANAGER, |
|
|
CCS_ID_ACL, |
|
|
CCS_ID_DOMAIN |
|
|
}; |
|
|
|
|
|
struct ccs_gc_entry { |
|
|
struct list_head list; |
|
|
int type; |
|
|
void *element; |
|
|
}; |
|
|
|
|
|
/* Caller holds ccs_policy_lock mutex. */ |
|
|
static bool ccs_add_to_gc(const int type, void *element, struct list_head *head) |
|
|
{ |
|
|
struct ccs_gc_entry *entry = kzalloc(sizeof(*entry), GFP_ATOMIC); |
|
|
if (!entry) |
|
|
return false; |
|
|
entry->type = type; |
|
|
entry->element = element; |
|
|
list_add(&entry->list, head); |
|
|
return true; |
|
|
} |
|
|
|
|
|
static size_t ccs_del_allow_read(struct ccs_globally_readable_file_entry *ptr) |
|
|
{ |
|
|
ccs_put_name(ptr->filename); |
|
|
return sizeof(*ptr); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_allow_env(struct ccs_globally_usable_env_entry *ptr) |
|
|
{ |
|
|
ccs_put_name(ptr->env); |
|
|
return sizeof(*ptr); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_file_pattern(struct ccs_pattern_entry *ptr) |
|
|
{ |
|
|
ccs_put_name(ptr->pattern); |
|
|
return sizeof(*ptr); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_no_rewrite(struct ccs_no_rewrite_entry *ptr) |
|
|
{ |
|
|
ccs_put_name(ptr->pattern); |
|
|
return sizeof(*ptr); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_domain_initializer(struct ccs_domain_initializer_entry * |
|
|
ptr) |
|
|
{ |
|
|
ccs_put_name(ptr->domainname); |
|
|
ccs_put_name(ptr->program); |
|
|
return sizeof(*ptr); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_domain_keeper(struct ccs_domain_keeper_entry *ptr) |
|
|
{ |
|
|
ccs_put_name(ptr->domainname); |
|
|
ccs_put_name(ptr->program); |
|
|
return sizeof(*ptr); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_alias(struct ccs_alias_entry *ptr) |
|
|
{ |
|
|
ccs_put_name(ptr->original_name); |
|
|
ccs_put_name(ptr->aliased_name); |
|
|
return sizeof(*ptr); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_aggregator(struct ccs_aggregator_entry *ptr) |
|
|
{ |
|
|
ccs_put_name(ptr->original_name); |
|
|
ccs_put_name(ptr->aggregated_name); |
|
|
return sizeof(*ptr); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_manager(struct ccs_policy_manager_entry *ptr) |
|
|
{ |
|
|
ccs_put_name(ptr->manager); |
|
|
return sizeof(*ptr); |
|
|
} |
|
|
|
|
|
/* For compatibility with older kernels. */ |
|
|
#ifndef for_each_process |
|
|
#define for_each_process for_each_task |
|
|
#endif |
|
|
|
|
289 |
/** |
/** |
290 |
* ccs_used_by_task - Check whether the given pointer is referenced by a task. |
* ccs_get_path - Get dentry/vfsmmount of a pathname. |
291 |
* |
* |
292 |
* @domain: Pointer to "struct ccs_domain_info". |
* @pathname: The pathname to solve. |
293 |
|
* @path: Pointer to "struct path". |
294 |
* |
* |
295 |
* Returns true if @ptr is in use, false otherwise. |
* Returns 0 on success, negative value otherwise. |
296 |
*/ |
*/ |
297 |
static bool ccs_used_by_task(struct ccs_domain_info *domain) |
int ccs_get_path(const char *pathname, struct path *path) |
|
{ |
|
|
bool in_use = false; |
|
|
struct task_struct *p; |
|
|
/***** CRITICAL SECTION START *****/ |
|
|
read_lock(&tasklist_lock); |
|
|
for_each_process(p) { |
|
|
if (p->ccs_domain_info != domain) |
|
|
continue; |
|
|
in_use = true; |
|
|
break; |
|
|
} |
|
|
read_unlock(&tasklist_lock); |
|
|
/***** CRITICAL SECTION END *****/ |
|
|
return in_use; |
|
|
} |
|
|
|
|
|
static size_t ccs_del_acl(struct ccs_acl_info *acl) |
|
|
{ |
|
|
size_t size; |
|
|
ccs_put_condition(acl->cond); |
|
|
switch (ccs_acl_type1(acl)) { |
|
|
case TYPE_SINGLE_PATH_ACL: |
|
|
{ |
|
|
struct ccs_single_path_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
if (entry->u_is_group) |
|
|
ccs_put_path_group(entry->u.group); |
|
|
else |
|
|
ccs_put_name(entry->u.filename); |
|
|
} |
|
|
break; |
|
|
case TYPE_DOUBLE_PATH_ACL: |
|
|
{ |
|
|
struct ccs_double_path_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
if (entry->u1_is_group) |
|
|
ccs_put_path_group(entry->u1.group1); |
|
|
else |
|
|
ccs_put_name(entry->u1.filename1); |
|
|
if (entry->u2_is_group) |
|
|
ccs_put_path_group(entry->u2.group2); |
|
|
else |
|
|
ccs_put_name(entry->u2.filename2); |
|
|
} |
|
|
break; |
|
|
case TYPE_IP_NETWORK_ACL: |
|
|
{ |
|
|
struct ccs_ip_network_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
if (entry->record_type == IP_RECORD_TYPE_ADDRESS_GROUP) |
|
|
ccs_put_address_group(entry->u.group); |
|
|
else if (entry->record_type == IP_RECORD_TYPE_IPv6) { |
|
|
ccs_put_ipv6_address(entry->u.ipv6.min); |
|
|
ccs_put_ipv6_address(entry->u.ipv6.max); |
|
|
} |
|
|
} |
|
|
break; |
|
|
case TYPE_IOCTL_ACL: |
|
|
{ |
|
|
struct ccs_ioctl_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
if (entry->u_is_group) |
|
|
ccs_put_path_group(entry->u.group); |
|
|
else |
|
|
ccs_put_name(entry->u.filename); |
|
|
} |
|
|
break; |
|
|
case TYPE_ARGV0_ACL: |
|
|
{ |
|
|
struct ccs_argv0_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
ccs_put_name(entry->argv0); |
|
|
} |
|
|
break; |
|
|
case TYPE_ENV_ACL: |
|
|
{ |
|
|
struct ccs_env_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
ccs_put_name(entry->env); |
|
|
} |
|
|
break; |
|
|
case TYPE_CAPABILITY_ACL: |
|
|
{ |
|
|
struct ccs_capability_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
} |
|
|
break; |
|
|
case TYPE_SIGNAL_ACL: |
|
|
{ |
|
|
struct ccs_signal_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
ccs_put_name(entry->domainname); |
|
|
} |
|
|
break; |
|
|
case TYPE_EXECUTE_HANDLER: |
|
|
case TYPE_DENIED_EXECUTE_HANDLER: |
|
|
{ |
|
|
struct ccs_execute_handler_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
ccs_put_name(entry->handler); |
|
|
} |
|
|
break; |
|
|
case TYPE_MOUNT_ACL: |
|
|
{ |
|
|
struct ccs_mount_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
ccs_put_name(entry->dev_name); |
|
|
ccs_put_name(entry->dir_name); |
|
|
ccs_put_name(entry->fs_type); |
|
|
} |
|
|
break; |
|
|
case TYPE_UMOUNT_ACL: |
|
|
{ |
|
|
struct ccs_umount_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
ccs_put_name(entry->dir); |
|
|
} |
|
|
break; |
|
|
case TYPE_CHROOT_ACL: |
|
|
{ |
|
|
struct ccs_chroot_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
ccs_put_name(entry->dir); |
|
|
} |
|
|
break; |
|
|
case TYPE_PIVOT_ROOT_ACL: |
|
|
{ |
|
|
struct ccs_pivot_root_acl_record *entry; |
|
|
size = sizeof(*entry); |
|
|
entry = container_of(acl, typeof(*entry), head); |
|
|
ccs_put_name(entry->old_root); |
|
|
ccs_put_name(entry->new_root); |
|
|
} |
|
|
break; |
|
|
default: |
|
|
size = 0; |
|
|
printk(KERN_WARNING "Unknown type\n"); |
|
|
break; |
|
|
} |
|
|
return size; |
|
|
} |
|
|
|
|
|
static size_t ccs_del_domain(struct ccs_domain_info *domain) |
|
|
{ |
|
|
struct ccs_acl_info *acl; |
|
|
struct ccs_acl_info *tmp; |
|
|
if (ccs_used_by_task(domain)) |
|
|
return 0; |
|
|
list_for_each_entry_safe(acl, tmp, &domain->acl_info_list, list) { |
|
|
size_t size = ccs_del_acl(acl); |
|
|
ccs_memory_free(acl, size); |
|
|
} |
|
|
ccs_put_name(domain->domainname); |
|
|
return sizeof(*domain); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_path_group_member(struct ccs_path_group_member *member) |
|
|
{ |
|
|
ccs_put_name(member->member_name); |
|
|
return sizeof(*member); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_path_group(struct ccs_path_group_entry *group) |
|
|
{ |
|
|
ccs_put_name(group->group_name); |
|
|
return sizeof(*group); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_address_group_member(struct ccs_address_group_member *member) |
|
|
{ |
|
|
if (member->is_ipv6) { |
|
|
ccs_put_ipv6_address(member->min.ipv6); |
|
|
ccs_put_ipv6_address(member->max.ipv6); |
|
|
} |
|
|
return sizeof(*member); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_address_group(struct ccs_address_group_entry *group) |
|
|
{ |
|
|
ccs_put_name(group->group_name); |
|
|
return sizeof(*group); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_reservedport(struct ccs_reserved_entry *ptr) |
|
|
{ |
|
|
return sizeof(*ptr); |
|
|
} |
|
|
|
|
|
static size_t ccs_del_condition(struct ccs_condition *ptr) |
|
298 |
{ |
{ |
299 |
int i; |
return ccs_kern_path(pathname, ccs_lookup_flags, path); |
|
u16 condc = ptr->condc; |
|
|
u16 argc = ptr->argc; |
|
|
u16 envc = ptr->envc; |
|
|
u16 symlinkc = ptr->symlinkc; |
|
|
unsigned long *ptr2 = (unsigned long *) (ptr + 1); |
|
|
struct ccs_argv_entry *argv = (struct ccs_argv_entry *) (ptr2 + condc); |
|
|
struct ccs_envp_entry *envp = (struct ccs_envp_entry *) (argv + argc); |
|
|
struct ccs_symlinkp_entry *symlinkp |
|
|
= (struct ccs_symlinkp_entry *) (envp + envc); |
|
|
for (i = 0; i < argc; i++) |
|
|
ccs_put_name(argv[i].value); |
|
|
for (i = 0; i < envc; i++) { |
|
|
ccs_put_name(envp[i].name); |
|
|
ccs_put_name(envp[i].value); |
|
|
} |
|
|
for (i = 0; i < symlinkc; i++) |
|
|
ccs_put_name(symlinkp[i].value); |
|
|
return ptr->size; |
|
300 |
} |
} |
|
|
|
|
static int ccs_gc_thread(void *unused) |
|
|
{ |
|
|
static DEFINE_MUTEX(ccs_gc_mutex); |
|
|
static LIST_HEAD(ccs_gc_queue); |
|
|
if (!mutex_trylock(&ccs_gc_mutex)) |
|
|
goto out; |
|
|
mutex_lock(&ccs_policy_lock); |
|
|
{ |
|
|
struct ccs_globally_readable_file_entry *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_globally_readable_list, |
|
|
list) { |
|
|
if (!ptr->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_GLOBALLY_READABLE, ptr, |
|
|
&ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_globally_usable_env_entry *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_globally_usable_env_list, |
|
|
list) { |
|
|
if (!ptr->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_GLOBAL_ENV, ptr, |
|
|
&ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_pattern_entry *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_pattern_list, list) { |
|
|
if (!ptr->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_PATTERN, ptr, |
|
|
&ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_no_rewrite_entry *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_no_rewrite_list, list) { |
|
|
if (!ptr->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_NO_REWRITE, ptr, |
|
|
&ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_domain_initializer_entry *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_domain_initializer_list, |
|
|
list) { |
|
|
if (!ptr->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_DOMAIN_INITIALIZER, |
|
|
ptr, &ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_domain_keeper_entry *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_domain_keeper_list, list) { |
|
|
if (!ptr->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_DOMAIN_KEEPER, ptr, |
|
|
&ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_alias_entry *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_alias_list, list) { |
|
|
if (!ptr->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_ALIAS, ptr, &ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_policy_manager_entry *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_policy_manager_list, list) { |
|
|
if (!ptr->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_MANAGER, ptr, &ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_aggregator_entry *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_aggregator_list, list) { |
|
|
if (!ptr->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_AGGREGATOR, ptr, |
|
|
&ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_domain_info *domain; |
|
|
list_for_each_entry_rcu(domain, &ccs_domain_list, list) { |
|
|
struct ccs_acl_info *acl; |
|
|
list_for_each_entry_rcu(acl, &domain->acl_info_list, |
|
|
list) { |
|
|
if (!(acl->type & ACL_DELETED)) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_ACL, acl, |
|
|
&ccs_gc_queue)) |
|
|
list_del_rcu(&acl->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
if (!domain->is_deleted || |
|
|
ccs_used_by_task(domain)) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_DOMAIN, domain, &ccs_gc_queue)) |
|
|
list_del_rcu(&domain->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_path_group_entry *group; |
|
|
list_for_each_entry_rcu(group, &ccs_path_group_list, list) { |
|
|
struct ccs_path_group_member *member; |
|
|
list_for_each_entry_rcu(member, |
|
|
&group->path_group_member_list, |
|
|
list) { |
|
|
if (!member->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_PATH_GROUP_MEMBER, |
|
|
member, &ccs_gc_queue)) |
|
|
list_del_rcu(&member->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
if (!list_empty(&group->path_group_member_list) || |
|
|
atomic_read(&group->users)) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_PATH_GROUP, group, |
|
|
&ccs_gc_queue)) |
|
|
list_del_rcu(&group->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_address_group_entry *group; |
|
|
list_for_each_entry_rcu(group, &ccs_address_group_list, list) { |
|
|
struct ccs_address_group_member *member; |
|
|
list_for_each_entry_rcu(member, |
|
|
&group->address_group_member_list, |
|
|
list) { |
|
|
if (!member->is_deleted) |
|
|
break; |
|
|
if (ccs_add_to_gc(CCS_ID_ADDRESS_GROUP_MEMBER, |
|
|
member, &ccs_gc_queue)) |
|
|
list_del_rcu(&member->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
if (!list_empty(&group->address_group_member_list) || |
|
|
atomic_read(&group->users)) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_ADDRESS_GROUP, group, |
|
|
&ccs_gc_queue)) |
|
|
list_del_rcu(&group->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_reserved_entry *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_reservedport_list, list) { |
|
|
if (!ptr->is_deleted) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_RESERVEDPORT, ptr, |
|
|
&ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
{ |
|
|
struct ccs_condition *ptr; |
|
|
list_for_each_entry_rcu(ptr, &ccs_condition_list, list) { |
|
|
if (atomic_read(&ptr->users)) |
|
|
continue; |
|
|
if (ccs_add_to_gc(CCS_ID_CONDITION, ptr, &ccs_gc_queue)) |
|
|
list_del_rcu(&ptr->list); |
|
|
else |
|
|
break; |
|
|
} |
|
|
} |
|
|
mutex_unlock(&ccs_policy_lock); |
|
|
if (list_empty(&ccs_gc_queue)) |
|
|
goto done; |
|
|
synchronize_srcu(&ccs_ss); |
|
|
{ |
|
|
struct ccs_gc_entry *p; |
|
|
struct ccs_gc_entry *tmp; |
|
|
size_t size = 0; |
|
|
list_for_each_entry_safe(p, tmp, &ccs_gc_queue, list) { |
|
|
switch (p->type) { |
|
|
case CCS_ID_DOMAIN_INITIALIZER: |
|
|
size = ccs_del_domain_initializer(p->element); |
|
|
break; |
|
|
case CCS_ID_DOMAIN_KEEPER: |
|
|
size = ccs_del_domain_keeper(p->element); |
|
|
break; |
|
|
case CCS_ID_ALIAS: |
|
|
size = ccs_del_alias(p->element); |
|
|
break; |
|
|
case CCS_ID_GLOBALLY_READABLE: |
|
|
size = ccs_del_allow_read(p->element); |
|
|
break; |
|
|
case CCS_ID_PATTERN: |
|
|
size = ccs_del_file_pattern(p->element); |
|
|
break; |
|
|
case CCS_ID_NO_REWRITE: |
|
|
size = ccs_del_no_rewrite(p->element); |
|
|
break; |
|
|
case CCS_ID_MANAGER: |
|
|
size = ccs_del_manager(p->element); |
|
|
break; |
|
|
case CCS_ID_GLOBAL_ENV: |
|
|
size = ccs_del_allow_env(p->element); |
|
|
break; |
|
|
case CCS_ID_AGGREGATOR: |
|
|
size = ccs_del_aggregator(p->element); |
|
|
break; |
|
|
case CCS_ID_PATH_GROUP_MEMBER: |
|
|
size = ccs_del_path_group_member(p->element); |
|
|
break; |
|
|
case CCS_ID_PATH_GROUP: |
|
|
size = ccs_del_path_group(p->element); |
|
|
break; |
|
|
case CCS_ID_ADDRESS_GROUP_MEMBER: |
|
|
size = ccs_del_address_group_member(p->element); |
|
|
break; |
|
|
case CCS_ID_ADDRESS_GROUP: |
|
|
size = ccs_del_address_group(p->element); |
|
|
break; |
|
|
case CCS_ID_RESERVEDPORT: |
|
|
size = ccs_del_reservedport(p->element); |
|
|
break; |
|
|
case CCS_ID_CONDITION: |
|
|
size = ccs_del_condition(p->element); |
|
|
break; |
|
|
case CCS_ID_ACL: |
|
|
size = ccs_del_acl(p->element); |
|
|
break; |
|
|
case CCS_ID_DOMAIN: |
|
|
size = ccs_del_domain(p->element); |
|
|
if (!size) |
|
|
continue; |
|
|
break; |
|
|
default: |
|
|
size = 0; |
|
|
printk(KERN_WARNING "Unknown type\n"); |
|
|
break; |
|
|
} |
|
|
ccs_memory_free(p->element, size); |
|
|
list_del(&p->list); |
|
|
kfree(p); |
|
|
} |
|
|
} |
|
|
done: |
|
|
mutex_unlock(&ccs_gc_mutex); |
|
|
out: |
|
|
do_exit(0); |
|
|
} |
|
|
|
|
|
void ccs_run_gc(void) |
|
|
{ |
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) |
|
|
struct task_struct *task = kthread_create(ccs_gc_thread, NULL, |
|
|
"GC for CCS"); |
|
|
if (!IS_ERR(task)) |
|
|
wake_up_process(task); |
|
|
#else |
|
|
kernel_thread(ccs_gc_thread, NULL, 0); |
|
|
#endif |
|
|
} |
|
|
|
|
|
#ifndef _LINUX_SRCU_H |
|
|
|
|
|
static DEFINE_SPINLOCK(ccs_counter_lock); |
|
|
|
|
|
int srcu_read_lock(struct srcu_struct *sp) |
|
|
{ |
|
|
int idx; |
|
|
spin_lock(&ccs_counter_lock); |
|
|
idx = sp->counter_idx; |
|
|
sp->counter[idx]++; |
|
|
spin_unlock(&ccs_counter_lock); |
|
|
return idx; |
|
|
} |
|
|
|
|
|
void srcu_read_unlock(struct srcu_struct *sp, const int idx) |
|
|
{ |
|
|
spin_lock(&ccs_counter_lock); |
|
|
sp->counter[idx]--; |
|
|
spin_unlock(&ccs_counter_lock); |
|
|
} |
|
|
|
|
|
void synchronize_srcu(struct srcu_struct *sp) |
|
|
{ |
|
|
int idx; |
|
|
int v; |
|
|
spin_lock(&ccs_counter_lock); |
|
|
idx = sp->counter_idx; |
|
|
sp->counter_idx ^= 1; |
|
|
v = sp->counter[idx]; |
|
|
spin_unlock(&ccs_counter_lock); |
|
|
while (v) { |
|
|
ssleep(1); |
|
|
spin_lock(&ccs_counter_lock); |
|
|
v = sp->counter[idx]; |
|
|
spin_unlock(&ccs_counter_lock); |
|
|
} |
|
|
} |
|
|
|
|
|
#endif |
|