postgresql/contrib/sepgsql/label.c

529 lines
12 KiB
C

/* -------------------------------------------------------------------------
*
* contrib/sepgsql/label.c
*
* Routines to support SELinux labels (security context)
*
* Copyright (c) 2010-2011, PostgreSQL Global Development Group
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "access/genam.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_attribute.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "commands/dbcommands.h"
#include "commands/seclabel.h"
#include "libpq/libpq-be.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/tqual.h"
#include "sepgsql.h"
#include <selinux/label.h>
/*
* client_label
*
* security label of the client process
*/
static char *client_label = NULL;
char *
sepgsql_get_client_label(void)
{
return client_label;
}
char *
sepgsql_set_client_label(char *new_label)
{
char *old_label = client_label;
client_label = new_label;
return old_label;
}
/*
* sepgsql_get_label
*
* It returns a security context of the specified database object.
* If unlabeled or incorrectly labeled, the system "unlabeled" label
* shall be returned.
*/
char *
sepgsql_get_label(Oid classId, Oid objectId, int32 subId)
{
ObjectAddress object;
char *label;
object.classId = classId;
object.objectId = objectId;
object.objectSubId = subId;
label = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG);
if (!label || security_check_context_raw((security_context_t) label))
{
security_context_t unlabeled;
if (security_get_initial_context_raw("unlabeled", &unlabeled) < 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("SELinux: failed to get initial security label: %m")));
PG_TRY();
{
label = pstrdup(unlabeled);
}
PG_CATCH();
{
freecon(unlabeled);
PG_RE_THROW();
}
PG_END_TRY();
freecon(unlabeled);
}
return label;
}
/*
* sepgsql_object_relabel
*
* An entrypoint of SECURITY LABEL statement
*/
void
sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
{
/*
* validate format of the supplied security label, if it is security
* context of selinux.
*/
if (seclabel &&
security_check_context_raw((security_context_t) seclabel) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
errmsg("SELinux: invalid security label: \"%s\"", seclabel)));
/*
* Do actual permission checks for each object classes
*/
switch (object->classId)
{
case NamespaceRelationId:
sepgsql_schema_relabel(object->objectId, seclabel);
break;
case RelationRelationId:
if (object->objectSubId == 0)
sepgsql_relation_relabel(object->objectId,
seclabel);
else
sepgsql_attribute_relabel(object->objectId,
object->objectSubId,
seclabel);
break;
case ProcedureRelationId:
sepgsql_proc_relabel(object->objectId, seclabel);
break;
default:
elog(ERROR, "unsupported object type: %u", object->classId);
break;
}
}
/*
* TEXT sepgsql_getcon(VOID)
*
* It returns the security label of the client.
*/
PG_FUNCTION_INFO_V1(sepgsql_getcon);
Datum
sepgsql_getcon(PG_FUNCTION_ARGS)
{
char *client_label;
if (!sepgsql_is_enabled())
PG_RETURN_NULL();
client_label = sepgsql_get_client_label();
PG_RETURN_TEXT_P(cstring_to_text(client_label));
}
/*
* TEXT sepgsql_mcstrans_in(TEXT)
*
* It translate the given qualified MLS/MCS range into raw format
* when mcstrans daemon is working.
*/
PG_FUNCTION_INFO_V1(sepgsql_mcstrans_in);
Datum
sepgsql_mcstrans_in(PG_FUNCTION_ARGS)
{
text *label = PG_GETARG_TEXT_P(0);
char *raw_label;
char *result;
if (!sepgsql_is_enabled())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("sepgsql is not enabled")));
if (selinux_trans_to_raw_context(text_to_cstring(label),
&raw_label) < 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("SELinux: could not translate security label: %m")));
PG_TRY();
{
result = pstrdup(raw_label);
}
PG_CATCH();
{
freecon(raw_label);
PG_RE_THROW();
}
PG_END_TRY();
freecon(raw_label);
PG_RETURN_TEXT_P(cstring_to_text(result));
}
/*
* TEXT sepgsql_mcstrans_out(TEXT)
*
* It translate the given raw MLS/MCS range into qualified format
* when mcstrans daemon is working.
*/
PG_FUNCTION_INFO_V1(sepgsql_mcstrans_out);
Datum
sepgsql_mcstrans_out(PG_FUNCTION_ARGS)
{
text *label = PG_GETARG_TEXT_P(0);
char *qual_label;
char *result;
if (!sepgsql_is_enabled())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("sepgsql is not currently enabled")));
if (selinux_raw_to_trans_context(text_to_cstring(label),
&qual_label) < 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("SELinux: could not translate security label: %m")));
PG_TRY();
{
result = pstrdup(qual_label);
}
PG_CATCH();
{
freecon(qual_label);
PG_RE_THROW();
}
PG_END_TRY();
freecon(qual_label);
PG_RETURN_TEXT_P(cstring_to_text(result));
}
/*
* quote_object_names
*
* It tries to quote the supplied identifiers
*/
static char *
quote_object_name(const char *src1, const char *src2,
const char *src3, const char *src4)
{
StringInfoData result;
const char *temp;
initStringInfo(&result);
if (src1)
{
temp = quote_identifier(src1);
appendStringInfo(&result, "%s", temp);
if (src1 != temp)
pfree((void *) temp);
}
if (src2)
{
temp = quote_identifier(src2);
appendStringInfo(&result, ".%s", temp);
if (src2 != temp)
pfree((void *) temp);
}
if (src3)
{
temp = quote_identifier(src3);
appendStringInfo(&result, ".%s", temp);
if (src3 != temp)
pfree((void *) temp);
}
if (src4)
{
temp = quote_identifier(src4);
appendStringInfo(&result, ".%s", temp);
if (src4 != temp)
pfree((void *) temp);
}
return result.data;
}
/*
* exec_object_restorecon
*
* This routine is a helper called by sepgsql_restorecon; it set up
* initial security labels of database objects within the supplied
* catalog OID.
*/
static void
exec_object_restorecon(struct selabel_handle * sehnd, Oid catalogId)
{
Relation rel;
SysScanDesc sscan;
HeapTuple tuple;
char *database_name = get_database_name(MyDatabaseId);
char *namespace_name;
Oid namespace_id;
char *relation_name;
/*
* Open the target catalog. We don't want to allow writable accesses by
* other session during initial labeling.
*/
rel = heap_open(catalogId, AccessShareLock);
sscan = systable_beginscan(rel, InvalidOid, false,
SnapshotNow, 0, NULL);
while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
{
Form_pg_namespace nspForm;
Form_pg_class relForm;
Form_pg_attribute attForm;
Form_pg_proc proForm;
char *objname;
int objtype = 1234;
ObjectAddress object;
security_context_t context;
/*
* The way to determine object name depends on object classes. So, any
* branches set up `objtype', `objname' and `object' here.
*/
switch (catalogId)
{
case NamespaceRelationId:
nspForm = (Form_pg_namespace) GETSTRUCT(tuple);
objtype = SELABEL_DB_SCHEMA;
objname = quote_object_name(database_name,
NameStr(nspForm->nspname),
NULL, NULL);
object.classId = NamespaceRelationId;
object.objectId = HeapTupleGetOid(tuple);
object.objectSubId = 0;
break;
case RelationRelationId:
relForm = (Form_pg_class) GETSTRUCT(tuple);
if (relForm->relkind == RELKIND_RELATION)
objtype = SELABEL_DB_TABLE;
else if (relForm->relkind == RELKIND_SEQUENCE)
objtype = SELABEL_DB_SEQUENCE;
else if (relForm->relkind == RELKIND_VIEW)
objtype = SELABEL_DB_VIEW;
else
continue; /* no need to assign security label */
namespace_name = get_namespace_name(relForm->relnamespace);
objname = quote_object_name(database_name,
namespace_name,
NameStr(relForm->relname),
NULL);
pfree(namespace_name);
object.classId = RelationRelationId;
object.objectId = HeapTupleGetOid(tuple);
object.objectSubId = 0;
break;
case AttributeRelationId:
attForm = (Form_pg_attribute) GETSTRUCT(tuple);
if (get_rel_relkind(attForm->attrelid) != RELKIND_RELATION)
continue; /* no need to assign security label */
objtype = SELABEL_DB_COLUMN;
namespace_id = get_rel_namespace(attForm->attrelid);
namespace_name = get_namespace_name(namespace_id);
relation_name = get_rel_name(attForm->attrelid);
objname = quote_object_name(database_name,
namespace_name,
relation_name,
NameStr(attForm->attname));
pfree(namespace_name);
pfree(relation_name);
object.classId = RelationRelationId;
object.objectId = attForm->attrelid;
object.objectSubId = attForm->attnum;
break;
case ProcedureRelationId:
proForm = (Form_pg_proc) GETSTRUCT(tuple);
objtype = SELABEL_DB_PROCEDURE;
namespace_name = get_namespace_name(proForm->pronamespace);
objname = quote_object_name(database_name,
namespace_name,
NameStr(proForm->proname),
NULL);
pfree(namespace_name);
object.classId = ProcedureRelationId;
object.objectId = HeapTupleGetOid(tuple);
object.objectSubId = 0;
break;
default:
elog(ERROR, "unexpected catalog id: %u", catalogId);
objname = NULL; /* for compiler quiet */
break;
}
if (selabel_lookup_raw(sehnd, &context, objname, objtype) == 0)
{
PG_TRY();
{
/*
* Check SELinux permission to relabel the fetched object,
* then do the actual relabeling.
*/
sepgsql_object_relabel(&object, context);
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, context);
}
PG_CATCH();
{
freecon(context);
PG_RE_THROW();
}
PG_END_TRY();
freecon(context);
}
else if (errno == ENOENT)
ereport(WARNING,
(errmsg("SELinux: no initial label assigned for %s (type=%d), skipping",
objname, objtype)));
else
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("SELinux: could not determine initial security label for %s (type=%d): %m", objname, objtype)));
pfree(objname);
}
systable_endscan(sscan);
heap_close(rel, NoLock);
}
/*
* BOOL sepgsql_restorecon(TEXT specfile)
*
* This function tries to assign initial security labels on all the object
* within the current database, according to the system setting.
* It is typically invoked by sepgsql-install script just after initdb, to
* assign initial security labels.
*
* If @specfile is not NULL, it uses explicitly specified specfile, instead
* of the system default.
*/
PG_FUNCTION_INFO_V1(sepgsql_restorecon);
Datum
sepgsql_restorecon(PG_FUNCTION_ARGS)
{
struct selabel_handle *sehnd;
struct selinux_opt seopts;
/*
* SELinux has to be enabled on the running platform.
*/
if (!sepgsql_is_enabled())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("sepgsql is not currently enabled")));
/*
* Check DAC permission. Only superuser can set up initial security
* labels, like root-user in filesystems
*/
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("SELinux: must be superuser to restore initial contexts")));
/*
* Open selabel_lookup(3) stuff. It provides a set of mapping between an
* initial security label and object class/name due to the system setting.
*/
if (PG_ARGISNULL(0))
{
seopts.type = SELABEL_OPT_UNUSED;
seopts.value = NULL;
}
else
{
seopts.type = SELABEL_OPT_PATH;
seopts.value = TextDatumGetCString(PG_GETARG_DATUM(0));
}
sehnd = selabel_open(SELABEL_CTX_DB, &seopts, 1);
if (!sehnd)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("SELinux: failed to initialize labeling handle: %m")));
PG_TRY();
{
/*
* Right now, we have no support labeling on the shared database
* objects, such as database, role, or tablespace.
*/
exec_object_restorecon(sehnd, NamespaceRelationId);
exec_object_restorecon(sehnd, RelationRelationId);
exec_object_restorecon(sehnd, AttributeRelationId);
exec_object_restorecon(sehnd, ProcedureRelationId);
}
PG_CATCH();
{
selabel_close(sehnd);
PG_RE_THROW();
}
PG_END_TRY();
selabel_close(sehnd);
PG_RETURN_BOOL(true);
}