mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	Author: Vignesh C Reviewed-by: Dilip Kumar, Michael Paquier Discussion: https://postgr.es/m/CALDaNm0ni+GAOe4+fbXiOxNrVudajMYmhJFtXGX-zBPoN8ixhw@mail.gmail.com
		
			
				
	
	
		
			364 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			364 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* -------------------------------------------------------------------------
 | 
						|
 *
 | 
						|
 * contrib/sepgsql/dml.c
 | 
						|
 *
 | 
						|
 * Routines to handle DML permission checks
 | 
						|
 *
 | 
						|
 * Copyright (c) 2010-2019, PostgreSQL Global Development Group
 | 
						|
 *
 | 
						|
 * -------------------------------------------------------------------------
 | 
						|
 */
 | 
						|
#include "postgres.h"
 | 
						|
 | 
						|
#include "access/htup_details.h"
 | 
						|
#include "access/sysattr.h"
 | 
						|
#include "access/tupdesc.h"
 | 
						|
#include "catalog/catalog.h"
 | 
						|
#include "catalog/dependency.h"
 | 
						|
#include "catalog/heap.h"
 | 
						|
#include "catalog/pg_attribute.h"
 | 
						|
#include "catalog/pg_class.h"
 | 
						|
#include "catalog/pg_inherits.h"
 | 
						|
#include "commands/seclabel.h"
 | 
						|
#include "commands/tablecmds.h"
 | 
						|
#include "executor/executor.h"
 | 
						|
#include "nodes/bitmapset.h"
 | 
						|
#include "sepgsql.h"
 | 
						|
#include "utils/lsyscache.h"
 | 
						|
#include "utils/syscache.h"
 | 
						|
 | 
						|
/*
 | 
						|
 * fixup_whole_row_references
 | 
						|
 *
 | 
						|
 * When user reference a whole of row, it is equivalent to reference to
 | 
						|
 * all the user columns (not system columns). So, we need to fix up the
 | 
						|
 * given bitmapset, if it contains a whole of the row reference.
 | 
						|
 */
 | 
						|
static Bitmapset *
 | 
						|
fixup_whole_row_references(Oid relOid, Bitmapset *columns)
 | 
						|
{
 | 
						|
	Bitmapset  *result;
 | 
						|
	HeapTuple	tuple;
 | 
						|
	AttrNumber	natts;
 | 
						|
	AttrNumber	attno;
 | 
						|
	int			index;
 | 
						|
 | 
						|
	/* if no whole of row references, do not anything */
 | 
						|
	index = InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber;
 | 
						|
	if (!bms_is_member(index, columns))
 | 
						|
		return columns;
 | 
						|
 | 
						|
	/* obtain number of attributes */
 | 
						|
	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
 | 
						|
	if (!HeapTupleIsValid(tuple))
 | 
						|
		elog(ERROR, "cache lookup failed for relation %u", relOid);
 | 
						|
	natts = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
 | 
						|
	ReleaseSysCache(tuple);
 | 
						|
 | 
						|
	/* fix up the given columns */
 | 
						|
	result = bms_copy(columns);
 | 
						|
	result = bms_del_member(result, index);
 | 
						|
 | 
						|
	for (attno = 1; attno <= natts; attno++)
 | 
						|
	{
 | 
						|
		tuple = SearchSysCache2(ATTNUM,
 | 
						|
								ObjectIdGetDatum(relOid),
 | 
						|
								Int16GetDatum(attno));
 | 
						|
		if (!HeapTupleIsValid(tuple))
 | 
						|
			continue;
 | 
						|
 | 
						|
		if (((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped)
 | 
						|
			continue;
 | 
						|
 | 
						|
		index = attno - FirstLowInvalidHeapAttributeNumber;
 | 
						|
 | 
						|
		result = bms_add_member(result, index);
 | 
						|
 | 
						|
		ReleaseSysCache(tuple);
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * fixup_inherited_columns
 | 
						|
 *
 | 
						|
 * When user is querying on a table with children, it implicitly accesses
 | 
						|
 * child tables also. So, we also need to check security label of child
 | 
						|
 * tables and columns, but here is no guarantee attribute numbers are
 | 
						|
 * same between the parent and children.
 | 
						|
 * It returns a bitmapset which contains attribute number of the child
 | 
						|
 * table based on the given bitmapset of the parent.
 | 
						|
 */
 | 
						|
static Bitmapset *
 | 
						|
fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns)
 | 
						|
{
 | 
						|
	Bitmapset  *result = NULL;
 | 
						|
	int			index;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * obviously, no need to do anything here
 | 
						|
	 */
 | 
						|
	if (parentId == childId)
 | 
						|
		return columns;
 | 
						|
 | 
						|
	index = -1;
 | 
						|
	while ((index = bms_next_member(columns, index)) >= 0)
 | 
						|
	{
 | 
						|
		/* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */
 | 
						|
		AttrNumber	attno = index + FirstLowInvalidHeapAttributeNumber;
 | 
						|
		char	   *attname;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * whole-row-reference shall be fixed-up later
 | 
						|
		 */
 | 
						|
		if (attno == InvalidAttrNumber)
 | 
						|
		{
 | 
						|
			result = bms_add_member(result, index);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		attname = get_attname(parentId, attno, false);
 | 
						|
		attno = get_attnum(childId, attname);
 | 
						|
		if (attno == InvalidAttrNumber)
 | 
						|
			elog(ERROR, "cache lookup failed for attribute %s of relation %u",
 | 
						|
				 attname, childId);
 | 
						|
 | 
						|
		result = bms_add_member(result,
 | 
						|
								attno - FirstLowInvalidHeapAttributeNumber);
 | 
						|
 | 
						|
		pfree(attname);
 | 
						|
	}
 | 
						|
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * check_relation_privileges
 | 
						|
 *
 | 
						|
 * It actually checks required permissions on a certain relation
 | 
						|
 * and its columns.
 | 
						|
 */
 | 
						|
static bool
 | 
						|
check_relation_privileges(Oid relOid,
 | 
						|
						  Bitmapset *selected,
 | 
						|
						  Bitmapset *inserted,
 | 
						|
						  Bitmapset *updated,
 | 
						|
						  uint32 required,
 | 
						|
						  bool abort_on_violation)
 | 
						|
{
 | 
						|
	ObjectAddress object;
 | 
						|
	char	   *audit_name;
 | 
						|
	Bitmapset  *columns;
 | 
						|
	int			index;
 | 
						|
	char		relkind = get_rel_relkind(relOid);
 | 
						|
	bool		result = true;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Hardwired Policies: SE-PostgreSQL enforces - clients cannot modify
 | 
						|
	 * system catalogs using DMLs - clients cannot reference/modify toast
 | 
						|
	 * relations using DMLs
 | 
						|
	 */
 | 
						|
	if (sepgsql_getenforce() > 0)
 | 
						|
	{
 | 
						|
		if ((required & (SEPG_DB_TABLE__UPDATE |
 | 
						|
						 SEPG_DB_TABLE__INSERT |
 | 
						|
						 SEPG_DB_TABLE__DELETE)) != 0 &&
 | 
						|
			IsCatalogRelationOid(relOid))
 | 
						|
			ereport(ERROR,
 | 
						|
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 | 
						|
					 errmsg("SELinux: hardwired security policy violation")));
 | 
						|
 | 
						|
		if (relkind == RELKIND_TOASTVALUE)
 | 
						|
			ereport(ERROR,
 | 
						|
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 | 
						|
					 errmsg("SELinux: hardwired security policy violation")));
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Check permissions on the relation
 | 
						|
	 */
 | 
						|
	object.classId = RelationRelationId;
 | 
						|
	object.objectId = relOid;
 | 
						|
	object.objectSubId = 0;
 | 
						|
	audit_name = getObjectIdentity(&object);
 | 
						|
	switch (relkind)
 | 
						|
	{
 | 
						|
		case RELKIND_RELATION:
 | 
						|
		case RELKIND_PARTITIONED_TABLE:
 | 
						|
			result = sepgsql_avc_check_perms(&object,
 | 
						|
											 SEPG_CLASS_DB_TABLE,
 | 
						|
											 required,
 | 
						|
											 audit_name,
 | 
						|
											 abort_on_violation);
 | 
						|
			break;
 | 
						|
 | 
						|
		case RELKIND_SEQUENCE:
 | 
						|
			Assert((required & ~SEPG_DB_TABLE__SELECT) == 0);
 | 
						|
 | 
						|
			if (required & SEPG_DB_TABLE__SELECT)
 | 
						|
				result = sepgsql_avc_check_perms(&object,
 | 
						|
												 SEPG_CLASS_DB_SEQUENCE,
 | 
						|
												 SEPG_DB_SEQUENCE__GET_VALUE,
 | 
						|
												 audit_name,
 | 
						|
												 abort_on_violation);
 | 
						|
			break;
 | 
						|
 | 
						|
		case RELKIND_VIEW:
 | 
						|
			result = sepgsql_avc_check_perms(&object,
 | 
						|
											 SEPG_CLASS_DB_VIEW,
 | 
						|
											 SEPG_DB_VIEW__EXPAND,
 | 
						|
											 audit_name,
 | 
						|
											 abort_on_violation);
 | 
						|
			break;
 | 
						|
 | 
						|
		default:
 | 
						|
			/* nothing to be checked */
 | 
						|
			break;
 | 
						|
	}
 | 
						|
	pfree(audit_name);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Only columns owned by relations shall be checked
 | 
						|
	 */
 | 
						|
	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 | 
						|
		return true;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Check permissions on the columns
 | 
						|
	 */
 | 
						|
	selected = fixup_whole_row_references(relOid, selected);
 | 
						|
	inserted = fixup_whole_row_references(relOid, inserted);
 | 
						|
	updated = fixup_whole_row_references(relOid, updated);
 | 
						|
	columns = bms_union(selected, bms_union(inserted, updated));
 | 
						|
 | 
						|
	while ((index = bms_first_member(columns)) >= 0)
 | 
						|
	{
 | 
						|
		AttrNumber	attnum;
 | 
						|
		uint32		column_perms = 0;
 | 
						|
 | 
						|
		if (bms_is_member(index, selected))
 | 
						|
			column_perms |= SEPG_DB_COLUMN__SELECT;
 | 
						|
		if (bms_is_member(index, inserted))
 | 
						|
		{
 | 
						|
			if (required & SEPG_DB_TABLE__INSERT)
 | 
						|
				column_perms |= SEPG_DB_COLUMN__INSERT;
 | 
						|
		}
 | 
						|
		if (bms_is_member(index, updated))
 | 
						|
		{
 | 
						|
			if (required & SEPG_DB_TABLE__UPDATE)
 | 
						|
				column_perms |= SEPG_DB_COLUMN__UPDATE;
 | 
						|
		}
 | 
						|
		if (column_perms == 0)
 | 
						|
			continue;
 | 
						|
 | 
						|
		/* obtain column's permission */
 | 
						|
		attnum = index + FirstLowInvalidHeapAttributeNumber;
 | 
						|
 | 
						|
		object.classId = RelationRelationId;
 | 
						|
		object.objectId = relOid;
 | 
						|
		object.objectSubId = attnum;
 | 
						|
		audit_name = getObjectDescription(&object);
 | 
						|
 | 
						|
		result = sepgsql_avc_check_perms(&object,
 | 
						|
										 SEPG_CLASS_DB_COLUMN,
 | 
						|
										 column_perms,
 | 
						|
										 audit_name,
 | 
						|
										 abort_on_violation);
 | 
						|
		pfree(audit_name);
 | 
						|
 | 
						|
		if (!result)
 | 
						|
			return result;
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * sepgsql_dml_privileges
 | 
						|
 *
 | 
						|
 * Entrypoint of the DML permission checks
 | 
						|
 */
 | 
						|
bool
 | 
						|
sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
 | 
						|
{
 | 
						|
	ListCell   *lr;
 | 
						|
 | 
						|
	foreach(lr, rangeTabls)
 | 
						|
	{
 | 
						|
		RangeTblEntry *rte = lfirst(lr);
 | 
						|
		uint32		required = 0;
 | 
						|
		List	   *tableIds;
 | 
						|
		ListCell   *li;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Only regular relations shall be checked
 | 
						|
		 */
 | 
						|
		if (rte->rtekind != RTE_RELATION)
 | 
						|
			continue;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Find out required permissions
 | 
						|
		 */
 | 
						|
		if (rte->requiredPerms & ACL_SELECT)
 | 
						|
			required |= SEPG_DB_TABLE__SELECT;
 | 
						|
		if (rte->requiredPerms & ACL_INSERT)
 | 
						|
			required |= SEPG_DB_TABLE__INSERT;
 | 
						|
		if (rte->requiredPerms & ACL_UPDATE)
 | 
						|
		{
 | 
						|
			if (!bms_is_empty(rte->updatedCols))
 | 
						|
				required |= SEPG_DB_TABLE__UPDATE;
 | 
						|
			else
 | 
						|
				required |= SEPG_DB_TABLE__LOCK;
 | 
						|
		}
 | 
						|
		if (rte->requiredPerms & ACL_DELETE)
 | 
						|
			required |= SEPG_DB_TABLE__DELETE;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Skip, if nothing to be checked
 | 
						|
		 */
 | 
						|
		if (required == 0)
 | 
						|
			continue;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * If this RangeTblEntry is also supposed to reference inherited
 | 
						|
		 * tables, we need to check security label of the child tables. So, we
 | 
						|
		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
 | 
						|
		 * checker routine will be invoked for each relations.
 | 
						|
		 */
 | 
						|
		if (!rte->inh)
 | 
						|
			tableIds = list_make1_oid(rte->relid);
 | 
						|
		else
 | 
						|
			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
 | 
						|
 | 
						|
		foreach(li, tableIds)
 | 
						|
		{
 | 
						|
			Oid			tableOid = lfirst_oid(li);
 | 
						|
			Bitmapset  *selectedCols;
 | 
						|
			Bitmapset  *insertedCols;
 | 
						|
			Bitmapset  *updatedCols;
 | 
						|
 | 
						|
			/*
 | 
						|
			 * child table has different attribute numbers, so we need to fix
 | 
						|
			 * up them.
 | 
						|
			 */
 | 
						|
			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
 | 
						|
												   rte->selectedCols);
 | 
						|
			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
 | 
						|
												   rte->insertedCols);
 | 
						|
			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
 | 
						|
												  rte->updatedCols);
 | 
						|
 | 
						|
			/*
 | 
						|
			 * check permissions on individual tables
 | 
						|
			 */
 | 
						|
			if (!check_relation_privileges(tableOid,
 | 
						|
										   selectedCols,
 | 
						|
										   insertedCols,
 | 
						|
										   updatedCols,
 | 
						|
										   required, abort_on_violation))
 | 
						|
				return false;
 | 
						|
		}
 | 
						|
		list_free(tableIds);
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
}
 |