mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-27 08:39:28 +08:00
ca3b37487b
Backpatch-through: 9.5
938 lines
23 KiB
C
938 lines
23 KiB
C
/* -------------------------------------------------------------------------
|
|
*
|
|
* contrib/sepgsql/label.c
|
|
*
|
|
* Routines to support SELinux labels (security context)
|
|
*
|
|
* Copyright (c) 2010-2021, PostgreSQL Global Development Group
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include <selinux/label.h>
|
|
|
|
#include "access/genam.h"
|
|
#include "access/htup_details.h"
|
|
#include "access/table.h"
|
|
#include "access/xact.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/dependency.h"
|
|
#include "catalog/pg_attribute.h"
|
|
#include "catalog/pg_class.h"
|
|
#include "catalog/pg_database.h"
|
|
#include "catalog/pg_namespace.h"
|
|
#include "catalog/pg_proc.h"
|
|
#include "commands/dbcommands.h"
|
|
#include "commands/seclabel.h"
|
|
#include "libpq/auth.h"
|
|
#include "libpq/libpq-be.h"
|
|
#include "miscadmin.h"
|
|
#include "sepgsql.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/rel.h"
|
|
|
|
/*
|
|
* Saved hook entries (if stacked)
|
|
*/
|
|
static ClientAuthentication_hook_type next_client_auth_hook = NULL;
|
|
static needs_fmgr_hook_type next_needs_fmgr_hook = NULL;
|
|
static fmgr_hook_type next_fmgr_hook = NULL;
|
|
|
|
/*
|
|
* client_label_*
|
|
*
|
|
* security label of the database client. Initially the client security label
|
|
* is equal to client_label_peer, and can be changed by one or more calls to
|
|
* sepgsql_setcon(), and also be temporarily overridden during execution of a
|
|
* trusted-procedure.
|
|
*
|
|
* sepgsql_setcon() is a transaction-aware operation; a (sub-)transaction
|
|
* rollback should also rollback the current client security label. Therefore
|
|
* we use the list client_label_pending of pending_label to keep track of which
|
|
* labels were set during the (sub-)transactions.
|
|
*/
|
|
static char *client_label_peer = NULL; /* set by getpeercon(3) */
|
|
static List *client_label_pending = NIL; /* pending list being set by
|
|
* sepgsql_setcon() */
|
|
static char *client_label_committed = NULL; /* set by sepgsql_setcon(), and
|
|
* already committed */
|
|
static char *client_label_func = NULL; /* set by trusted procedure */
|
|
|
|
typedef struct
|
|
{
|
|
SubTransactionId subid;
|
|
char *label;
|
|
} pending_label;
|
|
|
|
/*
|
|
* sepgsql_get_client_label
|
|
*
|
|
* Returns the current security label of the client. All code should use this
|
|
* routine to get the current label, instead of referring to the client_label_*
|
|
* variables above.
|
|
*/
|
|
char *
|
|
sepgsql_get_client_label(void)
|
|
{
|
|
/* trusted procedure client label override */
|
|
if (client_label_func)
|
|
return client_label_func;
|
|
|
|
/* uncommitted sepgsql_setcon() value */
|
|
if (client_label_pending)
|
|
{
|
|
pending_label *plabel = llast(client_label_pending);
|
|
|
|
if (plabel->label)
|
|
return plabel->label;
|
|
}
|
|
else if (client_label_committed)
|
|
return client_label_committed; /* set by sepgsql_setcon() committed */
|
|
|
|
/* default label */
|
|
Assert(client_label_peer != NULL);
|
|
return client_label_peer;
|
|
}
|
|
|
|
/*
|
|
* sepgsql_set_client_label
|
|
*
|
|
* This routine tries to switch the current security label of the client, and
|
|
* checks related permissions. The supplied new label shall be added to the
|
|
* client_label_pending list, then saved at transaction-commit time to ensure
|
|
* transaction-awareness.
|
|
*/
|
|
static void
|
|
sepgsql_set_client_label(const char *new_label)
|
|
{
|
|
const char *tcontext;
|
|
MemoryContext oldcxt;
|
|
pending_label *plabel;
|
|
|
|
/* Reset to the initial client label, if NULL */
|
|
if (!new_label)
|
|
tcontext = client_label_peer;
|
|
else
|
|
{
|
|
if (security_check_context_raw(new_label) < 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_NAME),
|
|
errmsg("SELinux: invalid security label: \"%s\"",
|
|
new_label)));
|
|
tcontext = new_label;
|
|
}
|
|
|
|
/* Check process:{setcurrent} permission. */
|
|
sepgsql_avc_check_perms_label(sepgsql_get_client_label(),
|
|
SEPG_CLASS_PROCESS,
|
|
SEPG_PROCESS__SETCURRENT,
|
|
NULL,
|
|
true);
|
|
/* Check process:{dyntransition} permission. */
|
|
sepgsql_avc_check_perms_label(tcontext,
|
|
SEPG_CLASS_PROCESS,
|
|
SEPG_PROCESS__DYNTRANSITION,
|
|
NULL,
|
|
true);
|
|
|
|
/*
|
|
* Append the supplied new_label on the pending list until the current
|
|
* transaction is committed.
|
|
*/
|
|
oldcxt = MemoryContextSwitchTo(CurTransactionContext);
|
|
|
|
plabel = palloc0(sizeof(pending_label));
|
|
plabel->subid = GetCurrentSubTransactionId();
|
|
if (new_label)
|
|
plabel->label = pstrdup(new_label);
|
|
client_label_pending = lappend(client_label_pending, plabel);
|
|
|
|
MemoryContextSwitchTo(oldcxt);
|
|
}
|
|
|
|
/*
|
|
* sepgsql_xact_callback
|
|
*
|
|
* A callback routine of transaction commit/abort/prepare. Commit or abort
|
|
* changes in the client_label_pending list.
|
|
*/
|
|
static void
|
|
sepgsql_xact_callback(XactEvent event, void *arg)
|
|
{
|
|
if (event == XACT_EVENT_COMMIT)
|
|
{
|
|
if (client_label_pending != NIL)
|
|
{
|
|
pending_label *plabel = llast(client_label_pending);
|
|
char *new_label;
|
|
|
|
if (plabel->label)
|
|
new_label = MemoryContextStrdup(TopMemoryContext,
|
|
plabel->label);
|
|
else
|
|
new_label = NULL;
|
|
|
|
if (client_label_committed)
|
|
pfree(client_label_committed);
|
|
|
|
client_label_committed = new_label;
|
|
|
|
/*
|
|
* XXX - Note that items of client_label_pending are allocated on
|
|
* CurTransactionContext, thus, all acquired memory region shall
|
|
* be released implicitly.
|
|
*/
|
|
client_label_pending = NIL;
|
|
}
|
|
}
|
|
else if (event == XACT_EVENT_ABORT)
|
|
client_label_pending = NIL;
|
|
}
|
|
|
|
/*
|
|
* sepgsql_subxact_callback
|
|
*
|
|
* A callback routine of sub-transaction start/abort/commit. Releases all
|
|
* security labels that are set within the sub-transaction that is aborted.
|
|
*/
|
|
static void
|
|
sepgsql_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
|
|
SubTransactionId parentSubid, void *arg)
|
|
{
|
|
ListCell *cell;
|
|
|
|
if (event == SUBXACT_EVENT_ABORT_SUB)
|
|
{
|
|
foreach(cell, client_label_pending)
|
|
{
|
|
pending_label *plabel = lfirst(cell);
|
|
|
|
if (plabel->subid == mySubid)
|
|
client_label_pending
|
|
= foreach_delete_current(client_label_pending, cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* sepgsql_client_auth
|
|
*
|
|
* Entrypoint of the client authentication hook.
|
|
* It switches the client label according to getpeercon(), and the current
|
|
* performing mode according to the GUC setting.
|
|
*/
|
|
static void
|
|
sepgsql_client_auth(Port *port, int status)
|
|
{
|
|
if (next_client_auth_hook)
|
|
(*next_client_auth_hook) (port, status);
|
|
|
|
/*
|
|
* In the case when authentication failed, the supplied socket shall be
|
|
* closed soon, so we don't need to do anything here.
|
|
*/
|
|
if (status != STATUS_OK)
|
|
return;
|
|
|
|
/*
|
|
* Getting security label of the peer process using API of libselinux.
|
|
*/
|
|
if (getpeercon_raw(port->sock, &client_label_peer) < 0)
|
|
ereport(FATAL,
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
|
errmsg("SELinux: unable to get peer label: %m")));
|
|
|
|
/*
|
|
* Switch the current performing mode from INTERNAL to either DEFAULT or
|
|
* PERMISSIVE.
|
|
*/
|
|
if (sepgsql_get_permissive())
|
|
sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE);
|
|
else
|
|
sepgsql_set_mode(SEPGSQL_MODE_DEFAULT);
|
|
}
|
|
|
|
/*
|
|
* sepgsql_needs_fmgr_hook
|
|
*
|
|
* It informs the core whether the supplied function is trusted procedure,
|
|
* or not. If true, sepgsql_fmgr_hook shall be invoked at start, end, and
|
|
* abort time of function invocation.
|
|
*/
|
|
static bool
|
|
sepgsql_needs_fmgr_hook(Oid functionId)
|
|
{
|
|
ObjectAddress object;
|
|
|
|
if (next_needs_fmgr_hook &&
|
|
(*next_needs_fmgr_hook) (functionId))
|
|
return true;
|
|
|
|
/*
|
|
* SELinux needs the function to be called via security_definer wrapper,
|
|
* if this invocation will take a domain-transition. We call these
|
|
* functions as trusted-procedure, if the security policy has a rule that
|
|
* switches security label of the client on execution.
|
|
*/
|
|
if (sepgsql_avc_trusted_proc(functionId) != NULL)
|
|
return true;
|
|
|
|
/*
|
|
* Even if not a trusted-procedure, this function should not be inlined
|
|
* unless the client has db_procedure:{execute} permission. Please note
|
|
* that it shall be actually failed later because of same reason with
|
|
* ACL_EXECUTE.
|
|
*/
|
|
object.classId = ProcedureRelationId;
|
|
object.objectId = functionId;
|
|
object.objectSubId = 0;
|
|
if (!sepgsql_avc_check_perms(&object,
|
|
SEPG_CLASS_DB_PROCEDURE,
|
|
SEPG_DB_PROCEDURE__EXECUTE |
|
|
SEPG_DB_PROCEDURE__ENTRYPOINT,
|
|
SEPGSQL_AVC_NOAUDIT, false))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* sepgsql_fmgr_hook
|
|
*
|
|
* It switches security label of the client on execution of trusted
|
|
* procedures.
|
|
*/
|
|
static void
|
|
sepgsql_fmgr_hook(FmgrHookEventType event,
|
|
FmgrInfo *flinfo, Datum *private)
|
|
{
|
|
struct
|
|
{
|
|
char *old_label;
|
|
char *new_label;
|
|
Datum next_private;
|
|
} *stack;
|
|
|
|
switch (event)
|
|
{
|
|
case FHET_START:
|
|
stack = (void *) DatumGetPointer(*private);
|
|
if (!stack)
|
|
{
|
|
MemoryContext oldcxt;
|
|
|
|
oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt);
|
|
stack = palloc(sizeof(*stack));
|
|
stack->old_label = NULL;
|
|
stack->new_label = sepgsql_avc_trusted_proc(flinfo->fn_oid);
|
|
stack->next_private = 0;
|
|
|
|
MemoryContextSwitchTo(oldcxt);
|
|
|
|
/*
|
|
* process:transition permission between old and new label,
|
|
* when user tries to switch security label of the client on
|
|
* execution of trusted procedure.
|
|
*
|
|
* Also, db_procedure:entrypoint permission should be checked
|
|
* whether this procedure can perform as an entrypoint of the
|
|
* trusted procedure, or not. Note that db_procedure:execute
|
|
* permission shall be checked individually.
|
|
*/
|
|
if (stack->new_label)
|
|
{
|
|
ObjectAddress object;
|
|
|
|
object.classId = ProcedureRelationId;
|
|
object.objectId = flinfo->fn_oid;
|
|
object.objectSubId = 0;
|
|
sepgsql_avc_check_perms(&object,
|
|
SEPG_CLASS_DB_PROCEDURE,
|
|
SEPG_DB_PROCEDURE__ENTRYPOINT,
|
|
getObjectDescription(&object, false),
|
|
true);
|
|
|
|
sepgsql_avc_check_perms_label(stack->new_label,
|
|
SEPG_CLASS_PROCESS,
|
|
SEPG_PROCESS__TRANSITION,
|
|
NULL, true);
|
|
}
|
|
*private = PointerGetDatum(stack);
|
|
}
|
|
Assert(!stack->old_label);
|
|
if (stack->new_label)
|
|
{
|
|
stack->old_label = client_label_func;
|
|
client_label_func = stack->new_label;
|
|
}
|
|
if (next_fmgr_hook)
|
|
(*next_fmgr_hook) (event, flinfo, &stack->next_private);
|
|
break;
|
|
|
|
case FHET_END:
|
|
case FHET_ABORT:
|
|
stack = (void *) DatumGetPointer(*private);
|
|
|
|
if (next_fmgr_hook)
|
|
(*next_fmgr_hook) (event, flinfo, &stack->next_private);
|
|
|
|
if (stack->new_label)
|
|
{
|
|
client_label_func = stack->old_label;
|
|
stack->old_label = NULL;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
elog(ERROR, "unexpected event type: %d", (int) event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* sepgsql_init_client_label
|
|
*
|
|
* Initializes the client security label and sets up related hooks for client
|
|
* label management.
|
|
*/
|
|
void
|
|
sepgsql_init_client_label(void)
|
|
{
|
|
/*
|
|
* Set up dummy client label.
|
|
*
|
|
* XXX - note that PostgreSQL launches background worker process like
|
|
* autovacuum without authentication steps. So, we initialize sepgsql_mode
|
|
* with SEPGSQL_MODE_INTERNAL, and client_label with the security context
|
|
* of server process. Later, it also launches background of user session.
|
|
* In this case, the process is always hooked on post-authentication, and
|
|
* we can initialize the sepgsql_mode and client_label correctly.
|
|
*/
|
|
if (getcon_raw(&client_label_peer) < 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
|
errmsg("SELinux: failed to get server security label: %m")));
|
|
|
|
/* Client authentication hook */
|
|
next_client_auth_hook = ClientAuthentication_hook;
|
|
ClientAuthentication_hook = sepgsql_client_auth;
|
|
|
|
/* Trusted procedure hooks */
|
|
next_needs_fmgr_hook = needs_fmgr_hook;
|
|
needs_fmgr_hook = sepgsql_needs_fmgr_hook;
|
|
|
|
next_fmgr_hook = fmgr_hook;
|
|
fmgr_hook = sepgsql_fmgr_hook;
|
|
|
|
/* Transaction/Sub-transaction callbacks */
|
|
RegisterXactCallback(sepgsql_xact_callback, NULL);
|
|
RegisterSubXactCallback(sepgsql_subxact_callback, NULL);
|
|
}
|
|
|
|
/*
|
|
* 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(label))
|
|
{
|
|
char *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_FINALLY();
|
|
{
|
|
freecon(unlabeled);
|
|
}
|
|
PG_END_TRY();
|
|
}
|
|
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(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 DatabaseRelationId:
|
|
sepgsql_database_relabel(object->objectId, seclabel);
|
|
break;
|
|
|
|
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:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("sepgsql provider does not support labels on %s",
|
|
getObjectTypeDescription(object, false))));
|
|
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));
|
|
}
|
|
|
|
/*
|
|
* BOOL sepgsql_setcon(TEXT)
|
|
*
|
|
* It switches the security label of the client.
|
|
*/
|
|
PG_FUNCTION_INFO_V1(sepgsql_setcon);
|
|
Datum
|
|
sepgsql_setcon(PG_FUNCTION_ARGS)
|
|
{
|
|
const char *new_label;
|
|
|
|
if (PG_ARGISNULL(0))
|
|
new_label = NULL;
|
|
else
|
|
new_label = TextDatumGetCString(PG_GETARG_DATUM(0));
|
|
|
|
sepgsql_set_client_label(new_label);
|
|
|
|
PG_RETURN_BOOL(true);
|
|
}
|
|
|
|
/*
|
|
* 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_PP(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_FINALLY();
|
|
{
|
|
freecon(raw_label);
|
|
}
|
|
PG_END_TRY();
|
|
|
|
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_PP(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_FINALLY();
|
|
{
|
|
freecon(qual_label);
|
|
}
|
|
PG_END_TRY();
|
|
|
|
PG_RETURN_TEXT_P(cstring_to_text(result));
|
|
}
|
|
|
|
/*
|
|
* quote_object_name
|
|
*
|
|
* 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);
|
|
appendStringInfoString(&result, 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 = table_open(catalogId, AccessShareLock);
|
|
|
|
sscan = systable_beginscan(rel, InvalidOid, false,
|
|
NULL, 0, NULL);
|
|
while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
|
|
{
|
|
Form_pg_database datForm;
|
|
Form_pg_namespace nspForm;
|
|
Form_pg_class relForm;
|
|
Form_pg_attribute attForm;
|
|
Form_pg_proc proForm;
|
|
char *objname;
|
|
int objtype = 1234;
|
|
ObjectAddress object;
|
|
char *context;
|
|
|
|
/*
|
|
* The way to determine object name depends on object classes. So, any
|
|
* branches set up `objtype', `objname' and `object' here.
|
|
*/
|
|
switch (catalogId)
|
|
{
|
|
case DatabaseRelationId:
|
|
datForm = (Form_pg_database) GETSTRUCT(tuple);
|
|
|
|
objtype = SELABEL_DB_DATABASE;
|
|
|
|
objname = quote_object_name(NameStr(datForm->datname),
|
|
NULL, NULL, NULL);
|
|
|
|
object.classId = DatabaseRelationId;
|
|
object.objectId = datForm->oid;
|
|
object.objectSubId = 0;
|
|
break;
|
|
|
|
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 = nspForm->oid;
|
|
object.objectSubId = 0;
|
|
break;
|
|
|
|
case RelationRelationId:
|
|
relForm = (Form_pg_class) GETSTRUCT(tuple);
|
|
|
|
if (relForm->relkind == RELKIND_RELATION ||
|
|
relForm->relkind == RELKIND_PARTITIONED_TABLE)
|
|
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 = relForm->oid;
|
|
object.objectSubId = 0;
|
|
break;
|
|
|
|
case AttributeRelationId:
|
|
attForm = (Form_pg_attribute) GETSTRUCT(tuple);
|
|
|
|
if (get_rel_relkind(attForm->attrelid) != RELKIND_RELATION &&
|
|
get_rel_relkind(attForm->attrelid) != RELKIND_PARTITIONED_TABLE)
|
|
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 = proForm->oid;
|
|
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_FINALLY();
|
|
{
|
|
freecon(context);
|
|
}
|
|
PG_END_TRY();
|
|
}
|
|
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);
|
|
|
|
table_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();
|
|
{
|
|
exec_object_restorecon(sehnd, DatabaseRelationId);
|
|
exec_object_restorecon(sehnd, NamespaceRelationId);
|
|
exec_object_restorecon(sehnd, RelationRelationId);
|
|
exec_object_restorecon(sehnd, AttributeRelationId);
|
|
exec_object_restorecon(sehnd, ProcedureRelationId);
|
|
}
|
|
PG_FINALLY();
|
|
{
|
|
selabel_close(sehnd);
|
|
}
|
|
PG_END_TRY();
|
|
|
|
PG_RETURN_BOOL(true);
|
|
}
|