/*
|
* Media contexts backend for DB objects
|
*
|
* Author: KaiGai Kohei <kaigai@ak.jp.nec.com>
|
*/
|
|
#include <sys/stat.h>
|
#include <string.h>
|
#include <stdio.h>
|
#include <stdio_ext.h>
|
#include <ctype.h>
|
#include <errno.h>
|
#include <limits.h>
|
#include <fnmatch.h>
|
#include "callbacks.h"
|
#include "label_internal.h"
|
|
/*
|
* Regular database object's security context interface
|
*
|
* It provides applications a regular security context for the given
|
* database objects. The pair of object's name and a security context
|
* are described in the specfile. In the default, it shall be stored
|
* in the /etc/selinux/$POLICYTYPE/contexts/sepgsql_contexts .
|
* (It assumes SE-PostgreSQL in the default. For other RDBMS, use the
|
* SELABEL_OPT_PATH option to specify different specfile.)
|
*
|
* Each line has the following format:
|
* <object class> <object name/identifier> <security context>
|
*
|
* For example:
|
* ----------------------------------------
|
* #
|
* # It is an example specfile for database obejcts
|
* #
|
* db_database template1 system_u:object_r:sepgsql_db_t:s0
|
*
|
* db_schema *.pg_catalog system_u:object_r:sepgsql_sys_schema_t:s0
|
*
|
* db_table *.pg_catalog.* system_u:object_r:sepgsql_sysobj_t:s0
|
* db_column *.pg_catalog.*.* system_u:object_r:sepgsql_sysobj_t:s0
|
* ----------------------------------------
|
*
|
* All the characters after the '#' are dealt as comments.
|
*
|
* The first token is object class. SELABEL_DB_* declared in label.h are
|
* corresponding to a certain database object.
|
*
|
* The object name/identifier is compared to the given key.
|
* A database object can have its own namespace hierarchy.
|
* In the case of SE-PgSQL, database is the top level object, and schema
|
* is deployed just under a database. A schema can contains various kind
|
* of objects, such as tables, procedures and so on.
|
* Thus, when we lookup an expected security context for a table of
|
* "pg_class", it is necessary to assume selabel_lookup() is called with
|
* "postgres.pg_catalog.pg_class", not just a "pg_class".
|
*
|
* Wildcards ('*' or '?') are available on the patterns, so if you want
|
* to match a table within any schema, you should set '*' on the upper
|
* namespaces of the table.
|
*
|
* The structure of namespace depends on RDBMS.
|
* For example, Trusted-RUBIX has an idea of "catalog" which performs
|
* as a namespace between a database and individual schemas. In this
|
* case, a table has upper three layers.
|
*/
|
|
/*
|
* spec_t : It holds a pair of a key and an expected security context
|
*/
|
typedef struct spec {
|
struct selabel_lookup_rec lr;
|
char *key;
|
int type;
|
int matches;
|
} spec_t;
|
|
/*
|
* catalog_t : An array of spec_t
|
*/
|
typedef struct catalog {
|
unsigned int nspec; /* number of specs in use */
|
unsigned int limit; /* physical limitation of specs[] */
|
spec_t specs[0];
|
} catalog_t;
|
|
/*
|
* Helper function to parse a line read from the specfile
|
*/
|
static int
|
process_line(const char *path, char *line_buf, unsigned int line_num,
|
catalog_t *catalog)
|
{
|
spec_t *spec = &catalog->specs[catalog->nspec];
|
char *type, *key, *context, *temp;
|
int items;
|
|
/* Cut off comments */
|
temp = strchr(line_buf, '#');
|
if (temp)
|
*temp = '\0';
|
|
/*
|
* Every entry must have the following format
|
* <object class> <object name> <security context>
|
*/
|
type = key = context = temp = NULL;
|
items = sscanf(line_buf, "%ms %ms %ms %ms",
|
&type, &key, &context, &temp);
|
if (items != 3) {
|
if (items > 0)
|
selinux_log(SELINUX_WARNING,
|
"%s: line %u has invalid format, skipped",
|
path, line_num);
|
goto skip;
|
}
|
|
/*
|
* Set up individual spec entry
|
*/
|
memset(spec, 0, sizeof(spec_t));
|
|
if (!strcmp(type, "db_database"))
|
spec->type = SELABEL_DB_DATABASE;
|
else if (!strcmp(type, "db_schema"))
|
spec->type = SELABEL_DB_SCHEMA;
|
else if (!strcmp(type, "db_table"))
|
spec->type = SELABEL_DB_TABLE;
|
else if (!strcmp(type, "db_column"))
|
spec->type = SELABEL_DB_COLUMN;
|
else if (!strcmp(type, "db_sequence"))
|
spec->type = SELABEL_DB_SEQUENCE;
|
else if (!strcmp(type, "db_view"))
|
spec->type = SELABEL_DB_VIEW;
|
else if (!strcmp(type, "db_procedure"))
|
spec->type = SELABEL_DB_PROCEDURE;
|
else if (!strcmp(type, "db_blob"))
|
spec->type = SELABEL_DB_BLOB;
|
else if (!strcmp(type, "db_tuple"))
|
spec->type = SELABEL_DB_TUPLE;
|
else if (!strcmp(type, "db_language"))
|
spec->type = SELABEL_DB_LANGUAGE;
|
else if (!strcmp(type, "db_exception"))
|
spec->type = SELABEL_DB_EXCEPTION;
|
else if (!strcmp(type, "db_datatype"))
|
spec->type = SELABEL_DB_DATATYPE;
|
else {
|
selinux_log(SELINUX_WARNING,
|
"%s: line %u has invalid object type %s\n",
|
path, line_num, type);
|
goto skip;
|
}
|
|
free(type);
|
spec->key = key;
|
spec->lr.ctx_raw = context;
|
|
catalog->nspec++;
|
|
return 0;
|
|
skip:
|
free(type);
|
free(key);
|
free(context);
|
free(temp);
|
|
return 0;
|
}
|
|
/*
|
* selabel_close() handler
|
*/
|
static void
|
db_close(struct selabel_handle *rec)
|
{
|
catalog_t *catalog = (catalog_t *)rec->data;
|
spec_t *spec;
|
unsigned int i;
|
|
for (i = 0; i < catalog->nspec; i++) {
|
spec = &catalog->specs[i];
|
free(spec->key);
|
free(spec->lr.ctx_raw);
|
free(spec->lr.ctx_trans);
|
}
|
free(catalog);
|
}
|
|
/*
|
* selabel_lookup() handler
|
*/
|
static struct selabel_lookup_rec *
|
db_lookup(struct selabel_handle *rec, const char *key, int type)
|
{
|
catalog_t *catalog = (catalog_t *)rec->data;
|
spec_t *spec;
|
unsigned int i;
|
|
for (i = 0; i < catalog->nspec; i++) {
|
spec = &catalog->specs[i];
|
|
if (spec->type != type)
|
continue;
|
if (!fnmatch(spec->key, key, 0)) {
|
spec->matches++;
|
|
return &spec->lr;
|
}
|
}
|
|
/* No found */
|
errno = ENOENT;
|
return NULL;
|
}
|
|
/*
|
* selabel_stats() handler
|
*/
|
static void
|
db_stats(struct selabel_handle *rec)
|
{
|
catalog_t *catalog = (catalog_t *)rec->data;
|
unsigned int i, total = 0;
|
|
for (i = 0; i < catalog->nspec; i++)
|
total += catalog->specs[i].matches;
|
|
selinux_log(SELINUX_INFO, "%u entries, %u matches made\n",
|
catalog->nspec, total);
|
}
|
|
/*
|
* selabel_open() handler
|
*/
|
static catalog_t *
|
db_init(const struct selinux_opt *opts, unsigned nopts,
|
struct selabel_handle *rec)
|
{
|
catalog_t *catalog;
|
FILE *filp;
|
const char *path = NULL;
|
char *line_buf = NULL;
|
size_t line_len = 0;
|
unsigned int line_num = 0;
|
unsigned int i;
|
struct stat sb;
|
|
/*
|
* Initialize catalog data structure
|
*/
|
catalog = malloc(sizeof(catalog_t) + 32 * sizeof(spec_t));
|
if (!catalog)
|
return NULL;
|
catalog->limit = 32;
|
catalog->nspec = 0;
|
|
/*
|
* Process arguments
|
*
|
* SELABEL_OPT_PATH:
|
* It allows to specify an alternative specification file instead of
|
* the default one. If RDBMS is not SE-PostgreSQL, it may need to
|
* specify an explicit specfile for database objects.
|
*/
|
while (nopts--) {
|
switch (opts[nopts].type) {
|
case SELABEL_OPT_PATH:
|
path = opts[nopts].value;
|
break;
|
}
|
}
|
|
/*
|
* Open the specification file
|
*/
|
if (!path)
|
path = selinux_sepgsql_context_path();
|
|
if ((filp = fopen(path, "rb")) == NULL) {
|
free(catalog);
|
return NULL;
|
}
|
if (fstat(fileno(filp), &sb) < 0) {
|
free(catalog);
|
fclose(filp);
|
return NULL;
|
}
|
if (!S_ISREG(sb.st_mode)) {
|
free(catalog);
|
fclose(filp);
|
errno = EINVAL;
|
return NULL;
|
}
|
rec->spec_file = strdup(path);
|
|
/*
|
* Parse for each lines
|
*/
|
while (getline(&line_buf, &line_len, filp) > 0) {
|
/*
|
* Expand catalog array, if necessary
|
*/
|
if (catalog->limit == catalog->nspec) {
|
size_t length;
|
unsigned int new_limit = 2 * catalog->limit;
|
catalog_t *new_catalog;
|
|
length = sizeof(catalog_t)
|
+ new_limit * sizeof(spec_t);
|
new_catalog = realloc(catalog, length);
|
if (!new_catalog)
|
goto out_error;
|
|
catalog = new_catalog;
|
catalog->limit = new_limit;
|
}
|
|
/*
|
* Parse a line
|
*/
|
if (process_line(path, line_buf, ++line_num, catalog) < 0)
|
goto out_error;
|
}
|
free(line_buf);
|
|
if (digest_add_specfile(rec->digest, filp, NULL, sb.st_size, path) < 0)
|
goto out_error;
|
|
digest_gen_hash(rec->digest);
|
|
fclose(filp);
|
|
return catalog;
|
|
out_error:
|
for (i = 0; i < catalog->nspec; i++) {
|
spec_t *spec = &catalog->specs[i];
|
|
free(spec->key);
|
free(spec->lr.ctx_raw);
|
free(spec->lr.ctx_trans);
|
}
|
free(catalog);
|
fclose(filp);
|
|
return NULL;
|
}
|
|
/*
|
* Initialize selabel_handle and load the entries of specfile
|
*/
|
int selabel_db_init(struct selabel_handle *rec,
|
const struct selinux_opt *opts, unsigned nopts)
|
{
|
rec->func_close = &db_close;
|
rec->func_lookup = &db_lookup;
|
rec->func_stats = &db_stats;
|
rec->data = db_init(opts, nopts, rec);
|
|
return !rec->data ? -1 : 0;
|
}
|