1 |
/* |
2 |
* include/linux/syaoran.h |
3 |
* |
4 |
* Implementation of the Tamper-Proof Device Filesystem. |
5 |
* |
6 |
* Copyright (C) 2005-2009 NTT DATA CORPORATION |
7 |
* |
8 |
* Version: 1.6.8 2009/05/28 |
9 |
* |
10 |
* This file is applicable to both 2.4.30 and 2.6.11 and later. |
11 |
* See README.ccs for ChangeLog. |
12 |
* |
13 |
*/ |
14 |
/* |
15 |
* A brief description about SYAORAN: |
16 |
* |
17 |
* SYAORAN stands for "Simple Yet All-important Object Realizing Abiding |
18 |
* Nexus". SYAORAN is a filesystem for /dev with Mandatory Access Control. |
19 |
* |
20 |
* /dev cannot be mounted for read-only mode, but this means that files on |
21 |
* /dev might be tampered with. In other words, a device file might have |
22 |
* inappropriate attributes (e.g. /dev/null has char-1-5 attributes). |
23 |
* SYAORAN can restrict combinations of (pathname, attribute) that |
24 |
* the system can create so that all files on this filesystem have appropriate |
25 |
* attributes (e.g. /dev/null has char-1-3 attributes). |
26 |
* |
27 |
* The attribute is one of directory, regular file, FIFO, UNIX domain socket, |
28 |
* symbolic link, character or block device file with major/minor device |
29 |
* numbers. |
30 |
* |
31 |
* You can use SYAORAN alone, but I recommend you to use SYAORAN |
32 |
* with SAKURA and TOMOYO. |
33 |
*/ |
34 |
|
35 |
#ifndef _LINUX_SYAORAN_H |
36 |
#define _LINUX_SYAORAN_H |
37 |
|
38 |
#include <linux/version.h> |
39 |
#include <linux/ccs_compat.h> |
40 |
|
41 |
/** |
42 |
* list_for_each_cookie - iterate over a list with cookie. |
43 |
* @pos: the &struct list_head to use as a loop cursor. |
44 |
* @cookie: the &struct list_head to use as a cookie. |
45 |
* @head: the head for your list. |
46 |
* |
47 |
* Same with list_for_each except that this primitive uses cookie |
48 |
* so that we can continue iteration. |
49 |
*/ |
50 |
#define list_for_each_cookie(pos, cookie, head) \ |
51 |
for (({ if (!cookie) \ |
52 |
cookie = head; }), pos = (cookie)->next; \ |
53 |
prefetch(pos->next), pos != (head) || ((cookie) = NULL); \ |
54 |
(cookie) = pos, pos = pos->next) |
55 |
|
56 |
/* The following constants are used to restrict operations.*/ |
57 |
#define MAY_CREATE 1 /* This file is allowed to mknod() */ |
58 |
#define MAY_DELETE 2 /* This file is allowed to unlink() */ |
59 |
#define MAY_CHMOD 4 /* This file is allowed to chmod() */ |
60 |
#define MAY_CHOWN 8 /* This file is allowed to chown() */ |
61 |
#define DEVICE_USED 16 /* This block or character device file is used. */ |
62 |
#define NO_CREATE_AT_MOUNT 32 /* Don't create this file at mount(). */ |
63 |
|
64 |
/* some random number */ |
65 |
#define CCS_MAGIC 0x2F646576 /* = '/dev' */ |
66 |
|
67 |
static void ccs_put_super(struct super_block *sb); |
68 |
static int ccs_initialize(struct super_block *sb, void *data); |
69 |
static void ccs_make_initial_nodes(struct super_block *sb); |
70 |
static int ccs_may_create_node(struct dentry *dentry, int mode, int dev); |
71 |
static int ccs_may_modify_node(struct dentry *dentry, unsigned int flags); |
72 |
static int ccs_create_tracelog(struct super_block *sb, |
73 |
const char *filename); |
74 |
|
75 |
/* Wraps blkdev_open() to trace open operation for block devices. */ |
76 |
static int (*ccs_org_blkdev_open) (struct inode *inode, struct file *filp); |
77 |
static struct file_operations ccs_wrapped_def_blk_fops; |
78 |
|
79 |
static int ccs_wrapped_blkdev_open(struct inode *inode, struct file *filp) |
80 |
{ |
81 |
int error = ccs_org_blkdev_open(inode, filp); |
82 |
if (error != -ENXIO) |
83 |
ccs_may_modify_node(filp->f_dentry, DEVICE_USED); |
84 |
return error; |
85 |
} |
86 |
|
87 |
/* Wraps chrdev_open() to trace open operation for character devices. */ |
88 |
static int (*ccs_org_chrdev_open) (struct inode *inode, struct file *filp); |
89 |
static struct file_operations ccs_wrapped_def_chr_fops; |
90 |
|
91 |
static int ccs_wrapped_chrdev_open(struct inode *inode, struct file *filp) |
92 |
{ |
93 |
int error = ccs_org_chrdev_open(inode, filp); |
94 |
if (error != -ENXIO) |
95 |
ccs_may_modify_node(filp->f_dentry, DEVICE_USED); |
96 |
return error; |
97 |
} |
98 |
|
99 |
/* lookup_create() without nameidata. Called only while initialization. */ |
100 |
static struct dentry *ccs_lookup_create2(const char *name, struct dentry *base, |
101 |
const bool is_dir) |
102 |
{ |
103 |
struct dentry *dentry; |
104 |
const int len = name ? strlen(name) : 0; |
105 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16) |
106 |
mutex_lock(&base->d_inode->i_mutex); |
107 |
#else |
108 |
down(&base->d_inode->i_sem); |
109 |
#endif |
110 |
dentry = lookup_one_len(name, base, len); |
111 |
if (IS_ERR(dentry)) |
112 |
goto fail; |
113 |
if (!is_dir && name[len] && !dentry->d_inode) |
114 |
goto enoent; |
115 |
return dentry; |
116 |
enoent: |
117 |
dput(dentry); |
118 |
dentry = ERR_PTR(-ENOENT); |
119 |
fail: |
120 |
return dentry; |
121 |
} |
122 |
|
123 |
/* mkdir(). Called only while initialization. */ |
124 |
static int ccs_fs_mkdir(const char *pathname, struct dentry *base, int mode, |
125 |
uid_t user, gid_t group) |
126 |
{ |
127 |
struct dentry *dentry = ccs_lookup_create2(pathname, base, 1); |
128 |
int error = PTR_ERR(dentry); |
129 |
if (!IS_ERR(dentry)) { |
130 |
#ifdef HAVE_VFSMOUNT_IN_VFS_HELPER |
131 |
error = vfs_mkdir(base->d_inode, dentry, NULL, mode); |
132 |
#else |
133 |
error = vfs_mkdir(base->d_inode, dentry, mode); |
134 |
#endif |
135 |
if (!error) { |
136 |
lock_kernel(); |
137 |
dentry->d_inode->i_uid = user; |
138 |
dentry->d_inode->i_gid = group; |
139 |
unlock_kernel(); |
140 |
} |
141 |
dput(dentry); |
142 |
} |
143 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16) |
144 |
mutex_unlock(&base->d_inode->i_mutex); |
145 |
#else |
146 |
up(&base->d_inode->i_sem); |
147 |
#endif |
148 |
return error; |
149 |
} |
150 |
|
151 |
/* mknod(). Called only while initialization. */ |
152 |
static int ccs_fs_mknod(const char *filename, struct dentry *base, int mode, |
153 |
dev_t dev, uid_t user, gid_t group) |
154 |
{ |
155 |
struct dentry *dentry; |
156 |
int error; |
157 |
switch (mode & S_IFMT) { |
158 |
case S_IFCHR: |
159 |
case S_IFBLK: |
160 |
case S_IFIFO: |
161 |
case S_IFSOCK: |
162 |
case S_IFREG: |
163 |
break; |
164 |
default: |
165 |
return -EPERM; |
166 |
} |
167 |
dentry = ccs_lookup_create2(filename, base, 0); |
168 |
error = PTR_ERR(dentry); |
169 |
if (!IS_ERR(dentry)) { |
170 |
#ifdef HAVE_VFSMOUNT_IN_VFS_HELPER |
171 |
error = vfs_mknod(base->d_inode, dentry, NULL, mode, dev); |
172 |
#else |
173 |
error = vfs_mknod(base->d_inode, dentry, mode, dev); |
174 |
#endif |
175 |
if (!error) { |
176 |
lock_kernel(); |
177 |
dentry->d_inode->i_uid = user; |
178 |
dentry->d_inode->i_gid = group; |
179 |
unlock_kernel(); |
180 |
} |
181 |
dput(dentry); |
182 |
} |
183 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16) |
184 |
mutex_unlock(&base->d_inode->i_mutex); |
185 |
#else |
186 |
up(&base->d_inode->i_sem); |
187 |
#endif |
188 |
return error; |
189 |
} |
190 |
|
191 |
/* symlink(). Called only while initialization. */ |
192 |
static int ccs_fs_symlink(const char *pathname, struct dentry *base, |
193 |
char *oldname, int mode, uid_t user, gid_t group) |
194 |
{ |
195 |
struct dentry *dentry = ccs_lookup_create2(pathname, base, 0); |
196 |
int error = PTR_ERR(dentry); |
197 |
if (!IS_ERR(dentry)) { |
198 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) && LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 26) |
199 |
#ifdef HAVE_VFSMOUNT_IN_VFS_HELPER |
200 |
error = vfs_symlink(base->d_inode, dentry, NULL, oldname, |
201 |
S_IALLUGO); |
202 |
#else |
203 |
error = vfs_symlink(base->d_inode, dentry, oldname, S_IALLUGO); |
204 |
#endif |
205 |
#else |
206 |
#ifdef HAVE_VFSMOUNT_IN_VFS_HELPER |
207 |
error = vfs_symlink(base->d_inode, dentry, NULL, oldname); |
208 |
#else |
209 |
error = vfs_symlink(base->d_inode, dentry, oldname); |
210 |
#endif |
211 |
#endif |
212 |
if (!error) { |
213 |
lock_kernel(); |
214 |
dentry->d_inode->i_mode = mode; |
215 |
dentry->d_inode->i_uid = user; |
216 |
dentry->d_inode->i_gid = group; |
217 |
unlock_kernel(); |
218 |
} |
219 |
dput(dentry); |
220 |
} |
221 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16) |
222 |
mutex_unlock(&base->d_inode->i_mutex); |
223 |
#else |
224 |
up(&base->d_inode->i_sem); |
225 |
#endif |
226 |
return error; |
227 |
} |
228 |
|
229 |
/* |
230 |
* Format string. |
231 |
* Leading and trailing whitespaces are removed. |
232 |
* Multiple whitespaces are packed into single space. |
233 |
*/ |
234 |
static void ccs_normalize_line(unsigned char *buffer) |
235 |
{ |
236 |
unsigned char *sp = buffer; |
237 |
unsigned char *dp = buffer; |
238 |
bool first = true; |
239 |
while (*sp && (*sp <= ' ' || *sp >= 127)) |
240 |
sp++; |
241 |
while (*sp) { |
242 |
if (!first) |
243 |
*dp++ = ' '; |
244 |
first = false; |
245 |
while (*sp > ' ' && *sp < 127) |
246 |
*dp++ = *sp++; |
247 |
while (*sp && (*sp <= ' ' || *sp >= 127)) |
248 |
sp++; |
249 |
} |
250 |
*dp = '\0'; |
251 |
} |
252 |
|
253 |
/* Convert text form of filename into binary form. */ |
254 |
static void ccs_unescape(char *filename) |
255 |
{ |
256 |
char *cp = filename; |
257 |
char c; |
258 |
char d; |
259 |
char e; |
260 |
if (!cp) |
261 |
return; |
262 |
while (1) { |
263 |
c = *filename++; |
264 |
if (!c) |
265 |
break; |
266 |
if (c != '\\') { |
267 |
*cp++ = c; |
268 |
continue; |
269 |
} |
270 |
c = *filename++; |
271 |
if (c == '\\') { |
272 |
*cp++ = c; |
273 |
continue; |
274 |
} |
275 |
if (c < '0' || c > '3') |
276 |
break; |
277 |
d = *filename++; |
278 |
if (d < '0' || d > '7') |
279 |
break; |
280 |
e = *filename++; |
281 |
if (e < '0' || e > '7') |
282 |
break; |
283 |
*(unsigned char *) cp++ = (unsigned char) |
284 |
(((unsigned char) (c - '0') << 6) |
285 |
+ ((unsigned char) (d - '0') << 3) |
286 |
+ (unsigned char) (e - '0')); |
287 |
} |
288 |
*cp = '\0'; |
289 |
} |
290 |
|
291 |
static char *strdup(const char *str) |
292 |
{ |
293 |
const int len = str ? strlen(str) + 1 : 0; |
294 |
char *cp = kzalloc(len, GFP_KERNEL); |
295 |
if (cp) |
296 |
memmove(cp, str, len); |
297 |
return cp; |
298 |
} |
299 |
|
300 |
/* -1: Not specified, 0: Enforce by default, 1: Accept by default. */ |
301 |
static int ccs_default_mode = -1; |
302 |
|
303 |
#if !defined(MODULE) |
304 |
static int __init ccs_setup(char *str) |
305 |
{ |
306 |
if (!strcmp(str, "accept")) |
307 |
ccs_default_mode = 1; |
308 |
else if (!strcmp(str, "enforce")) |
309 |
ccs_default_mode = 0; |
310 |
return 0; |
311 |
} |
312 |
|
313 |
__setup("SYAORAN=", ccs_setup); |
314 |
#endif |
315 |
|
316 |
/* The structure for possible device list. */ |
317 |
struct dev_entry { |
318 |
struct list_head list; |
319 |
/* Binary form of pathname under mount point. Never NULL. */ |
320 |
char *name; |
321 |
/* |
322 |
* Mode and permissions. |
323 |
* setuid/setgid/sticky bits are not supported. |
324 |
*/ |
325 |
mode_t mode; |
326 |
uid_t uid; |
327 |
gid_t gid; |
328 |
dev_t kdev; |
329 |
/* |
330 |
* Binary form of initial contents for the symlink. NULL if not symlink. |
331 |
*/ |
332 |
char *symlink_data; |
333 |
/* File access control flags. */ |
334 |
unsigned int flags; |
335 |
/* Text form of pathname under mount point. Never NULL. */ |
336 |
const char *printable_name; |
337 |
/* |
338 |
* Text form of initial contents for the symlink. NULL if not symlink. |
339 |
*/ |
340 |
const char *printable_symlink_data; |
341 |
}; |
342 |
|
343 |
struct ccs_sb_info { |
344 |
struct list_head list; |
345 |
bool initialize_done; /* False if initialization is in progress. */ |
346 |
bool is_permissive_mode; /* True if permissive mode. */ |
347 |
}; |
348 |
|
349 |
static int ccs_register_node_info(char *buffer, struct super_block *sb) |
350 |
{ |
351 |
enum { |
352 |
ARG_FILENAME = 0, |
353 |
ARG_PERMISSION = 1, |
354 |
ARG_UID = 2, |
355 |
ARG_GID = 3, |
356 |
ARG_FLAGS = 4, |
357 |
ARG_DEV_TYPE = 5, |
358 |
ARG_SYMLINK_DATA = 6, |
359 |
ARG_DEV_MAJOR = 6, |
360 |
ARG_DEV_MINOR = 7, |
361 |
MAX_ARG = 8 |
362 |
}; |
363 |
char *args[MAX_ARG]; |
364 |
int i; |
365 |
int error = -EINVAL; |
366 |
unsigned int perm; |
367 |
unsigned int uid; |
368 |
unsigned int gid; |
369 |
unsigned int flags; |
370 |
unsigned int major = 0; |
371 |
unsigned int minor = 0; |
372 |
struct ccs_sb_info *info = |
373 |
(struct ccs_sb_info *) sb->s_fs_info; |
374 |
struct dev_entry *entry; |
375 |
if (!info) |
376 |
return -EINVAL; |
377 |
memset(args, 0, sizeof(args)); |
378 |
args[0] = buffer; |
379 |
for (i = 1; i < MAX_ARG; i++) { |
380 |
args[i] = strchr(args[i - 1] + 1, ' '); |
381 |
if (!args[i]) |
382 |
break; |
383 |
*args[i]++ = '\0'; |
384 |
} |
385 |
/* |
386 |
printk(KERN_DEBUG "<%s> <%s> <%s> <%s> <%s> <%s> <%s> <%s>\n", |
387 |
args[0], args[1], args[2], args[3], args[4], args[5], args[6], |
388 |
args[7]); |
389 |
*/ |
390 |
if (!args[ARG_FILENAME] || !args[ARG_PERMISSION] || !args[ARG_UID] || |
391 |
!args[ARG_GID] || !args[ARG_DEV_TYPE] || !args[ARG_FLAGS]) |
392 |
goto out; |
393 |
if (sscanf(args[ARG_PERMISSION], "%o", &perm) != 1 || |
394 |
!(perm <= 0777) || sscanf(args[ARG_UID], "%u", &uid) != 1 || |
395 |
sscanf(args[ARG_GID], "%u", &gid) != 1 || |
396 |
sscanf(args[ARG_FLAGS], "%u", &flags) != 1 || |
397 |
*(args[ARG_DEV_TYPE] + 1)) |
398 |
goto out; |
399 |
switch (*args[ARG_DEV_TYPE]) { |
400 |
case 'c': |
401 |
perm |= S_IFCHR; |
402 |
if (!args[ARG_DEV_MAJOR] || |
403 |
sscanf(args[ARG_DEV_MAJOR], "%u", &major) != 1 || |
404 |
!args[ARG_DEV_MINOR] || |
405 |
sscanf(args[ARG_DEV_MINOR], "%u", &minor) != 1) |
406 |
goto out; |
407 |
break; |
408 |
case 'b': |
409 |
perm |= S_IFBLK; |
410 |
if (!args[ARG_DEV_MAJOR] || |
411 |
sscanf(args[ARG_DEV_MAJOR], "%u", &major) != 1 || |
412 |
!args[ARG_DEV_MINOR] || |
413 |
sscanf(args[ARG_DEV_MINOR], "%u", &minor) != 1) |
414 |
goto out; |
415 |
break; |
416 |
case 'l': |
417 |
perm |= S_IFLNK; |
418 |
if (!args[ARG_SYMLINK_DATA]) |
419 |
goto out; |
420 |
break; |
421 |
case 'd': |
422 |
perm |= S_IFDIR; |
423 |
break; |
424 |
case 's': |
425 |
perm |= S_IFSOCK; |
426 |
break; |
427 |
case 'p': |
428 |
perm |= S_IFIFO; |
429 |
break; |
430 |
case 'f': |
431 |
perm |= S_IFREG; |
432 |
break; |
433 |
default: |
434 |
goto out; |
435 |
} |
436 |
error = -ENOMEM; |
437 |
entry = kzalloc(sizeof(*entry), GFP_KERNEL); |
438 |
if (!entry) |
439 |
goto out; |
440 |
if (S_ISLNK(perm)) { |
441 |
entry->printable_symlink_data = strdup(args[ARG_SYMLINK_DATA]); |
442 |
if (!entry->printable_symlink_data) |
443 |
goto out_freemem; |
444 |
} |
445 |
entry->printable_name = strdup(args[ARG_FILENAME]); |
446 |
if (!entry->printable_name) |
447 |
goto out_freemem; |
448 |
if (S_ISLNK(perm)) { |
449 |
entry->symlink_data = strdup(entry->printable_symlink_data); |
450 |
if (!entry->symlink_data) |
451 |
goto out_freemem; |
452 |
ccs_unescape(entry->symlink_data); |
453 |
} |
454 |
entry->name = strdup(entry->printable_name); |
455 |
if (!entry->name) |
456 |
goto out_freemem; |
457 |
ccs_unescape(entry->name); |
458 |
{ |
459 |
/* |
460 |
* Drop trailing '/', for get_local_absolute_path() doesn't |
461 |
* append trailing '/'. |
462 |
*/ |
463 |
const int len = strlen(entry->name); |
464 |
if (len && entry->name[len - 1] == '/') |
465 |
entry->name[len - 1] = '\0'; |
466 |
} |
467 |
entry->mode = perm; |
468 |
entry->uid = uid; |
469 |
entry->gid = gid; |
470 |
entry->kdev = S_ISCHR(perm) || S_ISBLK(perm) ? MKDEV(major, minor) : 0; |
471 |
entry->flags = flags; |
472 |
list_add_tail(&entry->list, &info->list); |
473 |
/* printk(KERN_DEBUG "Entry added.\n"); */ |
474 |
error = 0; |
475 |
out: |
476 |
return error; |
477 |
out_freemem: |
478 |
kfree(entry->printable_symlink_data); |
479 |
kfree(entry->printable_name); |
480 |
kfree(entry->symlink_data); |
481 |
kfree(entry); |
482 |
goto out; |
483 |
} |
484 |
|
485 |
static void ccs_put_super(struct super_block *sb) |
486 |
{ |
487 |
struct ccs_sb_info *info; |
488 |
struct dev_entry *entry; |
489 |
struct dev_entry *tmp; |
490 |
if (!sb) |
491 |
return; |
492 |
info = (struct ccs_sb_info *) sb->s_fs_info; |
493 |
if (!info) |
494 |
return; |
495 |
sb->s_fs_info = NULL; |
496 |
list_for_each_entry_safe(entry, tmp, &info->list, list) { |
497 |
kfree(entry->name); |
498 |
kfree(entry->symlink_data); |
499 |
kfree(entry->printable_name); |
500 |
kfree(entry->printable_symlink_data); |
501 |
list_del(&entry->list); |
502 |
/* printk(KERN_DEBUG "Entry removed.\n"); */ |
503 |
kfree(entry); |
504 |
} |
505 |
kfree(info); |
506 |
printk(KERN_INFO "%s: Unused memory freed.\n", __func__); |
507 |
} |
508 |
|
509 |
static int ccs_read_config_file(struct file *file, struct super_block *sb) |
510 |
{ |
511 |
char *buffer; |
512 |
int len; |
513 |
char *cp; |
514 |
unsigned long offset = 0; |
515 |
int error = -ENOMEM; |
516 |
if (!file) |
517 |
return -EINVAL; |
518 |
buffer = kzalloc(PAGE_SIZE, GFP_KERNEL); |
519 |
if (!buffer) |
520 |
goto out; |
521 |
while (1) { |
522 |
len = kernel_read(file, offset, buffer, PAGE_SIZE); |
523 |
if (len <= 0) |
524 |
break; |
525 |
cp = memchr(buffer, '\n', len); |
526 |
if (!cp) |
527 |
break; |
528 |
*cp = '\0'; |
529 |
offset += cp - buffer + 1; |
530 |
ccs_normalize_line(buffer); |
531 |
if (ccs_register_node_info(buffer, sb) == -ENOMEM) |
532 |
goto out; |
533 |
} |
534 |
error = 0; |
535 |
out: |
536 |
kfree(buffer); |
537 |
return error; |
538 |
} |
539 |
|
540 |
static void ccs_make_node(struct dev_entry *entry, struct dentry *root) |
541 |
{ |
542 |
struct dentry *base = dget(root); |
543 |
char *filename = entry->name; |
544 |
char *name = filename; |
545 |
unsigned int c; |
546 |
const mode_t perm = entry->mode; |
547 |
const uid_t uid = entry->uid; |
548 |
const gid_t gid = entry->gid; |
549 |
goto start; |
550 |
while (1) { |
551 |
c = *(unsigned char *) filename; |
552 |
if (!c) |
553 |
break; |
554 |
if (c == '/') { |
555 |
struct dentry *new_base; |
556 |
const int len = filename - name; |
557 |
*filename = '\0'; |
558 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16) |
559 |
mutex_lock(&base->d_inode->i_mutex); |
560 |
new_base = lookup_one_len(name, base, len); |
561 |
mutex_unlock(&base->d_inode->i_mutex); |
562 |
#else |
563 |
down(&base->d_inode->i_sem); |
564 |
new_base = lookup_one_len(name, base, len); |
565 |
up(&base->d_inode->i_sem); |
566 |
#endif |
567 |
dput(base); |
568 |
*filename++ = '/'; |
569 |
if (IS_ERR(new_base)) { |
570 |
/* |
571 |
printk(KERN_DEBUG "'%s' = %ld\n", entry->name, |
572 |
PTR_ERR(new_base)); |
573 |
*/ |
574 |
return; |
575 |
} else if (!new_base->d_inode || |
576 |
!S_ISDIR(new_base->d_inode->i_mode)) { |
577 |
/* |
578 |
printk(KERN_DEBUG |
579 |
"Directory '%s' does not exist.\n", |
580 |
entry->name); |
581 |
*/ |
582 |
dput(new_base); |
583 |
return; |
584 |
} |
585 |
/* |
586 |
printk(KERN_DEBUG "Directory '%s' exists.\n", |
587 |
entry->name); |
588 |
*/ |
589 |
base = new_base; |
590 |
start: |
591 |
name = filename; |
592 |
} else { |
593 |
filename++; |
594 |
} |
595 |
} |
596 |
filename = (char *) name; |
597 |
if (S_ISLNK(perm)) |
598 |
ccs_fs_symlink(filename, base, entry->symlink_data, perm, uid, |
599 |
gid); |
600 |
else if (S_ISDIR(perm)) |
601 |
ccs_fs_mkdir(filename, base, perm ^ S_IFDIR, uid, gid); |
602 |
else if (S_ISSOCK(perm) || S_ISFIFO(perm) || S_ISREG(perm)) |
603 |
ccs_fs_mknod(filename, base, perm, 0, uid, gid); |
604 |
else if (S_ISCHR(perm) || S_ISBLK(perm)) |
605 |
ccs_fs_mknod(filename, base, perm, entry->kdev, uid, gid); |
606 |
dput(base); |
607 |
} |
608 |
|
609 |
/* Create files according to the policy file. */ |
610 |
static void ccs_make_initial_nodes(struct super_block *sb) |
611 |
{ |
612 |
struct ccs_sb_info *info; |
613 |
struct dev_entry *entry; |
614 |
if (!sb) |
615 |
return; |
616 |
info = (struct ccs_sb_info *) sb->s_fs_info; |
617 |
if (!info) |
618 |
return; |
619 |
if (info->is_permissive_mode) { |
620 |
ccs_create_tracelog(sb, ".syaoran"); |
621 |
ccs_create_tracelog(sb, ".syaoran_all"); |
622 |
} |
623 |
list_for_each_entry(entry, &info->list, list) { |
624 |
if ((entry->flags & NO_CREATE_AT_MOUNT) == 0) |
625 |
ccs_make_node(entry, sb->s_root); |
626 |
} |
627 |
info->initialize_done = true; |
628 |
} |
629 |
|
630 |
/* Read policy file. */ |
631 |
static int ccs_initialize(struct super_block *sb, void *data) |
632 |
{ |
633 |
int error = -EINVAL; |
634 |
struct file *f; |
635 |
char *filename = (char *) data; |
636 |
bool is_permissive_mode = ccs_default_mode; |
637 |
static bool first = true; |
638 |
if (first) { |
639 |
first = false; |
640 |
printk(KERN_INFO "SYAORAN: 1.6.8 2009/05/28\n"); |
641 |
} |
642 |
{ |
643 |
struct inode *inode = new_inode(sb); |
644 |
if (!inode) |
645 |
return -EINVAL; |
646 |
/* Create /dev/ram0 to get the value of blkdev_open(). */ |
647 |
init_special_inode(inode, S_IFBLK | 0666, MKDEV(1, 0)); |
648 |
ccs_wrapped_def_blk_fops = *inode->i_fop; |
649 |
iput(inode); |
650 |
ccs_org_blkdev_open = ccs_wrapped_def_blk_fops.open; |
651 |
ccs_wrapped_def_blk_fops.open = ccs_wrapped_blkdev_open; |
652 |
} |
653 |
{ |
654 |
struct inode *inode = new_inode(sb); |
655 |
if (!inode) |
656 |
return -EINVAL; |
657 |
/* Create /dev/null to get the value of chrdev_open(). */ |
658 |
init_special_inode(inode, S_IFCHR | 0666, MKDEV(1, 3)); |
659 |
ccs_wrapped_def_chr_fops = *inode->i_fop; |
660 |
iput(inode); |
661 |
ccs_org_chrdev_open = ccs_wrapped_def_chr_fops.open; |
662 |
ccs_wrapped_def_chr_fops.open = ccs_wrapped_chrdev_open; |
663 |
} |
664 |
if (!filename) { |
665 |
printk(KERN_WARNING "SYAORAN: Missing config-file path.\n"); |
666 |
return -EINVAL; |
667 |
} |
668 |
/* If mode is given with mount operation, use it. */ |
669 |
if (!strncmp(filename, "accept=", 7)) { |
670 |
filename += 7; |
671 |
is_permissive_mode = true; |
672 |
} else if (!strncmp(filename, "enforce=", 8)) { |
673 |
filename += 8; |
674 |
is_permissive_mode = false; |
675 |
} else if (ccs_default_mode == -1) { |
676 |
/* |
677 |
* If mode is not given with command line, |
678 |
* abort mount. |
679 |
*/ |
680 |
printk(KERN_WARNING |
681 |
"SYAORAN: Missing 'accept=' or 'enforce='.\n"); |
682 |
return -EINVAL; |
683 |
} |
684 |
f = filp_open(filename, O_RDONLY, 0600); |
685 |
if (IS_ERR(f)) { |
686 |
printk(KERN_WARNING "SYAORAN: Can't open '%s'\n", filename); |
687 |
return -EINVAL; |
688 |
} |
689 |
if (!S_ISREG(f->f_dentry->d_inode->i_mode)) |
690 |
goto out; |
691 |
sb->s_fs_info = kzalloc(sizeof(struct ccs_sb_info), GFP_KERNEL); |
692 |
if (!sb->s_fs_info) |
693 |
goto out; |
694 |
((struct ccs_sb_info *) sb->s_fs_info)->is_permissive_mode |
695 |
= is_permissive_mode; |
696 |
INIT_LIST_HEAD(&((struct ccs_sb_info *) sb->s_fs_info)->list); |
697 |
printk(KERN_INFO "SYAORAN: Reading '%s'\n", filename); |
698 |
error = ccs_read_config_file(f, sb); |
699 |
out: |
700 |
if (error) |
701 |
printk(KERN_WARNING "SYAORAN: Can't read '%s'\n", filename); |
702 |
filp_close(f, NULL); |
703 |
return error; |
704 |
} |
705 |
|
706 |
/* Get absolute pathname from mount point. */ |
707 |
static int get_local_absolute_path(struct dentry *dentry, char *buffer, |
708 |
int buflen) |
709 |
{ |
710 |
/***** CRITICAL SECTION START *****/ |
711 |
char *start = buffer; |
712 |
char *end = buffer + buflen; |
713 |
int namelen; |
714 |
|
715 |
if (buflen < 256) |
716 |
goto out; |
717 |
|
718 |
*--end = '\0'; |
719 |
buflen--; |
720 |
for (;;) { |
721 |
struct dentry *parent; |
722 |
if (IS_ROOT(dentry)) |
723 |
break; |
724 |
parent = dentry->d_parent; |
725 |
namelen = dentry->d_name.len; |
726 |
buflen -= namelen + 1; |
727 |
if (buflen < 0) |
728 |
goto out; |
729 |
end -= namelen; |
730 |
memcpy(end, dentry->d_name.name, namelen); |
731 |
*--end = '/'; |
732 |
dentry = parent; |
733 |
} |
734 |
if (*end == '/') { |
735 |
buflen++; |
736 |
end++; |
737 |
} |
738 |
namelen = dentry->d_name.len; |
739 |
buflen -= namelen; |
740 |
if (buflen < 0) |
741 |
goto out; |
742 |
end -= namelen; |
743 |
memcpy(end, dentry->d_name.name, namelen); |
744 |
memmove(start, end, strlen(end) + 1); |
745 |
return 0; |
746 |
out: |
747 |
return -ENOMEM; |
748 |
/***** CRITICAL SECTION END *****/ |
749 |
} |
750 |
|
751 |
/* Get absolute pathname of the given dentry from mount point. */ |
752 |
static int ccs_local_realpath_from_dentry(struct dentry *dentry, char *newname, |
753 |
int newname_len) |
754 |
{ |
755 |
/***** CRITICAL SECTION START *****/ |
756 |
int error; |
757 |
struct dentry *d_dentry; |
758 |
if (!dentry || !newname || newname_len <= 0) |
759 |
return -EINVAL; |
760 |
d_dentry = dget(dentry); |
761 |
spin_lock(&dcache_lock); |
762 |
error = get_local_absolute_path(d_dentry, newname, newname_len); |
763 |
spin_unlock(&dcache_lock); |
764 |
dput(d_dentry); |
765 |
return error; |
766 |
/***** CRITICAL SECTION END *****/ |
767 |
} |
768 |
|
769 |
static int ccs_check_flags(struct ccs_sb_info *info, |
770 |
struct dentry *dentry, |
771 |
int mode, int dev, unsigned int flags) |
772 |
{ |
773 |
int error; |
774 |
/* |
775 |
* I use static buffer, for ccs_local_realpath_from_dentry() needs |
776 |
* dcache_lock. |
777 |
*/ |
778 |
static char filename[PAGE_SIZE]; |
779 |
static DEFINE_SPINLOCK(lock); |
780 |
/***** CRITICAL SECTION START *****/ |
781 |
spin_lock(&lock); |
782 |
memset(filename, 0, sizeof(filename)); |
783 |
error = ccs_local_realpath_from_dentry(dentry, filename, |
784 |
sizeof(filename) - 1); |
785 |
if (!error) { |
786 |
struct dev_entry *entry; |
787 |
error = -EPERM; |
788 |
list_for_each_entry(entry, &info->list, list) { |
789 |
if ((mode & S_IFMT) != (entry->mode & S_IFMT)) |
790 |
continue; |
791 |
if ((S_ISBLK(mode) || S_ISCHR(mode)) && |
792 |
dev != entry->kdev) |
793 |
continue; |
794 |
if (strcmp(entry->name, filename + 1)) |
795 |
continue; |
796 |
if (info->is_permissive_mode) { |
797 |
entry->flags |= flags; |
798 |
error = 0; |
799 |
} else if ((entry->flags & flags) == flags) |
800 |
error = 0; |
801 |
break; |
802 |
} |
803 |
} |
804 |
if (error == -EPERM) { |
805 |
const char *name; |
806 |
const uid_t uid = current_fsuid(); |
807 |
const gid_t gid = current_fsgid(); |
808 |
const mode_t perm = mode & 0777; |
809 |
flags &= ~DEVICE_USED; |
810 |
{ |
811 |
char *end = filename + sizeof(filename) - 1; |
812 |
const char *cp = filename + strlen(filename) - 1; |
813 |
while (cp > filename && end > cp && |
814 |
end > filename + 16) { |
815 |
const unsigned char c = *cp--; |
816 |
if (c == '\\') { |
817 |
*--end = '\\'; |
818 |
*--end = '\\'; |
819 |
} else if (c > ' ' && c < 127) { |
820 |
*--end = c; |
821 |
} else { |
822 |
*--end = (c & 7) + '0'; |
823 |
*--end = ((c >> 3) & 7) + '0'; |
824 |
*--end = (c >> 6) + '0'; |
825 |
*--end = '\\'; |
826 |
} |
827 |
} |
828 |
name = end; |
829 |
} |
830 |
switch (mode & S_IFMT) { |
831 |
case S_IFCHR: |
832 |
printk(KERN_DEBUG |
833 |
"SYAORAN-ERROR: %s %3o %3u %3u %2u %c %3u %3u\n", |
834 |
name, perm, uid, gid, flags, 'c', |
835 |
MAJOR(dev), MINOR(dev)); |
836 |
break; |
837 |
case S_IFBLK: |
838 |
printk(KERN_DEBUG |
839 |
"SYAORAN-ERROR: %s %3o %3u %3u %2u %c %3u %3u\n", |
840 |
name, perm, uid, gid, flags, 'b', |
841 |
MAJOR(dev), MINOR(dev)); |
842 |
break; |
843 |
case S_IFIFO: |
844 |
printk(KERN_DEBUG |
845 |
"SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n", name, |
846 |
perm, uid, gid, flags, 'p'); |
847 |
break; |
848 |
case S_IFSOCK: |
849 |
printk(KERN_DEBUG |
850 |
"SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n", name, |
851 |
perm, uid, gid, flags, 's'); |
852 |
break; |
853 |
case S_IFDIR: |
854 |
printk(KERN_DEBUG |
855 |
"SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n", name, |
856 |
perm, uid, gid, flags, 'd'); |
857 |
break; |
858 |
case S_IFLNK: |
859 |
printk(KERN_DEBUG |
860 |
"SYAORAN-ERROR: %s %3o %3u %3u %2u %c %s\n", |
861 |
name, perm, uid, gid, flags, 'l', "unknown"); |
862 |
break; |
863 |
case S_IFREG: |
864 |
printk(KERN_DEBUG |
865 |
"SYAORAN-ERROR: %s %3o %3u %3u %2u %c\n", name, |
866 |
perm, uid, gid, flags, 'f'); |
867 |
break; |
868 |
} |
869 |
} |
870 |
spin_unlock(&lock); |
871 |
/***** CRITICAL SECTION END *****/ |
872 |
return error; |
873 |
} |
874 |
|
875 |
/* Check whether the given dentry is allowed to mknod. */ |
876 |
static int ccs_may_create_node(struct dentry *dentry, int mode, int dev) |
877 |
{ |
878 |
struct ccs_sb_info *info |
879 |
= (struct ccs_sb_info *) dentry->d_sb->s_fs_info; |
880 |
if (!info) { |
881 |
printk(KERN_WARNING "%s: dentry->d_sb->s_fs_info == NULL\n", |
882 |
__func__); |
883 |
return -EPERM; |
884 |
} |
885 |
if (!info->initialize_done) |
886 |
return 0; |
887 |
return ccs_check_flags(info, dentry, mode, dev, MAY_CREATE); |
888 |
} |
889 |
|
890 |
/* Check whether the given dentry is allowed to chmod/chown/unlink. */ |
891 |
static int ccs_may_modify_node(struct dentry *dentry, unsigned int flags) |
892 |
{ |
893 |
struct ccs_sb_info *info |
894 |
= (struct ccs_sb_info *) dentry->d_sb->s_fs_info; |
895 |
if (!info) { |
896 |
printk(KERN_WARNING "%s: dentry->d_sb->s_fs_info == NULL\n", |
897 |
__func__); |
898 |
return -EPERM; |
899 |
} |
900 |
if (flags == DEVICE_USED && !info->is_permissive_mode) |
901 |
return 0; |
902 |
if (!dentry->d_inode) |
903 |
return -ENOENT; |
904 |
return ccs_check_flags(info, dentry, dentry->d_inode->i_mode, |
905 |
dentry->d_inode->i_rdev, flags); |
906 |
} |
907 |
|
908 |
/* |
909 |
* The following structure and codes are used for transferring data |
910 |
* to interfaces files. |
911 |
*/ |
912 |
|
913 |
struct ccs_read_struct { |
914 |
char *buf; /* Buffer for reading. */ |
915 |
int avail; /* Bytes available for reading. */ |
916 |
struct super_block *sb; /* The super_block of this partition. */ |
917 |
struct dev_entry *entry; /* The entry currently reading from. */ |
918 |
bool read_all; /* Print all entries? */ |
919 |
struct list_head *pos; /* Current position. */ |
920 |
}; |
921 |
|
922 |
static void ccs_read_table(struct ccs_read_struct *head, char *buf, |
923 |
int count) |
924 |
{ |
925 |
struct super_block *sb = head->sb; |
926 |
struct ccs_sb_info *info = (struct ccs_sb_info *) sb->s_fs_info; |
927 |
struct list_head *pos; |
928 |
const bool read_all = head->read_all; |
929 |
if (!info) |
930 |
return; |
931 |
if (!head->pos) |
932 |
return; |
933 |
list_for_each_cookie(pos, head->pos, &info->list) { |
934 |
struct dev_entry *entry |
935 |
= list_entry(pos, struct dev_entry, list); |
936 |
const unsigned int flags |
937 |
= read_all ? entry->flags : entry->flags & ~DEVICE_USED; |
938 |
const char *name = entry->printable_name; |
939 |
const uid_t uid = entry->uid; |
940 |
const gid_t gid = entry->gid; |
941 |
const mode_t perm = entry->mode & 0777; |
942 |
int len = 0; |
943 |
switch (entry->mode & S_IFMT) { |
944 |
case S_IFCHR: |
945 |
if (!head->read_all && !(entry->flags & DEVICE_USED)) |
946 |
break; |
947 |
len = snprintf(buf, count, |
948 |
"%-20s %3o %3u %3u %2u %c %3u %3u\n", |
949 |
name, perm, uid, gid, flags, 'c', |
950 |
MAJOR(entry->kdev), MINOR(entry->kdev)); |
951 |
break; |
952 |
case S_IFBLK: |
953 |
if (!head->read_all && !(entry->flags & DEVICE_USED)) |
954 |
break; |
955 |
len = snprintf(buf, count, |
956 |
"%-20s %3o %3u %3u %2u %c %3u %3u\n", |
957 |
name, perm, uid, gid, flags, 'b', |
958 |
MAJOR(entry->kdev), MINOR(entry->kdev)); |
959 |
break; |
960 |
case S_IFIFO: |
961 |
len = snprintf(buf, count, "%-20s %3o %3u %3u %2u %c\n", |
962 |
name, perm, uid, gid, flags, 'p'); |
963 |
break; |
964 |
case S_IFSOCK: |
965 |
len = snprintf(buf, count, "%-20s %3o %3u %3u %2u %c\n", |
966 |
name, perm, uid, gid, flags, 's'); |
967 |
break; |
968 |
case S_IFDIR: |
969 |
len = snprintf(buf, count, "%-20s %3o %3u %3u %2u %c\n", |
970 |
name, perm, uid, gid, flags, 'd'); |
971 |
break; |
972 |
case S_IFLNK: |
973 |
len = snprintf(buf, count, |
974 |
"%-20s %3o %3u %3u %2u %c %s\n", name, |
975 |
perm, uid, gid, flags, 'l', |
976 |
entry->printable_symlink_data); |
977 |
break; |
978 |
case S_IFREG: |
979 |
len = snprintf(buf, count, "%-20s %3o %3u %3u %2u %c\n", |
980 |
name, perm, uid, gid, flags, 'f'); |
981 |
break; |
982 |
} |
983 |
if (len < 0 || count <= len) |
984 |
break; |
985 |
count -= len; |
986 |
buf += len; |
987 |
head->avail += len; |
988 |
} |
989 |
} |
990 |
|
991 |
static int ccs_trace_open(struct inode *inode, struct file *file) |
992 |
{ |
993 |
struct ccs_read_struct *head = kzalloc(sizeof(*head), GFP_KERNEL); |
994 |
if (!head) |
995 |
return -ENOMEM; |
996 |
head->sb = inode->i_sb; |
997 |
head->read_all |
998 |
= (strcmp(file->f_dentry->d_name.name, ".syaoran_all") == 0); |
999 |
head->pos = &((struct ccs_sb_info *) head->sb->s_fs_info)->list; |
1000 |
/* Don't allow open() after unmount() */ |
1001 |
if (head->sb->s_fs_info) |
1002 |
head->buf = kzalloc(PAGE_SIZE * 2, GFP_KERNEL); |
1003 |
if (!head->buf) { |
1004 |
kfree(head); |
1005 |
return -ENOMEM; |
1006 |
} |
1007 |
file->private_data = head; |
1008 |
return 0; |
1009 |
} |
1010 |
|
1011 |
static int ccs_trace_release(struct inode *inode, struct file *file) |
1012 |
{ |
1013 |
struct ccs_read_struct *head = file->private_data; |
1014 |
kfree(head->buf); |
1015 |
kfree(head); |
1016 |
file->private_data = NULL; |
1017 |
return 0; |
1018 |
} |
1019 |
|
1020 |
static ssize_t ccs_trace_read(struct file *file, char __user *buf, |
1021 |
size_t count, loff_t *ppos) |
1022 |
{ |
1023 |
struct ccs_read_struct *head |
1024 |
= (struct ccs_read_struct *) file->private_data; |
1025 |
int len = head->avail; |
1026 |
char *cp = head->buf; |
1027 |
if (!access_ok(VERIFY_WRITE, buf, count)) |
1028 |
return -EFAULT; |
1029 |
ccs_read_table(head, cp + len, PAGE_SIZE * 2 - len); |
1030 |
len = head->avail; |
1031 |
if (len > count) |
1032 |
len = count; |
1033 |
if (len > 0) { |
1034 |
if (copy_to_user(buf, cp, len)) |
1035 |
return -EFAULT; |
1036 |
head->avail -= len; |
1037 |
memmove(cp, cp + len, head->avail); |
1038 |
} |
1039 |
return len; |
1040 |
} |
1041 |
|
1042 |
static struct file_operations ccs_trace_operations = { |
1043 |
.open = ccs_trace_open, |
1044 |
.release = ccs_trace_release, |
1045 |
.read = ccs_trace_read, |
1046 |
}; |
1047 |
|
1048 |
/* Create interface files for reading status. */ |
1049 |
static int ccs_create_tracelog(struct super_block *sb, const char *filename) |
1050 |
{ |
1051 |
struct dentry *base = dget(sb->s_root); |
1052 |
struct dentry *dentry = ccs_lookup_create2(filename, base, 0); |
1053 |
int error = PTR_ERR(dentry); |
1054 |
if (!IS_ERR(dentry)) { |
1055 |
struct inode *inode = new_inode(sb); |
1056 |
if (inode) { |
1057 |
inode->i_mode = S_IFREG | 0400; |
1058 |
inode->i_uid = 0; |
1059 |
inode->i_gid = 0; |
1060 |
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) |
1061 |
#ifndef HAVE_NO_I_BLKSIZE_IN_INODE |
1062 |
inode->i_blksize = PAGE_CACHE_SIZE; |
1063 |
#endif |
1064 |
#endif |
1065 |
inode->i_blocks = 0; |
1066 |
inode->i_mapping->a_ops = &ccs_aops; |
1067 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) |
1068 |
inode->i_mapping->backing_dev_info |
1069 |
= &ccs_backing_dev_info; |
1070 |
inode->i_op = &ccs_file_inode_operations; |
1071 |
#else |
1072 |
inode->i_rdev = NODEV; |
1073 |
#endif |
1074 |
inode->i_ctime = CURRENT_TIME; |
1075 |
inode->i_mtime = inode->i_ctime; |
1076 |
inode->i_atime = inode->i_mtime; |
1077 |
inode->i_fop = &ccs_trace_operations; |
1078 |
d_instantiate(dentry, inode); |
1079 |
dget(dentry); /* Extra count - pin the dentry in core */ |
1080 |
error = 0; |
1081 |
} |
1082 |
dput(dentry); |
1083 |
} |
1084 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16) |
1085 |
mutex_unlock(&base->d_inode->i_mutex); |
1086 |
#else |
1087 |
up(&base->d_inode->i_sem); |
1088 |
#endif |
1089 |
dput(base); |
1090 |
return error; |
1091 |
} |
1092 |
|
1093 |
#endif |