Disable sysctl ANYBODY flag

Posted on July 11, 2020

The FreeBSD Operating System provides the sysctl system call and a wrapper utility to get or set the state of the kernel at run time (the handbook shows some useful example). Every user can get the value of a parameter:

% sysctl kern.maxprocperuid
kern.maxprocperuid: 6656

but, only the root user can usually set a value. Example:

% sysctl kern.maxprocperuid=10000
kern.maxprocperuid: 6656
sysctl: kern.maxprocperuid=10000: Operation not permitted
% su
# sysctl kern.maxprocperuid=10000
kern.maxprocperuid: 6656 -> 10000

Some parameter has the CTLFLAG_ANYBODY flag, so it is settable by every user. We can use the nsysctl utility to find them:

% nsysctl -aNGI | grep ANYBODY
kern.proc.args:  RD WR RW ANYBODY MPSAFE CAPWR
kern.proc.rlimit:  RD WR RW ANYBODY MPSAFE
kern.proc.osrel:  RD WR RW ANYBODY MPSAFE
kern.devname:  RD WR RW ANYBODY MPSAFE
hw.psm.elantech.natural_scroll:  RD WR RW ANYBODY DYN
hw.psm.elantech.three_finger_drag:  RD WR RW ANYBODY DYN
hw.psm.elantech.touchpad_off:  RD WR RW ANYBODY DYN
hw.psm.elantech.vscroll_div_max:  RD WR RW ANYBODY DYN
hw.psm.elantech.vscroll_div_min:  RD WR RW ANYBODY DYN
hw.psm.elantech.vscroll_min_delta:  RD WR RW ANYBODY DYN
hw.psm.elantech.vscroll_ver_area:  RD WR RW ANYBODY DYN
hw.psm.elantech.vscroll_hor_area:  RD WR RW ANYBODY DYN
hw.psm.elantech.taphold_timeout:  RD WR RW ANYBODY DYN
hw.psm.elantech.tap_min_queue:  RD WR RW ANYBODY DYN
hw.psm.elantech.tap_max_delta:  RD WR RW ANYBODY DYN
hw.psm.elantech.div_len:  RD WR RW ANYBODY DYN
hw.psm.elantech.div_max_na:  RD WR RW ANYBODY DYN
hw.psm.elantech.div_max:  RD WR RW ANYBODY DYN
hw.psm.elantech.div_min:  RD WR RW ANYBODY DYN
hw.psm.elantech.weight_len_squared:  RD WR RW ANYBODY DYN
hw.psm.elantech.weight_previous_na:  RD WR RW ANYBODY DYN
hw.psm.elantech.weight_previous:  RD WR RW ANYBODY DYN
hw.psm.elantech.weight_current:  RD WR RW ANYBODY DYN
hw.psm.elantech.multiplicator:  RD WR RW ANYBODY DYN
hw.psm.elantech.window_max:  RD WR RW ANYBODY DYN
hw.psm.elantech.window_min:  RD WR RW ANYBODY DYN
hw.psm.elantech.na_left:  RD WR RW ANYBODY DYN
hw.psm.elantech.na_bottom:  RD WR RW ANYBODY DYN
hw.psm.elantech.na_right:  RD WR RW ANYBODY DYN
hw.psm.elantech.na_top:  RD WR RW ANYBODY DYN
hw.psm.elantech.margin_left:  RD WR RW ANYBODY DYN
hw.psm.elantech.margin_bottom:  RD WR RW ANYBODY DYN
hw.psm.elantech.margin_right:  RD WR RW ANYBODY DYN
hw.psm.elantech.margin_top:  RD WR RW ANYBODY DYN
hw.psm.elantech.max_width:  RD WR RW ANYBODY DYN
hw.psm.elantech.max_pressure:  RD WR RW ANYBODY DYN
hw.psm.elantech.min_pressure:  RD WR RW ANYBODY DYN
hw.psm.elantech.two_finger_scroll:  RD WR RW ANYBODY DYN MPSAFE
hw.psm.elantech.max_y:  RD ANYBODY DYN MPSAFE
hw.psm.elantech.max_x:  RD ANYBODY DYN MPSAFE
hw.psm.elantech.directional_scrolls:  RD WR RW ANYBODY DYN MPSAFE
hw.snd.default_unit:  RD WR RW ANYBODY TUN RDTUN RWTUN NOFETCH

