/* -------------------------------------------------------------------------
*
* contrib / sepgsql / selinux . c
*
* Interactions between userspace and selinux in kernelspace ,
* using libselinux api .
*
* Copyright ( c ) 2010 - 2025 , PostgreSQL Global Development Group
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#include "lib/stringinfo.h"
#include "sepgsql.h"
/*
* selinux_catalog
*
* This mapping table enables to translate the name of object classes and
* access vectors to / from their own codes .
* When we ask SELinux whether the required privileges are allowed or not ,
* we use security_compute_av ( 3 ) . It needs us to represent object classes
* and access vectors using ' external ' codes defined in the security policy .
* It is determined in the runtime , not build time . So , it needs an internal
* service to translate object class / access vectors which we want to check
* into the code which kernel want to be given .
*/
static struct
{
const char *class_name;
uint16 class_code;
struct
{
const char *av_name;
uint32 av_code;
} av[32 ];
} selinux_catalog[] =
{
{
"process" , SEPG_CLASS_PROCESS,
{
{
"transition" , SEPG_PROCESS__TRANSITION
},
{
"dyntransition" , SEPG_PROCESS__DYNTRANSITION
},
{
"setcurrent" , SEPG_PROCESS__SETCURRENT
},
{
NULL, 0 UL
}
}
},
{
"file" , SEPG_CLASS_FILE,
{
{
"read" , SEPG_FILE__READ
},
{
"write" , SEPG_FILE__WRITE
},
{
"create" , SEPG_FILE__CREATE
},
{
"getattr" , SEPG_FILE__GETATTR
},
{
"unlink" , SEPG_FILE__UNLINK
},
{
"rename" , SEPG_FILE__RENAME
},
{
"append" , SEPG_FILE__APPEND
},
{
NULL, 0 UL
}
}
},
{
"dir" , SEPG_CLASS_DIR,
{
{
"read" , SEPG_DIR__READ
},
{
"write" , SEPG_DIR__WRITE
},
{
"create" , SEPG_DIR__CREATE
},
{
"getattr" , SEPG_DIR__GETATTR
},
{
"unlink" , SEPG_DIR__UNLINK
},
{
"rename" , SEPG_DIR__RENAME
},
{
"search" , SEPG_DIR__SEARCH
},
{
"add_name" , SEPG_DIR__ADD_NAME
},
{
"remove_name" , SEPG_DIR__REMOVE_NAME
},
{
"rmdir" , SEPG_DIR__RMDIR
},
{
"reparent" , SEPG_DIR__REPARENT
},
{
NULL, 0 UL
}
}
},
{
"lnk_file" , SEPG_CLASS_LNK_FILE,
{
{
"read" , SEPG_LNK_FILE__READ
},
{
"write" , SEPG_LNK_FILE__WRITE
},
{
"create" , SEPG_LNK_FILE__CREATE
},
{
"getattr" , SEPG_LNK_FILE__GETATTR
},
{
"unlink" , SEPG_LNK_FILE__UNLINK
},
{
"rename" , SEPG_LNK_FILE__RENAME
},
{
NULL, 0 UL
}
}
},
{
"chr_file" , SEPG_CLASS_CHR_FILE,
{
{
"read" , SEPG_CHR_FILE__READ
},
{
"write" , SEPG_CHR_FILE__WRITE
},
{
"create" , SEPG_CHR_FILE__CREATE
},
{
"getattr" , SEPG_CHR_FILE__GETATTR
},
{
"unlink" , SEPG_CHR_FILE__UNLINK
},
{
"rename" , SEPG_CHR_FILE__RENAME
},
{
NULL, 0 UL
}
}
},
{
"blk_file" , SEPG_CLASS_BLK_FILE,
{
{
"read" , SEPG_BLK_FILE__READ
},
{
"write" , SEPG_BLK_FILE__WRITE
},
{
"create" , SEPG_BLK_FILE__CREATE
},
{
"getattr" , SEPG_BLK_FILE__GETATTR
},
{
"unlink" , SEPG_BLK_FILE__UNLINK
},
{
"rename" , SEPG_BLK_FILE__RENAME
},
{
NULL, 0 UL
}
}
},
{
"sock_file" , SEPG_CLASS_SOCK_FILE,
{
{
"read" , SEPG_SOCK_FILE__READ
},
{
"write" , SEPG_SOCK_FILE__WRITE
},
{
"create" , SEPG_SOCK_FILE__CREATE
},
{
"getattr" , SEPG_SOCK_FILE__GETATTR
},
{
"unlink" , SEPG_SOCK_FILE__UNLINK
},
{
"rename" , SEPG_SOCK_FILE__RENAME
},
{
NULL, 0 UL
}
}
},
{
"fifo_file" , SEPG_CLASS_FIFO_FILE,
{
{
"read" , SEPG_FIFO_FILE__READ
},
{
"write" , SEPG_FIFO_FILE__WRITE
},
{
"create" , SEPG_FIFO_FILE__CREATE
},
{
"getattr" , SEPG_FIFO_FILE__GETATTR
},
{
"unlink" , SEPG_FIFO_FILE__UNLINK
},
{
"rename" , SEPG_FIFO_FILE__RENAME
},
{
NULL, 0 UL
}
}
},
{
"db_database" , SEPG_CLASS_DB_DATABASE,
{
{
"create" , SEPG_DB_DATABASE__CREATE
},
{
"drop" , SEPG_DB_DATABASE__DROP
},
{
"getattr" , SEPG_DB_DATABASE__GETATTR
},
{
"setattr" , SEPG_DB_DATABASE__SETATTR
},
{
"relabelfrom" , SEPG_DB_DATABASE__RELABELFROM
},
{
"relabelto" , SEPG_DB_DATABASE__RELABELTO
},
{
"access" , SEPG_DB_DATABASE__ACCESS
},
{
"load_module" , SEPG_DB_DATABASE__LOAD_MODULE
},
{
NULL, 0 UL
},
}
},
{
"db_schema" , SEPG_CLASS_DB_SCHEMA,
{
{
"create" , SEPG_DB_SCHEMA__CREATE
},
{
"drop" , SEPG_DB_SCHEMA__DROP
},
{
"getattr" , SEPG_DB_SCHEMA__GETATTR
},
{
"setattr" , SEPG_DB_SCHEMA__SETATTR
},
{
"relabelfrom" , SEPG_DB_SCHEMA__RELABELFROM
},
{
"relabelto" , SEPG_DB_SCHEMA__RELABELTO
},
{
"search" , SEPG_DB_SCHEMA__SEARCH
},
{
"add_name" , SEPG_DB_SCHEMA__ADD_NAME
},
{
"remove_name" , SEPG_DB_SCHEMA__REMOVE_NAME
},
{
NULL, 0 UL
},
}
},
{
"db_table" , SEPG_CLASS_DB_TABLE,
{
{
"create" , SEPG_DB_TABLE__CREATE
},
{
"drop" , SEPG_DB_TABLE__DROP
},
{
"getattr" , SEPG_DB_TABLE__GETATTR
},
{
"setattr" , SEPG_DB_TABLE__SETATTR
},
{
"relabelfrom" , SEPG_DB_TABLE__RELABELFROM
},
{
"relabelto" , SEPG_DB_TABLE__RELABELTO
},
{
"select" , SEPG_DB_TABLE__SELECT
},
{
"update" , SEPG_DB_TABLE__UPDATE
},
{
"insert" , SEPG_DB_TABLE__INSERT
},
{
"delete" , SEPG_DB_TABLE__DELETE
},
{
"lock" , SEPG_DB_TABLE__LOCK
},
{
"truncate" , SEPG_DB_TABLE__TRUNCATE
},
{
NULL, 0 UL
},
}
},
{
"db_sequence" , SEPG_CLASS_DB_SEQUENCE,
{
{
"create" , SEPG_DB_SEQUENCE__CREATE
},
{
"drop" , SEPG_DB_SEQUENCE__DROP
},
{
"getattr" , SEPG_DB_SEQUENCE__GETATTR
},
{
"setattr" , SEPG_DB_SEQUENCE__SETATTR
},
{
"relabelfrom" , SEPG_DB_SEQUENCE__RELABELFROM
},
{
"relabelto" , SEPG_DB_SEQUENCE__RELABELTO
},
{
"get_value" , SEPG_DB_SEQUENCE__GET_VALUE
},
{
"next_value" , SEPG_DB_SEQUENCE__NEXT_VALUE
},
{
"set_value" , SEPG_DB_SEQUENCE__SET_VALUE
},
{
NULL, 0 UL
},
}
},
{
"db_procedure" , SEPG_CLASS_DB_PROCEDURE,
{
{
"create" , SEPG_DB_PROCEDURE__CREATE
},
{
"drop" , SEPG_DB_PROCEDURE__DROP
},
{
"getattr" , SEPG_DB_PROCEDURE__GETATTR
},
{
"setattr" , SEPG_DB_PROCEDURE__SETATTR
},
{
"relabelfrom" , SEPG_DB_PROCEDURE__RELABELFROM
},
{
"relabelto" , SEPG_DB_PROCEDURE__RELABELTO
},
{
"execute" , SEPG_DB_PROCEDURE__EXECUTE
},
{
"entrypoint" , SEPG_DB_PROCEDURE__ENTRYPOINT
},
{
"install" , SEPG_DB_PROCEDURE__INSTALL
},
{
NULL, 0 UL
},
}
},
{
"db_column" , SEPG_CLASS_DB_COLUMN,
{
{
"create" , SEPG_DB_COLUMN__CREATE
},
{
"drop" , SEPG_DB_COLUMN__DROP
},
{
"getattr" , SEPG_DB_COLUMN__GETATTR
},
{
"setattr" , SEPG_DB_COLUMN__SETATTR
},
{
"relabelfrom" , SEPG_DB_COLUMN__RELABELFROM
},
{
"relabelto" , SEPG_DB_COLUMN__RELABELTO
},
{
"select" , SEPG_DB_COLUMN__SELECT
},
{
"update" , SEPG_DB_COLUMN__UPDATE
},
{
"insert" , SEPG_DB_COLUMN__INSERT
},
{
NULL, 0 UL
},
}
},
{
"db_tuple" , SEPG_CLASS_DB_TUPLE,
{
{
"relabelfrom" , SEPG_DB_TUPLE__RELABELFROM
},
{
"relabelto" , SEPG_DB_TUPLE__RELABELTO
},
{
"select" , SEPG_DB_TUPLE__SELECT
},
{
"update" , SEPG_DB_TUPLE__UPDATE
},
{
"insert" , SEPG_DB_TUPLE__INSERT
},
{
"delete" , SEPG_DB_TUPLE__DELETE
},
{
NULL, 0 UL
},
}
},
{
"db_blob" , SEPG_CLASS_DB_BLOB,
{
{
"create" , SEPG_DB_BLOB__CREATE
},
{
"drop" , SEPG_DB_BLOB__DROP
},
{
"getattr" , SEPG_DB_BLOB__GETATTR
},
{
"setattr" , SEPG_DB_BLOB__SETATTR
},
{
"relabelfrom" , SEPG_DB_BLOB__RELABELFROM
},
{
"relabelto" , SEPG_DB_BLOB__RELABELTO
},
{
"read" , SEPG_DB_BLOB__READ
},
{
"write" , SEPG_DB_BLOB__WRITE
},
{
"import" , SEPG_DB_BLOB__IMPORT
},
{
"export" , SEPG_DB_BLOB__EXPORT
},
{
NULL, 0 UL
},
}
},
{
"db_language" , SEPG_CLASS_DB_LANGUAGE,
{
{
"create" , SEPG_DB_LANGUAGE__CREATE
},
{
"drop" , SEPG_DB_LANGUAGE__DROP
},
{
"getattr" , SEPG_DB_LANGUAGE__GETATTR
},
{
"setattr" , SEPG_DB_LANGUAGE__SETATTR
},
{
"relabelfrom" , SEPG_DB_LANGUAGE__RELABELFROM
},
{
"relabelto" , SEPG_DB_LANGUAGE__RELABELTO
},
{
"implement" , SEPG_DB_LANGUAGE__IMPLEMENT
},
{
"execute" , SEPG_DB_LANGUAGE__EXECUTE
},
{
NULL, 0 UL
},
}
},
{
"db_view" , SEPG_CLASS_DB_VIEW,
{
{
"create" , SEPG_DB_VIEW__CREATE
},
{
"drop" , SEPG_DB_VIEW__DROP
},
{
"getattr" , SEPG_DB_VIEW__GETATTR
},
{
"setattr" , SEPG_DB_VIEW__SETATTR
},
{
"relabelfrom" , SEPG_DB_VIEW__RELABELFROM
},
{
"relabelto" , SEPG_DB_VIEW__RELABELTO
},
{
"expand" , SEPG_DB_VIEW__EXPAND
},
{
NULL, 0 UL
},
}
},
};
/*
* sepgsql_mode
*
* SEPGSQL_MODE_DISABLED : Disabled on runtime
* SEPGSQL_MODE_DEFAULT : Same as system settings
* SEPGSQL_MODE_PERMISSIVE : Always permissive mode
* SEPGSQL_MODE_INTERNAL : Same as permissive , except for no audit logs
*/
static int sepgsql_mode = SEPGSQL_MODE_INTERNAL;
/*
* sepgsql_is_enabled
*/
bool
sepgsql_is_enabled(void )
{
return (sepgsql_mode != SEPGSQL_MODE_DISABLED);
}
/*
* sepgsql_get_mode
*/
int
sepgsql_get_mode(void )
{
return sepgsql_mode;
}
/*
* sepgsql_set_mode
*/
int
sepgsql_set_mode(int new_mode)
{
int old_mode = sepgsql_mode;
sepgsql_mode = new_mode;
return old_mode;
}
/*
* sepgsql_getenforce
*
* It returns whether the current working mode tries to enforce access
* control decision , or not . It shall be enforced when sepgsql_mode is
* SEPGSQL_MODE_DEFAULT and system is running in enforcing mode .
*/
bool
sepgsql_getenforce(void )
{
if (sepgsql_mode == SEPGSQL_MODE_DEFAULT &&
selinux_status_getenforce() > 0 )
return true ;
return false ;
}
/*
* sepgsql_audit_log
*
* It generates a security audit record . It writes out audit records
* into standard PG ' s logfile .
*
* SELinux can control what should be audited and should not using
* " auditdeny " and " auditallow " rules in the security policy . In the
* default , all the access violations are audited , and all the access
* allowed are not audited . But we can set up the security policy , so
* we can have exceptions . So , it is necessary to follow the suggestion
* come from the security policy . ( av_decision . auditallow and auditdeny )
*
* Security audit is an important feature , because it enables us to check
* what was happen if we have a security incident . In fact , ISO / IEC15408
* defines several security functionalities for audit features .
*/
void
sepgsql_audit_log(bool denied,
bool enforcing,
const char *scontext,
const char *tcontext,
uint16 tclass,
uint32 audited,
const char *audit_name)
{
StringInfoData buf;
const char *class_name;
const char *av_name;
int i;
/* lookup name of the object class */
Assert(tclass < SEPG_CLASS_MAX);
class_name = selinux_catalog[tclass].class_name;
/* lookup name of the permissions */
initStringInfo(&buf);
appendStringInfo(&buf, "%s {" ,
(denied ? "denied" : "allowed" ));
for (i = 0 ; selinux_catalog[tclass].av[i].av_name; i++)
{
if (audited & (1 UL << i))
{
av_name = selinux_catalog[tclass].av[i].av_name;
appendStringInfo(&buf, " %s" , av_name);
}
}
appendStringInfoString(&buf, " }" );
/*
* Call external audit module , if loaded
*/
appendStringInfo(&buf, " scontext=%s tcontext=%s tclass=%s" ,
scontext, tcontext, class_name);
if (audit_name)
appendStringInfo(&buf, " name=\" %s\"" , audit_name);
if (enforcing)
appendStringInfoString(&buf, " permissive=0" );
else
appendStringInfoString(&buf, " permissive=1" );
ereport(LOG, (errmsg("SELinux: %s" , buf.data)));
}
/*
* sepgsql_compute_avd
*
* It actually asks SELinux what permissions are allowed on a pair of
* the security contexts and object class . It also returns what permissions
* should be audited on access violation or allowed .
* In most cases , subject ' s security context ( scontext ) is a client , and
* target security context ( tcontext ) is a database object .
*
* The access control decision shall be set on the given av_decision .
* The av_decision . allowed has a bitmask of SEPG_ < class > _ _ < perms >
* to suggest a set of allowed actions in this object class .
*/
void
sepgsql_compute_avd(const char *scontext,
const char *tcontext,
uint16 tclass,
struct av_decision *avd)
{
const char *tclass_name;
security_class_t tclass_ex;
struct av_decision avd_ex;
int i,
deny_unknown = security_deny_unknown();
/* Get external code of the object class */
Assert(tclass < SEPG_CLASS_MAX);
Assert(tclass == selinux_catalog[tclass].class_code);
tclass_name = selinux_catalog[tclass].class_name;
tclass_ex = string_to_security_class(tclass_name);
if (tclass_ex == 0 )
{
/*
* If the current security policy does not support permissions
* corresponding to database objects , we fill up them with dummy data .
* If security_deny_unknown ( ) returns positive value , undefined
* permissions should be denied . Otherwise , allowed
*/
avd->allowed = (security_deny_unknown() > 0 ? 0 : ~0 );
avd->auditallow = 0 U;
avd->auditdeny = ~0 U;
avd->flags = 0 ;
return ;
}
/*
* Ask SELinux what is allowed set of permissions on a pair of the
* security contexts and the given object class .
*/
if (security_compute_av_flags_raw(scontext,
tcontext,
tclass_ex, 0 , &avd_ex) < 0 )
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("SELinux could not compute av_decision: "
"scontext=%s tcontext=%s tclass=%s: %m" ,
scontext, tcontext, tclass_name)));
/*
* SELinux returns its access control decision as a set of permissions
* represented in external code which depends on run - time environment . So ,
* we need to translate it to the internal representation before returning
* results for the caller .
*/
memset(avd, 0 , sizeof (struct av_decision));
for (i = 0 ; selinux_catalog[tclass].av[i].av_name; i++)
{
access_vector_t av_code_ex;
const char *av_name = selinux_catalog[tclass].av[i].av_name;
uint32 av_code = selinux_catalog[tclass].av[i].av_code;
av_code_ex = string_to_av_perm(tclass_ex, av_name);
if (av_code_ex == 0 )
{
/* fill up undefined permissions */
if (!deny_unknown)
avd->allowed |= av_code;
avd->auditdeny |= av_code;
continue ;
}
if (avd_ex.allowed & av_code_ex)
avd->allowed |= av_code;
if (avd_ex.auditallow & av_code_ex)
avd->auditallow |= av_code;
if (avd_ex.auditdeny & av_code_ex)
avd->auditdeny |= av_code;
}
}
/*
* sepgsql_compute_create
*
* It returns a default security context to be assigned on a new database
* object . SELinux compute it based on a combination of client , upper object
* which owns the new object and object class .
*
* For example , when a client ( staff_u : staff_r : staff_t : s0 ) tries to create
* a new table within a schema ( system_u : object_r : sepgsql_schema_t : s0 ) ,
* SELinux looks - up its security policy . If it has a special rule on the
* combination of these security contexts and object class ( db_table ) ,
* it returns the security context suggested by the special rule .
* Otherwise , it returns the security context of schema , as is .
*
* We expect the caller already applies sanity / validation checks on the
* given security context .
*
* scontext : security context of the subject ( mostly , peer process ) .
* tcontext : security context of the upper database object .
* tclass : class code ( SEPG_CLASS_ * ) of the new object in creation
*/
char *
sepgsql_compute_create(const char *scontext,
const char *tcontext,
uint16 tclass,
const char *objname)
{
char *ncontext;
security_class_t tclass_ex;
const char *tclass_name;
char *result;
/* Get external code of the object class */
Assert(tclass < SEPG_CLASS_MAX);
tclass_name = selinux_catalog[tclass].class_name;
tclass_ex = string_to_security_class(tclass_name);
/*
* Ask SELinux what is the default context for the given object class on a
* pair of security contexts
*/
if (security_compute_create_name_raw(scontext,
tcontext,
tclass_ex,
objname,
&ncontext) < 0 )
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("SELinux could not compute a new context: "
"scontext=%s tcontext=%s tclass=%s: %m" ,
scontext, tcontext, tclass_name)));
/*
* libselinux returns malloc ( ) ' ed string , so we need to copy it on the
* palloc ( ) ' ed region .
*/
PG_TRY();
{
result = pstrdup(ncontext);
}
PG_FINALLY();
{
freecon(ncontext);
}
PG_END_TRY();
return result;
}
Messung V0.5 in Prozent C=98 H=93 G=95
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland