diff options
author | Eric Paris <eparis@redhat.com> | 2011-04-21 20:23:20 -0400 |
---|---|---|
committer | Eric Paris <eparis@redhat.com> | 2011-04-25 16:24:41 -0400 |
commit | 0dc1ba24f7fff659725eecbba2c9ad679a0954cd (patch) | |
tree | ad5831b52b38ca8157dd3ba4e5dfb75768bd372f | |
parent | 1c9904297451f558191e211a48d8838b4bf792b0 (diff) |
SELINUX: Make selinux cache VFS RCU walks safe
Now that the security modules can decide whether they support the
dcache RCU walk or not it's possible to make selinux a bit more
RCU friendly. The SELinux AVC and security server access decision
code is RCU safe. A specific piece of the LSM audit code may not
be RCU safe.
This patch makes the VFS RCU walk retry if it would hit the non RCU
safe chunk of code. It will normally just work under RCU. This is
done simply by passing the VFS RCU state as a flag down into the
avc_audit() code and returning ECHILD there if it would have an issue.
Based-on-patch-by: Andi Kleen <ak@linux.intel.com>
Signed-off-by: Eric Paris <eparis@redhat.com>
-rw-r--r-- | security/selinux/avc.c | 36 | ||||
-rw-r--r-- | security/selinux/hooks.c | 26 | ||||
-rw-r--r-- | security/selinux/include/avc.h | 18 |
3 files changed, 55 insertions, 25 deletions
diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 9da6420e2056..1d027e29ce8d 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c | |||
@@ -471,6 +471,7 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a) | |||
471 | * @avd: access vector decisions | 471 | * @avd: access vector decisions |
472 | * @result: result from avc_has_perm_noaudit | 472 | * @result: result from avc_has_perm_noaudit |
473 | * @a: auxiliary audit data | 473 | * @a: auxiliary audit data |
474 | * @flags: VFS walk flags | ||
474 | * | 475 | * |
475 | * Audit the granting or denial of permissions in accordance | 476 | * Audit the granting or denial of permissions in accordance |
476 | * with the policy. This function is typically called by | 477 | * with the policy. This function is typically called by |
@@ -481,9 +482,10 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a) | |||
481 | * be performed under a lock, to allow the lock to be released | 482 | * be performed under a lock, to allow the lock to be released |
482 | * before calling the auditing code. | 483 | * before calling the auditing code. |
483 | */ | 484 | */ |
484 | void avc_audit(u32 ssid, u32 tsid, | 485 | int avc_audit(u32 ssid, u32 tsid, |
485 | u16 tclass, u32 requested, | 486 | u16 tclass, u32 requested, |
486 | struct av_decision *avd, int result, struct common_audit_data *a) | 487 | struct av_decision *avd, int result, struct common_audit_data *a, |
488 | unsigned flags) | ||
487 | { | 489 | { |
488 | struct common_audit_data stack_data; | 490 | struct common_audit_data stack_data; |
489 | u32 denied, audited; | 491 | u32 denied, audited; |
@@ -515,11 +517,24 @@ void avc_audit(u32 ssid, u32 tsid, | |||
515 | else | 517 | else |
516 | audited = requested & avd->auditallow; | 518 | audited = requested & avd->auditallow; |
517 | if (!audited) | 519 | if (!audited) |
518 | return; | 520 | return 0; |
521 | |||
519 | if (!a) { | 522 | if (!a) { |
520 | a = &stack_data; | 523 | a = &stack_data; |
521 | COMMON_AUDIT_DATA_INIT(a, NONE); | 524 | COMMON_AUDIT_DATA_INIT(a, NONE); |
522 | } | 525 | } |
526 | |||
527 | /* | ||
528 | * When in a RCU walk do the audit on the RCU retry. This is because | ||
529 | * the collection of the dname in an inode audit message is not RCU | ||
530 | * safe. Note this may drop some audits when the situation changes | ||
531 | * during retry. However this is logically just as if the operation | ||
532 | * happened a little later. | ||
533 | */ | ||
534 | if ((a->type == LSM_AUDIT_DATA_FS) && | ||
535 | (flags & IPERM_FLAG_RCU)) | ||
536 | return -ECHILD; | ||
537 | |||
523 | a->selinux_audit_data.tclass = tclass; | 538 | a->selinux_audit_data.tclass = tclass; |
524 | a->selinux_audit_data.requested = requested; | 539 | a->selinux_audit_data.requested = requested; |
525 | a->selinux_audit_data.ssid = ssid; | 540 | a->selinux_audit_data.ssid = ssid; |
@@ -529,6 +544,7 @@ void avc_audit(u32 ssid, u32 tsid, | |||
529 | a->lsm_pre_audit = avc_audit_pre_callback; | 544 | a->lsm_pre_audit = avc_audit_pre_callback; |
530 | a->lsm_post_audit = avc_audit_post_callback; | 545 | a->lsm_post_audit = avc_audit_post_callback; |
531 | common_lsm_audit(a); | 546 | common_lsm_audit(a); |
547 | return 0; | ||
532 | } | 548 | } |
533 | 549 | ||
534 | /** | 550 | /** |
@@ -793,6 +809,7 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid, | |||
793 | * @tclass: target security class | 809 | * @tclass: target security class |
794 | * @requested: requested permissions, interpreted based on @tclass | 810 | * @requested: requested permissions, interpreted based on @tclass |
795 | * @auditdata: auxiliary audit data | 811 | * @auditdata: auxiliary audit data |
812 | * @flags: VFS walk flags | ||
796 | * | 813 | * |
797 | * Check the AVC to determine whether the @requested permissions are granted | 814 | * Check the AVC to determine whether the @requested permissions are granted |
798 | * for the SID pair (@ssid, @tsid), interpreting the permissions | 815 | * for the SID pair (@ssid, @tsid), interpreting the permissions |
@@ -802,14 +819,19 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid, | |||
802 | * permissions are granted, -%EACCES if any permissions are denied, or | 819 | * permissions are granted, -%EACCES if any permissions are denied, or |
803 | * another -errno upon other errors. | 820 | * another -errno upon other errors. |
804 | */ | 821 | */ |
805 | int avc_has_perm(u32 ssid, u32 tsid, u16 tclass, | 822 | int avc_has_perm_flags(u32 ssid, u32 tsid, u16 tclass, |
806 | u32 requested, struct common_audit_data *auditdata) | 823 | u32 requested, struct common_audit_data *auditdata, |
824 | unsigned flags) | ||
807 | { | 825 | { |
808 | struct av_decision avd; | 826 | struct av_decision avd; |
809 | int rc; | 827 | int rc, rc2; |
810 | 828 | ||
811 | rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd); | 829 | rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd); |
812 | avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata); | 830 | |
831 | rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata, | ||
832 | flags); | ||
833 | if (rc2) | ||
834 | return rc2; | ||
813 | return rc; | 835 | return rc; |
814 | } | 836 | } |
815 | 837 | ||
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 9a220be17a3f..ed5f29aa0a38 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c | |||
@@ -1447,8 +1447,11 @@ static int task_has_capability(struct task_struct *tsk, | |||
1447 | } | 1447 | } |
1448 | 1448 | ||
1449 | rc = avc_has_perm_noaudit(sid, sid, sclass, av, 0, &avd); | 1449 | rc = avc_has_perm_noaudit(sid, sid, sclass, av, 0, &avd); |
1450 | if (audit == SECURITY_CAP_AUDIT) | 1450 | if (audit == SECURITY_CAP_AUDIT) { |
1451 | avc_audit(sid, sid, sclass, av, &avd, rc, &ad); | 1451 | int rc2 = avc_audit(sid, sid, sclass, av, &avd, rc, &ad, 0); |
1452 | if (rc2) | ||
1453 | return rc2; | ||
1454 | } | ||
1452 | return rc; | 1455 | return rc; |
1453 | } | 1456 | } |
1454 | 1457 | ||
@@ -1468,7 +1471,8 @@ static int task_has_system(struct task_struct *tsk, | |||
1468 | static int inode_has_perm(const struct cred *cred, | 1471 | static int inode_has_perm(const struct cred *cred, |
1469 | struct inode *inode, | 1472 | struct inode *inode, |
1470 | u32 perms, | 1473 | u32 perms, |
1471 | struct common_audit_data *adp) | 1474 | struct common_audit_data *adp, |
1475 | unsigned flags) | ||
1472 | { | 1476 | { |
1473 | struct inode_security_struct *isec; | 1477 | struct inode_security_struct *isec; |
1474 | struct common_audit_data ad; | 1478 | struct common_audit_data ad; |
@@ -1488,7 +1492,7 @@ static int inode_has_perm(const struct cred *cred, | |||
1488 | ad.u.fs.inode = inode; | 1492 | ad.u.fs.inode = inode; |
1489 | } | 1493 | } |
1490 | 1494 | ||
1491 | return avc_has_perm(sid, isec->sid, isec->sclass, perms, adp); | 1495 | return avc_has_perm_flags(sid, isec->sid, isec->sclass, perms, adp, flags); |
1492 | } | 1496 | } |
1493 | 1497 | ||
1494 | /* Same as inode_has_perm, but pass explicit audit data containing | 1498 | /* Same as inode_has_perm, but pass explicit audit data containing |
@@ -1505,7 +1509,7 @@ static inline int dentry_has_perm(const struct cred *cred, | |||
1505 | COMMON_AUDIT_DATA_INIT(&ad, FS); | 1509 | COMMON_AUDIT_DATA_INIT(&ad, FS); |
1506 | ad.u.fs.path.mnt = mnt; | 1510 | ad.u.fs.path.mnt = mnt; |
1507 | ad.u.fs.path.dentry = dentry; | 1511 | ad.u.fs.path.dentry = dentry; |
1508 | return inode_has_perm(cred, inode, av, &ad); | 1512 | return inode_has_perm(cred, inode, av, &ad, 0); |
1509 | } | 1513 | } |
1510 | 1514 | ||
1511 | /* Check whether a task can use an open file descriptor to | 1515 | /* Check whether a task can use an open file descriptor to |
@@ -1541,7 +1545,7 @@ static int file_has_perm(const struct cred *cred, | |||
1541 | /* av is zero if only checking access to the descriptor. */ | 1545 | /* av is zero if only checking access to the descriptor. */ |
1542 | rc = 0; | 1546 | rc = 0; |
1543 | if (av) | 1547 | if (av) |
1544 | rc = inode_has_perm(cred, inode, av, &ad); | 1548 | rc = inode_has_perm(cred, inode, av, &ad, 0); |
1545 | 1549 | ||
1546 | out: | 1550 | out: |
1547 | return rc; | 1551 | return rc; |
@@ -2103,7 +2107,7 @@ static inline void flush_unauthorized_files(const struct cred *cred, | |||
2103 | file = file_priv->file; | 2107 | file = file_priv->file; |
2104 | inode = file->f_path.dentry->d_inode; | 2108 | inode = file->f_path.dentry->d_inode; |
2105 | if (inode_has_perm(cred, inode, | 2109 | if (inode_has_perm(cred, inode, |
2106 | FILE__READ | FILE__WRITE, NULL)) { | 2110 | FILE__READ | FILE__WRITE, NULL, 0)) { |
2107 | drop_tty = 1; | 2111 | drop_tty = 1; |
2108 | } | 2112 | } |
2109 | } | 2113 | } |
@@ -2649,10 +2653,6 @@ static int selinux_inode_permission(struct inode *inode, int mask, unsigned flag | |||
2649 | if (!mask) | 2653 | if (!mask) |
2650 | return 0; | 2654 | return 0; |
2651 | 2655 | ||
2652 | /* May be droppable after audit */ | ||
2653 | if (flags & IPERM_FLAG_RCU) | ||
2654 | return -ECHILD; | ||
2655 | |||
2656 | COMMON_AUDIT_DATA_INIT(&ad, FS); | 2656 | COMMON_AUDIT_DATA_INIT(&ad, FS); |
2657 | ad.u.fs.inode = inode; | 2657 | ad.u.fs.inode = inode; |
2658 | 2658 | ||
@@ -2661,7 +2661,7 @@ static int selinux_inode_permission(struct inode *inode, int mask, unsigned flag | |||
2661 | 2661 | ||
2662 | perms = file_mask_to_av(inode->i_mode, mask); | 2662 | perms = file_mask_to_av(inode->i_mode, mask); |
2663 | 2663 | ||
2664 | return inode_has_perm(cred, inode, perms, &ad); | 2664 | return inode_has_perm(cred, inode, perms, &ad, flags); |
2665 | } | 2665 | } |
2666 | 2666 | ||
2667 | static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) | 2667 | static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) |
@@ -3208,7 +3208,7 @@ static int selinux_dentry_open(struct file *file, const struct cred *cred) | |||
3208 | * new inode label or new policy. | 3208 | * new inode label or new policy. |
3209 | * This check is not redundant - do not remove. | 3209 | * This check is not redundant - do not remove. |
3210 | */ | 3210 | */ |
3211 | return inode_has_perm(cred, inode, open_file_to_av(file), NULL); | 3211 | return inode_has_perm(cred, inode, open_file_to_av(file), NULL, 0); |
3212 | } | 3212 | } |
3213 | 3213 | ||
3214 | /* task security operations */ | 3214 | /* task security operations */ |
diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index 5615081b73ec..e77b2ac2908b 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h | |||
@@ -54,11 +54,11 @@ struct avc_cache_stats { | |||
54 | 54 | ||
55 | void __init avc_init(void); | 55 | void __init avc_init(void); |
56 | 56 | ||
57 | void avc_audit(u32 ssid, u32 tsid, | 57 | int avc_audit(u32 ssid, u32 tsid, |
58 | u16 tclass, u32 requested, | 58 | u16 tclass, u32 requested, |
59 | struct av_decision *avd, | 59 | struct av_decision *avd, |
60 | int result, | 60 | int result, |
61 | struct common_audit_data *a); | 61 | struct common_audit_data *a, unsigned flags); |
62 | 62 | ||
63 | #define AVC_STRICT 1 /* Ignore permissive mode. */ | 63 | #define AVC_STRICT 1 /* Ignore permissive mode. */ |
64 | int avc_has_perm_noaudit(u32 ssid, u32 tsid, | 64 | int avc_has_perm_noaudit(u32 ssid, u32 tsid, |
@@ -66,9 +66,17 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid, | |||
66 | unsigned flags, | 66 | unsigned flags, |
67 | struct av_decision *avd); | 67 | struct av_decision *avd); |
68 | 68 | ||
69 | int avc_has_perm(u32 ssid, u32 tsid, | 69 | int avc_has_perm_flags(u32 ssid, u32 tsid, |
70 | u16 tclass, u32 requested, | 70 | u16 tclass, u32 requested, |
71 | struct common_audit_data *auditdata); | 71 | struct common_audit_data *auditdata, |
72 | unsigned); | ||
73 | |||
74 | static inline int avc_has_perm(u32 ssid, u32 tsid, | ||
75 | u16 tclass, u32 requested, | ||
76 | struct common_audit_data *auditdata) | ||
77 | { | ||
78 | return avc_has_perm_flags(ssid, tsid, tclass, requested, auditdata, 0); | ||
79 | } | ||
72 | 80 | ||
73 | u32 avc_policy_seqno(void); | 81 | u32 avc_policy_seqno(void); |
74 | 82 | ||