1 | /* ------------------------------------------------------------------------- |
2 | * |
3 | * seclabel.c |
4 | * routines to support security label feature. |
5 | * |
6 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
7 | * Portions Copyright (c) 1994, Regents of the University of California |
8 | * |
9 | * ------------------------------------------------------------------------- |
10 | */ |
11 | #include "postgres.h" |
12 | |
13 | #include "access/genam.h" |
14 | #include "access/htup_details.h" |
15 | #include "access/relation.h" |
16 | #include "access/table.h" |
17 | #include "catalog/catalog.h" |
18 | #include "catalog/indexing.h" |
19 | #include "catalog/pg_seclabel.h" |
20 | #include "catalog/pg_shseclabel.h" |
21 | #include "commands/seclabel.h" |
22 | #include "miscadmin.h" |
23 | #include "utils/builtins.h" |
24 | #include "utils/fmgroids.h" |
25 | #include "utils/memutils.h" |
26 | #include "utils/rel.h" |
27 | |
28 | typedef struct |
29 | { |
30 | const char *provider_name; |
31 | check_object_relabel_type hook; |
32 | } LabelProvider; |
33 | |
34 | static List *label_provider_list = NIL; |
35 | |
36 | /* |
37 | * ExecSecLabelStmt -- |
38 | * |
39 | * Apply a security label to a database object. |
40 | * |
41 | * Returns the ObjectAddress of the object to which the policy was applied. |
42 | */ |
43 | ObjectAddress |
44 | ExecSecLabelStmt(SecLabelStmt *stmt) |
45 | { |
46 | LabelProvider *provider = NULL; |
47 | ObjectAddress address; |
48 | Relation relation; |
49 | ListCell *lc; |
50 | |
51 | /* |
52 | * Find the named label provider, or if none specified, check whether |
53 | * there's exactly one, and if so use it. |
54 | */ |
55 | if (stmt->provider == NULL) |
56 | { |
57 | if (label_provider_list == NIL) |
58 | ereport(ERROR, |
59 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
60 | errmsg("no security label providers have been loaded" ))); |
61 | if (lnext(list_head(label_provider_list)) != NULL) |
62 | ereport(ERROR, |
63 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
64 | errmsg("must specify provider when multiple security label providers have been loaded" ))); |
65 | provider = (LabelProvider *) linitial(label_provider_list); |
66 | } |
67 | else |
68 | { |
69 | foreach(lc, label_provider_list) |
70 | { |
71 | LabelProvider *lp = lfirst(lc); |
72 | |
73 | if (strcmp(stmt->provider, lp->provider_name) == 0) |
74 | { |
75 | provider = lp; |
76 | break; |
77 | } |
78 | } |
79 | if (provider == NULL) |
80 | ereport(ERROR, |
81 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
82 | errmsg("security label provider \"%s\" is not loaded" , |
83 | stmt->provider))); |
84 | } |
85 | |
86 | /* |
87 | * Translate the parser representation which identifies this object into |
88 | * an ObjectAddress. get_object_address() will throw an error if the |
89 | * object does not exist, and will also acquire a lock on the target to |
90 | * guard against concurrent modifications. |
91 | */ |
92 | address = get_object_address(stmt->objtype, stmt->object, |
93 | &relation, ShareUpdateExclusiveLock, false); |
94 | |
95 | /* Require ownership of the target object. */ |
96 | check_object_ownership(GetUserId(), stmt->objtype, address, |
97 | stmt->object, relation); |
98 | |
99 | /* Perform other integrity checks as needed. */ |
100 | switch (stmt->objtype) |
101 | { |
102 | case OBJECT_COLUMN: |
103 | |
104 | /* |
105 | * Allow security labels only on columns of tables, views, |
106 | * materialized views, composite types, and foreign tables (which |
107 | * are the only relkinds for which pg_dump will dump labels). |
108 | */ |
109 | if (relation->rd_rel->relkind != RELKIND_RELATION && |
110 | relation->rd_rel->relkind != RELKIND_VIEW && |
111 | relation->rd_rel->relkind != RELKIND_MATVIEW && |
112 | relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && |
113 | relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE && |
114 | relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) |
115 | ereport(ERROR, |
116 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
117 | errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table" , |
118 | RelationGetRelationName(relation)))); |
119 | break; |
120 | default: |
121 | break; |
122 | } |
123 | |
124 | /* Provider gets control here, may throw ERROR to veto new label. */ |
125 | provider->hook(&address, stmt->label); |
126 | |
127 | /* Apply new label. */ |
128 | SetSecurityLabel(&address, provider->provider_name, stmt->label); |
129 | |
130 | /* |
131 | * If get_object_address() opened the relation for us, we close it to keep |
132 | * the reference count correct - but we retain any locks acquired by |
133 | * get_object_address() until commit time, to guard against concurrent |
134 | * activity. |
135 | */ |
136 | if (relation != NULL) |
137 | relation_close(relation, NoLock); |
138 | |
139 | return address; |
140 | } |
141 | |
142 | /* |
143 | * GetSharedSecurityLabel returns the security label for a shared object for |
144 | * a given provider, or NULL if there is no such label. |
145 | */ |
146 | static char * |
147 | GetSharedSecurityLabel(const ObjectAddress *object, const char *provider) |
148 | { |
149 | Relation pg_shseclabel; |
150 | ScanKeyData keys[3]; |
151 | SysScanDesc scan; |
152 | HeapTuple tuple; |
153 | Datum datum; |
154 | bool isnull; |
155 | char *seclabel = NULL; |
156 | |
157 | ScanKeyInit(&keys[0], |
158 | Anum_pg_shseclabel_objoid, |
159 | BTEqualStrategyNumber, F_OIDEQ, |
160 | ObjectIdGetDatum(object->objectId)); |
161 | ScanKeyInit(&keys[1], |
162 | Anum_pg_shseclabel_classoid, |
163 | BTEqualStrategyNumber, F_OIDEQ, |
164 | ObjectIdGetDatum(object->classId)); |
165 | ScanKeyInit(&keys[2], |
166 | Anum_pg_shseclabel_provider, |
167 | BTEqualStrategyNumber, F_TEXTEQ, |
168 | CStringGetTextDatum(provider)); |
169 | |
170 | pg_shseclabel = table_open(SharedSecLabelRelationId, AccessShareLock); |
171 | |
172 | scan = systable_beginscan(pg_shseclabel, SharedSecLabelObjectIndexId, true, |
173 | NULL, 3, keys); |
174 | |
175 | tuple = systable_getnext(scan); |
176 | if (HeapTupleIsValid(tuple)) |
177 | { |
178 | datum = heap_getattr(tuple, Anum_pg_shseclabel_label, |
179 | RelationGetDescr(pg_shseclabel), &isnull); |
180 | if (!isnull) |
181 | seclabel = TextDatumGetCString(datum); |
182 | } |
183 | systable_endscan(scan); |
184 | |
185 | table_close(pg_shseclabel, AccessShareLock); |
186 | |
187 | return seclabel; |
188 | } |
189 | |
190 | /* |
191 | * GetSecurityLabel returns the security label for a shared or database object |
192 | * for a given provider, or NULL if there is no such label. |
193 | */ |
194 | char * |
195 | GetSecurityLabel(const ObjectAddress *object, const char *provider) |
196 | { |
197 | Relation pg_seclabel; |
198 | ScanKeyData keys[4]; |
199 | SysScanDesc scan; |
200 | HeapTuple tuple; |
201 | Datum datum; |
202 | bool isnull; |
203 | char *seclabel = NULL; |
204 | |
205 | /* Shared objects have their own security label catalog. */ |
206 | if (IsSharedRelation(object->classId)) |
207 | return GetSharedSecurityLabel(object, provider); |
208 | |
209 | /* Must be an unshared object, so examine pg_seclabel. */ |
210 | ScanKeyInit(&keys[0], |
211 | Anum_pg_seclabel_objoid, |
212 | BTEqualStrategyNumber, F_OIDEQ, |
213 | ObjectIdGetDatum(object->objectId)); |
214 | ScanKeyInit(&keys[1], |
215 | Anum_pg_seclabel_classoid, |
216 | BTEqualStrategyNumber, F_OIDEQ, |
217 | ObjectIdGetDatum(object->classId)); |
218 | ScanKeyInit(&keys[2], |
219 | Anum_pg_seclabel_objsubid, |
220 | BTEqualStrategyNumber, F_INT4EQ, |
221 | Int32GetDatum(object->objectSubId)); |
222 | ScanKeyInit(&keys[3], |
223 | Anum_pg_seclabel_provider, |
224 | BTEqualStrategyNumber, F_TEXTEQ, |
225 | CStringGetTextDatum(provider)); |
226 | |
227 | pg_seclabel = table_open(SecLabelRelationId, AccessShareLock); |
228 | |
229 | scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, |
230 | NULL, 4, keys); |
231 | |
232 | tuple = systable_getnext(scan); |
233 | if (HeapTupleIsValid(tuple)) |
234 | { |
235 | datum = heap_getattr(tuple, Anum_pg_seclabel_label, |
236 | RelationGetDescr(pg_seclabel), &isnull); |
237 | if (!isnull) |
238 | seclabel = TextDatumGetCString(datum); |
239 | } |
240 | systable_endscan(scan); |
241 | |
242 | table_close(pg_seclabel, AccessShareLock); |
243 | |
244 | return seclabel; |
245 | } |
246 | |
247 | /* |
248 | * SetSharedSecurityLabel is a helper function of SetSecurityLabel to |
249 | * handle shared database objects. |
250 | */ |
251 | static void |
252 | SetSharedSecurityLabel(const ObjectAddress *object, |
253 | const char *provider, const char *label) |
254 | { |
255 | Relation pg_shseclabel; |
256 | ScanKeyData keys[4]; |
257 | SysScanDesc scan; |
258 | HeapTuple oldtup; |
259 | HeapTuple newtup = NULL; |
260 | Datum values[Natts_pg_shseclabel]; |
261 | bool nulls[Natts_pg_shseclabel]; |
262 | bool replaces[Natts_pg_shseclabel]; |
263 | |
264 | /* Prepare to form or update a tuple, if necessary. */ |
265 | memset(nulls, false, sizeof(nulls)); |
266 | memset(replaces, false, sizeof(replaces)); |
267 | values[Anum_pg_shseclabel_objoid - 1] = ObjectIdGetDatum(object->objectId); |
268 | values[Anum_pg_shseclabel_classoid - 1] = ObjectIdGetDatum(object->classId); |
269 | values[Anum_pg_shseclabel_provider - 1] = CStringGetTextDatum(provider); |
270 | if (label != NULL) |
271 | values[Anum_pg_shseclabel_label - 1] = CStringGetTextDatum(label); |
272 | |
273 | /* Use the index to search for a matching old tuple */ |
274 | ScanKeyInit(&keys[0], |
275 | Anum_pg_shseclabel_objoid, |
276 | BTEqualStrategyNumber, F_OIDEQ, |
277 | ObjectIdGetDatum(object->objectId)); |
278 | ScanKeyInit(&keys[1], |
279 | Anum_pg_shseclabel_classoid, |
280 | BTEqualStrategyNumber, F_OIDEQ, |
281 | ObjectIdGetDatum(object->classId)); |
282 | ScanKeyInit(&keys[2], |
283 | Anum_pg_shseclabel_provider, |
284 | BTEqualStrategyNumber, F_TEXTEQ, |
285 | CStringGetTextDatum(provider)); |
286 | |
287 | pg_shseclabel = table_open(SharedSecLabelRelationId, RowExclusiveLock); |
288 | |
289 | scan = systable_beginscan(pg_shseclabel, SharedSecLabelObjectIndexId, true, |
290 | NULL, 3, keys); |
291 | |
292 | oldtup = systable_getnext(scan); |
293 | if (HeapTupleIsValid(oldtup)) |
294 | { |
295 | if (label == NULL) |
296 | CatalogTupleDelete(pg_shseclabel, &oldtup->t_self); |
297 | else |
298 | { |
299 | replaces[Anum_pg_shseclabel_label - 1] = true; |
300 | newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_shseclabel), |
301 | values, nulls, replaces); |
302 | CatalogTupleUpdate(pg_shseclabel, &oldtup->t_self, newtup); |
303 | } |
304 | } |
305 | systable_endscan(scan); |
306 | |
307 | /* If we didn't find an old tuple, insert a new one */ |
308 | if (newtup == NULL && label != NULL) |
309 | { |
310 | newtup = heap_form_tuple(RelationGetDescr(pg_shseclabel), |
311 | values, nulls); |
312 | CatalogTupleInsert(pg_shseclabel, newtup); |
313 | } |
314 | |
315 | if (newtup != NULL) |
316 | heap_freetuple(newtup); |
317 | |
318 | table_close(pg_shseclabel, RowExclusiveLock); |
319 | } |
320 | |
321 | /* |
322 | * SetSecurityLabel attempts to set the security label for the specified |
323 | * provider on the specified object to the given value. NULL means that any |
324 | * existing label should be deleted. |
325 | */ |
326 | void |
327 | SetSecurityLabel(const ObjectAddress *object, |
328 | const char *provider, const char *label) |
329 | { |
330 | Relation pg_seclabel; |
331 | ScanKeyData keys[4]; |
332 | SysScanDesc scan; |
333 | HeapTuple oldtup; |
334 | HeapTuple newtup = NULL; |
335 | Datum values[Natts_pg_seclabel]; |
336 | bool nulls[Natts_pg_seclabel]; |
337 | bool replaces[Natts_pg_seclabel]; |
338 | |
339 | /* Shared objects have their own security label catalog. */ |
340 | if (IsSharedRelation(object->classId)) |
341 | { |
342 | SetSharedSecurityLabel(object, provider, label); |
343 | return; |
344 | } |
345 | |
346 | /* Prepare to form or update a tuple, if necessary. */ |
347 | memset(nulls, false, sizeof(nulls)); |
348 | memset(replaces, false, sizeof(replaces)); |
349 | values[Anum_pg_seclabel_objoid - 1] = ObjectIdGetDatum(object->objectId); |
350 | values[Anum_pg_seclabel_classoid - 1] = ObjectIdGetDatum(object->classId); |
351 | values[Anum_pg_seclabel_objsubid - 1] = Int32GetDatum(object->objectSubId); |
352 | values[Anum_pg_seclabel_provider - 1] = CStringGetTextDatum(provider); |
353 | if (label != NULL) |
354 | values[Anum_pg_seclabel_label - 1] = CStringGetTextDatum(label); |
355 | |
356 | /* Use the index to search for a matching old tuple */ |
357 | ScanKeyInit(&keys[0], |
358 | Anum_pg_seclabel_objoid, |
359 | BTEqualStrategyNumber, F_OIDEQ, |
360 | ObjectIdGetDatum(object->objectId)); |
361 | ScanKeyInit(&keys[1], |
362 | Anum_pg_seclabel_classoid, |
363 | BTEqualStrategyNumber, F_OIDEQ, |
364 | ObjectIdGetDatum(object->classId)); |
365 | ScanKeyInit(&keys[2], |
366 | Anum_pg_seclabel_objsubid, |
367 | BTEqualStrategyNumber, F_INT4EQ, |
368 | Int32GetDatum(object->objectSubId)); |
369 | ScanKeyInit(&keys[3], |
370 | Anum_pg_seclabel_provider, |
371 | BTEqualStrategyNumber, F_TEXTEQ, |
372 | CStringGetTextDatum(provider)); |
373 | |
374 | pg_seclabel = table_open(SecLabelRelationId, RowExclusiveLock); |
375 | |
376 | scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, |
377 | NULL, 4, keys); |
378 | |
379 | oldtup = systable_getnext(scan); |
380 | if (HeapTupleIsValid(oldtup)) |
381 | { |
382 | if (label == NULL) |
383 | CatalogTupleDelete(pg_seclabel, &oldtup->t_self); |
384 | else |
385 | { |
386 | replaces[Anum_pg_seclabel_label - 1] = true; |
387 | newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_seclabel), |
388 | values, nulls, replaces); |
389 | CatalogTupleUpdate(pg_seclabel, &oldtup->t_self, newtup); |
390 | } |
391 | } |
392 | systable_endscan(scan); |
393 | |
394 | /* If we didn't find an old tuple, insert a new one */ |
395 | if (newtup == NULL && label != NULL) |
396 | { |
397 | newtup = heap_form_tuple(RelationGetDescr(pg_seclabel), |
398 | values, nulls); |
399 | CatalogTupleInsert(pg_seclabel, newtup); |
400 | } |
401 | |
402 | /* Update indexes, if necessary */ |
403 | if (newtup != NULL) |
404 | heap_freetuple(newtup); |
405 | |
406 | table_close(pg_seclabel, RowExclusiveLock); |
407 | } |
408 | |
409 | /* |
410 | * DeleteSharedSecurityLabel is a helper function of DeleteSecurityLabel |
411 | * to handle shared database objects. |
412 | */ |
413 | void |
414 | DeleteSharedSecurityLabel(Oid objectId, Oid classId) |
415 | { |
416 | Relation pg_shseclabel; |
417 | ScanKeyData skey[2]; |
418 | SysScanDesc scan; |
419 | HeapTuple oldtup; |
420 | |
421 | ScanKeyInit(&skey[0], |
422 | Anum_pg_shseclabel_objoid, |
423 | BTEqualStrategyNumber, F_OIDEQ, |
424 | ObjectIdGetDatum(objectId)); |
425 | ScanKeyInit(&skey[1], |
426 | Anum_pg_shseclabel_classoid, |
427 | BTEqualStrategyNumber, F_OIDEQ, |
428 | ObjectIdGetDatum(classId)); |
429 | |
430 | pg_shseclabel = table_open(SharedSecLabelRelationId, RowExclusiveLock); |
431 | |
432 | scan = systable_beginscan(pg_shseclabel, SharedSecLabelObjectIndexId, true, |
433 | NULL, 2, skey); |
434 | while (HeapTupleIsValid(oldtup = systable_getnext(scan))) |
435 | CatalogTupleDelete(pg_shseclabel, &oldtup->t_self); |
436 | systable_endscan(scan); |
437 | |
438 | table_close(pg_shseclabel, RowExclusiveLock); |
439 | } |
440 | |
441 | /* |
442 | * DeleteSecurityLabel removes all security labels for an object (and any |
443 | * sub-objects, if applicable). |
444 | */ |
445 | void |
446 | DeleteSecurityLabel(const ObjectAddress *object) |
447 | { |
448 | Relation pg_seclabel; |
449 | ScanKeyData skey[3]; |
450 | SysScanDesc scan; |
451 | HeapTuple oldtup; |
452 | int nkeys; |
453 | |
454 | /* Shared objects have their own security label catalog. */ |
455 | if (IsSharedRelation(object->classId)) |
456 | { |
457 | Assert(object->objectSubId == 0); |
458 | DeleteSharedSecurityLabel(object->objectId, object->classId); |
459 | return; |
460 | } |
461 | |
462 | ScanKeyInit(&skey[0], |
463 | Anum_pg_seclabel_objoid, |
464 | BTEqualStrategyNumber, F_OIDEQ, |
465 | ObjectIdGetDatum(object->objectId)); |
466 | ScanKeyInit(&skey[1], |
467 | Anum_pg_seclabel_classoid, |
468 | BTEqualStrategyNumber, F_OIDEQ, |
469 | ObjectIdGetDatum(object->classId)); |
470 | if (object->objectSubId != 0) |
471 | { |
472 | ScanKeyInit(&skey[2], |
473 | Anum_pg_seclabel_objsubid, |
474 | BTEqualStrategyNumber, F_INT4EQ, |
475 | Int32GetDatum(object->objectSubId)); |
476 | nkeys = 3; |
477 | } |
478 | else |
479 | nkeys = 2; |
480 | |
481 | pg_seclabel = table_open(SecLabelRelationId, RowExclusiveLock); |
482 | |
483 | scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, |
484 | NULL, nkeys, skey); |
485 | while (HeapTupleIsValid(oldtup = systable_getnext(scan))) |
486 | CatalogTupleDelete(pg_seclabel, &oldtup->t_self); |
487 | systable_endscan(scan); |
488 | |
489 | table_close(pg_seclabel, RowExclusiveLock); |
490 | } |
491 | |
492 | void |
493 | register_label_provider(const char *provider_name, check_object_relabel_type hook) |
494 | { |
495 | LabelProvider *provider; |
496 | MemoryContext oldcxt; |
497 | |
498 | oldcxt = MemoryContextSwitchTo(TopMemoryContext); |
499 | provider = palloc(sizeof(LabelProvider)); |
500 | provider->provider_name = pstrdup(provider_name); |
501 | provider->hook = hook; |
502 | label_provider_list = lappend(label_provider_list, provider); |
503 | MemoryContextSwitchTo(oldcxt); |
504 | } |
505 | |