diff options
author | Paul Moore <paul.moore@hp.com> | 2006-11-17 17:38:53 -0500 |
---|---|---|
committer | David S. Miller <davem@sunset.davemloft.net> | 2006-12-03 00:24:13 -0500 |
commit | 9f2ad66509b182b399a5b03de487f45bde623524 (patch) | |
tree | 8376dc2db99a78c1b043644f019c4dc224187f16 /security | |
parent | 9bb5fd2b05cb4dba229e225536faa59eaadd837d (diff) |
NetLabel: SELinux cleanups
This patch does a lot of cleanup in the SELinux NetLabel support code. A
summary of the changes include:
* Use RCU locking for the NetLabel state variable in the skk_security_struct
instead of using the inode_security_struct mutex.
* Remove unnecessary parameters in selinux_netlbl_socket_post_create().
* Rename selinux_netlbl_sk_clone_security() to
selinux_netlbl_sk_security_clone() to better fit the other NetLabel
sk_security functions.
* Improvements to selinux_netlbl_inode_permission() to help reduce the cost of
the common case.
Signed-off-by: Paul Moore <paul.moore@hp.com>
Signed-off-by: James Morris <jmorris@namei.org>
Diffstat (limited to 'security')
-rw-r--r-- | security/selinux/hooks.c | 10 | ||||
-rw-r--r-- | security/selinux/include/objsec.h | 2 | ||||
-rw-r--r-- | security/selinux/include/selinux_netlabel.h | 21 | ||||
-rw-r--r-- | security/selinux/ss/services.c | 107 |
4 files changed, 86 insertions, 54 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 0cf98740ddc6..975c0dfb5a11 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c | |||
@@ -3140,9 +3140,7 @@ static int selinux_socket_post_create(struct socket *sock, int family, | |||
3140 | if (sock->sk) { | 3140 | if (sock->sk) { |
3141 | sksec = sock->sk->sk_security; | 3141 | sksec = sock->sk->sk_security; |
3142 | sksec->sid = isec->sid; | 3142 | sksec->sid = isec->sid; |
3143 | err = selinux_netlbl_socket_post_create(sock, | 3143 | err = selinux_netlbl_socket_post_create(sock); |
3144 | family, | ||
3145 | isec->sid); | ||
3146 | } | 3144 | } |
3147 | 3145 | ||
3148 | return err; | 3146 | return err; |
@@ -3661,7 +3659,7 @@ static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk) | |||
3661 | newssec->sid = ssec->sid; | 3659 | newssec->sid = ssec->sid; |
3662 | newssec->peer_sid = ssec->peer_sid; | 3660 | newssec->peer_sid = ssec->peer_sid; |
3663 | 3661 | ||
3664 | selinux_netlbl_sk_clone_security(ssec, newssec); | 3662 | selinux_netlbl_sk_security_clone(ssec, newssec); |
3665 | } | 3663 | } |
3666 | 3664 | ||
3667 | static void selinux_sk_getsecid(struct sock *sk, u32 *secid) | 3665 | static void selinux_sk_getsecid(struct sock *sk, u32 *secid) |
@@ -3730,7 +3728,9 @@ static void selinux_inet_csk_clone(struct sock *newsk, | |||
3730 | So we will wait until sock_graft to do it, by which | 3728 | So we will wait until sock_graft to do it, by which |
3731 | time it will have been created and available. */ | 3729 | time it will have been created and available. */ |
3732 | 3730 | ||
3733 | selinux_netlbl_sk_security_init(newsksec, req->rsk_ops->family); | 3731 | /* We don't need to take any sort of lock here as we are the only |
3732 | * thread with access to newsksec */ | ||
3733 | selinux_netlbl_sk_security_reset(newsksec, req->rsk_ops->family); | ||
3734 | } | 3734 | } |
3735 | 3735 | ||
3736 | static void selinux_inet_conn_established(struct sock *sk, | 3736 | static void selinux_inet_conn_established(struct sock *sk, |
diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index ef2267fea8bd..91b88f0ba20c 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h | |||
@@ -23,6 +23,7 @@ | |||
23 | #include <linux/fs.h> | 23 | #include <linux/fs.h> |
24 | #include <linux/binfmts.h> | 24 | #include <linux/binfmts.h> |
25 | #include <linux/in.h> | 25 | #include <linux/in.h> |
26 | #include <linux/spinlock.h> | ||
26 | #include "flask.h" | 27 | #include "flask.h" |
27 | #include "avc.h" | 28 | #include "avc.h" |
28 | 29 | ||
@@ -108,6 +109,7 @@ struct sk_security_struct { | |||
108 | NLBL_REQUIRE, | 109 | NLBL_REQUIRE, |
109 | NLBL_LABELED, | 110 | NLBL_LABELED, |
110 | } nlbl_state; | 111 | } nlbl_state; |
112 | spinlock_t nlbl_lock; /* protects nlbl_state */ | ||
111 | #endif | 113 | #endif |
112 | }; | 114 | }; |
113 | 115 | ||
diff --git a/security/selinux/include/selinux_netlabel.h b/security/selinux/include/selinux_netlabel.h index 9de10cc2cef2..57943f4a8f90 100644 --- a/security/selinux/include/selinux_netlabel.h +++ b/security/selinux/include/selinux_netlabel.h | |||
@@ -38,9 +38,7 @@ | |||
38 | 38 | ||
39 | #ifdef CONFIG_NETLABEL | 39 | #ifdef CONFIG_NETLABEL |
40 | void selinux_netlbl_cache_invalidate(void); | 40 | void selinux_netlbl_cache_invalidate(void); |
41 | int selinux_netlbl_socket_post_create(struct socket *sock, | 41 | int selinux_netlbl_socket_post_create(struct socket *sock); |
42 | int sock_family, | ||
43 | u32 sid); | ||
44 | void selinux_netlbl_sock_graft(struct sock *sk, struct socket *sock); | 42 | void selinux_netlbl_sock_graft(struct sock *sk, struct socket *sock); |
45 | u32 selinux_netlbl_inet_conn_request(struct sk_buff *skb, u32 sock_sid); | 43 | u32 selinux_netlbl_inet_conn_request(struct sk_buff *skb, u32 sock_sid); |
46 | int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, | 44 | int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, |
@@ -48,9 +46,11 @@ int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, | |||
48 | struct avc_audit_data *ad); | 46 | struct avc_audit_data *ad); |
49 | u32 selinux_netlbl_socket_getpeersec_stream(struct socket *sock); | 47 | u32 selinux_netlbl_socket_getpeersec_stream(struct socket *sock); |
50 | u32 selinux_netlbl_socket_getpeersec_dgram(struct sk_buff *skb); | 48 | u32 selinux_netlbl_socket_getpeersec_dgram(struct sk_buff *skb); |
49 | void selinux_netlbl_sk_security_reset(struct sk_security_struct *ssec, | ||
50 | int family); | ||
51 | void selinux_netlbl_sk_security_init(struct sk_security_struct *ssec, | 51 | void selinux_netlbl_sk_security_init(struct sk_security_struct *ssec, |
52 | int family); | 52 | int family); |
53 | void selinux_netlbl_sk_clone_security(struct sk_security_struct *ssec, | 53 | void selinux_netlbl_sk_security_clone(struct sk_security_struct *ssec, |
54 | struct sk_security_struct *newssec); | 54 | struct sk_security_struct *newssec); |
55 | int selinux_netlbl_inode_permission(struct inode *inode, int mask); | 55 | int selinux_netlbl_inode_permission(struct inode *inode, int mask); |
56 | int selinux_netlbl_socket_setsockopt(struct socket *sock, | 56 | int selinux_netlbl_socket_setsockopt(struct socket *sock, |
@@ -62,9 +62,7 @@ static inline void selinux_netlbl_cache_invalidate(void) | |||
62 | return; | 62 | return; |
63 | } | 63 | } |
64 | 64 | ||
65 | static inline int selinux_netlbl_socket_post_create(struct socket *sock, | 65 | static inline int selinux_netlbl_socket_post_create(struct socket *sock) |
66 | int sock_family, | ||
67 | u32 sid) | ||
68 | { | 66 | { |
69 | return 0; | 67 | return 0; |
70 | } | 68 | } |
@@ -98,6 +96,13 @@ static inline u32 selinux_netlbl_socket_getpeersec_dgram(struct sk_buff *skb) | |||
98 | return SECSID_NULL; | 96 | return SECSID_NULL; |
99 | } | 97 | } |
100 | 98 | ||
99 | static inline void selinux_netlbl_sk_security_reset( | ||
100 | struct sk_security_struct *ssec, | ||
101 | int family) | ||
102 | { | ||
103 | return; | ||
104 | } | ||
105 | |||
101 | static inline void selinux_netlbl_sk_security_init( | 106 | static inline void selinux_netlbl_sk_security_init( |
102 | struct sk_security_struct *ssec, | 107 | struct sk_security_struct *ssec, |
103 | int family) | 108 | int family) |
@@ -105,7 +110,7 @@ static inline void selinux_netlbl_sk_security_init( | |||
105 | return; | 110 | return; |
106 | } | 111 | } |
107 | 112 | ||
108 | static inline void selinux_netlbl_sk_clone_security( | 113 | static inline void selinux_netlbl_sk_security_clone( |
109 | struct sk_security_struct *ssec, | 114 | struct sk_security_struct *ssec, |
110 | struct sk_security_struct *newssec) | 115 | struct sk_security_struct *newssec) |
111 | { | 116 | { |
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 1f5bbb246d28..b66b454fe72b 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c | |||
@@ -33,6 +33,7 @@ | |||
33 | #include <linux/slab.h> | 33 | #include <linux/slab.h> |
34 | #include <linux/string.h> | 34 | #include <linux/string.h> |
35 | #include <linux/spinlock.h> | 35 | #include <linux/spinlock.h> |
36 | #include <linux/rcupdate.h> | ||
36 | #include <linux/errno.h> | 37 | #include <linux/errno.h> |
37 | #include <linux/in.h> | 38 | #include <linux/in.h> |
38 | #include <linux/sched.h> | 39 | #include <linux/sched.h> |
@@ -2435,7 +2436,9 @@ static int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, | |||
2435 | * | 2436 | * |
2436 | * Description: | 2437 | * Description: |
2437 | * Attempt to label a socket using the NetLabel mechanism using the given | 2438 | * Attempt to label a socket using the NetLabel mechanism using the given |
2438 | * SID. Returns zero values on success, negative values on failure. | 2439 | * SID. Returns zero values on success, negative values on failure. The |
2440 | * caller is responsibile for calling rcu_read_lock() before calling this | ||
2441 | * this function and rcu_read_unlock() after this function returns. | ||
2439 | * | 2442 | * |
2440 | */ | 2443 | */ |
2441 | static int selinux_netlbl_socket_setsid(struct socket *sock, u32 sid) | 2444 | static int selinux_netlbl_socket_setsid(struct socket *sock, u32 sid) |
@@ -2472,8 +2475,11 @@ static int selinux_netlbl_socket_setsid(struct socket *sock, u32 sid) | |||
2472 | secattr.flags |= NETLBL_SECATTR_MLS_CAT; | 2475 | secattr.flags |= NETLBL_SECATTR_MLS_CAT; |
2473 | 2476 | ||
2474 | rc = netlbl_socket_setattr(sock, &secattr); | 2477 | rc = netlbl_socket_setattr(sock, &secattr); |
2475 | if (rc == 0) | 2478 | if (rc == 0) { |
2479 | spin_lock(&sksec->nlbl_lock); | ||
2476 | sksec->nlbl_state = NLBL_LABELED; | 2480 | sksec->nlbl_state = NLBL_LABELED; |
2481 | spin_unlock(&sksec->nlbl_lock); | ||
2482 | } | ||
2477 | 2483 | ||
2478 | netlbl_socket_setsid_return: | 2484 | netlbl_socket_setsid_return: |
2479 | POLICY_RDUNLOCK; | 2485 | POLICY_RDUNLOCK; |
@@ -2482,6 +2488,25 @@ netlbl_socket_setsid_return: | |||
2482 | } | 2488 | } |
2483 | 2489 | ||
2484 | /** | 2490 | /** |
2491 | * selinux_netlbl_sk_security_reset - Reset the NetLabel fields | ||
2492 | * @ssec: the sk_security_struct | ||
2493 | * @family: the socket family | ||
2494 | * | ||
2495 | * Description: | ||
2496 | * Called when the NetLabel state of a sk_security_struct needs to be reset. | ||
2497 | * The caller is responsibile for all the NetLabel sk_security_struct locking. | ||
2498 | * | ||
2499 | */ | ||
2500 | void selinux_netlbl_sk_security_reset(struct sk_security_struct *ssec, | ||
2501 | int family) | ||
2502 | { | ||
2503 | if (family == PF_INET) | ||
2504 | ssec->nlbl_state = NLBL_REQUIRE; | ||
2505 | else | ||
2506 | ssec->nlbl_state = NLBL_UNSET; | ||
2507 | } | ||
2508 | |||
2509 | /** | ||
2485 | * selinux_netlbl_sk_security_init - Setup the NetLabel fields | 2510 | * selinux_netlbl_sk_security_init - Setup the NetLabel fields |
2486 | * @ssec: the sk_security_struct | 2511 | * @ssec: the sk_security_struct |
2487 | * @family: the socket family | 2512 | * @family: the socket family |
@@ -2494,14 +2519,13 @@ netlbl_socket_setsid_return: | |||
2494 | void selinux_netlbl_sk_security_init(struct sk_security_struct *ssec, | 2519 | void selinux_netlbl_sk_security_init(struct sk_security_struct *ssec, |
2495 | int family) | 2520 | int family) |
2496 | { | 2521 | { |
2497 | if (family == PF_INET) | 2522 | /* No locking needed, we are the only one who has access to ssec */ |
2498 | ssec->nlbl_state = NLBL_REQUIRE; | 2523 | selinux_netlbl_sk_security_reset(ssec, family); |
2499 | else | 2524 | spin_lock_init(&ssec->nlbl_lock); |
2500 | ssec->nlbl_state = NLBL_UNSET; | ||
2501 | } | 2525 | } |
2502 | 2526 | ||
2503 | /** | 2527 | /** |
2504 | * selinux_netlbl_sk_clone_security - Copy the NetLabel fields | 2528 | * selinux_netlbl_sk_security_clone - Copy the NetLabel fields |
2505 | * @ssec: the original sk_security_struct | 2529 | * @ssec: the original sk_security_struct |
2506 | * @newssec: the cloned sk_security_struct | 2530 | * @newssec: the cloned sk_security_struct |
2507 | * | 2531 | * |
@@ -2510,41 +2534,41 @@ void selinux_netlbl_sk_security_init(struct sk_security_struct *ssec, | |||
2510 | * @newssec. | 2534 | * @newssec. |
2511 | * | 2535 | * |
2512 | */ | 2536 | */ |
2513 | void selinux_netlbl_sk_clone_security(struct sk_security_struct *ssec, | 2537 | void selinux_netlbl_sk_security_clone(struct sk_security_struct *ssec, |
2514 | struct sk_security_struct *newssec) | 2538 | struct sk_security_struct *newssec) |
2515 | { | 2539 | { |
2540 | /* We don't need to take newssec->nlbl_lock because we are the only | ||
2541 | * thread with access to newssec, but we do need to take the RCU read | ||
2542 | * lock as other threads could have access to ssec */ | ||
2543 | rcu_read_lock(); | ||
2544 | selinux_netlbl_sk_security_reset(newssec, ssec->sk->sk_family); | ||
2516 | newssec->sclass = ssec->sclass; | 2545 | newssec->sclass = ssec->sclass; |
2517 | if (ssec->nlbl_state != NLBL_UNSET) | 2546 | rcu_read_unlock(); |
2518 | newssec->nlbl_state = NLBL_REQUIRE; | ||
2519 | else | ||
2520 | newssec->nlbl_state = NLBL_UNSET; | ||
2521 | } | 2547 | } |
2522 | 2548 | ||
2523 | /** | 2549 | /** |
2524 | * selinux_netlbl_socket_post_create - Label a socket using NetLabel | 2550 | * selinux_netlbl_socket_post_create - Label a socket using NetLabel |
2525 | * @sock: the socket to label | 2551 | * @sock: the socket to label |
2526 | * @sock_family: the socket family | ||
2527 | * @sid: the SID to use | ||
2528 | * | 2552 | * |
2529 | * Description: | 2553 | * Description: |
2530 | * Attempt to label a socket using the NetLabel mechanism using the given | 2554 | * Attempt to label a socket using the NetLabel mechanism using the given |
2531 | * SID. Returns zero values on success, negative values on failure. | 2555 | * SID. Returns zero values on success, negative values on failure. |
2532 | * | 2556 | * |
2533 | */ | 2557 | */ |
2534 | int selinux_netlbl_socket_post_create(struct socket *sock, | 2558 | int selinux_netlbl_socket_post_create(struct socket *sock) |
2535 | int sock_family, | ||
2536 | u32 sid) | ||
2537 | { | 2559 | { |
2560 | int rc = 0; | ||
2538 | struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; | 2561 | struct inode_security_struct *isec = SOCK_INODE(sock)->i_security; |
2539 | struct sk_security_struct *sksec = sock->sk->sk_security; | 2562 | struct sk_security_struct *sksec = sock->sk->sk_security; |
2540 | 2563 | ||
2541 | sksec->sclass = isec->sclass; | 2564 | sksec->sclass = isec->sclass; |
2542 | 2565 | ||
2543 | if (sock_family != PF_INET) | 2566 | rcu_read_lock(); |
2544 | return 0; | 2567 | if (sksec->nlbl_state == NLBL_REQUIRE) |
2568 | rc = selinux_netlbl_socket_setsid(sock, sksec->sid); | ||
2569 | rcu_read_unlock(); | ||
2545 | 2570 | ||
2546 | sksec->nlbl_state = NLBL_REQUIRE; | 2571 | return rc; |
2547 | return selinux_netlbl_socket_setsid(sock, sid); | ||
2548 | } | 2572 | } |
2549 | 2573 | ||
2550 | /** | 2574 | /** |
@@ -2566,8 +2590,12 @@ void selinux_netlbl_sock_graft(struct sock *sk, struct socket *sock) | |||
2566 | 2590 | ||
2567 | sksec->sclass = isec->sclass; | 2591 | sksec->sclass = isec->sclass; |
2568 | 2592 | ||
2569 | if (sk->sk_family != PF_INET) | 2593 | rcu_read_lock(); |
2594 | |||
2595 | if (sksec->nlbl_state != NLBL_REQUIRE) { | ||
2596 | rcu_read_unlock(); | ||
2570 | return; | 2597 | return; |
2598 | } | ||
2571 | 2599 | ||
2572 | netlbl_secattr_init(&secattr); | 2600 | netlbl_secattr_init(&secattr); |
2573 | if (netlbl_sock_getattr(sk, &secattr) == 0 && | 2601 | if (netlbl_sock_getattr(sk, &secattr) == 0 && |
@@ -2579,12 +2607,12 @@ void selinux_netlbl_sock_graft(struct sock *sk, struct socket *sock) | |||
2579 | sksec->peer_sid = nlbl_peer_sid; | 2607 | sksec->peer_sid = nlbl_peer_sid; |
2580 | netlbl_secattr_destroy(&secattr); | 2608 | netlbl_secattr_destroy(&secattr); |
2581 | 2609 | ||
2582 | sksec->nlbl_state = NLBL_REQUIRE; | ||
2583 | |||
2584 | /* Try to set the NetLabel on the socket to save time later, if we fail | 2610 | /* Try to set the NetLabel on the socket to save time later, if we fail |
2585 | * here we will pick up the pieces in later calls to | 2611 | * here we will pick up the pieces in later calls to |
2586 | * selinux_netlbl_inode_permission(). */ | 2612 | * selinux_netlbl_inode_permission(). */ |
2587 | selinux_netlbl_socket_setsid(sock, sksec->sid); | 2613 | selinux_netlbl_socket_setsid(sock, sksec->sid); |
2614 | |||
2615 | rcu_read_unlock(); | ||
2588 | } | 2616 | } |
2589 | 2617 | ||
2590 | /** | 2618 | /** |
@@ -2625,25 +2653,24 @@ u32 selinux_netlbl_inet_conn_request(struct sk_buff *skb, u32 sock_sid) | |||
2625 | int selinux_netlbl_inode_permission(struct inode *inode, int mask) | 2653 | int selinux_netlbl_inode_permission(struct inode *inode, int mask) |
2626 | { | 2654 | { |
2627 | int rc; | 2655 | int rc; |
2628 | struct inode_security_struct *isec; | ||
2629 | struct sk_security_struct *sksec; | 2656 | struct sk_security_struct *sksec; |
2630 | struct socket *sock; | 2657 | struct socket *sock; |
2631 | 2658 | ||
2632 | if (!S_ISSOCK(inode->i_mode)) | 2659 | if (!S_ISSOCK(inode->i_mode) || |
2660 | ((mask & (MAY_WRITE | MAY_APPEND)) == 0)) | ||
2633 | return 0; | 2661 | return 0; |
2634 | |||
2635 | sock = SOCKET_I(inode); | 2662 | sock = SOCKET_I(inode); |
2636 | isec = inode->i_security; | ||
2637 | sksec = sock->sk->sk_security; | 2663 | sksec = sock->sk->sk_security; |
2638 | mutex_lock(&isec->lock); | 2664 | |
2639 | if (unlikely(sksec->nlbl_state == NLBL_REQUIRE && | 2665 | rcu_read_lock(); |
2640 | (mask & (MAY_WRITE | MAY_APPEND)))) { | 2666 | if (sksec->nlbl_state != NLBL_REQUIRE) { |
2641 | lock_sock(sock->sk); | 2667 | rcu_read_unlock(); |
2642 | rc = selinux_netlbl_socket_setsid(sock, sksec->sid); | 2668 | return 0; |
2643 | release_sock(sock->sk); | 2669 | } |
2644 | } else | 2670 | lock_sock(sock->sk); |
2645 | rc = 0; | 2671 | rc = selinux_netlbl_socket_setsid(sock, sksec->sid); |
2646 | mutex_unlock(&isec->lock); | 2672 | release_sock(sock->sk); |
2673 | rcu_read_unlock(); | ||
2647 | 2674 | ||
2648 | return rc; | 2675 | return rc; |
2649 | } | 2676 | } |
@@ -2754,12 +2781,10 @@ int selinux_netlbl_socket_setsockopt(struct socket *sock, | |||
2754 | int optname) | 2781 | int optname) |
2755 | { | 2782 | { |
2756 | int rc = 0; | 2783 | int rc = 0; |
2757 | struct inode *inode = SOCK_INODE(sock); | ||
2758 | struct sk_security_struct *sksec = sock->sk->sk_security; | 2784 | struct sk_security_struct *sksec = sock->sk->sk_security; |
2759 | struct inode_security_struct *isec = inode->i_security; | ||
2760 | struct netlbl_lsm_secattr secattr; | 2785 | struct netlbl_lsm_secattr secattr; |
2761 | 2786 | ||
2762 | mutex_lock(&isec->lock); | 2787 | rcu_read_lock(); |
2763 | if (level == IPPROTO_IP && optname == IP_OPTIONS && | 2788 | if (level == IPPROTO_IP && optname == IP_OPTIONS && |
2764 | sksec->nlbl_state == NLBL_LABELED) { | 2789 | sksec->nlbl_state == NLBL_LABELED) { |
2765 | netlbl_secattr_init(&secattr); | 2790 | netlbl_secattr_init(&secattr); |
@@ -2768,7 +2793,7 @@ int selinux_netlbl_socket_setsockopt(struct socket *sock, | |||
2768 | rc = -EACCES; | 2793 | rc = -EACCES; |
2769 | netlbl_secattr_destroy(&secattr); | 2794 | netlbl_secattr_destroy(&secattr); |
2770 | } | 2795 | } |
2771 | mutex_unlock(&isec->lock); | 2796 | rcu_read_unlock(); |
2772 | 2797 | ||
2773 | return rc; | 2798 | return rc; |
2774 | } | 2799 | } |