Disable sysctl ANYBODY flag
Updated Post: alfonsosiciliano.gitlab.io/posts/2020-07-11-disable-sysctl-anybody-flag.html
Please refer to the new Blog alfonsosiciliano.gitlab.io for updates.
FreeBSD 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
FreeBSD hacker mailing list