diff options
author | Venkat Yekkirala <vyekkirala@TrustedCS.com> | 2006-07-25 02:29:07 -0400 |
---|---|---|
committer | David S. Miller <davem@sunset.davemloft.net> | 2006-09-22 17:53:24 -0400 |
commit | e0d1caa7b0d5f02e4f34aa09c695d04251310c6c (patch) | |
tree | bf023c17abf6813f2694ebf5fafff82edd6a1023 /security/selinux | |
parent | b6340fcd761acf9249b3acbc95c4dc555d9beb07 (diff) |
[MLSXFRM]: Flow based matching of xfrm policy and state
This implements a seemless mechanism for xfrm policy selection and
state matching based on the flow sid. This also includes the necessary
SELinux enforcement pieces.
Signed-off-by: Venkat Yekkirala <vyekkirala@TrustedCS.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'security/selinux')
-rw-r--r-- | security/selinux/hooks.c | 7 | ||||
-rw-r--r-- | security/selinux/include/xfrm.h | 23 | ||||
-rw-r--r-- | security/selinux/xfrm.c | 199 |
3 files changed, 188 insertions, 41 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index d67abf77584a..5c189da07bc9 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c | |||
@@ -3468,7 +3468,7 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) | |||
3468 | if (err) | 3468 | if (err) |
3469 | goto out; | 3469 | goto out; |
3470 | 3470 | ||
3471 | err = selinux_xfrm_sock_rcv_skb(sock_sid, skb); | 3471 | err = selinux_xfrm_sock_rcv_skb(sock_sid, skb, &ad); |
3472 | out: | 3472 | out: |
3473 | return err; | 3473 | return err; |
3474 | } | 3474 | } |
@@ -3720,7 +3720,7 @@ static unsigned int selinux_ip_postroute_last(unsigned int hooknum, | |||
3720 | if (err) | 3720 | if (err) |
3721 | goto out; | 3721 | goto out; |
3722 | 3722 | ||
3723 | err = selinux_xfrm_postroute_last(isec->sid, skb); | 3723 | err = selinux_xfrm_postroute_last(isec->sid, skb, &ad); |
3724 | out: | 3724 | out: |
3725 | return err ? NF_DROP : NF_ACCEPT; | 3725 | return err ? NF_DROP : NF_ACCEPT; |
3726 | } | 3726 | } |
@@ -4633,6 +4633,9 @@ static struct security_operations selinux_ops = { | |||
4633 | .xfrm_state_free_security = selinux_xfrm_state_free, | 4633 | .xfrm_state_free_security = selinux_xfrm_state_free, |
4634 | .xfrm_state_delete_security = selinux_xfrm_state_delete, | 4634 | .xfrm_state_delete_security = selinux_xfrm_state_delete, |
4635 | .xfrm_policy_lookup = selinux_xfrm_policy_lookup, | 4635 | .xfrm_policy_lookup = selinux_xfrm_policy_lookup, |
4636 | .xfrm_state_pol_flow_match = selinux_xfrm_state_pol_flow_match, | ||
4637 | .xfrm_flow_state_match = selinux_xfrm_flow_state_match, | ||
4638 | .xfrm_decode_session = selinux_xfrm_decode_session, | ||
4636 | #endif | 4639 | #endif |
4637 | 4640 | ||
4638 | #ifdef CONFIG_KEYS | 4641 | #ifdef CONFIG_KEYS |
diff --git a/security/selinux/include/xfrm.h b/security/selinux/include/xfrm.h index c96498a10eb8..f51a3e84bd9b 100644 --- a/security/selinux/include/xfrm.h +++ b/security/selinux/include/xfrm.h | |||
@@ -2,6 +2,7 @@ | |||
2 | * SELinux support for the XFRM LSM hooks | 2 | * SELinux support for the XFRM LSM hooks |
3 | * | 3 | * |
4 | * Author : Trent Jaeger, <jaegert@us.ibm.com> | 4 | * Author : Trent Jaeger, <jaegert@us.ibm.com> |
5 | * Updated : Venkat Yekkirala, <vyekkirala@TrustedCS.com> | ||
5 | */ | 6 | */ |
6 | #ifndef _SELINUX_XFRM_H_ | 7 | #ifndef _SELINUX_XFRM_H_ |
7 | #define _SELINUX_XFRM_H_ | 8 | #define _SELINUX_XFRM_H_ |
@@ -10,10 +11,16 @@ int selinux_xfrm_policy_alloc(struct xfrm_policy *xp, struct xfrm_user_sec_ctx * | |||
10 | int selinux_xfrm_policy_clone(struct xfrm_policy *old, struct xfrm_policy *new); | 11 | int selinux_xfrm_policy_clone(struct xfrm_policy *old, struct xfrm_policy *new); |
11 | void selinux_xfrm_policy_free(struct xfrm_policy *xp); | 12 | void selinux_xfrm_policy_free(struct xfrm_policy *xp); |
12 | int selinux_xfrm_policy_delete(struct xfrm_policy *xp); | 13 | int selinux_xfrm_policy_delete(struct xfrm_policy *xp); |
13 | int selinux_xfrm_state_alloc(struct xfrm_state *x, struct xfrm_user_sec_ctx *sec_ctx); | 14 | int selinux_xfrm_state_alloc(struct xfrm_state *x, |
15 | struct xfrm_user_sec_ctx *sec_ctx, struct xfrm_sec_ctx *pol, u32 secid); | ||
14 | void selinux_xfrm_state_free(struct xfrm_state *x); | 16 | void selinux_xfrm_state_free(struct xfrm_state *x); |
15 | int selinux_xfrm_state_delete(struct xfrm_state *x); | 17 | int selinux_xfrm_state_delete(struct xfrm_state *x); |
16 | int selinux_xfrm_policy_lookup(struct xfrm_policy *xp, u32 sk_sid, u8 dir); | 18 | int selinux_xfrm_policy_lookup(struct xfrm_policy *xp, u32 fl_secid, u8 dir); |
19 | int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, | ||
20 | struct xfrm_policy *xp, struct flowi *fl); | ||
21 | int selinux_xfrm_flow_state_match(struct flowi *fl, struct xfrm_state *xfrm); | ||
22 | int selinux_xfrm_decode_session(struct sk_buff *skb, struct flowi *fl); | ||
23 | |||
17 | 24 | ||
18 | /* | 25 | /* |
19 | * Extract the security blob from the sock (it's actually on the socket) | 26 | * Extract the security blob from the sock (it's actually on the socket) |
@@ -39,17 +46,21 @@ static inline u32 selinux_no_sk_sid(struct flowi *fl) | |||
39 | } | 46 | } |
40 | 47 | ||
41 | #ifdef CONFIG_SECURITY_NETWORK_XFRM | 48 | #ifdef CONFIG_SECURITY_NETWORK_XFRM |
42 | int selinux_xfrm_sock_rcv_skb(u32 sid, struct sk_buff *skb); | 49 | int selinux_xfrm_sock_rcv_skb(u32 sid, struct sk_buff *skb, |
43 | int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb); | 50 | struct avc_audit_data *ad); |
51 | int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb, | ||
52 | struct avc_audit_data *ad); | ||
44 | u32 selinux_socket_getpeer_stream(struct sock *sk); | 53 | u32 selinux_socket_getpeer_stream(struct sock *sk); |
45 | u32 selinux_socket_getpeer_dgram(struct sk_buff *skb); | 54 | u32 selinux_socket_getpeer_dgram(struct sk_buff *skb); |
46 | #else | 55 | #else |
47 | static inline int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb) | 56 | static inline int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb, |
57 | struct avc_audit_data *ad) | ||
48 | { | 58 | { |
49 | return 0; | 59 | return 0; |
50 | } | 60 | } |
51 | 61 | ||
52 | static inline int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb) | 62 | static inline int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb, |
63 | struct avc_audit_data *ad) | ||
53 | { | 64 | { |
54 | return 0; | 65 | return 0; |
55 | } | 66 | } |
diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c index 6c985ced8102..a502b0540e3d 100644 --- a/security/selinux/xfrm.c +++ b/security/selinux/xfrm.c | |||
@@ -6,7 +6,12 @@ | |||
6 | * Authors: Serge Hallyn <sergeh@us.ibm.com> | 6 | * Authors: Serge Hallyn <sergeh@us.ibm.com> |
7 | * Trent Jaeger <jaegert@us.ibm.com> | 7 | * Trent Jaeger <jaegert@us.ibm.com> |
8 | * | 8 | * |
9 | * Updated: Venkat Yekkirala <vyekkirala@TrustedCS.com> | ||
10 | * | ||
11 | * Granular IPSec Associations for use in MLS environments. | ||
12 | * | ||
9 | * Copyright (C) 2005 International Business Machines Corporation | 13 | * Copyright (C) 2005 International Business Machines Corporation |
14 | * Copyright (C) 2006 Trusted Computer Solutions, Inc. | ||
10 | * | 15 | * |
11 | * This program is free software; you can redistribute it and/or modify | 16 | * This program is free software; you can redistribute it and/or modify |
12 | * it under the terms of the GNU General Public License version 2, | 17 | * it under the terms of the GNU General Public License version 2, |
@@ -67,10 +72,10 @@ static inline int selinux_authorizable_xfrm(struct xfrm_state *x) | |||
67 | } | 72 | } |
68 | 73 | ||
69 | /* | 74 | /* |
70 | * LSM hook implementation that authorizes that a socket can be used | 75 | * LSM hook implementation that authorizes that a flow can use |
71 | * with the corresponding xfrm_sec_ctx and direction. | 76 | * a xfrm policy rule. |
72 | */ | 77 | */ |
73 | int selinux_xfrm_policy_lookup(struct xfrm_policy *xp, u32 sk_sid, u8 dir) | 78 | int selinux_xfrm_policy_lookup(struct xfrm_policy *xp, u32 fl_secid, u8 dir) |
74 | { | 79 | { |
75 | int rc = 0; | 80 | int rc = 0; |
76 | u32 sel_sid = SECINITSID_UNLABELED; | 81 | u32 sel_sid = SECINITSID_UNLABELED; |
@@ -84,27 +89,129 @@ int selinux_xfrm_policy_lookup(struct xfrm_policy *xp, u32 sk_sid, u8 dir) | |||
84 | sel_sid = ctx->ctx_sid; | 89 | sel_sid = ctx->ctx_sid; |
85 | } | 90 | } |
86 | 91 | ||
87 | rc = avc_has_perm(sk_sid, sel_sid, SECCLASS_ASSOCIATION, | 92 | rc = avc_has_perm(fl_secid, sel_sid, SECCLASS_ASSOCIATION, |
88 | ((dir == FLOW_DIR_IN) ? ASSOCIATION__RECVFROM : | 93 | ASSOCIATION__POLMATCH, |
89 | ((dir == FLOW_DIR_OUT) ? ASSOCIATION__SENDTO : | ||
90 | (ASSOCIATION__SENDTO | ASSOCIATION__RECVFROM))), | ||
91 | NULL); | 94 | NULL); |
92 | 95 | ||
93 | return rc; | 96 | return rc; |
94 | } | 97 | } |
95 | 98 | ||
96 | /* | 99 | /* |
100 | * LSM hook implementation that authorizes that a state matches | ||
101 | * the given policy, flow combo. | ||
102 | */ | ||
103 | |||
104 | int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, struct xfrm_policy *xp, | ||
105 | struct flowi *fl) | ||
106 | { | ||
107 | u32 state_sid; | ||
108 | u32 pol_sid; | ||
109 | int err; | ||
110 | |||
111 | if (x->security) | ||
112 | state_sid = x->security->ctx_sid; | ||
113 | else | ||
114 | state_sid = SECINITSID_UNLABELED; | ||
115 | |||
116 | if (xp->security) | ||
117 | pol_sid = xp->security->ctx_sid; | ||
118 | else | ||
119 | pol_sid = SECINITSID_UNLABELED; | ||
120 | |||
121 | err = avc_has_perm(state_sid, pol_sid, SECCLASS_ASSOCIATION, | ||
122 | ASSOCIATION__POLMATCH, | ||
123 | NULL); | ||
124 | |||
125 | if (err) | ||
126 | return 0; | ||
127 | |||
128 | return selinux_xfrm_flow_state_match(fl, x); | ||
129 | } | ||
130 | |||
131 | /* | ||
132 | * LSM hook implementation that authorizes that a particular outgoing flow | ||
133 | * can use a given security association. | ||
134 | */ | ||
135 | |||
136 | int selinux_xfrm_flow_state_match(struct flowi *fl, struct xfrm_state *xfrm) | ||
137 | { | ||
138 | int rc = 0; | ||
139 | u32 sel_sid = SECINITSID_UNLABELED; | ||
140 | struct xfrm_sec_ctx *ctx; | ||
141 | |||
142 | /* Context sid is either set to label or ANY_ASSOC */ | ||
143 | if ((ctx = xfrm->security)) { | ||
144 | if (!selinux_authorizable_ctx(ctx)) | ||
145 | return 0; | ||
146 | |||
147 | sel_sid = ctx->ctx_sid; | ||
148 | } | ||
149 | |||
150 | rc = avc_has_perm(fl->secid, sel_sid, SECCLASS_ASSOCIATION, | ||
151 | ASSOCIATION__SENDTO, | ||
152 | NULL)? 0:1; | ||
153 | |||
154 | return rc; | ||
155 | } | ||
156 | |||
157 | /* | ||
158 | * LSM hook implementation that determines the sid for the session. | ||
159 | */ | ||
160 | |||
161 | int selinux_xfrm_decode_session(struct sk_buff *skb, struct flowi *fl) | ||
162 | { | ||
163 | struct sec_path *sp; | ||
164 | |||
165 | fl->secid = SECSID_NULL; | ||
166 | |||
167 | if (skb == NULL) | ||
168 | return 0; | ||
169 | |||
170 | sp = skb->sp; | ||
171 | if (sp) { | ||
172 | int i, sid_set = 0; | ||
173 | |||
174 | for (i = sp->len-1; i >= 0; i--) { | ||
175 | struct xfrm_state *x = sp->xvec[i]; | ||
176 | if (selinux_authorizable_xfrm(x)) { | ||
177 | struct xfrm_sec_ctx *ctx = x->security; | ||
178 | |||
179 | if (!sid_set) { | ||
180 | fl->secid = ctx->ctx_sid; | ||
181 | sid_set = 1; | ||
182 | } | ||
183 | else if (fl->secid != ctx->ctx_sid) | ||
184 | return -EINVAL; | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | |||
189 | return 0; | ||
190 | } | ||
191 | |||
192 | /* | ||
97 | * Security blob allocation for xfrm_policy and xfrm_state | 193 | * Security blob allocation for xfrm_policy and xfrm_state |
98 | * CTX does not have a meaningful value on input | 194 | * CTX does not have a meaningful value on input |
99 | */ | 195 | */ |
100 | static int selinux_xfrm_sec_ctx_alloc(struct xfrm_sec_ctx **ctxp, struct xfrm_user_sec_ctx *uctx) | 196 | static int selinux_xfrm_sec_ctx_alloc(struct xfrm_sec_ctx **ctxp, |
197 | struct xfrm_user_sec_ctx *uctx, struct xfrm_sec_ctx *pol, u32 sid) | ||
101 | { | 198 | { |
102 | int rc = 0; | 199 | int rc = 0; |
103 | struct task_security_struct *tsec = current->security; | 200 | struct task_security_struct *tsec = current->security; |
104 | struct xfrm_sec_ctx *ctx; | 201 | struct xfrm_sec_ctx *ctx = NULL; |
202 | char *ctx_str = NULL; | ||
203 | u32 str_len; | ||
204 | u32 ctx_sid; | ||
205 | |||
206 | BUG_ON(uctx && pol); | ||
207 | |||
208 | if (pol) | ||
209 | goto from_policy; | ||
105 | 210 | ||
106 | BUG_ON(!uctx); | 211 | BUG_ON(!uctx); |
107 | BUG_ON(uctx->ctx_doi != XFRM_SC_ALG_SELINUX); | 212 | |
213 | if (uctx->ctx_doi != XFRM_SC_ALG_SELINUX) | ||
214 | return -EINVAL; | ||
108 | 215 | ||
109 | if (uctx->ctx_len >= PAGE_SIZE) | 216 | if (uctx->ctx_len >= PAGE_SIZE) |
110 | return -ENOMEM; | 217 | return -ENOMEM; |
@@ -141,9 +248,41 @@ static int selinux_xfrm_sec_ctx_alloc(struct xfrm_sec_ctx **ctxp, struct xfrm_us | |||
141 | 248 | ||
142 | return rc; | 249 | return rc; |
143 | 250 | ||
251 | from_policy: | ||
252 | BUG_ON(!pol); | ||
253 | rc = security_sid_mls_copy(pol->ctx_sid, sid, &ctx_sid); | ||
254 | if (rc) | ||
255 | goto out; | ||
256 | |||
257 | rc = security_sid_to_context(ctx_sid, &ctx_str, &str_len); | ||
258 | if (rc) | ||
259 | goto out; | ||
260 | |||
261 | *ctxp = ctx = kmalloc(sizeof(*ctx) + | ||
262 | str_len, | ||
263 | GFP_ATOMIC); | ||
264 | |||
265 | if (!ctx) { | ||
266 | rc = -ENOMEM; | ||
267 | goto out; | ||
268 | } | ||
269 | |||
270 | |||
271 | ctx->ctx_doi = XFRM_SC_DOI_LSM; | ||
272 | ctx->ctx_alg = XFRM_SC_ALG_SELINUX; | ||
273 | ctx->ctx_sid = ctx_sid; | ||
274 | ctx->ctx_len = str_len; | ||
275 | memcpy(ctx->ctx_str, | ||
276 | ctx_str, | ||
277 | str_len); | ||
278 | |||
279 | goto out2; | ||
280 | |||
144 | out: | 281 | out: |
145 | *ctxp = NULL; | 282 | *ctxp = NULL; |
146 | kfree(ctx); | 283 | kfree(ctx); |
284 | out2: | ||
285 | kfree(ctx_str); | ||
147 | return rc; | 286 | return rc; |
148 | } | 287 | } |
149 | 288 | ||
@@ -157,7 +296,7 @@ int selinux_xfrm_policy_alloc(struct xfrm_policy *xp, struct xfrm_user_sec_ctx * | |||
157 | 296 | ||
158 | BUG_ON(!xp); | 297 | BUG_ON(!xp); |
159 | 298 | ||
160 | err = selinux_xfrm_sec_ctx_alloc(&xp->security, uctx); | 299 | err = selinux_xfrm_sec_ctx_alloc(&xp->security, uctx, NULL, 0); |
161 | return err; | 300 | return err; |
162 | } | 301 | } |
163 | 302 | ||
@@ -217,13 +356,14 @@ int selinux_xfrm_policy_delete(struct xfrm_policy *xp) | |||
217 | * LSM hook implementation that allocs and transfers sec_ctx spec to | 356 | * LSM hook implementation that allocs and transfers sec_ctx spec to |
218 | * xfrm_state. | 357 | * xfrm_state. |
219 | */ | 358 | */ |
220 | int selinux_xfrm_state_alloc(struct xfrm_state *x, struct xfrm_user_sec_ctx *uctx) | 359 | int selinux_xfrm_state_alloc(struct xfrm_state *x, struct xfrm_user_sec_ctx *uctx, |
360 | struct xfrm_sec_ctx *pol, u32 secid) | ||
221 | { | 361 | { |
222 | int err; | 362 | int err; |
223 | 363 | ||
224 | BUG_ON(!x); | 364 | BUG_ON(!x); |
225 | 365 | ||
226 | err = selinux_xfrm_sec_ctx_alloc(&x->security, uctx); | 366 | err = selinux_xfrm_sec_ctx_alloc(&x->security, uctx, pol, secid); |
227 | return err; | 367 | return err; |
228 | } | 368 | } |
229 | 369 | ||
@@ -329,38 +469,30 @@ int selinux_xfrm_state_delete(struct xfrm_state *x) | |||
329 | * we need to check for unlabelled access since this may not have | 469 | * we need to check for unlabelled access since this may not have |
330 | * gone thru the IPSec process. | 470 | * gone thru the IPSec process. |
331 | */ | 471 | */ |
332 | int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb) | 472 | int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb, |
473 | struct avc_audit_data *ad) | ||
333 | { | 474 | { |
334 | int i, rc = 0; | 475 | int i, rc = 0; |
335 | struct sec_path *sp; | 476 | struct sec_path *sp; |
477 | u32 sel_sid = SECINITSID_UNLABELED; | ||
336 | 478 | ||
337 | sp = skb->sp; | 479 | sp = skb->sp; |
338 | 480 | ||
339 | if (sp) { | 481 | if (sp) { |
340 | /* | ||
341 | * __xfrm_policy_check does not approve unless xfrm_policy_ok | ||
342 | * says that spi's match for policy and the socket. | ||
343 | * | ||
344 | * Only need to verify the existence of an authorizable sp. | ||
345 | */ | ||
346 | for (i = 0; i < sp->len; i++) { | 482 | for (i = 0; i < sp->len; i++) { |
347 | struct xfrm_state *x = sp->xvec[i]; | 483 | struct xfrm_state *x = sp->xvec[i]; |
348 | 484 | ||
349 | if (x && selinux_authorizable_xfrm(x)) | 485 | if (x && selinux_authorizable_xfrm(x)) { |
350 | goto accept; | 486 | struct xfrm_sec_ctx *ctx = x->security; |
487 | sel_sid = ctx->ctx_sid; | ||
488 | break; | ||
489 | } | ||
351 | } | 490 | } |
352 | } | 491 | } |
353 | 492 | ||
354 | /* check SELinux sock for unlabelled access */ | 493 | rc = avc_has_perm(isec_sid, sel_sid, SECCLASS_ASSOCIATION, |
355 | rc = avc_has_perm(isec_sid, SECINITSID_UNLABELED, SECCLASS_ASSOCIATION, | 494 | ASSOCIATION__RECVFROM, ad); |
356 | ASSOCIATION__RECVFROM, NULL); | ||
357 | if (rc) | ||
358 | goto drop; | ||
359 | |||
360 | accept: | ||
361 | return 0; | ||
362 | 495 | ||
363 | drop: | ||
364 | return rc; | 496 | return rc; |
365 | } | 497 | } |
366 | 498 | ||
@@ -371,7 +503,8 @@ drop: | |||
371 | * If we do have a authorizable security association, then it has already been | 503 | * If we do have a authorizable security association, then it has already been |
372 | * checked in xfrm_policy_lookup hook. | 504 | * checked in xfrm_policy_lookup hook. |
373 | */ | 505 | */ |
374 | int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb) | 506 | int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb, |
507 | struct avc_audit_data *ad) | ||
375 | { | 508 | { |
376 | struct dst_entry *dst; | 509 | struct dst_entry *dst; |
377 | int rc = 0; | 510 | int rc = 0; |
@@ -391,7 +524,7 @@ int selinux_xfrm_postroute_last(u32 isec_sid, struct sk_buff *skb) | |||
391 | } | 524 | } |
392 | 525 | ||
393 | rc = avc_has_perm(isec_sid, SECINITSID_UNLABELED, SECCLASS_ASSOCIATION, | 526 | rc = avc_has_perm(isec_sid, SECINITSID_UNLABELED, SECCLASS_ASSOCIATION, |
394 | ASSOCIATION__SENDTO, NULL); | 527 | ASSOCIATION__SENDTO, ad); |
395 | out: | 528 | out: |
396 | return rc; | 529 | return rc; |
397 | } | 530 | } |