All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ivan Gyurdiev <ivg2@cornell.edu>
To: SELinux List <SELinux@tycho.nsa.gov>
Cc: Stephen Smalley <sds@tycho.nsa.gov>,
	Daniel J Walsh <dwalsh@redhat.com>,
	Joshua Brindle <jbrindle@tresys.com>
Subject: Genhomedircon - C or Python?
Date: Fri, 17 Feb 2006 00:39:05 -0500	[thread overview]
Message-ID: <43F56179.3080008@cornell.edu> (raw)

[-- Attachment #1: Type: text/plain, Size: 3225 bytes --]

Ok, Dan pointed out that a C genhomedircon is less flexible, and harder 
to work with than one written in python.
That is correct. However, I still argue that it should be written in C, 
because that's the language libsemanage is written in, and genhomedircon 
should be integrated into libsemanage. Please suggest alternative 
solutions to the problems below - I don't want to write genhomedircon in 
C either, but it just seems necessary.

To make discussion more substantiated, attached is a patch which 
implements the basic genhomedircon functionality, minus  things like 
reading the min_uid from other programs' config files, whitelisting 
shells, or HOME_ROOT expansion (don't have that working yet). On the 
other hand, it supports an additional variable (SELINUX_USER), and is 
capable of operating on top of any dbase (with any backend, whether it's 
a file, LDAP, or anything..). Comments appreciated, I'm sure parts of 
this file are ugly, and might need to be replaced. Execution starts at 
the bottom function.

============================

1) Genhomedircon output is not verified. Either invalid policy, or 
broken genhomedircon can generate invalid contexts, breaking the 
integrity of the system, which is unnecessary. Genhomedircon checking 
its context would amount to replicating code that is already in libsemanage.

        It was suggested that genhomedircon use libsemanage to validate 
its output. However, writing a checking API for contexts seems like a 
bad idea, because it has a single user - genhomedircon. It was also 
suggested that genhomedircon use libsemanage to add contexts as a 
"local" modification". This won't work, because genhomedircon cannot 
call semanange_commit(), because semanage_commit() calls genhomedircon back.

2) Genhomedircon is ran after the system sandbox is commited. This means 
between the time the sandbox is commited, and genhomedircon is ran, the 
file_contexts.homedirs installed are inconsistent with policy. This may 
or may not be a problem, depending on what kind of consistency 
requirements we have. However, a genhomedircon in libsemanage, executed 
before commit does not suffer from this problem.

3) Genhomedircon uses standard output to display errors - it does not 
hook into the ERR macros of libsemanage. If called remotely, 
genhomedircon output will not be seen by the client.

4) Genhomedircon is a standalone python program. For better integration 
with C clients that might want to invoke it (like useradd, libuser), it 
should be in a shared library.

5) Genhomedircon processes a split-out template. In the future we may 
want to process incoming requests to libsemanage (via the fcontext API) 
by genhomedircon helpers, in order to allow expansion of local 
modifications.

6) Genhomedircon currently understands the file_contexts format. 
Integration into libsemanage will reduce the things that need to 
understand that format to (1) libsemanage (in 
fcontext_file.c:print()/parse()), and (2) matchpathcon.

7) Integration of genhomedircon into libsemanage would allow the (local 
+ policy), and any future policy-only database to show all contexts, 
including the ones created by genhomedircon, which are currently not 
displayed.


