1
2 /*
3 * Author : Stephen Smalley, <sds@epoch.ncsc.mil>
4 */
5 /*
6 * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com>
7 *
8 * Support for enhanced MLS infrastructure.
9 *
10 * Updated: Frank Mayer <mayerf@tresys.com>
11 * and Karl MacMillan <kmacmillan@tresys.com>
12 *
13 * Added conditional policy language extensions
14 *
15 * Updated: Red Hat, Inc. James Morris <jmorris@redhat.com>
16 *
17 * Fine-grained netlink support
18 * IPv6 support
19 * Code cleanup
20 *
21 * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc.
22 * Copyright (C) 2003 - 2004 Tresys Technology, LLC
23 * Copyright (C) 2003 - 2004 Red Hat, Inc.
24 *
25 * This library is free software; you can redistribute it and/or
26 * modify it under the terms of the GNU Lesser General Public
27 * License as published by the Free Software Foundation; either
28 * version 2.1 of the License, or (at your option) any later version.
29 *
30 * This library is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
33 * Lesser General Public License for more details.
34 *
35 * You should have received a copy of the GNU Lesser General Public
36 * License along with this library; if not, write to the Free Software
37 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
38 */
39
40 /* FLASK */
41
42 /*
43 * Implementation of the security services.
44 */
45
46 #include <stdlib.h>
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <netinet/in.h>
50 #include <arpa/inet.h>
51
52 #include <sepol/policydb/policydb.h>
53 #include <sepol/policydb/sidtab.h>
54 #include <sepol/policydb/services.h>
55 #include <sepol/policydb/conditional.h>
56 #include <sepol/policydb/flask.h>
57
58 #include "debug.h"
59 #include "private.h"
60 #include "context.h"
61 #include "av_permissions.h"
62 #include "dso.h"
63 #include "mls.h"
64
65 #define BUG() do { ERR(NULL, "Badness at %s:%d", __FILE__, __LINE__); } while (0)
66 #define BUG_ON(x) do { if (x) ERR(NULL, "Badness at %s:%d", __FILE__, __LINE__); } while (0)
67
68 static int selinux_enforcing = 1;
69
70 static sidtab_t mysidtab, *sidtab = &mysidtab;
71 static policydb_t mypolicydb, *policydb = &mypolicydb;
72
sepol_set_sidtab(sidtab_t * s)73 int hidden sepol_set_sidtab(sidtab_t * s)
74 {
75 sidtab = s;
76 return 0;
77 }
78
sepol_set_policydb(policydb_t * p)79 int hidden sepol_set_policydb(policydb_t * p)
80 {
81 policydb = p;
82 return 0;
83 }
84
sepol_set_policydb_from_file(FILE * fp)85 int sepol_set_policydb_from_file(FILE * fp)
86 {
87 struct policy_file pf;
88
89 policy_file_init(&pf);
90 pf.fp = fp;
91 pf.type = PF_USE_STDIO;
92 if (mypolicydb.policy_type)
93 policydb_destroy(&mypolicydb);
94 if (policydb_init(&mypolicydb)) {
95 ERR(NULL, "Out of memory!");
96 return -1;
97 }
98 if (policydb_read(&mypolicydb, &pf, 0)) {
99 policydb_destroy(&mypolicydb);
100 ERR(NULL, "can't read binary policy: %s", strerror(errno));
101 return -1;
102 }
103 policydb = &mypolicydb;
104 return sepol_sidtab_init(sidtab);
105 }
106
107 /*
108 * The largest sequence number that has been used when
109 * providing an access decision to the access vector cache.
110 * The sequence number only changes when a policy change
111 * occurs.
112 */
113 static uint32_t latest_granting = 0;
114
115 /*
116 * Return the boolean value of a constraint expression
117 * when it is applied to the specified source and target
118 * security contexts.
119 *
120 * xcontext is a special beast... It is used by the validatetrans rules
121 * only. For these rules, scontext is the context before the transition,
122 * tcontext is the context after the transition, and xcontext is the context
123 * of the process performing the transition. All other callers of
124 * constraint_expr_eval should pass in NULL for xcontext.
125 */
constraint_expr_eval(context_struct_t * scontext,context_struct_t * tcontext,context_struct_t * xcontext,constraint_expr_t * cexpr)126 static int constraint_expr_eval(context_struct_t * scontext,
127 context_struct_t * tcontext,
128 context_struct_t * xcontext,
129 constraint_expr_t * cexpr)
130 {
131 uint32_t val1, val2;
132 context_struct_t *c;
133 role_datum_t *r1, *r2;
134 mls_level_t *l1, *l2;
135 constraint_expr_t *e;
136 int s[CEXPR_MAXDEPTH];
137 int sp = -1;
138
139 for (e = cexpr; e; e = e->next) {
140 switch (e->expr_type) {
141 case CEXPR_NOT:
142 BUG_ON(sp < 0);
143 s[sp] = !s[sp];
144 break;
145 case CEXPR_AND:
146 BUG_ON(sp < 1);
147 sp--;
148 s[sp] &= s[sp + 1];
149 break;
150 case CEXPR_OR:
151 BUG_ON(sp < 1);
152 sp--;
153 s[sp] |= s[sp + 1];
154 break;
155 case CEXPR_ATTR:
156 if (sp == (CEXPR_MAXDEPTH - 1))
157 return 0;
158 switch (e->attr) {
159 case CEXPR_USER:
160 val1 = scontext->user;
161 val2 = tcontext->user;
162 break;
163 case CEXPR_TYPE:
164 val1 = scontext->type;
165 val2 = tcontext->type;
166 break;
167 case CEXPR_ROLE:
168 val1 = scontext->role;
169 val2 = tcontext->role;
170 r1 = policydb->role_val_to_struct[val1 - 1];
171 r2 = policydb->role_val_to_struct[val2 - 1];
172 switch (e->op) {
173 case CEXPR_DOM:
174 s[++sp] =
175 ebitmap_get_bit(&r1->dominates,
176 val2 - 1);
177 continue;
178 case CEXPR_DOMBY:
179 s[++sp] =
180 ebitmap_get_bit(&r2->dominates,
181 val1 - 1);
182 continue;
183 case CEXPR_INCOMP:
184 s[++sp] =
185 (!ebitmap_get_bit
186 (&r1->dominates, val2 - 1)
187 && !ebitmap_get_bit(&r2->dominates,
188 val1 - 1));
189 continue;
190 default:
191 break;
192 }
193 break;
194 case CEXPR_L1L2:
195 l1 = &(scontext->range.level[0]);
196 l2 = &(tcontext->range.level[0]);
197 goto mls_ops;
198 case CEXPR_L1H2:
199 l1 = &(scontext->range.level[0]);
200 l2 = &(tcontext->range.level[1]);
201 goto mls_ops;
202 case CEXPR_H1L2:
203 l1 = &(scontext->range.level[1]);
204 l2 = &(tcontext->range.level[0]);
205 goto mls_ops;
206 case CEXPR_H1H2:
207 l1 = &(scontext->range.level[1]);
208 l2 = &(tcontext->range.level[1]);
209 goto mls_ops;
210 case CEXPR_L1H1:
211 l1 = &(scontext->range.level[0]);
212 l2 = &(scontext->range.level[1]);
213 goto mls_ops;
214 case CEXPR_L2H2:
215 l1 = &(tcontext->range.level[0]);
216 l2 = &(tcontext->range.level[1]);
217 goto mls_ops;
218 mls_ops:
219 switch (e->op) {
220 case CEXPR_EQ:
221 s[++sp] = mls_level_eq(l1, l2);
222 continue;
223 case CEXPR_NEQ:
224 s[++sp] = !mls_level_eq(l1, l2);
225 continue;
226 case CEXPR_DOM:
227 s[++sp] = mls_level_dom(l1, l2);
228 continue;
229 case CEXPR_DOMBY:
230 s[++sp] = mls_level_dom(l2, l1);
231 continue;
232 case CEXPR_INCOMP:
233 s[++sp] = mls_level_incomp(l2, l1);
234 continue;
235 default:
236 BUG();
237 return 0;
238 }
239 break;
240 default:
241 BUG();
242 return 0;
243 }
244
245 switch (e->op) {
246 case CEXPR_EQ:
247 s[++sp] = (val1 == val2);
248 break;
249 case CEXPR_NEQ:
250 s[++sp] = (val1 != val2);
251 break;
252 default:
253 BUG();
254 return 0;
255 }
256 break;
257 case CEXPR_NAMES:
258 if (sp == (CEXPR_MAXDEPTH - 1))
259 return 0;
260 c = scontext;
261 if (e->attr & CEXPR_TARGET)
262 c = tcontext;
263 else if (e->attr & CEXPR_XTARGET) {
264 c = xcontext;
265 if (!c) {
266 BUG();
267 return 0;
268 }
269 }
270 if (e->attr & CEXPR_USER)
271 val1 = c->user;
272 else if (e->attr & CEXPR_ROLE)
273 val1 = c->role;
274 else if (e->attr & CEXPR_TYPE)
275 val1 = c->type;
276 else {
277 BUG();
278 return 0;
279 }
280
281 switch (e->op) {
282 case CEXPR_EQ:
283 s[++sp] = ebitmap_get_bit(&e->names, val1 - 1);
284 break;
285 case CEXPR_NEQ:
286 s[++sp] = !ebitmap_get_bit(&e->names, val1 - 1);
287 break;
288 default:
289 BUG();
290 return 0;
291 }
292 break;
293 default:
294 BUG();
295 return 0;
296 }
297 }
298
299 BUG_ON(sp != 0);
300 return s[0];
301 }
302
303 /*
304 * Compute access vectors based on a context structure pair for
305 * the permissions in a particular class.
306 */
context_struct_compute_av(context_struct_t * scontext,context_struct_t * tcontext,sepol_security_class_t tclass,sepol_access_vector_t requested,struct sepol_av_decision * avd,unsigned int * reason)307 static int context_struct_compute_av(context_struct_t * scontext,
308 context_struct_t * tcontext,
309 sepol_security_class_t tclass,
310 sepol_access_vector_t requested,
311 struct sepol_av_decision *avd,
312 unsigned int *reason)
313 {
314 constraint_node_t *constraint;
315 struct role_allow *ra;
316 avtab_key_t avkey;
317 class_datum_t *tclass_datum;
318 avtab_ptr_t node;
319 ebitmap_t *sattr, *tattr;
320 ebitmap_node_t *snode, *tnode;
321 unsigned int i, j;
322
323 if (!tclass || tclass > policydb->p_classes.nprim) {
324 ERR(NULL, "unrecognized class %d", tclass);
325 return -EINVAL;
326 }
327 tclass_datum = policydb->class_val_to_struct[tclass - 1];
328
329 /*
330 * Initialize the access vectors to the default values.
331 */
332 avd->allowed = 0;
333 avd->decided = 0xffffffff;
334 avd->auditallow = 0;
335 avd->auditdeny = 0xffffffff;
336 avd->seqno = latest_granting;
337 *reason = 0;
338
339 /*
340 * If a specific type enforcement rule was defined for
341 * this permission check, then use it.
342 */
343 avkey.target_class = tclass;
344 avkey.specified = AVTAB_AV;
345 sattr = &policydb->type_attr_map[scontext->type - 1];
346 tattr = &policydb->type_attr_map[tcontext->type - 1];
347 ebitmap_for_each_bit(sattr, snode, i) {
348 if (!ebitmap_node_get_bit(snode, i))
349 continue;
350 ebitmap_for_each_bit(tattr, tnode, j) {
351 if (!ebitmap_node_get_bit(tnode, j))
352 continue;
353 avkey.source_type = i + 1;
354 avkey.target_type = j + 1;
355 for (node =
356 avtab_search_node(&policydb->te_avtab, &avkey);
357 node != NULL;
358 node =
359 avtab_search_node_next(node, avkey.specified)) {
360 if (node->key.specified == AVTAB_ALLOWED)
361 avd->allowed |= node->datum.data;
362 else if (node->key.specified ==
363 AVTAB_AUDITALLOW)
364 avd->auditallow |= node->datum.data;
365 else if (node->key.specified == AVTAB_AUDITDENY)
366 avd->auditdeny &= node->datum.data;
367 }
368
369 /* Check conditional av table for additional permissions */
370 cond_compute_av(&policydb->te_cond_avtab, &avkey, avd);
371
372 }
373 }
374
375 if (requested & ~avd->allowed) {
376 *reason |= SEPOL_COMPUTEAV_TE;
377 requested &= avd->allowed;
378 }
379
380 /*
381 * Remove any permissions prohibited by a constraint (this includes
382 * the MLS policy).
383 */
384 constraint = tclass_datum->constraints;
385 while (constraint) {
386 if ((constraint->permissions & (avd->allowed)) &&
387 !constraint_expr_eval(scontext, tcontext, NULL,
388 constraint->expr)) {
389 avd->allowed =
390 (avd->allowed) & ~(constraint->permissions);
391 }
392 constraint = constraint->next;
393 }
394
395 if (requested & ~avd->allowed) {
396 *reason |= SEPOL_COMPUTEAV_CONS;
397 requested &= avd->allowed;
398 }
399
400 /*
401 * If checking process transition permission and the
402 * role is changing, then check the (current_role, new_role)
403 * pair.
404 */
405 if (tclass == SECCLASS_PROCESS &&
406 (avd->allowed & (PROCESS__TRANSITION | PROCESS__DYNTRANSITION)) &&
407 scontext->role != tcontext->role) {
408 for (ra = policydb->role_allow; ra; ra = ra->next) {
409 if (scontext->role == ra->role &&
410 tcontext->role == ra->new_role)
411 break;
412 }
413 if (!ra)
414 avd->allowed = (avd->allowed) & ~(PROCESS__TRANSITION |
415 PROCESS__DYNTRANSITION);
416 }
417
418 if (requested & ~avd->allowed) {
419 *reason |= SEPOL_COMPUTEAV_RBAC;
420 requested &= avd->allowed;
421 }
422
423 return 0;
424 }
425
sepol_validate_transition(sepol_security_id_t oldsid,sepol_security_id_t newsid,sepol_security_id_t tasksid,sepol_security_class_t tclass)426 int hidden sepol_validate_transition(sepol_security_id_t oldsid,
427 sepol_security_id_t newsid,
428 sepol_security_id_t tasksid,
429 sepol_security_class_t tclass)
430 {
431 context_struct_t *ocontext;
432 context_struct_t *ncontext;
433 context_struct_t *tcontext;
434 class_datum_t *tclass_datum;
435 constraint_node_t *constraint;
436
437 if (!tclass || tclass > policydb->p_classes.nprim) {
438 ERR(NULL, "unrecognized class %d", tclass);
439 return -EINVAL;
440 }
441 tclass_datum = policydb->class_val_to_struct[tclass - 1];
442
443 ocontext = sepol_sidtab_search(sidtab, oldsid);
444 if (!ocontext) {
445 ERR(NULL, "unrecognized SID %d", oldsid);
446 return -EINVAL;
447 }
448
449 ncontext = sepol_sidtab_search(sidtab, newsid);
450 if (!ncontext) {
451 ERR(NULL, "unrecognized SID %d", newsid);
452 return -EINVAL;
453 }
454
455 tcontext = sepol_sidtab_search(sidtab, tasksid);
456 if (!tcontext) {
457 ERR(NULL, "unrecognized SID %d", tasksid);
458 return -EINVAL;
459 }
460
461 constraint = tclass_datum->validatetrans;
462 while (constraint) {
463 if (!constraint_expr_eval(ocontext, ncontext, tcontext,
464 constraint->expr)) {
465 return -EPERM;
466 }
467 constraint = constraint->next;
468 }
469
470 return 0;
471 }
472
sepol_compute_av_reason(sepol_security_id_t ssid,sepol_security_id_t tsid,sepol_security_class_t tclass,sepol_access_vector_t requested,struct sepol_av_decision * avd,unsigned int * reason)473 int hidden sepol_compute_av_reason(sepol_security_id_t ssid,
474 sepol_security_id_t tsid,
475 sepol_security_class_t tclass,
476 sepol_access_vector_t requested,
477 struct sepol_av_decision *avd,
478 unsigned int *reason)
479 {
480 context_struct_t *scontext = 0, *tcontext = 0;
481 int rc = 0;
482
483 scontext = sepol_sidtab_search(sidtab, ssid);
484 if (!scontext) {
485 ERR(NULL, "unrecognized SID %d", ssid);
486 rc = -EINVAL;
487 goto out;
488 }
489 tcontext = sepol_sidtab_search(sidtab, tsid);
490 if (!tcontext) {
491 ERR(NULL, "unrecognized SID %d", tsid);
492 rc = -EINVAL;
493 goto out;
494 }
495
496 rc = context_struct_compute_av(scontext, tcontext, tclass,
497 requested, avd, reason);
498 out:
499 return rc;
500 }
501
sepol_compute_av(sepol_security_id_t ssid,sepol_security_id_t tsid,sepol_security_class_t tclass,sepol_access_vector_t requested,struct sepol_av_decision * avd)502 int hidden sepol_compute_av(sepol_security_id_t ssid,
503 sepol_security_id_t tsid,
504 sepol_security_class_t tclass,
505 sepol_access_vector_t requested,
506 struct sepol_av_decision *avd)
507 {
508 unsigned int reason = 0;
509 return sepol_compute_av_reason(ssid, tsid, tclass, requested, avd,
510 &reason);
511 }
512
513 /*
514 * Write the security context string representation of
515 * the context associated with `sid' into a dynamically
516 * allocated string of the correct size. Set `*scontext'
517 * to point to this string and set `*scontext_len' to
518 * the length of the string.
519 */
sepol_sid_to_context(sepol_security_id_t sid,sepol_security_context_t * scontext,size_t * scontext_len)520 int hidden sepol_sid_to_context(sepol_security_id_t sid,
521 sepol_security_context_t * scontext,
522 size_t * scontext_len)
523 {
524 context_struct_t *context;
525 int rc = 0;
526
527 context = sepol_sidtab_search(sidtab, sid);
528 if (!context) {
529 ERR(NULL, "unrecognized SID %d", sid);
530 rc = -EINVAL;
531 goto out;
532 }
533 rc = context_to_string(NULL, policydb, context, scontext, scontext_len);
534 out:
535 return rc;
536
537 }
538
539 /*
540 * Return a SID associated with the security context that
541 * has the string representation specified by `scontext'.
542 */
sepol_context_to_sid(const sepol_security_context_t scontext,size_t scontext_len,sepol_security_id_t * sid)543 int hidden sepol_context_to_sid(const sepol_security_context_t scontext,
544 size_t scontext_len, sepol_security_id_t * sid)
545 {
546
547 context_struct_t *context = NULL;
548
549 /* First, create the context */
550 if (context_from_string(NULL, policydb, &context,
551 scontext, scontext_len) < 0)
552 goto err;
553
554 /* Obtain the new sid */
555 if (sid && (sepol_sidtab_context_to_sid(sidtab, context, sid) < 0))
556 goto err;
557
558 context_destroy(context);
559 free(context);
560 return STATUS_SUCCESS;
561
562 err:
563 if (context) {
564 context_destroy(context);
565 free(context);
566 }
567 ERR(NULL, "could not convert %s to sid", scontext);
568 return STATUS_ERR;
569 }
570
compute_sid_handle_invalid_context(context_struct_t * scontext,context_struct_t * tcontext,sepol_security_class_t tclass,context_struct_t * newcontext)571 static inline int compute_sid_handle_invalid_context(context_struct_t *
572 scontext,
573 context_struct_t *
574 tcontext,
575 sepol_security_class_t
576 tclass,
577 context_struct_t *
578 newcontext)
579 {
580 if (selinux_enforcing) {
581 return -EACCES;
582 } else {
583 sepol_security_context_t s, t, n;
584 size_t slen, tlen, nlen;
585
586 context_to_string(NULL, policydb, scontext, &s, &slen);
587 context_to_string(NULL, policydb, tcontext, &t, &tlen);
588 context_to_string(NULL, policydb, newcontext, &n, &nlen);
589 ERR(NULL, "invalid context %s for "
590 "scontext=%s tcontext=%s tclass=%s",
591 n, s, t, policydb->p_class_val_to_name[tclass - 1]);
592 free(s);
593 free(t);
594 free(n);
595 return 0;
596 }
597 }
598
sepol_compute_sid(sepol_security_id_t ssid,sepol_security_id_t tsid,sepol_security_class_t tclass,uint32_t specified,sepol_security_id_t * out_sid)599 static int sepol_compute_sid(sepol_security_id_t ssid,
600 sepol_security_id_t tsid,
601 sepol_security_class_t tclass,
602 uint32_t specified, sepol_security_id_t * out_sid)
603 {
604 context_struct_t *scontext = 0, *tcontext = 0, newcontext;
605 struct role_trans *roletr = 0;
606 avtab_key_t avkey;
607 avtab_datum_t *avdatum;
608 avtab_ptr_t node;
609 int rc = 0;
610
611 scontext = sepol_sidtab_search(sidtab, ssid);
612 if (!scontext) {
613 ERR(NULL, "unrecognized SID %d", ssid);
614 rc = -EINVAL;
615 goto out;
616 }
617 tcontext = sepol_sidtab_search(sidtab, tsid);
618 if (!tcontext) {
619 ERR(NULL, "unrecognized SID %d", tsid);
620 rc = -EINVAL;
621 goto out;
622 }
623
624 context_init(&newcontext);
625
626 /* Set the user identity. */
627 switch (specified) {
628 case AVTAB_TRANSITION:
629 case AVTAB_CHANGE:
630 /* Use the process user identity. */
631 newcontext.user = scontext->user;
632 break;
633 case AVTAB_MEMBER:
634 /* Use the related object owner. */
635 newcontext.user = tcontext->user;
636 break;
637 }
638
639 /* Set the role and type to default values. */
640 switch (tclass) {
641 case SECCLASS_PROCESS:
642 /* Use the current role and type of process. */
643 newcontext.role = scontext->role;
644 newcontext.type = scontext->type;
645 break;
646 default:
647 /* Use the well-defined object role. */
648 newcontext.role = OBJECT_R_VAL;
649 /* Use the type of the related object. */
650 newcontext.type = tcontext->type;
651 }
652
653 /* Look for a type transition/member/change rule. */
654 avkey.source_type = scontext->type;
655 avkey.target_type = tcontext->type;
656 avkey.target_class = tclass;
657 avkey.specified = specified;
658 avdatum = avtab_search(&policydb->te_avtab, &avkey);
659
660 /* If no permanent rule, also check for enabled conditional rules */
661 if (!avdatum) {
662 node = avtab_search_node(&policydb->te_cond_avtab, &avkey);
663 for (; node != NULL;
664 node = avtab_search_node_next(node, specified)) {
665 if (node->key.specified & AVTAB_ENABLED) {
666 avdatum = &node->datum;
667 break;
668 }
669 }
670 }
671
672 if (avdatum) {
673 /* Use the type from the type transition/member/change rule. */
674 newcontext.type = avdatum->data;
675 }
676
677 /* Check for class-specific changes. */
678 switch (tclass) {
679 case SECCLASS_PROCESS:
680 if (specified & AVTAB_TRANSITION) {
681 /* Look for a role transition rule. */
682 for (roletr = policydb->role_tr; roletr;
683 roletr = roletr->next) {
684 if (roletr->role == scontext->role &&
685 roletr->type == tcontext->type) {
686 /* Use the role transition rule. */
687 newcontext.role = roletr->new_role;
688 break;
689 }
690 }
691 }
692 break;
693 default:
694 break;
695 }
696
697 /* Set the MLS attributes.
698 This is done last because it may allocate memory. */
699 rc = mls_compute_sid(policydb, scontext, tcontext, tclass, specified,
700 &newcontext);
701 if (rc)
702 goto out;
703
704 /* Check the validity of the context. */
705 if (!policydb_context_isvalid(policydb, &newcontext)) {
706 rc = compute_sid_handle_invalid_context(scontext,
707 tcontext,
708 tclass, &newcontext);
709 if (rc)
710 goto out;
711 }
712 /* Obtain the sid for the context. */
713 rc = sepol_sidtab_context_to_sid(sidtab, &newcontext, out_sid);
714 out:
715 context_destroy(&newcontext);
716 return rc;
717 }
718
719 /*
720 * Compute a SID to use for labeling a new object in the
721 * class `tclass' based on a SID pair.
722 */
sepol_transition_sid(sepol_security_id_t ssid,sepol_security_id_t tsid,sepol_security_class_t tclass,sepol_security_id_t * out_sid)723 int hidden sepol_transition_sid(sepol_security_id_t ssid,
724 sepol_security_id_t tsid,
725 sepol_security_class_t tclass,
726 sepol_security_id_t * out_sid)
727 {
728 return sepol_compute_sid(ssid, tsid, tclass, AVTAB_TRANSITION, out_sid);
729 }
730
731 /*
732 * Compute a SID to use when selecting a member of a
733 * polyinstantiated object of class `tclass' based on
734 * a SID pair.
735 */
sepol_member_sid(sepol_security_id_t ssid,sepol_security_id_t tsid,sepol_security_class_t tclass,sepol_security_id_t * out_sid)736 int hidden sepol_member_sid(sepol_security_id_t ssid,
737 sepol_security_id_t tsid,
738 sepol_security_class_t tclass,
739 sepol_security_id_t * out_sid)
740 {
741 return sepol_compute_sid(ssid, tsid, tclass, AVTAB_MEMBER, out_sid);
742 }
743
744 /*
745 * Compute a SID to use for relabeling an object in the
746 * class `tclass' based on a SID pair.
747 */
sepol_change_sid(sepol_security_id_t ssid,sepol_security_id_t tsid,sepol_security_class_t tclass,sepol_security_id_t * out_sid)748 int hidden sepol_change_sid(sepol_security_id_t ssid,
749 sepol_security_id_t tsid,
750 sepol_security_class_t tclass,
751 sepol_security_id_t * out_sid)
752 {
753 return sepol_compute_sid(ssid, tsid, tclass, AVTAB_CHANGE, out_sid);
754 }
755
756 /*
757 * Verify that each permission that is defined under the
758 * existing policy is still defined with the same value
759 * in the new policy.
760 */
validate_perm(hashtab_key_t key,hashtab_datum_t datum,void * p)761 static int validate_perm(hashtab_key_t key, hashtab_datum_t datum, void *p)
762 {
763 hashtab_t h;
764 perm_datum_t *perdatum, *perdatum2;
765
766 h = (hashtab_t) p;
767 perdatum = (perm_datum_t *) datum;
768
769 perdatum2 = (perm_datum_t *) hashtab_search(h, key);
770 if (!perdatum2) {
771 ERR(NULL, "permission %s disappeared", key);
772 return -1;
773 }
774 if (perdatum->s.value != perdatum2->s.value) {
775 ERR(NULL, "the value of permissions %s changed", key);
776 return -1;
777 }
778 return 0;
779 }
780
781 /*
782 * Verify that each class that is defined under the
783 * existing policy is still defined with the same
784 * attributes in the new policy.
785 */
validate_class(hashtab_key_t key,hashtab_datum_t datum,void * p)786 static int validate_class(hashtab_key_t key, hashtab_datum_t datum, void *p)
787 {
788 policydb_t *newp;
789 class_datum_t *cladatum, *cladatum2;
790
791 newp = (policydb_t *) p;
792 cladatum = (class_datum_t *) datum;
793
794 cladatum2 =
795 (class_datum_t *) hashtab_search(newp->p_classes.table, key);
796 if (!cladatum2) {
797 ERR(NULL, "class %s disappeared", key);
798 return -1;
799 }
800 if (cladatum->s.value != cladatum2->s.value) {
801 ERR(NULL, "the value of class %s changed", key);
802 return -1;
803 }
804 if ((cladatum->comdatum && !cladatum2->comdatum) ||
805 (!cladatum->comdatum && cladatum2->comdatum)) {
806 ERR(NULL, "the inherits clause for the access "
807 "vector definition for class %s changed", key);
808 return -1;
809 }
810 if (cladatum->comdatum) {
811 if (hashtab_map
812 (cladatum->comdatum->permissions.table, validate_perm,
813 cladatum2->comdatum->permissions.table)) {
814 ERR(NULL,
815 " in the access vector definition "
816 "for class %s\n", key);
817 return -1;
818 }
819 }
820 if (hashtab_map(cladatum->permissions.table, validate_perm,
821 cladatum2->permissions.table)) {
822 ERR(NULL, " in access vector definition for class %s", key);
823 return -1;
824 }
825 return 0;
826 }
827
828 /* Clone the SID into the new SID table. */
clone_sid(sepol_security_id_t sid,context_struct_t * context,void * arg)829 static int clone_sid(sepol_security_id_t sid,
830 context_struct_t * context, void *arg)
831 {
832 sidtab_t *s = arg;
833
834 return sepol_sidtab_insert(s, sid, context);
835 }
836
convert_context_handle_invalid_context(context_struct_t * context)837 static inline int convert_context_handle_invalid_context(context_struct_t *
838 context)
839 {
840 if (selinux_enforcing) {
841 return -EINVAL;
842 } else {
843 sepol_security_context_t s;
844 size_t len;
845
846 context_to_string(NULL, policydb, context, &s, &len);
847 ERR(NULL, "context %s is invalid", s);
848 free(s);
849 return 0;
850 }
851 }
852
853 typedef struct {
854 policydb_t *oldp;
855 policydb_t *newp;
856 } convert_context_args_t;
857
858 /*
859 * Convert the values in the security context
860 * structure `c' from the values specified
861 * in the policy `p->oldp' to the values specified
862 * in the policy `p->newp'. Verify that the
863 * context is valid under the new policy.
864 */
convert_context(sepol_security_id_t key,context_struct_t * c,void * p)865 static int convert_context(sepol_security_id_t key __attribute__ ((unused)),
866 context_struct_t * c, void *p)
867 {
868 convert_context_args_t *args;
869 context_struct_t oldc;
870 role_datum_t *role;
871 type_datum_t *typdatum;
872 user_datum_t *usrdatum;
873 sepol_security_context_t s;
874 size_t len;
875 int rc = -EINVAL;
876
877 args = (convert_context_args_t *) p;
878
879 if (context_cpy(&oldc, c))
880 return -ENOMEM;
881
882 /* Convert the user. */
883 usrdatum = (user_datum_t *) hashtab_search(args->newp->p_users.table,
884 args->oldp->
885 p_user_val_to_name[c->user -
886 1]);
887
888 if (!usrdatum) {
889 goto bad;
890 }
891 c->user = usrdatum->s.value;
892
893 /* Convert the role. */
894 role = (role_datum_t *) hashtab_search(args->newp->p_roles.table,
895 args->oldp->
896 p_role_val_to_name[c->role - 1]);
897 if (!role) {
898 goto bad;
899 }
900 c->role = role->s.value;
901
902 /* Convert the type. */
903 typdatum = (type_datum_t *)
904 hashtab_search(args->newp->p_types.table,
905 args->oldp->p_type_val_to_name[c->type - 1]);
906 if (!typdatum) {
907 goto bad;
908 }
909 c->type = typdatum->s.value;
910
911 rc = mls_convert_context(args->oldp, args->newp, c);
912 if (rc)
913 goto bad;
914
915 /* Check the validity of the new context. */
916 if (!policydb_context_isvalid(args->newp, c)) {
917 rc = convert_context_handle_invalid_context(&oldc);
918 if (rc)
919 goto bad;
920 }
921
922 context_destroy(&oldc);
923 return 0;
924
925 bad:
926 context_to_string(NULL, policydb, &oldc, &s, &len);
927 context_destroy(&oldc);
928 ERR(NULL, "invalidating context %s", s);
929 free(s);
930 return rc;
931 }
932
933 /* Reading from a policy "file". */
next_entry(void * buf,struct policy_file * fp,size_t bytes)934 int hidden next_entry(void *buf, struct policy_file *fp, size_t bytes)
935 {
936 size_t nread;
937
938 switch (fp->type) {
939 case PF_USE_STDIO:
940 nread = fread(buf, bytes, 1, fp->fp);
941
942 if (nread != 1)
943 return -1;
944 break;
945 case PF_USE_MEMORY:
946 if (bytes > fp->len)
947 return -1;
948 memcpy(buf, fp->data, bytes);
949 fp->data += bytes;
950 fp->len -= bytes;
951 break;
952 default:
953 return -1;
954 }
955 return 0;
956 }
957
put_entry(const void * ptr,size_t size,size_t n,struct policy_file * fp)958 size_t hidden put_entry(const void *ptr, size_t size, size_t n,
959 struct policy_file *fp)
960 {
961 size_t bytes = size * n;
962
963 switch (fp->type) {
964 case PF_USE_STDIO:
965 return fwrite(ptr, size, n, fp->fp);
966 case PF_USE_MEMORY:
967 if (bytes > fp->len) {
968 errno = ENOSPC;
969 return 0;
970 }
971
972 memcpy(fp->data, ptr, bytes);
973 fp->data += bytes;
974 fp->len -= bytes;
975 return n;
976 case PF_LEN:
977 fp->len += bytes;
978 return n;
979 default:
980 return 0;
981 }
982 return 0;
983 }
984
985 /*
986 * Read a new set of configuration data from
987 * a policy database binary representation file.
988 *
989 * Verify that each class that is defined under the
990 * existing policy is still defined with the same
991 * attributes in the new policy.
992 *
993 * Convert the context structures in the SID table to the
994 * new representation and verify that all entries
995 * in the SID table are valid under the new policy.
996 *
997 * Change the active policy database to use the new
998 * configuration data.
999 *
1000 * Reset the access vector cache.
1001 */
sepol_load_policy(void * data,size_t len)1002 int hidden sepol_load_policy(void *data, size_t len)
1003 {
1004 policydb_t oldpolicydb, newpolicydb;
1005 sidtab_t oldsidtab, newsidtab;
1006 convert_context_args_t args;
1007 int rc = 0;
1008 struct policy_file file, *fp;
1009
1010 policy_file_init(&file);
1011 file.type = PF_USE_MEMORY;
1012 file.data = data;
1013 file.len = len;
1014 fp = &file;
1015
1016 if (policydb_init(&newpolicydb))
1017 return -ENOMEM;
1018
1019 if (policydb_read(&newpolicydb, fp, 1)) {
1020 policydb_destroy(&newpolicydb);
1021 return -EINVAL;
1022 }
1023
1024 sepol_sidtab_init(&newsidtab);
1025
1026 /* Verify that the existing classes did not change. */
1027 if (hashtab_map
1028 (policydb->p_classes.table, validate_class, &newpolicydb)) {
1029 ERR(NULL, "the definition of an existing class changed");
1030 rc = -EINVAL;
1031 goto err;
1032 }
1033
1034 /* Clone the SID table. */
1035 sepol_sidtab_shutdown(sidtab);
1036 if (sepol_sidtab_map(sidtab, clone_sid, &newsidtab)) {
1037 rc = -ENOMEM;
1038 goto err;
1039 }
1040
1041 /* Convert the internal representations of contexts
1042 in the new SID table and remove invalid SIDs. */
1043 args.oldp = policydb;
1044 args.newp = &newpolicydb;
1045 sepol_sidtab_map_remove_on_error(&newsidtab, convert_context, &args);
1046
1047 /* Save the old policydb and SID table to free later. */
1048 memcpy(&oldpolicydb, policydb, sizeof *policydb);
1049 sepol_sidtab_set(&oldsidtab, sidtab);
1050
1051 /* Install the new policydb and SID table. */
1052 memcpy(policydb, &newpolicydb, sizeof *policydb);
1053 sepol_sidtab_set(sidtab, &newsidtab);
1054
1055 /* Free the old policydb and SID table. */
1056 policydb_destroy(&oldpolicydb);
1057 sepol_sidtab_destroy(&oldsidtab);
1058
1059 return 0;
1060
1061 err:
1062 sepol_sidtab_destroy(&newsidtab);
1063 policydb_destroy(&newpolicydb);
1064 return rc;
1065
1066 }
1067
1068 /*
1069 * Return the SIDs to use for an unlabeled file system
1070 * that is being mounted from the device with the
1071 * the kdevname `name'. The `fs_sid' SID is returned for
1072 * the file system and the `file_sid' SID is returned
1073 * for all files within that file system.
1074 */
sepol_fs_sid(char * name,sepol_security_id_t * fs_sid,sepol_security_id_t * file_sid)1075 int hidden sepol_fs_sid(char *name,
1076 sepol_security_id_t * fs_sid,
1077 sepol_security_id_t * file_sid)
1078 {
1079 int rc = 0;
1080 ocontext_t *c;
1081
1082 c = policydb->ocontexts[OCON_FS];
1083 while (c) {
1084 if (strcmp(c->u.name, name) == 0)
1085 break;
1086 c = c->next;
1087 }
1088
1089 if (c) {
1090 if (!c->sid[0] || !c->sid[1]) {
1091 rc = sepol_sidtab_context_to_sid(sidtab,
1092 &c->context[0],
1093 &c->sid[0]);
1094 if (rc)
1095 goto out;
1096 rc = sepol_sidtab_context_to_sid(sidtab,
1097 &c->context[1],
1098 &c->sid[1]);
1099 if (rc)
1100 goto out;
1101 }
1102 *fs_sid = c->sid[0];
1103 *file_sid = c->sid[1];
1104 } else {
1105 *fs_sid = SECINITSID_FS;
1106 *file_sid = SECINITSID_FILE;
1107 }
1108
1109 out:
1110 return rc;
1111 }
1112
1113 /*
1114 * Return the SID of the port specified by
1115 * `domain', `type', `protocol', and `port'.
1116 */
sepol_port_sid(uint16_t domain,uint16_t type,uint8_t protocol,uint16_t port,sepol_security_id_t * out_sid)1117 int hidden sepol_port_sid(uint16_t domain __attribute__ ((unused)),
1118 uint16_t type __attribute__ ((unused)),
1119 uint8_t protocol,
1120 uint16_t port, sepol_security_id_t * out_sid)
1121 {
1122 ocontext_t *c;
1123 int rc = 0;
1124
1125 c = policydb->ocontexts[OCON_PORT];
1126 while (c) {
1127 if (c->u.port.protocol == protocol &&
1128 c->u.port.low_port <= port && c->u.port.high_port >= port)
1129 break;
1130 c = c->next;
1131 }
1132
1133 if (c) {
1134 if (!c->sid[0]) {
1135 rc = sepol_sidtab_context_to_sid(sidtab,
1136 &c->context[0],
1137 &c->sid[0]);
1138 if (rc)
1139 goto out;
1140 }
1141 *out_sid = c->sid[0];
1142 } else {
1143 *out_sid = SECINITSID_PORT;
1144 }
1145
1146 out:
1147 return rc;
1148 }
1149
1150 /*
1151 * Return the SIDs to use for a network interface
1152 * with the name `name'. The `if_sid' SID is returned for
1153 * the interface and the `msg_sid' SID is returned as
1154 * the default SID for messages received on the
1155 * interface.
1156 */
sepol_netif_sid(char * name,sepol_security_id_t * if_sid,sepol_security_id_t * msg_sid)1157 int hidden sepol_netif_sid(char *name,
1158 sepol_security_id_t * if_sid,
1159 sepol_security_id_t * msg_sid)
1160 {
1161 int rc = 0;
1162 ocontext_t *c;
1163
1164 c = policydb->ocontexts[OCON_NETIF];
1165 while (c) {
1166 if (strcmp(name, c->u.name) == 0)
1167 break;
1168 c = c->next;
1169 }
1170
1171 if (c) {
1172 if (!c->sid[0] || !c->sid[1]) {
1173 rc = sepol_sidtab_context_to_sid(sidtab,
1174 &c->context[0],
1175 &c->sid[0]);
1176 if (rc)
1177 goto out;
1178 rc = sepol_sidtab_context_to_sid(sidtab,
1179 &c->context[1],
1180 &c->sid[1]);
1181 if (rc)
1182 goto out;
1183 }
1184 *if_sid = c->sid[0];
1185 *msg_sid = c->sid[1];
1186 } else {
1187 *if_sid = SECINITSID_NETIF;
1188 *msg_sid = SECINITSID_NETMSG;
1189 }
1190
1191 out:
1192 return rc;
1193 }
1194
match_ipv6_addrmask(uint32_t * input,uint32_t * addr,uint32_t * mask)1195 static int match_ipv6_addrmask(uint32_t * input, uint32_t * addr,
1196 uint32_t * mask)
1197 {
1198 int i, fail = 0;
1199
1200 for (i = 0; i < 4; i++)
1201 if (addr[i] != (input[i] & mask[i])) {
1202 fail = 1;
1203 break;
1204 }
1205
1206 return !fail;
1207 }
1208
1209 /*
1210 * Return the SID of the node specified by the address
1211 * `addrp' where `addrlen' is the length of the address
1212 * in bytes and `domain' is the communications domain or
1213 * address family in which the address should be interpreted.
1214 */
sepol_node_sid(uint16_t domain,void * addrp,size_t addrlen,sepol_security_id_t * out_sid)1215 int hidden sepol_node_sid(uint16_t domain,
1216 void *addrp,
1217 size_t addrlen, sepol_security_id_t * out_sid)
1218 {
1219 int rc = 0;
1220 ocontext_t *c;
1221
1222 switch (domain) {
1223 case AF_INET:{
1224 uint32_t addr;
1225
1226 if (addrlen != sizeof(uint32_t)) {
1227 rc = -EINVAL;
1228 goto out;
1229 }
1230
1231 addr = *((uint32_t *) addrp);
1232
1233 c = policydb->ocontexts[OCON_NODE];
1234 while (c) {
1235 if (c->u.node.addr == (addr & c->u.node.mask))
1236 break;
1237 c = c->next;
1238 }
1239 break;
1240 }
1241
1242 case AF_INET6:
1243 if (addrlen != sizeof(uint64_t) * 2) {
1244 rc = -EINVAL;
1245 goto out;
1246 }
1247
1248 c = policydb->ocontexts[OCON_NODE6];
1249 while (c) {
1250 if (match_ipv6_addrmask(addrp, c->u.node6.addr,
1251 c->u.node6.mask))
1252 break;
1253 c = c->next;
1254 }
1255 break;
1256
1257 default:
1258 *out_sid = SECINITSID_NODE;
1259 goto out;
1260 }
1261
1262 if (c) {
1263 if (!c->sid[0]) {
1264 rc = sepol_sidtab_context_to_sid(sidtab,
1265 &c->context[0],
1266 &c->sid[0]);
1267 if (rc)
1268 goto out;
1269 }
1270 *out_sid = c->sid[0];
1271 } else {
1272 *out_sid = SECINITSID_NODE;
1273 }
1274
1275 out:
1276 return rc;
1277 }
1278
1279 /*
1280 * Generate the set of SIDs for legal security contexts
1281 * for a given user that can be reached by `fromsid'.
1282 * Set `*sids' to point to a dynamically allocated
1283 * array containing the set of SIDs. Set `*nel' to the
1284 * number of elements in the array.
1285 */
1286 #define SIDS_NEL 25
1287
sepol_get_user_sids(sepol_security_id_t fromsid,char * username,sepol_security_id_t ** sids,uint32_t * nel)1288 int hidden sepol_get_user_sids(sepol_security_id_t fromsid,
1289 char *username,
1290 sepol_security_id_t ** sids, uint32_t * nel)
1291 {
1292 context_struct_t *fromcon, usercon;
1293 sepol_security_id_t *mysids, *mysids2, sid;
1294 uint32_t mynel = 0, maxnel = SIDS_NEL;
1295 user_datum_t *user;
1296 role_datum_t *role;
1297 struct sepol_av_decision avd;
1298 int rc = 0;
1299 unsigned int i, j, reason;
1300 ebitmap_node_t *rnode, *tnode;
1301
1302 fromcon = sepol_sidtab_search(sidtab, fromsid);
1303 if (!fromcon) {
1304 rc = -EINVAL;
1305 goto out;
1306 }
1307
1308 user = (user_datum_t *) hashtab_search(policydb->p_users.table,
1309 username);
1310 if (!user) {
1311 rc = -EINVAL;
1312 goto out;
1313 }
1314 usercon.user = user->s.value;
1315
1316 mysids = malloc(maxnel * sizeof(sepol_security_id_t));
1317 if (!mysids) {
1318 rc = -ENOMEM;
1319 goto out;
1320 }
1321 memset(mysids, 0, maxnel * sizeof(sepol_security_id_t));
1322
1323 ebitmap_for_each_bit(&user->roles.roles, rnode, i) {
1324 if (!ebitmap_node_get_bit(rnode, i))
1325 continue;
1326 role = policydb->role_val_to_struct[i];
1327 usercon.role = i + 1;
1328 ebitmap_for_each_bit(&role->types.types, tnode, j) {
1329 if (!ebitmap_node_get_bit(tnode, j))
1330 continue;
1331 usercon.type = j + 1;
1332 if (usercon.type == fromcon->type)
1333 continue;
1334
1335 if (mls_setup_user_range
1336 (fromcon, user, &usercon, policydb->mls))
1337 continue;
1338
1339 rc = context_struct_compute_av(fromcon, &usercon,
1340 SECCLASS_PROCESS,
1341 PROCESS__TRANSITION,
1342 &avd, &reason);
1343 if (rc || !(avd.allowed & PROCESS__TRANSITION))
1344 continue;
1345 rc = sepol_sidtab_context_to_sid(sidtab, &usercon,
1346 &sid);
1347 if (rc) {
1348 free(mysids);
1349 goto out;
1350 }
1351 if (mynel < maxnel) {
1352 mysids[mynel++] = sid;
1353 } else {
1354 maxnel += SIDS_NEL;
1355 mysids2 =
1356 malloc(maxnel *
1357 sizeof(sepol_security_id_t));
1358
1359 if (!mysids2) {
1360 rc = -ENOMEM;
1361 free(mysids);
1362 goto out;
1363 }
1364 memset(mysids2, 0,
1365 maxnel * sizeof(sepol_security_id_t));
1366 memcpy(mysids2, mysids,
1367 mynel * sizeof(sepol_security_id_t));
1368 free(mysids);
1369 mysids = mysids2;
1370 mysids[mynel++] = sid;
1371 }
1372 }
1373 }
1374
1375 *sids = mysids;
1376 *nel = mynel;
1377
1378 out:
1379 return rc;
1380 }
1381
1382 /*
1383 * Return the SID to use for a file in a filesystem
1384 * that cannot support a persistent label mapping or use another
1385 * fixed labeling behavior like transition SIDs or task SIDs.
1386 */
sepol_genfs_sid(const char * fstype,char * path,sepol_security_class_t sclass,sepol_security_id_t * sid)1387 int hidden sepol_genfs_sid(const char *fstype,
1388 char *path,
1389 sepol_security_class_t sclass,
1390 sepol_security_id_t * sid)
1391 {
1392 size_t len;
1393 genfs_t *genfs;
1394 ocontext_t *c;
1395 int rc = 0, cmp = 0;
1396
1397 for (genfs = policydb->genfs; genfs; genfs = genfs->next) {
1398 cmp = strcmp(fstype, genfs->fstype);
1399 if (cmp <= 0)
1400 break;
1401 }
1402
1403 if (!genfs || cmp) {
1404 *sid = SECINITSID_UNLABELED;
1405 rc = -ENOENT;
1406 goto out;
1407 }
1408
1409 for (c = genfs->head; c; c = c->next) {
1410 len = strlen(c->u.name);
1411 if ((!c->v.sclass || sclass == c->v.sclass) &&
1412 (strncmp(c->u.name, path, len) == 0))
1413 break;
1414 }
1415
1416 if (!c) {
1417 *sid = SECINITSID_UNLABELED;
1418 rc = -ENOENT;
1419 goto out;
1420 }
1421
1422 if (!c->sid[0]) {
1423 rc = sepol_sidtab_context_to_sid(sidtab,
1424 &c->context[0], &c->sid[0]);
1425 if (rc)
1426 goto out;
1427 }
1428
1429 *sid = c->sid[0];
1430 out:
1431 return rc;
1432 }
1433
sepol_fs_use(const char * fstype,unsigned int * behavior,sepol_security_id_t * sid)1434 int hidden sepol_fs_use(const char *fstype,
1435 unsigned int *behavior, sepol_security_id_t * sid)
1436 {
1437 int rc = 0;
1438 ocontext_t *c;
1439
1440 c = policydb->ocontexts[OCON_FSUSE];
1441 while (c) {
1442 if (strcmp(fstype, c->u.name) == 0)
1443 break;
1444 c = c->next;
1445 }
1446
1447 if (c) {
1448 *behavior = c->v.behavior;
1449 if (!c->sid[0]) {
1450 rc = sepol_sidtab_context_to_sid(sidtab,
1451 &c->context[0],
1452 &c->sid[0]);
1453 if (rc)
1454 goto out;
1455 }
1456 *sid = c->sid[0];
1457 } else {
1458 rc = sepol_genfs_sid(fstype, "/", SECCLASS_DIR, sid);
1459 if (rc) {
1460 *behavior = SECURITY_FS_USE_NONE;
1461 rc = 0;
1462 } else {
1463 *behavior = SECURITY_FS_USE_GENFS;
1464 }
1465 }
1466
1467 out:
1468 return rc;
1469 }
1470
1471 /* FLASK */
1472