Generally they are the parameters for setting up a Desktop/Laptop, hw.psm.* for the touchpad and hw.snd.default_unit to define the default audio unit, the others parameters are not writable because are CTLTYPE_NODEs

% nsysctl -Nt kern.proc.args kern.proc.rlimit kern.proc.osrel kern.devname
kern.proc.args: node
kern.proc.rlimit: node
kern.proc.osrel: node
kern.devname: opaque

or CTLTYPE_OPAQUE (devname(3)).


The Problem

I think this flag is an useful feature, indeed a not-root user can use mixertui to set hw.snd.default_unit just pressing F8. However, a question in the FreeBSD hacker mailing list was about “an option to disable CTLFLAG_ANYBODY flag: only the root user should set a sysctl value”.

Solution 1

The first solution was to change an if condition in kern_sysctl.c

	/* Is this sysctl writable by only privileged users? */
	if (req->newptr && !(oid->oid_kind & CTLFLAG_ANYBODY)) {
		int priv;

		if (oid->oid_kind & CTLFLAG_PRISON)
			priv = PRIV_SYSCTL_WRITEJAIL;
#ifdef VIMAGE
		else if ((oid->oid_kind & CTLFLAG_VNET) &&
		     prison_owns_vnet(req->td->td_ucred))
			priv = PRIV_SYSCTL_WRITEJAIL;
#endif
		else
			priv = PRIV_SYSCTL_WRITE;
		error = priv_check(req->td, priv);
		if (error)
			goto out;
	}

from

/* Is this sysctl writable by only privileged users? */
if (req->newptr && !(oid->oid_kind & CTLFLAG_ANYBODY)) {

to

/* Is this sysctl writable? */
if (req->newptr) {

so, the ANYBODY flag is deleted: sysctl() has to call/check priv_check() every time a new value is passed. This solution is correct because only the root user can set a value but it has a problem: a normal user can’t use the utility to get a value:

% sysctl hw.snd.default_unit
sysctl: unknown oid 'hw.snd.default_unit'
% su
# sysctl hw.snd.default_unit
hw.snd.default_unit: 1

Solution 2

What is the unknown oid error with a not-root user?
The utility uses a special sysctl object: sysctl.name2oid to convert the name of a parameter in its corresponding Object ID, indeed the system call uses a numeric ID (OID) to specify a parameter not its name. The utility calls sysctl() with the name in the new buffer (req->newptr) and sysctl.name2oid fills the old buffer with the OID, pseudocode:

sysctl( 0.3 sysctl.name2oid, OID, sizeOID, "name", strlen("name")+1);

sysctl thinks to set a new value to the parameter, but after the first solution only the root can set a new value so the error of the utility:

sysctl: unknown oid 'hw.snd.default_unit'

Another solution solves the problem, changing the previous if condition in kern_sysctl.c:

from

/* Is this sysctl writable by only privileged users? */
if (req->newptr && !(oid->oid_kind & CTLFLAG_ANYBODY)) {

to

/*
 * Is this sysctl writable?
 * Does it belong to the undocumented interface or sysctlinfo?
 */
if (req->newptr && !(SYSCTL_CHILDREN(&sysctl___sysctl) == oid->oid_parent)) {

Testing: a not-root user can get the value of an object

% sysctl hw.snd.default_unit
hw.snd.default_unit: 1

but cannot set a value

% sysctl hw.snd.default_unit=0
hw.snd.default_unit: 1
sysctl: hw.snd.default_unit=0: Operation not permitted

despite the object has the ANYBODY flag

% nsysctl -NG hw.snd.default_unit
hw.snd.default_unit:  RD WR RW ANYBODY TUN RDTUN RWTUN NOFETCH

Technical study

A not-root user can use the sysctl utility (with the second solution) because it can “set” a new value to sysctl.name2oid, properly sysctl.name2oid belongs to an undocumented kernel interface, you could refer to the README of the sysctlinfo interface for a more thorough description.


Links