[-- Attachment #2: genhomedircon.c --]
[-- Type: text/x-csrc, Size: 12128 bytes --]

/* Copyright (C) 2005 Red Hat, Inc. */

struct semanage_fcontext;
struct semanage_fcontext_key;
typedef struct semanage_fcontext_key record_key_t;
typedef struct semanage_fcontext record_t;
#define DBASE_RECORD_DEFINED

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include "user_internal.h"
#include "seuser_internal.h"
#include "fcontext_internal.h"
#include "context_internal.h"
#include "debug.h"
#include "handle.h"
#include "database.h"

typedef struct seuser_entry {

	/* From Linux passwd dbase */
	char* name;
	char* homedir;

	/* From Libsemanage seusers dbase */
	char* selinux_name;

	/* From Libsemanage users dbase */
	char* prefix;

	struct seuser_entry* next;
} seuser_entry_t;

/* Destroys an entries list */
static void entries_destroy(
	seuser_entry_t* entries) {

	seuser_entry_t *next, *current = entries;
	while (current) {
		next = current->next;
		free(current->name);
		free(current->homedir);
		free(current->selinux_name);
		free(current->prefix);
		free(current);
		current = next;
	}
}

/* Constructs the entry list, and fills the
 * name and homedirs fields */

#define BUFLEN 4096
#define START_UID 500 /* FIXME - shouldn't be hardcoded */ 
static int fetch_homedirs( 
	semanage_handle_t* handle,
	seuser_entry_t** entries) {	

	seuser_entry_t *tmp, *entry = NULL;

	while (1) {
		char buf[BUFLEN];
		struct passwd pw, *pwp;
		
		int rc = getpwent_r(&pw, buf, BUFLEN, &pwp);
		if (rc != 0 &&  rc != ENOENT) {
			ERR(handle, "could not read next password "
				"entry: %s", strerror(errno));
			goto err;
		}

		/* ENOENT */
		else if (rc != 0)
			break;

		if (pw.pw_uid < START_UID) 
			continue;

		tmp = entry;
		entry = calloc(1, sizeof(seuser_entry_t));
		if (!entry)
			goto omem;
	
		entry->name = strdup(pw.pw_name);
		entry->homedir = strdup(pw.pw_dir);
		entry->next = tmp;
	
		if (!entry->name || !entry->homedir)
			goto omem;
	}		
		
	*entries = entry;
	return STATUS_SUCCESS;

	omem:
	ERR(handle, "out of memory");

	err:
	ERR(handle, "could not collect passwd information for context expansion"); 
	entries_destroy(entry);
	return STATUS_ERR;
}

/* For each entry, fills the selinux_name field */
static int fetch_seusers( 
	semanage_handle_t* handle,
	seuser_entry_t* entries) {

	seuser_entry_t* entry;
	semanage_seuser_t** seusers = NULL;
	unsigned int nseusers = 0;
	semanage_seuser_key_t* key = NULL;
	semanage_seuser_key_t* def_key = NULL;
	semanage_seuser_t** def_seuser;
	unsigned int i;

	if (semanage_seuser_list(handle, &seusers, &nseusers) < 0)
		goto err;

	qsort(seusers, nseusers, sizeof(semanage_user_t*),
		(int (*) (const void*, const void*)) &semanage_seuser_compare2_qsort);

	/* Fetch default user */
	if (semanage_seuser_key_create(handle, "__default__", &def_key) < 0)
		goto err;

	def_seuser = bsearch(def_key, seusers, nseusers, sizeof(semanage_seuser_t*), 
			(int (*) (const void*, const void*)) &semanage_seuser_compare_qsort);

	semanage_seuser_key_free(def_key);

	if (!def_seuser) {
		ERR(handle, "could not find default seuser");
		goto err;
	}		

	for (entry = entries; entry; entry = entry->next) {

		semanage_seuser_t** match;

		if (semanage_seuser_key_create(handle, entry->name, &key) < 0)
			goto err;

		match = bsearch(key, seusers, nseusers, sizeof(semanage_seuser_t*),
			(int (*) (const void*, const void*)) &semanage_seuser_compare_qsort);
		
		if (!match)
			match = def_seuser;
	
		entry->selinux_name = strdup(semanage_seuser_get_sename(*match));
		if (!entry->selinux_name)
			goto omem;

		semanage_seuser_key_free(key);
	}

	for (i = 0; i < nseusers; i++)
		semanage_seuser_free(seusers[i]);
	free(seusers);
	return STATUS_SUCCESS;

	omem:
	ERR(handle, "out of memory");

	err:
	ERR(handle, "could not collect seuser information for context expansion");
	for (i = 0; i < nseusers; i++)
		semanage_seuser_free(seusers[i]);
	free(seusers);
	semanage_seuser_key_free(key);
	return STATUS_ERR;
}


/* For each entry, fills the prefix field */
static int fetch_users( 
	semanage_handle_t* handle,
	seuser_entry_t* entries) {

	seuser_entry_t* entry;
	semanage_user_t** users = NULL;
	unsigned int nusers = 0;
	semanage_user_key_t* key = NULL;
	unsigned int i;

	if (semanage_user_list(handle, &users, &nusers) < 0)
		goto err;

	qsort(users, nusers, sizeof(semanage_user_t*),
		(int (*) (const void*, const void*)) &semanage_user_compare2_qsort);

	for (entry = entries; entry; entry = entry->next) {

		semanage_user_t** match;

		if (semanage_user_key_create(handle, entry->selinux_name, &key) < 0)
			goto err;

		match = bsearch(key, users, nusers, sizeof(semanage_user_t*),
			(int (*) (const void*, const void*)) &semanage_user_compare_qsort);

		/* Should never happen */
		if (!match) {
			ERR(handle, "could not find selinux user %s for user %s, "
				"internal error", entry->selinux_name, entry->name);
			goto err;
		}
	
		entry->prefix = strdup(semanage_user_get_prefix(*match));
		if (!entry->prefix)
			goto omem;	

		semanage_user_key_free(key);
	}

	for (i = 0; i < nusers; i ++)
		semanage_user_free(users[i]);
	free(users);
	return STATUS_SUCCESS;
	
	omem:
	ERR(handle, "out of memory");

	err:
	ERR(handle, "could not collect user information for context expansion");
	semanage_user_key_free(key);
	for (i = 0; i < nusers; i ++)
		semanage_user_free(users[i]);
	return STATUS_ERR;
}

/* Check if the given symbol contains templates
 * for expansion */
static int contains_templates(
	const char* symbol) {

	if (!symbol)
		return 0;

	return strstr(symbol, "USER") ||
	       strstr(symbol, "SELINUX_USER") ||
	       strstr(symbol, "ROLE") ||
	       strstr(symbol, "HOME_DIR") ||
	       strstr(symbol, "HOME_ROOT");
}

/* Delete the given templated fcon specification 
 * from the target database */
static int delete_fcon(
	semanage_handle_t* handle,
	dbase_config_t* target,
	semanage_fcontext_t* fcon) {

	dbase_t* dbase = target->dbase;
	dbase_table_t* dtable = target->dtable;
	record_table_t* rtable = dtable->get_rtable(dbase);
	semanage_fcontext_key_t* key = NULL;

	/* Before using any dtable functions, cache() must be called */
	if (dtable->cache(handle, dbase) < 0)
		goto err;

	if (rtable->key_extract(handle, fcon, &key) < 0)
		goto err;

	if (dtable->del(handle, dbase, key) < 0)
		goto err;

	rtable->key_free(key);
	return STATUS_SUCCESS;
	
	err:
	ERR(handle, "could not delete templated context for %s [%s]",
		semanage_fcontext_get_expr(fcon),
		semanage_fcontext_get_type_str(
			semanage_fcontext_get_type(fcon)));
	rtable->key_free(key);
	return STATUS_ERR;
}

static int expand_symbol(
	semanage_handle_t* handle,
	const char* symbol,
	const seuser_entry_t* entry,
	char** new_symbol) {	

	/* First, allocate a buffer, 
	 * set sz (its size), and len (its current length) */

	int sz = strlen(symbol) * 2;
	int len = 0;
	char* buffer = malloc(sz);
	char* buffer_ptr = buffer;

	/* Define expansions */
	struct {
		const char* exp_name;
		const char* exp_data;

	} expansions[] = {

		{ "USER", entry->name },
		{ "SELINUX_USER", entry->selinux_name },
		{ "ROLE", entry->prefix },
		{ "HOME_DIR", entry->homedir },
		{ "HOME_ROOT", "" }
	};
	const unsigned int ECOUNT = sizeof(expansions)/sizeof(expansions[0]);

	while (*symbol != '\0') {
		unsigned int i;
		const char* target;
		unsigned int target_len;

		/* Try each expansion */
		int match = 0;
		for (i = 0 ; i < ECOUNT; i++) {
			const char* exp_name = expansions[i].exp_name;
			const char* exp_data = expansions[i].exp_data;
			int exp_name_len = strlen(exp_name);
			int exp_data_len = strlen(exp_data);

			if (!strncmp(symbol, exp_name, exp_name_len)) {
				symbol += exp_name_len;
				target = exp_data;
				target_len = exp_data_len;
				match = 1;
				break;
			}
		}

		/* None work, move on */
		if (!match) {
			target = symbol;
			target_len = 1;
			symbol ++;
		}

		/* Resize the buffer aggressively, so we don't have to do
	 	 * it again anytime soon */
		if ((sz - len) < (target_len + 1)) {

			/* Okay, this seems large enough */
			sz = (sz + target_len + 1) * 2;

			char* buffer_realloc = realloc(buffer, sz);
			if (!buffer_realloc)
				goto omem;

			buffer = buffer_realloc; 
		}
		
		/* Now store the target string */
		strncpy(buffer_ptr, target, target_len);
		len += target_len;
		buffer_ptr += target_len;
	}

	/* Null terminate the buffer */
	*buffer_ptr = '\0';

	*new_symbol = buffer;
	return STATUS_SUCCESS;

	omem:
	ERR(handle, "out of memory, could not expand symbol %s", symbol);
	free(buffer);
	return STATUS_ERR;
}

static int expand_contexts(
	semanage_handle_t* handle,
	seuser_entry_t* entries,
	dbase_config_t* target) {

	char* con_str = NULL;
	seuser_entry_t* entry;
	semanage_fcontext_t** fcontexts = NULL;
	unsigned int i, nfcontexts = 0;

	dbase_t* dbase = target->dbase;
	dbase_table_t* dtable = target->dtable;

	/* Before using any dtable functions, cache() must be called */
	if (dtable->cache(handle, dbase) < 0)
		goto err;

	if (dtable->list(handle, dbase, &fcontexts, &nfcontexts) < 0)
		goto err;

	for (i =0; i < nfcontexts; i++) {
		const char* expr = semanage_fcontext_get_expr(fcontexts[i]);
		semanage_context_t* con = semanage_fcontext_get_con(fcontexts[i]);

		if (con) {
			if (semanage_context_to_string(handle, con, &con_str) < 0)
				goto err;
		}

		/* Don't do anything - no templates */
		if (!contains_templates(expr) && !contains_templates(con_str)) {
			free(con_str); 
			con_str = NULL;
			continue;
		}

		printf ("Expanding expr=(%s), con=(%s) to: \n", expr, con_str);

		/* Otherwise before we do anything else, delete the 
		 * templated spec from the database */
		if (delete_fcon(handle, target, fcontexts[i]) < 0)
			goto err; 

		for (entry = entries; entry; entry = entry->next) {

			char* new_expr = NULL;
			char* new_con_str = NULL;
			semanage_context_t* new_con = NULL;			
			semanage_fcontext_t* new_fcon = NULL;
			semanage_fcontext_key_t* new_fcon_key = NULL;

			if (semanage_fcontext_clone(handle, fcontexts[i], &new_fcon) < 0)
				goto loop_err;

			if (expand_symbol(handle, expr, entry, &new_expr) < 0)
				goto loop_err;

			if (semanage_fcontext_set_expr(handle, new_fcon, new_expr) <  0)
				goto loop_err;

			if (con) {

				if (expand_symbol(handle, con_str, entry, &new_con_str) < 0) 
					goto loop_err;

				if (semanage_context_from_string(handle, new_con_str, &new_con) < 0)
					goto loop_err;

				if (semanage_fcontext_set_con(handle, new_fcon, new_con) < 0)
					goto loop_err;
				new_con = NULL;
			}

			if (semanage_fcontext_key_extract(handle, new_fcon, &new_fcon_key) < 0)
				goto loop_err;

			/* FIXME1: This is O(n) */
			/* FIXME2: an exists check on a templated contexts will not catch duplicate */
			if (dtable->modify(handle, dbase, new_fcon_key, new_fcon) < 0)
				goto loop_err;

			printf ("	expr=(%s), con=(%s)\n", new_expr, new_con_str);

			/* Success path */
			free(new_con_str);
			free(new_expr);
			semanage_fcontext_free(new_fcon);
			semanage_fcontext_key_free(new_fcon_key);
			new_con_str = NULL;
			new_expr = NULL;
			new_fcon = NULL;
			new_fcon_key = NULL;
			continue;

			/* Error path (loop) */
			loop_err:
			free(new_con_str);
			free(new_expr);
			semanage_context_free(new_con);
			semanage_fcontext_free(new_fcon);
			semanage_fcontext_key_free(new_fcon_key);
			goto err;
		}
	}

	for (i = 0; i < nfcontexts; i++)
		semanage_fcontext_free(fcontexts[i]);
	free(fcontexts);
	return STATUS_SUCCESS;

	err:
	free(con_str);
	for (i = 0; i < nfcontexts; i++)
		semanage_fcontext_free(fcontexts[i]);
	free(fcontexts);
	ERR(handle, "could not expand file contexts");
	return STATUS_ERR;
}

int hidden semanage_fcontext_expand(
	semanage_handle_t* handle,
	dbase_config_t* target) {

	seuser_entry_t* entries = NULL;

	/* First, collect all the information we need */
	if (fetch_homedirs(handle, &entries) < 0)
		goto err;

	if (fetch_seusers(handle, entries) < 0)
		goto err;

	if (fetch_users(handle, entries) < 0)
		goto err;

	if (expand_contexts(handle, entries, target) < 0)
		goto err;

	entries_destroy(entries);
	return STATUS_SUCCESS;

	err:
	ERR(handle, "could not expand file contexts");
	entries_destroy(entries);
	return STATUS_ERR;
}

             reply	other threads:[~2006-02-17  5:39 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2006-02-17  5:39 Ivan Gyurdiev [this message]
2006-02-18 15:23 ` Genhomedircon - C or Python? Ivan Gyurdiev
2006-02-18 15:27   ` Ivan Gyurdiev

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=43F56179.3080008@cornell.edu \
    --to=ivg2@cornell.edu \
    --cc=SELinux@tycho.nsa.gov \
    --cc=dwalsh@redhat.com \
    --cc=jbrindle@tresys.com \
    --cc=sds@tycho.nsa.gov \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.