posted 2017-09-29
Running chown or chmod as root is dangerous. Especially in ebuilds, when the target is on the live filesystem. Cut that shit out.
(This article is targeted at Gentoo developers.)
In the
last “end root chowning” article, I convinced you
not to call chown and
chmod in your init scripts. Well, the
same problem exists in the pkg_postinst
phase—and to a
lesser extent the pkg_config
phase—of your ebuilds.
The following (more or less) appears in mail-filter/amavisd-new-2.11.0-r3.ebuild:
That code is calling chown on every path under (and including) /var/amavis whenever the amavisd-new package is upgraded or reinstalled. That can be exploited by the amavis user (or anyone in the amavis group) to gain root. After amavisd-new is installed, the amavis user owns /var/amavis, and he can create anything he likes in that directory. If he creates a hard link inside /var/amavis pointing to a root-owned file, then the next time that amavisd-new is (re)installed or upgraded, chown will give ownership of the hard link's target to the amavis user. From there it's easy to gain full root access.
Seeing is believing, so believe:
root # emerge --oneshot amavisd-new
root # su --shell /bin/sh --command "ln /etc/passwd /var/amavis/x" amavis
root # emerge --oneshot amavisd-new
root # ls -l /etc/passwd
-rw-r--r-- 1 amavis amavis 3.0K 2017-09-22 11:04 /etc/passwd
The same problem exists with the pkg_preinst
phase, and
both are extremely dangerous because they get run every time the
package is installed. But, the pkg_config
phase can be
abused too. If pkg_config
calls
chown recursively and if the user happens
to run the phase twice, then the same exploit is possible. For a
concrete example, consider net-analyzer/munin-2.0.33-r1.ebuild, which does
pkg_config() {
...
# generate one rsa (for legacy) and one ecdsa (for new systems)
ssh-keygen -t rsa -f /var/lib/munin/.ssh/id_rsa -N '' \
-C "created by portage for ${CATEGORY}/${PN}" || die
ssh-keygen -t ecdsa -f /var/lib/munin/.ssh/id_ecdsa -N '' -C \
"created by portage for ${CATEGORY}/${PN}" || die
chown -R munin:munin /var/lib/munin/.ssh || die
...
}
You can easily verify that the same exploit works, should the user happen to configure the package twice:
root # emerge --oneshot munin
root # emerge --config munin
root # su --shell /bin/sh --command "ln /etc/passwd /var/lib/munin/.ssh/x" munin
root # emerge --config munin
root # ls -l /etc/passwd
-rw-r--r-- 1 munin munin 3.0K 2017-09-22 17:19 /etc/passwd
Cut that shit out. Calling chown or
chmod recursively on the live filesystem
is, in and of itself, a security hazard. Even if your ebuild creates
/var/foo, you have no idea what lives in
/var/foo during pkg_postinst
, and
you have no business fiddling with the owner and permissions of
things that don't belong to you. Maybe I hide my pornography in
/var/amavis with mode 0600
and
owner:group root:root
. Your ebuild shouldn't make
that stuff public, trust me. Figure out a way to not do it.
As a user, you defend against this the same way you did last time.
You'll see people calling chown and chmod in those phase functions for a few different reasons. The first reason is “fuck it, let's change the ownership of everything just in case that's the way it's supposed to be.” For example, app-misc/uptimed-0.4.0-r1.ebuild tries to “fix” the permissions in /var/spool/uptimed for absolutely no reason:
src_install() {
...
keepdir /var/spool/uptimed
fowners uptimed:uptimed /var/spool/uptimed
...
}
pkg_postinst() {
einfo "Fixing permissions in /var/spool/${PN}"
chown -R uptimed:uptimed /var/spool/${PN}
...
}
Don't fucking do that.
The second, less absurd reason is because you've created something as root, but want it to be owned by somebody else. For example, take app-admin/logcheck-1.3.18.ebuild:
src_install() {
...
keepdir /var/lib/logcheck
...
}
pkg_postinst() {
chown -R logcheck:logcheck ... /var/lib/logcheck || die
...
}
If you create something as root during installation and later change
its ownership, then you should do so in ${D}
with
fowners, and not on the live filesystem:
If you're creating something on the live filesystem (outside of
${D}
) and need to change its owner, then the previous
solution won't work. The aforementioned pkg_config
phase from net-analyzer/munin-2.0.33-r1.ebuild is a good example of this:
pkg_config() {
...
# generate one rsa (for legacy) and one ecdsa (for new systems)
ssh-keygen -t rsa -f /var/lib/munin/.ssh/id_rsa -N '' \
-C "created by portage for ${CATEGORY}/${PN}" || die
ssh-keygen -t ecdsa -f /var/lib/munin/.ssh/id_ecdsa -N '' -C \
"created by portage for ${CATEGORY}/${PN}" || die
chown -R munin:munin /var/lib/munin/.ssh || die
...
}
The ${D}
variable would be meaningless here, because the
package is already installed.
If you're creating a path as root after installation and then trying to give it away to an unprivileged user, then there's usually a better way: create the path as the unprivileged user in the first place. The munin user is perfectly capable of running ssh-keygen himself, and /var/lib/munin is his home directory, so everything will work out:
pkg_config() {
...
# generate one rsa (for legacy) and one ecdsa (for new systems)
su --shell /bin/sh --command "ssh-keygen -t rsa -f ..." munin
su --shell /bin/sh --command "ssh-keygen -t ecdsa -f ..." munin
...
}
Now everything is created with the correct ownership and permissions, and we don't have to chown or chmod anything. As part of the fix for Gentoo bug #630822, commit b19f619 serves as another good example of this strategy.
There is one rare situation where a package legitimately wants to mess with the live filesystem. If an earlier version of a package installed something with the wrong ownership or permissions, then a later version of the package can call chown or chmod on the live filesystem to fix them; the package manager won't alter the pre-existing ownership or permissions otherwise. One example is net-vpn/peervpn-0.044-r4.ebuild:
pkg_preinst() {
if ! has_version '>=net-vpn/peervpn-0.044-r4' && \
[[ -d ${EROOT}etc/${PN} &&
$(find "${EROOT}etc/peervpn" ! -user root -print) ]]; then
ewarn "Tightening '${EROOT}etc/${PN}' permissions for bug 629418"
chown -R root:${PN} "${EROOT}etc/${PN}" || die
chmod -R g+rX-w,o-rwx "${EROOT}etc/${PN}" || die
fi
}
This is a tough one. If you absolutely must fix the permissions in your ebuild, then heed the following warnings:
REPLACING_VERSIONS
(devmanual,
PMS)
and fix permissions only when upgrading from a version that set
them incorrectly.