オープンソース・ソフトウェアの開発とダウンロード

Subversion リポジトリの参照

Contents of /trunk/1.8.x/ccs-patch/security/ccsecurity/domain.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2915 - (show annotations) (download) (as text)
Mon Aug 17 10:56:21 2009 UTC (14 years, 9 months ago) by kumaneko
Original Path: branches/ccs-patch/security/ccsecurity/domain.c
File MIME type: text/x-csrc
File size: 35822 byte(s)


1 /*
2 * security/ccsecurity/domain.c
3 *
4 * Copyright (C) 2005-2009 NTT DATA CORPORATION
5 *
6 * Version: 1.7.0-pre 2009/08/08
7 *
8 * This file is applicable to both 2.4.30 and 2.6.11 and later.
9 * See README.ccs for ChangeLog.
10 *
11 */
12
13 #include <linux/slab.h>
14 #include <linux/highmem.h>
15 #include <linux/version.h>
16 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
17 #include <linux/namei.h>
18 #include <linux/mount.h>
19 #endif
20 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30)
21 #include <linux/fs_struct.h>
22 #endif
23 #include "internal.h"
24
25 /* For compatibility with older kernels. */
26 #ifndef for_each_process
27 #define for_each_process for_each_task
28 #endif
29
30 /* Variables definitions.*/
31
32 /* The initial domain. */
33 struct ccs_domain_info ccs_kernel_domain;
34
35 /* The list for "struct ccs_domain_info". */
36 LIST_HEAD(ccs_domain_list);
37
38 /**
39 * ccs_get_last_name - Get last component of a domainname.
40 *
41 * @domain: Pointer to "struct ccs_domain_info".
42 *
43 * Returns the last component of the domainname.
44 */
45 const char *ccs_get_last_name(const struct ccs_domain_info *domain)
46 {
47 const char *cp0 = domain->domainname->name;
48 const char *cp1 = strrchr(cp0, ' ');
49 if (cp1)
50 return cp1 + 1;
51 return cp0;
52 }
53
54 /**
55 * ccs_audit_execute_handler_log - Audit execute_handler log.
56 *
57 * @ee: Pointer to "struct ccs_execve_entry".
58 * @is_default: True if it is "execute_handler" log.
59 *
60 * Returns 0 on success, negative value otherwise.
61 */
62 static int ccs_audit_execute_handler_log(struct ccs_execve_entry *ee,
63 const bool is_default)
64 {
65 struct ccs_request_info *r = &ee->r;
66 const char *handler = ee->handler->name;
67 r->mode = ccs_check_flags(r->domain, CCS_MAC_EXECUTE);
68 return ccs_write_audit_log(true, r, "%s %s\n",
69 is_default ? CCS_KEYWORD_EXECUTE_HANDLER :
70 CCS_KEYWORD_DENIED_EXECUTE_HANDLER, handler);
71 }
72
73 /**
74 * ccs_audit_domain_creation_log - Audit domain creation log.
75 *
76 * @domain: Pointer to "struct ccs_domain_info".
77 *
78 * Returns 0 on success, negative value otherwise.
79 */
80 static int ccs_audit_domain_creation_log(struct ccs_domain_info *domain)
81 {
82 int error;
83 struct ccs_request_info r;
84 ccs_init_request_info(&r, domain, CCS_MAC_EXECUTE);
85 error = ccs_write_audit_log(false, &r, "use_profile %u\n", r.profile);
86 return error;
87 }
88
89 /* The list for "struct ccs_domain_initializer_entry". */
90 LIST_HEAD(ccs_domain_initializer_list);
91
92 /**
93 * ccs_update_domain_initializer_entry - Update "struct ccs_domain_initializer_entry" list.
94 *
95 * @domainname: The name of domain. May be NULL.
96 * @program: The name of program.
97 * @is_not: True if it is "no_initialize_domain" entry.
98 * @is_delete: True if it is a delete request.
99 *
100 * Returns 0 on success, negative value otherwise.
101 */
102 static int ccs_update_domain_initializer_entry(const char *domainname,
103 const char *program,
104 const bool is_not,
105 const bool is_delete)
106 {
107 struct ccs_domain_initializer_entry *entry = NULL;
108 struct ccs_domain_initializer_entry *ptr;
109 struct ccs_domain_initializer_entry e = { .is_not = is_not };
110 int error = is_delete ? -ENOENT : -ENOMEM;
111 if (!ccs_is_correct_path(program, 1, -1, -1))
112 return -EINVAL; /* No patterns allowed. */
113 if (domainname) {
114 if (!ccs_is_domain_def(domainname) &&
115 ccs_is_correct_path(domainname, 1, -1, -1))
116 e.is_last_name = true;
117 else if (!ccs_is_correct_domain(domainname))
118 return -EINVAL;
119 e.domainname = ccs_get_name(domainname);
120 if (!e.domainname)
121 goto out;
122 }
123 e.program = ccs_get_name(program);
124 if (!e.program)
125 goto out;
126 if (!is_delete)
127 entry = kmalloc(sizeof(e), GFP_KERNEL);
128 mutex_lock(&ccs_policy_lock);
129 list_for_each_entry_rcu(ptr, &ccs_domain_initializer_list, list) {
130 if (ccs_memcmp(ptr, &e, offsetof(typeof(e), is_not),
131 sizeof(e)))
132 continue;
133 ptr->is_deleted = is_delete;
134 error = 0;
135 break;
136 }
137 if (!is_delete && error && ccs_commit_ok(entry, &e, sizeof(e))) {
138 list_add_tail_rcu(&entry->list, &ccs_domain_initializer_list);
139 entry = NULL;
140 error = 0;
141 }
142 mutex_unlock(&ccs_policy_lock);
143 out:
144 ccs_put_name(e.domainname);
145 ccs_put_name(e.program);
146 kfree(entry);
147 return error;
148 }
149
150 /**
151 * ccs_read_domain_initializer_policy - Read "struct ccs_domain_initializer_entry" list.
152 *
153 * @head: Pointer to "struct ccs_io_buffer".
154 *
155 * Returns true on success, false otherwise.
156 *
157 * Caller holds ccs_read_lock().
158 */
159 bool ccs_read_domain_initializer_policy(struct ccs_io_buffer *head)
160 {
161 struct list_head *pos;
162 bool done = true;
163 ccs_check_read_lock();
164 list_for_each_cookie(pos, head->read_var2,
165 &ccs_domain_initializer_list) {
166 const char *no;
167 const char *from = "";
168 const char *domain = "";
169 struct ccs_domain_initializer_entry *ptr;
170 ptr = list_entry(pos, struct ccs_domain_initializer_entry,
171 list);
172 if (ptr->is_deleted)
173 continue;
174 no = ptr->is_not ? "no_" : "";
175 if (ptr->domainname) {
176 from = " from ";
177 domain = ptr->domainname->name;
178 }
179 done = ccs_io_printf(head,
180 "%s" CCS_KEYWORD_INITIALIZE_DOMAIN "%s%s%s\n",
181 no, ptr->program->name, from, domain);
182 if (!done)
183 break;
184 }
185 return done;
186 }
187
188 /**
189 * ccs_write_domain_initializer_policy - Write "struct ccs_domain_initializer_entry" list.
190 *
191 * @data: String to parse.
192 * @is_not: True if it is "no_initialize_domain" entry.
193 * @is_delete: True if it is a delete request.
194 *
195 * Returns 0 on success, negative value otherwise.
196 */
197 int ccs_write_domain_initializer_policy(char *data, const bool is_not,
198 const bool is_delete)
199 {
200 char *cp = strstr(data, " from ");
201 if (cp) {
202 *cp = '\0';
203 return ccs_update_domain_initializer_entry(cp + 6, data,
204 is_not, is_delete);
205 }
206 return ccs_update_domain_initializer_entry(NULL, data, is_not,
207 is_delete);
208 }
209
210 /**
211 * ccs_is_domain_initializer - Check whether the given program causes domainname reinitialization.
212 *
213 * @domainname: The name of domain.
214 * @program: The name of program.
215 * @last_name: The last component of @domainname.
216 *
217 * Returns true if executing @program reinitializes domain transition,
218 * false otherwise.
219 *
220 * Caller holds ccs_read_lock().
221 */
222 static bool ccs_is_domain_initializer(const struct ccs_path_info *domainname,
223 const struct ccs_path_info *program,
224 const struct ccs_path_info *last_name)
225 {
226 struct ccs_domain_initializer_entry *ptr;
227 bool flag = false;
228 ccs_check_read_lock();
229 list_for_each_entry_rcu(ptr, &ccs_domain_initializer_list, list) {
230 if (ptr->is_deleted)
231 continue;
232 if (ptr->domainname) {
233 if (!ptr->is_last_name) {
234 if (ptr->domainname != domainname)
235 continue;
236 } else {
237 if (ccs_pathcmp(ptr->domainname, last_name))
238 continue;
239 }
240 }
241 if (ccs_pathcmp(ptr->program, program))
242 continue;
243 if (ptr->is_not) {
244 flag = false;
245 break;
246 }
247 flag = true;
248 }
249 return flag;
250 }
251
252 /* The list for "struct ccs_domain_keeper_entry". */
253 LIST_HEAD(ccs_domain_keeper_list);
254
255 /**
256 * ccs_update_domain_keeper_entry - Update "struct ccs_domain_keeper_entry" list.
257 *
258 * @domainname: The name of domain.
259 * @program: The name of program. May be NULL.
260 * @is_not: True if it is "no_keep_domain" entry.
261 * @is_delete: True if it is a delete request.
262 *
263 * Returns 0 on success, negative value otherwise.
264 */
265 static int ccs_update_domain_keeper_entry(const char *domainname,
266 const char *program,
267 const bool is_not,
268 const bool is_delete)
269 {
270 struct ccs_domain_keeper_entry *entry = NULL;
271 struct ccs_domain_keeper_entry *ptr;
272 struct ccs_domain_keeper_entry e = { .is_not = is_not };
273 int error = is_delete ? -ENOENT : -ENOMEM;
274 if (!ccs_is_domain_def(domainname) &&
275 ccs_is_correct_path(domainname, 1, -1, -1))
276 e.is_last_name = true;
277 else if (!ccs_is_correct_domain(domainname))
278 return -EINVAL;
279 if (program) {
280 if (!ccs_is_correct_path(program, 1, -1, -1))
281 return -EINVAL;
282 e.program = ccs_get_name(program);
283 if (!e.program)
284 goto out;
285 }
286 e.domainname = ccs_get_name(domainname);
287 if (!e.domainname)
288 goto out;
289 if (!is_delete)
290 entry = kmalloc(sizeof(e), GFP_KERNEL);
291 mutex_lock(&ccs_policy_lock);
292 list_for_each_entry_rcu(ptr, &ccs_domain_keeper_list, list) {
293 if (ccs_memcmp(ptr, &e, offsetof(typeof(e), is_not),
294 sizeof(e)))
295 continue;
296 ptr->is_deleted = is_delete;
297 error = 0;
298 break;
299 }
300 if (!is_delete && error && ccs_commit_ok(entry, &e, sizeof(e))) {
301 list_add_tail_rcu(&entry->list, &ccs_domain_keeper_list);
302 entry = NULL;
303 error = 0;
304 }
305 mutex_unlock(&ccs_policy_lock);
306 out:
307 ccs_put_name(e.domainname);
308 ccs_put_name(e.program);
309 kfree(entry);
310 return error;
311 }
312
313 /**
314 * ccs_write_domain_keeper_policy - Write "struct ccs_domain_keeper_entry" list.
315 *
316 * @data: String to parse.
317 * @is_not: True if it is "no_keep_domain" entry.
318 * @is_delete: True if it is a delete request.
319 *
320 */
321 int ccs_write_domain_keeper_policy(char *data, const bool is_not,
322 const bool is_delete)
323 {
324 char *cp = strstr(data, " from ");
325 if (cp) {
326 *cp = '\0';
327 return ccs_update_domain_keeper_entry(cp + 6, data,
328 is_not, is_delete);
329 }
330 return ccs_update_domain_keeper_entry(data, NULL, is_not, is_delete);
331 }
332
333 /**
334 * ccs_read_domain_keeper_policy - Read "struct ccs_domain_keeper_entry" list.
335 *
336 * @head: Pointer to "struct ccs_io_buffer".
337 *
338 * Returns true on success, false otherwise.
339 *
340 * Caller holds ccs_read_lock().
341 */
342 bool ccs_read_domain_keeper_policy(struct ccs_io_buffer *head)
343 {
344 struct list_head *pos;
345 bool done = true;
346 ccs_check_read_lock();
347 list_for_each_cookie(pos, head->read_var2,
348 &ccs_domain_keeper_list) {
349 struct ccs_domain_keeper_entry *ptr;
350 const char *no;
351 const char *from = "";
352 const char *program = "";
353 ptr = list_entry(pos, struct ccs_domain_keeper_entry, list);
354 if (ptr->is_deleted)
355 continue;
356 no = ptr->is_not ? "no_" : "";
357 if (ptr->program) {
358 from = " from ";
359 program = ptr->program->name;
360 }
361 done = ccs_io_printf(head, "%s" CCS_KEYWORD_KEEP_DOMAIN
362 "%s%s%s\n", no, program, from,
363 ptr->domainname->name);
364 if (!done)
365 break;
366 }
367 return done;
368 }
369
370 /**
371 * ccs_is_domain_keeper - Check whether the given program causes domain transition suppression.
372 *
373 * @domainname: The name of domain.
374 * @program: The name of program.
375 * @last_name: The last component of @domainname.
376 *
377 * Returns true if executing @program supresses domain transition,
378 * false otherwise.
379 *
380 * Caller holds ccs_read_lock().
381 */
382 static bool ccs_is_domain_keeper(const struct ccs_path_info *domainname,
383 const struct ccs_path_info *program,
384 const struct ccs_path_info *last_name)
385 {
386 struct ccs_domain_keeper_entry *ptr;
387 bool flag = false;
388 ccs_check_read_lock();
389 list_for_each_entry_rcu(ptr, &ccs_domain_keeper_list, list) {
390 if (ptr->is_deleted)
391 continue;
392 if (!ptr->is_last_name) {
393 if (ptr->domainname != domainname)
394 continue;
395 } else {
396 if (ccs_pathcmp(ptr->domainname, last_name))
397 continue;
398 }
399 if (ptr->program && ccs_pathcmp(ptr->program, program))
400 continue;
401 if (ptr->is_not) {
402 flag = false;
403 break;
404 }
405 flag = true;
406 }
407 return flag;
408 }
409
410 /* The list for "struct ccs_aggregator_entry". */
411 LIST_HEAD(ccs_aggregator_list);
412
413 /**
414 * ccs_update_aggregator_entry - Update "struct ccs_aggregator_entry" list.
415 *
416 * @original_name: The original program's name.
417 * @aggregated_name: The aggregated program's name.
418 * @is_delete: True if it is a delete request.
419 *
420 * Returns 0 on success, negative value otherwise.
421 */
422 static int ccs_update_aggregator_entry(const char *original_name,
423 const char *aggregated_name,
424 const bool is_delete)
425 {
426 struct ccs_aggregator_entry *entry = NULL;
427 struct ccs_aggregator_entry *ptr;
428 struct ccs_aggregator_entry e = { };
429 int error = is_delete ? -ENOENT : -ENOMEM;
430 if (!ccs_is_correct_path(original_name, 1, 0, -1) ||
431 !ccs_is_correct_path(aggregated_name, 1, -1, -1))
432 return -EINVAL;
433 e.original_name = ccs_get_name(original_name);
434 e.aggregated_name = ccs_get_name(aggregated_name);
435 if (!e.original_name || !e.aggregated_name)
436 goto out;
437 if (!is_delete)
438 entry = kmalloc(sizeof(e), GFP_KERNEL);
439 mutex_lock(&ccs_policy_lock);
440 list_for_each_entry_rcu(ptr, &ccs_aggregator_list, list) {
441 if (ccs_memcmp(ptr, &e, offsetof(typeof(e), original_name),
442 sizeof(e)))
443 continue;
444 ptr->is_deleted = is_delete;
445 error = 0;
446 break;
447 }
448 if (!is_delete && error && ccs_commit_ok(entry, &e, sizeof(e))) {
449 list_add_tail_rcu(&entry->list, &ccs_aggregator_list);
450 entry = NULL;
451 error = 0;
452 }
453 mutex_unlock(&ccs_policy_lock);
454 out:
455 ccs_put_name(e.original_name);
456 ccs_put_name(e.aggregated_name);
457 kfree(entry);
458 return error;
459 }
460
461 /**
462 * ccs_read_aggregator_policy - Read "struct ccs_aggregator_entry" list.
463 *
464 * @head: Pointer to "struct ccs_io_buffer".
465 *
466 * Returns true on success, false otherwise.
467 *
468 * Caller holds ccs_read_lock().
469 */
470 bool ccs_read_aggregator_policy(struct ccs_io_buffer *head)
471 {
472 struct list_head *pos;
473 bool done = true;
474 ccs_check_read_lock();
475 list_for_each_cookie(pos, head->read_var2, &ccs_aggregator_list) {
476 struct ccs_aggregator_entry *ptr;
477 ptr = list_entry(pos, struct ccs_aggregator_entry, list);
478 if (ptr->is_deleted)
479 continue;
480 done = ccs_io_printf(head, CCS_KEYWORD_AGGREGATOR "%s %s\n",
481 ptr->original_name->name,
482 ptr->aggregated_name->name);
483 if (!done)
484 break;
485 }
486 return done;
487 }
488
489 /**
490 * ccs_write_aggregator_policy - Write "struct ccs_aggregator_entry" list.
491 *
492 * @data: String to parse.
493 * @is_delete: True if it is a delete request.
494 *
495 * Returns 0 on success, negative value otherwise.
496 */
497 int ccs_write_aggregator_policy(char *data, const bool is_delete)
498 {
499 char *w[2];
500 if (!ccs_tokenize(data, w, sizeof(w)) || !w[1][0])
501 return -EINVAL;
502 return ccs_update_aggregator_entry(w[0], w[1], is_delete);
503 }
504
505 /* Domain create/delete handler. */
506
507 /**
508 * ccs_delete_domain - Delete a domain.
509 *
510 * @domainname: The name of domain.
511 *
512 * Returns 0.
513 */
514 int ccs_delete_domain(char *domainname)
515 {
516 struct ccs_domain_info *domain;
517 struct ccs_path_info name;
518 name.name = domainname;
519 ccs_fill_path_info(&name);
520 mutex_lock(&ccs_policy_lock);
521 /* Is there an active domain? */
522 list_for_each_entry_rcu(domain, &ccs_domain_list, list) {
523 /* Never delete ccs_kernel_domain */
524 if (domain == &ccs_kernel_domain)
525 continue;
526 if (domain->is_deleted ||
527 ccs_pathcmp(domain->domainname, &name))
528 continue;
529 domain->is_deleted = true;
530 break;
531 }
532 mutex_unlock(&ccs_policy_lock);
533 return 0;
534 }
535
536 /**
537 * ccs_find_or_assign_new_domain - Create a domain.
538 *
539 * @domainname: The name of domain.
540 * @profile: Profile number to assign if the domain was newly created.
541 *
542 * Returns pointer to "struct ccs_domain_info" on success, NULL otherwise.
543 */
544 struct ccs_domain_info *ccs_find_or_assign_new_domain(const char *domainname,
545 const u8 profile)
546 {
547 struct ccs_domain_info *entry;
548 struct ccs_domain_info *domain;
549 const struct ccs_path_info *saved_domainname;
550 bool found = false;
551
552 if (!ccs_is_correct_domain(domainname))
553 return NULL;
554 saved_domainname = ccs_get_name(domainname);
555 if (!saved_domainname)
556 return NULL;
557 entry = kzalloc(sizeof(*entry), GFP_KERNEL);
558 mutex_lock(&ccs_policy_lock);
559 list_for_each_entry_rcu(domain, &ccs_domain_list, list) {
560 if (domain->is_deleted ||
561 ccs_pathcmp(saved_domainname, domain->domainname))
562 continue;
563 found = true;
564 break;
565 }
566 if (!found && ccs_memory_ok(entry, sizeof(*entry))) {
567 INIT_LIST_HEAD(&entry->acl_info_list);
568 entry->domainname = saved_domainname;
569 saved_domainname = NULL;
570 entry->profile = profile;
571 list_add_tail_rcu(&entry->list, &ccs_domain_list);
572 domain = entry;
573 entry = NULL;
574 found = true;
575 }
576 mutex_unlock(&ccs_policy_lock);
577 ccs_put_name(saved_domainname);
578 kfree(entry);
579 return found ? domain : NULL;
580 }
581
582 /**
583 * ccs_find_next_domain - Find a domain.
584 *
585 * @ee: Pointer to "struct ccs_execve_entry".
586 *
587 * Returns 0 on success, negative value otherwise.
588 *
589 * Caller holds ccs_read_lock().
590 */
591 static int ccs_find_next_domain(struct ccs_execve_entry *ee)
592 {
593 struct ccs_request_info *r = &ee->r;
594 const struct ccs_path_info *handler = ee->handler;
595 struct ccs_domain_info *domain = NULL;
596 const char *old_domain_name = r->domain->domainname->name;
597 struct linux_binprm *bprm = ee->bprm;
598 const u8 mode = r->mode;
599 const bool is_enforce = (mode == 3);
600 const u32 ccs_flags = current->ccs_flags;
601 char *new_domain_name = NULL;
602 struct ccs_path_info rn; /* real name */
603 struct ccs_path_info ln; /* last name */
604 int retval;
605 ccs_check_read_lock();
606 retry:
607 current->ccs_flags = ccs_flags;
608 r->cond = NULL;
609 /* Get symlink's pathname of program. */
610 retval = ccs_symlink_path(bprm->filename, ee);
611 if (retval < 0)
612 goto out;
613
614 rn.name = ee->program_path;
615 ccs_fill_path_info(&rn);
616 ln.name = ccs_get_last_name(r->domain);
617 ccs_fill_path_info(&ln);
618
619 if (handler) {
620 if (ccs_pathcmp(&rn, handler)) {
621 /* Failed to verify execute handler. */
622 static u8 counter = 20;
623 if (counter) {
624 counter--;
625 printk(KERN_WARNING "Failed to verify: %s\n",
626 handler->name);
627 }
628 goto out;
629 }
630 goto calculate_domain;
631 }
632
633 /* Check 'aggregator' directive. */
634 {
635 struct ccs_aggregator_entry *ptr;
636 /* Is this program allowed to be aggregated? */
637 list_for_each_entry_rcu(ptr, &ccs_aggregator_list, list) {
638 if (ptr->is_deleted ||
639 !ccs_path_matches_pattern(&rn, ptr->original_name))
640 continue;
641 strncpy(ee->program_path, ptr->aggregated_name->name,
642 CCS_MAX_PATHNAME_LEN - 1);
643 ccs_fill_path_info(&rn);
644 break;
645 }
646 }
647
648 /* Check execute permission. */
649 r->mode = mode;
650 retval = ccs_check_exec_perm(r, &rn);
651 if (retval == 1)
652 goto retry;
653 if (retval < 0)
654 goto out;
655
656 calculate_domain:
657 new_domain_name = ee->tmp;
658 if (ccs_is_domain_initializer(r->domain->domainname, &rn, &ln)) {
659 /* Transit to the child of ccs_kernel_domain domain. */
660 snprintf(new_domain_name, CCS_EXEC_TMPSIZE - 1,
661 ROOT_NAME " " "%s", ee->program_path);
662 } else if (r->domain == &ccs_kernel_domain && !ccs_policy_loaded) {
663 /*
664 * Needn't to transit from kernel domain before starting
665 * /sbin/init. But transit from kernel domain if executing
666 * initializers because they might start before /sbin/init.
667 */
668 domain = r->domain;
669 } else if (ccs_is_domain_keeper(r->domain->domainname, &rn, &ln)) {
670 /* Keep current domain. */
671 domain = r->domain;
672 } else {
673 /* Normal domain transition. */
674 snprintf(new_domain_name, CCS_EXEC_TMPSIZE - 1,
675 "%s %s", old_domain_name, ee->program_path);
676 }
677 if (domain || strlen(new_domain_name) >= CCS_MAX_PATHNAME_LEN)
678 goto done;
679 domain = ccs_find_domain(new_domain_name);
680 if (domain)
681 goto done;
682 if (is_enforce) {
683 int error = ccs_check_supervisor(r,
684 "# wants to create domain\n"
685 "%s\n", new_domain_name);
686 if (error == 1)
687 goto retry;
688 if (error < 0)
689 goto done;
690 }
691 domain = ccs_find_or_assign_new_domain(new_domain_name, r->profile);
692 if (domain)
693 ccs_audit_domain_creation_log(r->domain);
694 done:
695 if (!domain) {
696 printk(KERN_WARNING "TOMOYO-ERROR: Domain '%s' not defined.\n",
697 new_domain_name);
698 if (is_enforce)
699 retval = -EPERM;
700 else {
701 retval = 0;
702 r->domain->domain_transition_failed = true;
703 }
704 } else {
705 retval = 0;
706 }
707 out:
708 if (domain)
709 r->domain = domain;
710 return retval;
711 }
712
713 /**
714 * ccs_check_environ - Check permission for environment variable names.
715 *
716 * @ee: Pointer to "struct ccs_execve_entry".
717 *
718 * Returns 0 on success, negative value otherwise.
719 */
720 static int ccs_check_environ(struct ccs_execve_entry *ee)
721 {
722 struct ccs_request_info *r = &ee->r;
723 struct linux_binprm *bprm = ee->bprm;
724 char *arg_ptr = ee->tmp;
725 int arg_len = 0;
726 unsigned long pos = bprm->p;
727 int offset = pos % PAGE_SIZE;
728 int argv_count = bprm->argc;
729 int envp_count = bprm->envc;
730 /* printk(KERN_DEBUG "start %d %d\n", argv_count, envp_count); */
731 int error = -ENOMEM;
732 if (!r->mode || !envp_count)
733 return 0;
734 while (error == -ENOMEM) {
735 if (!ccs_dump_page(bprm, pos, &ee->dump))
736 goto out;
737 pos += PAGE_SIZE - offset;
738 /* Read. */
739 while (argv_count && offset < PAGE_SIZE) {
740 const char *kaddr = ee->dump.data;
741 if (!kaddr[offset++])
742 argv_count--;
743 }
744 if (argv_count) {
745 offset = 0;
746 continue;
747 }
748 while (offset < PAGE_SIZE) {
749 const char *kaddr = ee->dump.data;
750 const unsigned char c = kaddr[offset++];
751 if (c && arg_len < CCS_MAX_PATHNAME_LEN - 10) {
752 if (c == '=') {
753 arg_ptr[arg_len++] = '\0';
754 } else if (c == '\\') {
755 arg_ptr[arg_len++] = '\\';
756 arg_ptr[arg_len++] = '\\';
757 } else if (c > ' ' && c < 127) {
758 arg_ptr[arg_len++] = c;
759 } else {
760 arg_ptr[arg_len++] = '\\';
761 arg_ptr[arg_len++] = (c >> 6) + '0';
762 arg_ptr[arg_len++]
763 = ((c >> 3) & 7) + '0';
764 arg_ptr[arg_len++] = (c & 7) + '0';
765 }
766 } else {
767 arg_ptr[arg_len] = '\0';
768 }
769 if (c)
770 continue;
771 if (ccs_check_env_perm(r, arg_ptr)) {
772 error = -EPERM;
773 break;
774 }
775 if (!--envp_count) {
776 error = 0;
777 break;
778 }
779 arg_len = 0;
780 }
781 offset = 0;
782 }
783 out:
784 if (r->mode != 3)
785 error = 0;
786 return error;
787 }
788
789 /**
790 * ccs_unescape - Unescape escaped string.
791 *
792 * @dest: String to unescape.
793 *
794 * Returns nothing.
795 */
796 static void ccs_unescape(unsigned char *dest)
797 {
798 unsigned char *src = dest;
799 unsigned char c;
800 unsigned char d;
801 unsigned char e;
802 while (1) {
803 c = *src++;
804 if (!c)
805 break;
806 if (c != '\\') {
807 *dest++ = c;
808 continue;
809 }
810 c = *src++;
811 if (c == '\\') {
812 *dest++ = c;
813 continue;
814 }
815 if (c < '0' || c > '3')
816 break;
817 d = *src++;
818 if (d < '0' || d > '7')
819 break;
820 e = *src++;
821 if (e < '0' || e > '7')
822 break;
823 *dest++ = ((c - '0') << 6) + ((d - '0') << 3) + (e - '0');
824 }
825 *dest = '\0';
826 }
827
828 /**
829 * ccs_root_depth - Get number of directories to strip.
830 *
831 * @dentry: Pointer to "struct dentry".
832 * @vfsmnt: Pointer to "struct vfsmount".
833 *
834 * Returns number of directories to strip.
835 */
836 static inline int ccs_root_depth(struct dentry *dentry, struct vfsmount *vfsmnt)
837 {
838 int depth = 0;
839 /***** CRITICAL SECTION START *****/
840 ccs_realpath_lock();
841 for (;;) {
842 if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
843 /* Global root? */
844 if (vfsmnt->mnt_parent == vfsmnt)
845 break;
846 dentry = vfsmnt->mnt_mountpoint;
847 vfsmnt = vfsmnt->mnt_parent;
848 continue;
849 }
850 dentry = dentry->d_parent;
851 depth++;
852 }
853 ccs_realpath_unlock();
854 /***** CRITICAL SECTION END *****/
855 return depth;
856 }
857
858 /**
859 * ccs_get_root_depth - return the depth of root directory.
860 *
861 * Returns number of directories to strip.
862 */
863 static int ccs_get_root_depth(void)
864 {
865 int depth;
866 struct dentry *dentry;
867 struct vfsmount *vfsmnt;
868 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25)
869 struct path root;
870 #endif
871 /***** CRITICAL SECTION START *****/
872 read_lock(&current->fs->lock);
873 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25)
874 root = current->fs->root;
875 path_get(&current->fs->root);
876 dentry = root.dentry;
877 vfsmnt = root.mnt;
878 #else
879 dentry = dget(current->fs->root);
880 vfsmnt = mntget(current->fs->rootmnt);
881 #endif
882 read_unlock(&current->fs->lock);
883 /***** CRITICAL SECTION END *****/
884 depth = ccs_root_depth(dentry, vfsmnt);
885 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25)
886 path_put(&root);
887 #else
888 dput(dentry);
889 mntput(vfsmnt);
890 #endif
891 return depth;
892 }
893
894 static LIST_HEAD(ccs_execve_list);
895 static DEFINE_SPINLOCK(ccs_execve_list_lock);
896
897 /**
898 * ccs_allocate_execve_entry - Allocate memory for execve().
899 *
900 * Returns pointer to "struct ccs_execve_entry" on success, NULL otherwise.
901 */
902 static struct ccs_execve_entry *ccs_allocate_execve_entry(void)
903 {
904 struct ccs_execve_entry *ee = kzalloc(sizeof(*ee), GFP_KERNEL);
905 if (!ee)
906 return NULL;
907 ee->program_path = kzalloc(CCS_MAX_PATHNAME_LEN, GFP_KERNEL);
908 ee->tmp = kzalloc(CCS_EXEC_TMPSIZE, GFP_KERNEL);
909 if (!ee->program_path || !ee->tmp) {
910 kfree(ee->program_path);
911 kfree(ee->tmp);
912 kfree(ee);
913 return NULL;
914 }
915 ee->reader_idx = ccs_read_lock();
916 /* ee->dump->data is allocated by ccs_dump_page(). */
917 ee->task = current;
918 /***** CRITICAL SECTION START *****/
919 spin_lock(&ccs_execve_list_lock);
920 list_add(&ee->list, &ccs_execve_list);
921 spin_unlock(&ccs_execve_list_lock);
922 /***** CRITICAL SECTION END *****/
923 return ee;
924 }
925
926 /**
927 * ccs_find_execve_entry - Find ccs_execve_entry of current process.
928 *
929 * Returns pointer to "struct ccs_execve_entry" on success, NULL otherwise.
930 */
931 static struct ccs_execve_entry *ccs_find_execve_entry(void)
932 {
933 struct task_struct *task = current;
934 struct ccs_execve_entry *ee = NULL;
935 struct ccs_execve_entry *p;
936 /***** CRITICAL SECTION START *****/
937 spin_lock(&ccs_execve_list_lock);
938 list_for_each_entry(p, &ccs_execve_list, list) {
939 if (p->task != task)
940 continue;
941 ee = p;
942 break;
943 }
944 spin_unlock(&ccs_execve_list_lock);
945 /***** CRITICAL SECTION END *****/
946 return ee;
947 }
948
949 /**
950 * ccs_free_execve_entry - Free memory for execve().
951 *
952 * @ee: Pointer to "struct ccs_execve_entry".
953 */
954 static void ccs_free_execve_entry(struct ccs_execve_entry *ee)
955 {
956 if (!ee)
957 return;
958 /***** CRITICAL SECTION START *****/
959 spin_lock(&ccs_execve_list_lock);
960 list_del(&ee->list);
961 spin_unlock(&ccs_execve_list_lock);
962 /***** CRITICAL SECTION END *****/
963 kfree(ee->program_path);
964 kfree(ee->tmp);
965 kfree(ee->dump.data);
966 ccs_read_unlock(ee->reader_idx);
967 kfree(ee);
968 }
969
970 /**
971 * ccs_try_alt_exec - Try to start execute handler.
972 *
973 * @ee: Pointer to "struct ccs_execve_entry".
974 *
975 * Returns 0 on success, negative value otherwise.
976 */
977 static int ccs_try_alt_exec(struct ccs_execve_entry *ee)
978 {
979 /*
980 * Contents of modified bprm.
981 * The envp[] in original bprm is moved to argv[] so that
982 * the alternatively executed program won't be affected by
983 * some dangerous environment variables like LD_PRELOAD.
984 *
985 * modified bprm->argc
986 * = original bprm->argc + original bprm->envc + 7
987 * modified bprm->envc
988 * = 0
989 *
990 * modified bprm->argv[0]
991 * = the program's name specified by execute_handler
992 * modified bprm->argv[1]
993 * = ccs_current_domain()->domainname->name
994 * modified bprm->argv[2]
995 * = the current process's name
996 * modified bprm->argv[3]
997 * = the current process's information (e.g. uid/gid).
998 * modified bprm->argv[4]
999 * = original bprm->filename
1000 * modified bprm->argv[5]
1001 * = original bprm->argc in string expression
1002 * modified bprm->argv[6]
1003 * = original bprm->envc in string expression
1004 * modified bprm->argv[7]
1005 * = original bprm->argv[0]
1006 * ...
1007 * modified bprm->argv[bprm->argc + 6]
1008 * = original bprm->argv[bprm->argc - 1]
1009 * modified bprm->argv[bprm->argc + 7]
1010 * = original bprm->envp[0]
1011 * ...
1012 * modified bprm->argv[bprm->envc + bprm->argc + 6]
1013 * = original bprm->envp[bprm->envc - 1]
1014 */
1015 struct linux_binprm *bprm = ee->bprm;
1016 struct file *filp;
1017 int retval;
1018 const int original_argc = bprm->argc;
1019 const int original_envc = bprm->envc;
1020 struct task_struct *task = current;
1021
1022 /* Close the requested program's dentry. */
1023 allow_write_access(bprm->file);
1024 fput(bprm->file);
1025 bprm->file = NULL;
1026
1027 /* Invalidate page dump cache. */
1028 ee->dump.page = NULL;
1029
1030 /* Move envp[] to argv[] */
1031 bprm->argc += bprm->envc;
1032 bprm->envc = 0;
1033
1034 /* Set argv[6] */
1035 {
1036 char *cp = ee->tmp;
1037 snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, "%d", original_envc);
1038 retval = copy_strings_kernel(1, &cp, bprm);
1039 if (retval < 0)
1040 goto out;
1041 bprm->argc++;
1042 }
1043
1044 /* Set argv[5] */
1045 {
1046 char *cp = ee->tmp;
1047 snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1, "%d", original_argc);
1048 retval = copy_strings_kernel(1, &cp, bprm);
1049 if (retval < 0)
1050 goto out;
1051 bprm->argc++;
1052 }
1053
1054 /* Set argv[4] */
1055 {
1056 retval = copy_strings_kernel(1, &bprm->filename, bprm);
1057 if (retval < 0)
1058 goto out;
1059 bprm->argc++;
1060 }
1061
1062 /* Set argv[3] */
1063 {
1064 char *cp = ee->tmp;
1065 const u32 ccs_flags = task->ccs_flags;
1066 snprintf(ee->tmp, CCS_EXEC_TMPSIZE - 1,
1067 "pid=%d uid=%d gid=%d euid=%d egid=%d suid=%d "
1068 "sgid=%d fsuid=%d fsgid=%d state[0]=%u "
1069 "state[1]=%u state[2]=%u",
1070 (pid_t) sys_getpid(), current_uid(), current_gid(),
1071 current_euid(), current_egid(), current_suid(),
1072 current_sgid(), current_fsuid(), current_fsgid(),
1073 (u8) (ccs_flags >> 24), (u8) (ccs_flags >> 16),
1074 (u8) (ccs_flags >> 8));
1075 retval = copy_strings_kernel(1, &cp, bprm);
1076 if (retval < 0)
1077 goto out;
1078 bprm->argc++;
1079 }
1080
1081 /* Set argv[2] */
1082 {
1083 char *exe = (char *) ccs_get_exe();
1084 if (exe) {
1085 retval = copy_strings_kernel(1, &exe, bprm);
1086 kfree(exe);
1087 } else {
1088 exe = ee->tmp;
1089 strncpy(ee->tmp, "<unknown>", CCS_EXEC_TMPSIZE - 1);
1090 retval = copy_strings_kernel(1, &exe, bprm);
1091 }
1092 if (retval < 0)
1093 goto out;
1094 bprm->argc++;
1095 }
1096
1097 /* Set argv[1] */
1098 {
1099 char *cp = ee->tmp;
1100 strncpy(ee->tmp, ccs_current_domain()->domainname->name,
1101 CCS_EXEC_TMPSIZE - 1);
1102 retval = copy_strings_kernel(1, &cp, bprm);
1103 if (retval < 0)
1104 goto out;
1105 bprm->argc++;
1106 }
1107
1108 /* Set argv[0] */
1109 {
1110 int depth = ccs_get_root_depth();
1111 char *cp = ee->program_path;
1112 strncpy(cp, ee->handler->name, CCS_MAX_PATHNAME_LEN - 1);
1113 ccs_unescape(cp);
1114 retval = -ENOENT;
1115 if (!*cp || *cp != '/')
1116 goto out;
1117 /* Adjust root directory for open_exec(). */
1118 while (depth) {
1119 cp = strchr(cp + 1, '/');
1120 if (!cp)
1121 goto out;
1122 depth--;
1123 }
1124 memmove(ee->program_path, cp, strlen(cp) + 1);
1125 cp = ee->program_path;
1126 retval = copy_strings_kernel(1, &cp, bprm);
1127 if (retval < 0)
1128 goto out;
1129 bprm->argc++;
1130 }
1131 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23)
1132 #if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24)
1133 bprm->argv_len = bprm->exec - bprm->p;
1134 #endif
1135 #endif
1136
1137 /* OK, now restart the process with execute handler program's dentry. */
1138 filp = open_exec(ee->program_path);
1139 if (IS_ERR(filp)) {
1140 retval = PTR_ERR(filp);
1141 goto out;
1142 }
1143 bprm->file = filp;
1144 bprm->filename = ee->program_path;
1145 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
1146 bprm->interp = bprm->filename;
1147 #endif
1148 retval = prepare_binprm(bprm);
1149 if (retval < 0)
1150 goto out;
1151 {
1152 /*
1153 * Backup ee->program_path because ccs_find_next_domain() will
1154 * overwrite ee->program_path and ee->tmp.
1155 */
1156 const int len = strlen(ee->program_path) + 1;
1157 char *cp = kzalloc(len, GFP_KERNEL);
1158 if (!cp) {
1159 retval = -ENOMEM;
1160 goto out;
1161 }
1162 memmove(cp, ee->program_path, len);
1163 bprm->filename = cp;
1164 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
1165 bprm->interp = bprm->filename;
1166 #endif
1167 task->ccs_flags |= CCS_DONT_SLEEP_ON_ENFORCE_ERROR;
1168 retval = ccs_find_next_domain(ee);
1169 task->ccs_flags &= ~CCS_DONT_SLEEP_ON_ENFORCE_ERROR;
1170 /* Restore ee->program_path for search_binary_handler(). */
1171 memmove(ee->program_path, cp, len);
1172 bprm->filename = ee->program_path;
1173 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
1174 bprm->interp = bprm->filename;
1175 #endif
1176 kfree(cp);
1177 }
1178 out:
1179 return retval;
1180 }
1181
1182 /**
1183 * ccs_find_execute_handler - Find an execute handler.
1184 *
1185 * @ee: Pointer to "struct ccs_execve_entry".
1186 * @type: Type of execute handler.
1187 *
1188 * Returns true if found, false otherwise.
1189 *
1190 * Caller holds ccs_read_lock().
1191 */
1192 static bool ccs_find_execute_handler(struct ccs_execve_entry *ee,
1193 const u8 type)
1194 {
1195 struct task_struct *task = current;
1196 const struct ccs_domain_info *domain = ccs_current_domain();
1197 struct ccs_acl_info *ptr;
1198 bool found = false;
1199 ccs_check_read_lock();
1200 /*
1201 * Don't use execute handler if the current process is
1202 * marked as execute handler to avoid infinite execute handler loop.
1203 */
1204 if (task->ccs_flags & CCS_TASK_IS_EXECUTE_HANDLER)
1205 return false;
1206 list_for_each_entry(ptr, &domain->acl_info_list, list) {
1207 struct ccs_execute_handler_record *acl;
1208 if (ptr->type != type)
1209 continue;
1210 acl = container_of(ptr, struct ccs_execute_handler_record,
1211 head);
1212 ee->handler = acl->handler;
1213 found = true;
1214 break;
1215 }
1216 return found;
1217 }
1218
1219 /**
1220 * ccs_dump_page - Dump a page to buffer.
1221 *
1222 * @bprm: Pointer to "struct linux_binprm".
1223 * @pos: Location to dump.
1224 * @dump: Poiner to "struct ccs_page_dump".
1225 *
1226 * Returns true on success, false otherwise.
1227 */
1228 bool ccs_dump_page(struct linux_binprm *bprm, unsigned long pos,
1229 struct ccs_page_dump *dump)
1230 {
1231 struct page *page;
1232 /* dump->data is released by ccs_free_execve_entry(). */
1233 if (!dump->data) {
1234 dump->data = kzalloc(PAGE_SIZE, GFP_KERNEL);
1235 if (!dump->data)
1236 return false;
1237 }
1238 /* Same with get_arg_page(bprm, pos, 0) in fs/exec.c */
1239 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) && defined(CONFIG_MMU)
1240 if (get_user_pages(current, bprm->mm, pos, 1, 0, 1, &page, NULL) <= 0)
1241 return false;
1242 #elif defined(RHEL_MAJOR) && RHEL_MAJOR == 5 && defined(RHEL_MINOR) && RHEL_MINOR == 3 && defined(CONFIG_MMU)
1243 if (get_user_pages(current, bprm->mm, pos, 1, 0, 1, &page, NULL) <= 0)
1244 return false;
1245 #else
1246 page = bprm->page[pos / PAGE_SIZE];
1247 #endif
1248 if (page != dump->page) {
1249 const unsigned int offset = pos % PAGE_SIZE;
1250 /*
1251 * Maybe kmap()/kunmap() should be used here.
1252 * But remove_arg_zero() uses kmap_atomic()/kunmap_atomic().
1253 * So do I.
1254 */
1255 char *kaddr = kmap_atomic(page, KM_USER0);
1256 dump->page = page;
1257 memcpy(dump->data + offset, kaddr + offset, PAGE_SIZE - offset);
1258 kunmap_atomic(kaddr, KM_USER0);
1259 }
1260 /* Same with put_arg_page(page) in fs/exec.c */
1261 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) && defined(CONFIG_MMU)
1262 put_page(page);
1263 #elif defined(RHEL_MAJOR) && RHEL_MAJOR == 5 && defined(RHEL_MINOR) && RHEL_MINOR == 3 && defined(CONFIG_MMU)
1264 put_page(page);
1265 #endif
1266 return true;
1267 }
1268
1269 /**
1270 * ccs_fetch_next_domain - Fetch next_domain from the list.
1271 *
1272 * Returns pointer to "struct ccs_domain_info" which will be used if execve()
1273 * succeeds. This function does not return NULL.
1274 */
1275 struct ccs_domain_info *ccs_fetch_next_domain(void)
1276 {
1277 struct ccs_execve_entry *ee = ccs_find_execve_entry();
1278 struct ccs_domain_info *next_domain = NULL;
1279 if (ee)
1280 next_domain = ee->r.domain;
1281 if (!next_domain)
1282 next_domain = ccs_current_domain();
1283 return next_domain;
1284 }
1285
1286 /**
1287 * ccs_start_execve - Prepare for execve() operation.
1288 *
1289 * @bprm: Pointer to "struct linux_binprm".
1290 *
1291 * Returns 0 on success, negative value otherwise.
1292 */
1293 int ccs_start_execve(struct linux_binprm *bprm)
1294 {
1295 int retval;
1296 struct task_struct *task = current;
1297 struct ccs_execve_entry *ee = ccs_allocate_execve_entry();
1298 if (!ccs_policy_loaded)
1299 ccs_load_policy(bprm->filename);
1300 if (!ee)
1301 return -ENOMEM;
1302 ccs_init_request_info(&ee->r, NULL, CCS_MAC_EXECUTE);
1303 ee->r.ee = ee;
1304 ee->bprm = bprm;
1305 ee->r.obj = &ee->obj;
1306 ee->obj.path1.dentry = bprm->file->f_dentry;
1307 ee->obj.path1.mnt = bprm->file->f_vfsmnt;
1308 /* Clear manager flag. */
1309 task->ccs_flags &= ~CCS_TASK_IS_POLICY_MANAGER;
1310 if (ccs_find_execute_handler(ee, CCS_TYPE_EXECUTE_HANDLER)) {
1311 retval = ccs_try_alt_exec(ee);
1312 if (!retval)
1313 ccs_audit_execute_handler_log(ee, true);
1314 goto ok;
1315 }
1316 retval = ccs_find_next_domain(ee);
1317 if (retval != -EPERM)
1318 goto ok;
1319 if (ccs_find_execute_handler(ee, CCS_TYPE_DENIED_EXECUTE_HANDLER)) {
1320 retval = ccs_try_alt_exec(ee);
1321 if (!retval)
1322 ccs_audit_execute_handler_log(ee, false);
1323 }
1324 ok:
1325 if (retval < 0)
1326 goto out;
1327 ee->r.mode = ccs_check_flags(ee->r.domain, CCS_MAC_ENVIRON);
1328 retval = ccs_check_environ(ee);
1329 if (retval < 0)
1330 goto out;
1331 task->ccs_flags |= CCS_CHECK_READ_FOR_OPEN_EXEC;
1332 retval = 0;
1333 out:
1334 if (retval)
1335 ccs_finish_execve(retval);
1336 return retval;
1337 }
1338
1339 /**
1340 * ccs_finish_execve - Clean up execve() operation.
1341 *
1342 * @retval: Return code of an execve() operation.
1343 *
1344 * Caller holds ccs_read_lock().
1345 */
1346 void ccs_finish_execve(int retval)
1347 {
1348 struct task_struct *task = current;
1349 struct ccs_execve_entry *ee = ccs_find_execve_entry();
1350 ccs_check_read_lock();
1351 task->ccs_flags &= ~CCS_CHECK_READ_FOR_OPEN_EXEC;
1352 if (!ee)
1353 return;
1354 if (retval < 0)
1355 goto out;
1356 /* Proceed to next domain if execution suceeded. */
1357 task->ccs_domain_info = ee->r.domain;
1358 /* Mark the current process as execute handler. */
1359 if (ee->handler)
1360 task->ccs_flags |= CCS_TASK_IS_EXECUTE_HANDLER;
1361 /* Mark the current process as normal process. */
1362 else
1363 task->ccs_flags &= ~CCS_TASK_IS_EXECUTE_HANDLER;
1364 out:
1365 ccs_free_execve_entry(ee);
1366 }

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26