Age Owner Branch data TLA Line data Source code
1 : : /* -------------------------------------------------------------------------
2 : : *
3 : : * contrib/sepgsql/uavc.c
4 : : *
5 : : * Implementation of userspace access vector cache; that enables to cache
6 : : * access control decisions recently used, and reduce number of kernel
7 : : * invocations to avoid unnecessary performance hit.
8 : : *
9 : : * Copyright (c) 2011-2024, PostgreSQL Global Development Group
10 : : *
11 : : * -------------------------------------------------------------------------
12 : : */
13 : : #include "postgres.h"
14 : :
15 : : #include "catalog/pg_proc.h"
16 : : #include "commands/seclabel.h"
17 : : #include "common/hashfn.h"
18 : : #include "sepgsql.h"
19 : : #include "storage/ipc.h"
20 : : #include "utils/guc.h"
21 : : #include "utils/memutils.h"
22 : :
23 : : /*
24 : : * avc_cache
25 : : *
26 : : * It enables to cache access control decision (and behavior on execution of
27 : : * trusted procedure, db_procedure class only) for a particular pair of
28 : : * security labels and object class in userspace.
29 : : */
30 : : typedef struct
31 : : {
32 : : uint32 hash; /* hash value of this cache entry */
33 : : char *scontext; /* security context of the subject */
34 : : char *tcontext; /* security context of the target */
35 : : uint16 tclass; /* object class of the target */
36 : :
37 : : uint32 allowed; /* permissions to be allowed */
38 : : uint32 auditallow; /* permissions to be audited on allowed */
39 : : uint32 auditdeny; /* permissions to be audited on denied */
40 : :
41 : : bool permissive; /* true, if permissive rule */
42 : : bool hot_cache; /* true, if recently referenced */
43 : : bool tcontext_is_valid;
44 : : /* true, if tcontext is valid */
45 : : char *ncontext; /* temporary scontext on execution of trusted
46 : : * procedure, or NULL elsewhere */
47 : : } avc_cache;
48 : :
49 : : /*
50 : : * Declaration of static variables
51 : : */
52 : : #define AVC_NUM_SLOTS 512
53 : : #define AVC_NUM_RECLAIM 16
54 : : #define AVC_DEF_THRESHOLD 384
55 : :
56 : : static MemoryContext avc_mem_cxt;
57 : : static List *avc_slots[AVC_NUM_SLOTS]; /* avc's hash buckets */
58 : : static int avc_num_caches; /* number of caches currently used */
59 : : static int avc_lru_hint; /* index of the buckets to be reclaimed next */
60 : : static int avc_threshold; /* threshold to launch cache-reclaiming */
61 : : static char *avc_unlabeled; /* system 'unlabeled' label */
62 : :
63 : : /*
64 : : * Hash function
65 : : */
66 : : static uint32
4609 rhaas@postgresql.org 67 :UBC 0 : sepgsql_avc_hash(const char *scontext, const char *tcontext, uint16 tclass)
68 : : {
4326 bruce@momjian.us 69 : 0 : return hash_any((const unsigned char *) scontext, strlen(scontext))
70 : 0 : ^ hash_any((const unsigned char *) tcontext, strlen(tcontext))
4609 rhaas@postgresql.org 71 : 0 : ^ tclass;
72 : : }
73 : :
74 : : /*
75 : : * Reset all the avc caches
76 : : */
77 : : static void
78 : 0 : sepgsql_avc_reset(void)
79 : : {
80 : 0 : MemoryContextReset(avc_mem_cxt);
81 : :
82 : 0 : memset(avc_slots, 0, sizeof(List *) * AVC_NUM_SLOTS);
83 : 0 : avc_num_caches = 0;
84 : 0 : avc_lru_hint = 0;
85 : 0 : avc_unlabeled = NULL;
86 : 0 : }
87 : :
88 : : /*
89 : : * Reclaim caches recently unreferenced
90 : : */
91 : : static void
92 : 0 : sepgsql_avc_reclaim(void)
93 : : {
94 : : ListCell *cell;
95 : : int index;
96 : :
97 [ # # ]: 0 : while (avc_num_caches >= avc_threshold - AVC_NUM_RECLAIM)
98 : : {
99 : 0 : index = avc_lru_hint;
100 : :
1735 tgl@sss.pgh.pa.us 101 [ # # # # : 0 : foreach(cell, avc_slots[index])
# # ]
102 : : {
4609 rhaas@postgresql.org 103 : 0 : avc_cache *cache = lfirst(cell);
104 : :
105 [ # # ]: 0 : if (!cache->hot_cache)
106 : : {
107 : : avc_slots[index]
1735 tgl@sss.pgh.pa.us 108 : 0 : = foreach_delete_current(avc_slots[index], cell);
109 : :
4609 rhaas@postgresql.org 110 : 0 : pfree(cache->scontext);
111 : 0 : pfree(cache->tcontext);
112 [ # # ]: 0 : if (cache->ncontext)
113 : 0 : pfree(cache->ncontext);
114 : 0 : pfree(cache);
115 : :
116 : 0 : avc_num_caches--;
117 : : }
118 : : else
119 : : {
120 : 0 : cache->hot_cache = false;
121 : : }
122 : : }
123 : 0 : avc_lru_hint = (avc_lru_hint + 1) % AVC_NUM_SLOTS;
124 : : }
125 : 0 : }
126 : :
127 : : /* -------------------------------------------------------------------------
128 : : *
129 : : * sepgsql_avc_check_valid
130 : : *
131 : : * This function checks whether the cached entries are still valid. If
132 : : * the security policy has been reloaded (or any other events that requires
133 : : * resetting userspace caches has occurred) since the last reference to
134 : : * the access vector cache, we must flush the cache.
135 : : *
136 : : * Access control decisions must be atomic, but multiple system calls may
137 : : * be required to make a decision; thus, when referencing the access vector
138 : : * cache, we must loop until we complete without an intervening cache flush
139 : : * event. In practice, looping even once should be very rare. Callers should
140 : : * do something like this:
141 : : *
142 : : * sepgsql_avc_check_valid();
143 : : * do {
144 : : * :
145 : : * <reference to uavc>
146 : : * :
147 : : * } while (!sepgsql_avc_check_valid())
148 : : *
149 : : * -------------------------------------------------------------------------
150 : : */
151 : : static bool
152 : 0 : sepgsql_avc_check_valid(void)
153 : : {
154 [ # # ]: 0 : if (selinux_status_updated() > 0)
155 : : {
156 : 0 : sepgsql_avc_reset();
157 : :
158 : 0 : return false;
159 : : }
160 : 0 : return true;
161 : : }
162 : :
163 : : /*
164 : : * sepgsql_avc_unlabeled
165 : : *
166 : : * Returns an alternative label to be applied when no label or an invalid
167 : : * label would otherwise be assigned.
168 : : */
169 : : static char *
170 : 0 : sepgsql_avc_unlabeled(void)
171 : : {
172 [ # # ]: 0 : if (!avc_unlabeled)
173 : : {
174 : : char *unlabeled;
175 : :
176 [ # # ]: 0 : if (security_get_initial_context_raw("unlabeled", &unlabeled) < 0)
177 [ # # ]: 0 : ereport(ERROR,
178 : : (errcode(ERRCODE_INTERNAL_ERROR),
179 : : errmsg("SELinux: failed to get initial security label: %m")));
180 [ # # ]: 0 : PG_TRY();
181 : : {
182 : 0 : avc_unlabeled = MemoryContextStrdup(avc_mem_cxt, unlabeled);
183 : : }
1626 peter@eisentraut.org 184 : 0 : PG_FINALLY();
185 : : {
4609 rhaas@postgresql.org 186 : 0 : freecon(unlabeled);
187 : : }
188 [ # # ]: 0 : PG_END_TRY();
189 : : }
190 : 0 : return avc_unlabeled;
191 : : }
192 : :
193 : : /*
194 : : * sepgsql_avc_compute
195 : : *
196 : : * A fallback path, when cache mishit. It asks SELinux its access control
197 : : * decision for the supplied pair of security context and object class.
198 : : */
199 : : static avc_cache *
200 : 0 : sepgsql_avc_compute(const char *scontext, const char *tcontext, uint16 tclass)
201 : : {
4326 bruce@momjian.us 202 : 0 : char *ucontext = NULL;
203 : 0 : char *ncontext = NULL;
204 : : MemoryContext oldctx;
205 : : avc_cache *cache;
206 : : uint32 hash;
207 : : int index;
208 : : struct av_decision avd;
209 : :
4609 rhaas@postgresql.org 210 : 0 : hash = sepgsql_avc_hash(scontext, tcontext, tclass);
211 : 0 : index = hash % AVC_NUM_SLOTS;
212 : :
213 : : /*
214 : : * Validation check of the supplied security context. Because it always
215 : : * invoke system-call, frequent check should be avoided. Unless security
216 : : * policy is reloaded, validation status shall be kept, so we also cache
217 : : * whether the supplied security context was valid, or not.
218 : : */
1339 michael@paquier.xyz 219 [ # # ]: 0 : if (security_check_context_raw(tcontext) != 0)
4609 rhaas@postgresql.org 220 : 0 : ucontext = sepgsql_avc_unlabeled();
221 : :
222 : : /*
223 : : * Ask SELinux its access control decision
224 : : */
225 [ # # ]: 0 : if (!ucontext)
226 : 0 : sepgsql_compute_avd(scontext, tcontext, tclass, &avd);
227 : : else
228 : 0 : sepgsql_compute_avd(scontext, ucontext, tclass, &avd);
229 : :
230 : : /*
231 : : * It also caches a security label to be switched when a client labeled as
232 : : * 'scontext' executes a procedure labeled as 'tcontext', not only access
233 : : * control decision on the procedure. The security label to be switched
234 : : * shall be computed uniquely on a pair of 'scontext' and 'tcontext',
235 : : * thus, it is reasonable to cache the new label on avc, and enables to
236 : : * reduce unnecessary system calls. It shall be referenced at
237 : : * sepgsql_needs_fmgr_hook to check whether the supplied function is a
238 : : * trusted procedure, or not.
239 : : */
240 [ # # ]: 0 : if (tclass == SEPG_CLASS_DB_PROCEDURE)
241 : : {
242 [ # # ]: 0 : if (!ucontext)
243 : 0 : ncontext = sepgsql_compute_create(scontext, tcontext,
244 : : SEPG_CLASS_PROCESS, NULL);
245 : : else
246 : 0 : ncontext = sepgsql_compute_create(scontext, ucontext,
247 : : SEPG_CLASS_PROCESS, NULL);
248 [ # # ]: 0 : if (strcmp(scontext, ncontext) == 0)
249 : : {
250 : 0 : pfree(ncontext);
251 : 0 : ncontext = NULL;
252 : : }
253 : : }
254 : :
255 : : /*
256 : : * Set up an avc_cache object
257 : : */
258 : 0 : oldctx = MemoryContextSwitchTo(avc_mem_cxt);
259 : :
260 : 0 : cache = palloc0(sizeof(avc_cache));
261 : :
4326 bruce@momjian.us 262 : 0 : cache->hash = hash;
4609 rhaas@postgresql.org 263 : 0 : cache->scontext = pstrdup(scontext);
264 : 0 : cache->tcontext = pstrdup(tcontext);
265 : 0 : cache->tclass = tclass;
266 : :
267 : 0 : cache->allowed = avd.allowed;
268 : 0 : cache->auditallow = avd.auditallow;
269 : 0 : cache->auditdeny = avd.auditdeny;
270 : 0 : cache->hot_cache = true;
271 [ # # ]: 0 : if (avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE)
272 : 0 : cache->permissive = true;
273 [ # # ]: 0 : if (!ucontext)
274 : 0 : cache->tcontext_is_valid = true;
275 [ # # ]: 0 : if (ncontext)
276 : 0 : cache->ncontext = pstrdup(ncontext);
277 : :
278 : 0 : avc_num_caches++;
279 : :
280 [ # # ]: 0 : if (avc_num_caches > avc_threshold)
281 : 0 : sepgsql_avc_reclaim();
282 : :
283 : 0 : avc_slots[index] = lcons(cache, avc_slots[index]);
284 : :
285 : 0 : MemoryContextSwitchTo(oldctx);
286 : :
287 : 0 : return cache;
288 : : }
289 : :
290 : : /*
291 : : * sepgsql_avc_lookup
292 : : *
293 : : * Look up a cache entry that matches the supplied security contexts and
294 : : * object class. If not found, create a new cache entry.
295 : : */
296 : : static avc_cache *
297 : 0 : sepgsql_avc_lookup(const char *scontext, const char *tcontext, uint16 tclass)
298 : : {
299 : : avc_cache *cache;
300 : : ListCell *cell;
301 : : uint32 hash;
302 : : int index;
303 : :
304 : 0 : hash = sepgsql_avc_hash(scontext, tcontext, tclass);
305 : 0 : index = hash % AVC_NUM_SLOTS;
306 : :
4326 bruce@momjian.us 307 [ # # # # : 0 : foreach(cell, avc_slots[index])
# # ]
308 : : {
4609 rhaas@postgresql.org 309 : 0 : cache = lfirst(cell);
310 : :
311 [ # # ]: 0 : if (cache->hash == hash &&
312 [ # # ]: 0 : cache->tclass == tclass &&
313 [ # # ]: 0 : strcmp(cache->tcontext, tcontext) == 0 &&
314 [ # # ]: 0 : strcmp(cache->scontext, scontext) == 0)
315 : : {
316 : 0 : cache->hot_cache = true;
317 : 0 : return cache;
318 : : }
319 : : }
320 : : /* not found, so insert a new cache */
321 : 0 : return sepgsql_avc_compute(scontext, tcontext, tclass);
322 : : }
323 : :
324 : : /*
325 : : * sepgsql_avc_check_perms(_label)
326 : : *
327 : : * It returns 'true', if the security policy suggested to allow the required
328 : : * permissions. Otherwise, it returns 'false' or raises an error according
329 : : * to the 'abort_on_violation' argument.
330 : : * The 'tobject' and 'tclass' identify the target object being referenced,
331 : : * and 'required' is a bitmask of permissions (SEPG_*__*) defined for each
332 : : * object classes.
333 : : * The 'audit_name' is the object name (optional). If SEPGSQL_AVC_NOAUDIT
334 : : * was supplied, it means to skip all the audit messages.
335 : : */
336 : : bool
337 : 0 : sepgsql_avc_check_perms_label(const char *tcontext,
338 : : uint16 tclass, uint32 required,
339 : : const char *audit_name,
340 : : bool abort_on_violation)
341 : : {
4326 bruce@momjian.us 342 : 0 : char *scontext = sepgsql_get_client_label();
343 : : avc_cache *cache;
344 : : uint32 denied;
345 : : uint32 audited;
346 : : bool result;
347 : :
4609 rhaas@postgresql.org 348 : 0 : sepgsql_avc_check_valid();
349 : : do
350 : : {
351 : 0 : result = true;
352 : :
353 : : /*
354 : : * If the target object is unlabeled, we perform the check using the
355 : : * label supplied by sepgsql_avc_unlabeled().
356 : : */
357 [ # # ]: 0 : if (tcontext)
358 : 0 : cache = sepgsql_avc_lookup(scontext, tcontext, tclass);
359 : : else
360 : 0 : cache = sepgsql_avc_lookup(scontext,
361 : 0 : sepgsql_avc_unlabeled(), tclass);
362 : :
363 : 0 : denied = required & ~cache->allowed;
364 : :
365 : : /*
366 : : * Compute permissions to be audited
367 : : */
368 [ # # ]: 0 : if (sepgsql_get_debug_audit())
369 [ # # ]: 0 : audited = (denied ? (denied & ~0) : (required & ~0));
370 : : else
371 : 0 : audited = denied ? (denied & cache->auditdeny)
4326 bruce@momjian.us 372 [ # # ]: 0 : : (required & cache->auditallow);
373 : :
4609 rhaas@postgresql.org 374 [ # # ]: 0 : if (denied)
375 : : {
376 : : /*
377 : : * In permissive mode or permissive domain, violated permissions
378 : : * shall be audited to the log files at once, and then implicitly
379 : : * allowed to avoid a flood of access denied logs, because the
380 : : * purpose of permissive mode/domain is to collect a violation log
381 : : * that will make it possible to fix up the security policy.
382 : : */
383 [ # # # # ]: 0 : if (!sepgsql_getenforce() || cache->permissive)
384 : 0 : cache->allowed |= required;
385 : : else
386 : 0 : result = false;
387 : : }
388 [ # # ]: 0 : } while (!sepgsql_avc_check_valid());
389 : :
390 : : /*
391 : : * In the case when we have something auditable actions here,
392 : : * sepgsql_audit_log shall be called with text representation of security
393 : : * labels for both of subject and object. It records this access
394 : : * violation, so DBA will be able to find out unexpected security problems
395 : : * later.
396 : : */
397 [ # # # # ]: 0 : if (audited != 0 &&
398 [ # # ]: 0 : audit_name != SEPGSQL_AVC_NOAUDIT &&
399 : 0 : sepgsql_get_mode() != SEPGSQL_MODE_INTERNAL)
400 : : {
2940 andres@anarazel.de 401 : 0 : sepgsql_audit_log(denied != 0,
823 tgl@sss.pgh.pa.us 402 [ # # # # ]: 0 : (sepgsql_getenforce() && !cache->permissive),
4609 rhaas@postgresql.org 403 : 0 : cache->scontext,
404 : 0 : cache->tcontext_is_valid ?
405 : 0 : cache->tcontext : sepgsql_avc_unlabeled(),
406 [ # # ]: 0 : cache->tclass,
407 : : audited,
4609 rhaas@postgresql.org 408 :EUB : audit_name);
409 : : }
410 : :
4239 rhaas@postgresql.org 411 [ # # # # ]:UBC 0 : if (abort_on_violation && !result)
4609 412 [ # # ]: 0 : ereport(ERROR,
413 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
414 : : errmsg("SELinux: security policy violation")));
415 : :
416 : 0 : return result;
417 : : }
418 : :
419 : : bool
420 : 0 : sepgsql_avc_check_perms(const ObjectAddress *tobject,
421 : : uint16 tclass, uint32 required,
422 : : const char *audit_name,
423 : : bool abort_on_violation)
424 : : {
4326 bruce@momjian.us 425 : 0 : char *tcontext = GetSecurityLabel(tobject, SEPGSQL_LABEL_TAG);
426 : : bool rc;
427 : :
4609 rhaas@postgresql.org 428 : 0 : rc = sepgsql_avc_check_perms_label(tcontext,
429 : : tclass, required,
430 : : audit_name, abort_on_violation);
431 [ # # ]: 0 : if (tcontext)
432 : 0 : pfree(tcontext);
433 : :
434 : 0 : return rc;
435 : : }
436 : :
437 : : /*
438 : : * sepgsql_avc_trusted_proc
439 : : *
440 : : * If the supplied function OID is configured as a trusted procedure, this
441 : : * function will return a security label to be used during the execution of
442 : : * that function. Otherwise, it returns NULL.
443 : : */
444 : : char *
445 : 0 : sepgsql_avc_trusted_proc(Oid functionId)
446 : : {
4326 bruce@momjian.us 447 : 0 : char *scontext = sepgsql_get_client_label();
448 : : char *tcontext;
449 : : ObjectAddress tobject;
450 : : avc_cache *cache;
451 : :
4609 rhaas@postgresql.org 452 : 0 : tobject.classId = ProcedureRelationId;
453 : 0 : tobject.objectId = functionId;
454 : 0 : tobject.objectSubId = 0;
455 : 0 : tcontext = GetSecurityLabel(&tobject, SEPGSQL_LABEL_TAG);
456 : :
457 : 0 : sepgsql_avc_check_valid();
458 : : do
459 : : {
460 [ # # ]: 0 : if (tcontext)
461 : 0 : cache = sepgsql_avc_lookup(scontext, tcontext,
462 : : SEPG_CLASS_DB_PROCEDURE);
463 : : else
464 : 0 : cache = sepgsql_avc_lookup(scontext, sepgsql_avc_unlabeled(),
465 : : SEPG_CLASS_DB_PROCEDURE);
466 [ # # ]: 0 : } while (!sepgsql_avc_check_valid());
467 : :
468 : 0 : return cache->ncontext;
469 : : }
470 : :
471 : : /*
472 : : * sepgsql_avc_exit
473 : : *
474 : : * Clean up userspace AVC on process exit.
475 : : */
476 : : static void
477 : 0 : sepgsql_avc_exit(int code, Datum arg)
478 : : {
479 : 0 : selinux_status_close();
480 : 0 : }
481 : :
482 : : /*
483 : : * sepgsql_avc_init
484 : : *
485 : : * Initialize the userspace AVC. This should be called from _PG_init.
486 : : */
487 : : void
488 : 0 : sepgsql_avc_init(void)
489 : : {
490 : : int rc;
491 : :
492 : : /*
493 : : * All the avc stuff shall be allocated in avc_mem_cxt
494 : : */
495 : 0 : avc_mem_cxt = AllocSetContextCreate(TopMemoryContext,
496 : : "userspace access vector cache",
497 : : ALLOCSET_DEFAULT_SIZES);
498 : 0 : memset(avc_slots, 0, sizeof(avc_slots));
499 : 0 : avc_num_caches = 0;
500 : 0 : avc_lru_hint = 0;
501 : 0 : avc_threshold = AVC_DEF_THRESHOLD;
502 : :
503 : : /*
504 : : * SELinux allows to mmap(2) its kernel status page in read-only mode to
505 : : * inform userspace applications its status updating (such as policy
506 : : * reloading) without system-call invocations. This feature is only
507 : : * supported in Linux-2.6.38 or later, however, libselinux provides a
508 : : * fallback mode to know its status using netlink sockets.
509 : : */
510 : 0 : rc = selinux_status_open(1);
511 [ # # ]: 0 : if (rc < 0)
512 [ # # ]: 0 : ereport(ERROR,
513 : : (errcode(ERRCODE_INTERNAL_ERROR),
514 : : errmsg("SELinux: could not open selinux status : %m")));
515 [ # # ]: 0 : else if (rc > 0)
516 [ # # ]: 0 : ereport(LOG,
517 : : (errmsg("SELinux: kernel status page uses fallback mode")));
518 : :
519 : : /* Arrange to close selinux status page on process exit. */
520 : 0 : on_proc_exit(sepgsql_avc_exit, 0);
521 : 0 : }
|