Stephanie for OpenBSD 3.6

Performance issues with Stephanie for OpenBSD 3.6

There are some claims about the performance hit Stephanie introduces. Not only these claims come from people who apparently forgot about the OpenBSD goals, they are also incorrect. Stephanie, in my opinion, does not present a tremendous performance hit - just the performance 'hit' the extra code requires.

Below is a detailed description of every Stephanie component and the performance you can expect from it.

Trust list management
Trust status is saved in the credentials of the process and is evaluated on each use of a functionality requiring trust. Trust status is inherited, meaning on a system with a 'static' trust list the only evaluation will be done during login.

Trust evaluation has a worst case of O(n) - where 'n' is the number of groups the user trust is being evaluated for is member of (we're using groupmember() which is a linear search routine) - and O(1) in a best case where trust has already been evaluated and the admin didn't change the trusted group or the user in question is a superuser.

Code snippets from kern/kern_security.c:trusted()

/* O(1) - trust group is the same since last evaluation */
if ((security_trust_gid == TRUST_INVAL) ||
    (cred->p_trustgid == security_trust_gid))
	return (1);

/* O(1) - user in question is a superuser */
if (suser(p, 0) == 0) {
	cred->p_trustgid = security_trust_gid;
	return (1);
}

/* O(n) - trust needs to be evaluated. Linear search on group array */
if (groupmember(security_trust_gid, p->p_ucred)) {
	cred->p_trustgid = security_trust_gid;
	return (1);
}

Vexec
Vexec in Stephanie for OpenBSD 3.6 is completely revamped from previous versions. Vexec uses hash tables for the mapping between device/inode to Vexec fingerprint data. Big-Oh notation for components in Vexec:

Retrieving hash table for given device - O(n) where 'n' is the number of devices that have fingerprinted files on them. Usually this would be two. (for / and /usr)

Code snippet from kern/kern_vexec.c:vexec_tblfind()

LIST_FOREACH(tbl, &vexec_tables, hash_list) {
	if (tbl->hash_dev == device)
		return (tbl);
}

Searching for per-inode fingerprint Vexec data in the hash table - Best case of O(1), unless there's a collision, which will be O(n) where 'n' is the number of inodes hashed to the same index.

Code snippet from kern/kern_vexec.c:vexec_lookup()

indx = VEXEC_HASH(tbl, inode);
tble = &(tbl->hash_tbl[indx & VEXEC_HASH_MASK(tbl)]);

LIST_FOREACH(e, tble, vhe_list) {
	if (e->vhe_inode == inode)
		return (e);
}

Trusted Path Execution (TPE)
Trusted path is a path that's owned by root and writable by neither 'group' or 'other'. The evaluation for a trusted path is done using the TRUSTED_PATH() macro, which is O(1).

Code snippet from sys/security.h:

#define TRUSTED_PATH(va)	(((va).va_uid == 0) && \
				!((va).va_mode & (S_IWGRP | S_IWOTH)))

Process privacy
Process privacy is checked by matching two PCBs. In the case where trust overrides privacy, there might be a call to the previously described trusted() routine. This gives us overall O(1), unless - as stated above - the trust group has changed since the last evaluation.

Code snippet from kern/kern_security.c:proc_cansee()

/* O(1), unless entering trusted(), where worst case is O(n) */
if (!security_privacy ||
    (looker->p_ucred->cr_uid == object->p_ucred->cr_uid) ||
    (suser(looker, 0) == 0) ||
    (security_trust_override && trusted(looker)))
	return (1);

Userland privacy

Userland programs that have privacy hooks usually perform three sysctl() calls one time when the program starts. The first to see if privacy for this program is enabled, the second to check if trust status overrides privacy restrictions, and if it does, a third call to check if the user running the program is trusted.

Several programs receive additional call(s) to getpw*() routines and strcmp() in several places.

Code snippet from a patch to /usr/src/usr.bin/who/who.c:main()

/* O(1), unless entering trusted(), where worst case is O(n) */
if (PRIV_WHO()) {
	do_priv = 1;

	if (TRUST_OVERRIDE() && TRUSTED())
		do_priv = 0;
	else {
		pwdbp = getpwuid(geteuid());
		if ((pwdbp == NULL) || (pwdbp->pw_uid == 0))
			do_priv = 0;
		else
			memcpy(&pwdb, pwdbp, sizeof(pwdb));
	}
}

Lower in the code, filtering based on username from utmp record:

if (*usr.ut_name && *usr.ut_line && 
    !(do_priv && strcmp(pwdb.pw_name, usr.ut_name)))
	...


If you think the described above is a serious performance hit for your uses, please tell me about it, and tell me also what are your needs. If you're an OpenBSD user and think it's not that bad compared to the benefit, please try it out and send me feedback. Send me any other comments aswell.