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)))
...