Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 03 Jun 2012 20:42:04 -0500
From:      Bryan Drewery <bryan@shatow.net>
To:        freebsd-hackers@freebsd.org
Subject:   [RFC] last(1) with security.bsd.see_other_uids support
Message-ID:  <4FCC126C.1020600@shatow.net>

next in thread | raw e-mail | index | archive | help
This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
--------------enig5FAF18043A57B2C0A76F8C09
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable

Hi,

I've written up a patch to add some privacy to last(1) while still
giving non-privileged users access to their own login history.

This is still a work in progress. I am reaching out to make sure my
approach is proper and to get some input on code sharing. My goal is to
add this support to w(1) and who(1) as well. FWIW I have been running a
similar patch on my own shared-hosting systems (pre-utmpx) for a few year=
s.

Changes:

* Added utmp group
* All utmpx files are 660 root:utmp
* last(1) runs setgid(utmp) and drops this as soon as the utmpx files
are opened.
* Users in the wheel or utmp group can see all entries
* IFF security.bsd.see_other_uids=3D0: users only see their own entries,
as well as shutdown/boot/init times.
* If security.bsd.see_other_uids=3D1, all entries are always shown, as it=

does now.

Justifications:

Why the changes? This makes sense for shared hosting environments where
jails are not practical. A user should be able to see their own login
history, to see if someone has been accessing their account, but not to
see the IPs of other users. The intention is *not* to disallow them to
see that other users of the system. Obviously they can just cat
/etc/passwd. This is just about IP privacy.

Why the setgid? Allow reading the entries, but disallow directly opening
the utx files. I've seen some shared hosts incorrectly chmod 0
/usr/bin/last, but still leave the utmp files wide open for reading!

Why the utmp group? It's consistent with other systems (OpenBSD, Linux),
and allows giving a user access to see all entries, without granting
them wheel or allowing a non-privileged user to run as setgid(wheel). It
also helps mitigates security concerns by using a specific group only
having extra privilege to utmpx files.

I originally had not planned for security.bsd.see_other_uids, but
considering POLA and consistency, it makes sense.

Questions:

To add this support to w(1) and who(1), I want to share the
is_user_restricted() function among all 3 binaries. I don't think this
really belongs in libc/libutil, but maybe it does. I could just add a
shared file into usr.bin/last/ and link it in with all 3, but I don't
really like this approach as then usr.bin/{w,who} would depend on
usr.bin/last.

The libc fchown() changes to pututxline.c may not be needed. I am open
to removing this.

On the other hand, perhaps most of my changes belong in utx functions in
libc. Thus all utx readers would benefit and solve my sharing problem.
The readers/clients would still need to be setgid(utmp) though to open
the utx files.

I still need to update the relevant man pages for these changes as well.

Regards,
Bryan Drewery

Patch available at: http://www.shatow.net/freebsd/last-privacy.txt

diff --git a/etc/group b/etc/group
index 54d5c59..211ff32 100644
--- a/etc/group
+++ b/etc/group
@@ -17,6 +17,7 @@ sshd:*:22:
 smmsp:*:25:
 mailnull:*:26:
 guest:*:31:
+utmp:*:45:
 bind:*:53:
 proxy:*:62:
 authpf:*:63:
diff --git a/etc/newsyslog.conf b/etc/newsyslog.conf
index 44aff12..1466939 100644
--- a/etc/newsyslog.conf
+++ b/etc/newsyslog.conf
@@ -34,6 +34,6 @@
 /var/log/ppp.log	root:network	640  3	   100	*     JC
 /var/log/security			600  10	   100	*     JC
 /var/log/sendmail.st			640  10	   *	168   B
-/var/log/utx.log			644  3	   *	@01T05 B
+/var/log/utx.log	root:utmp	660  3	   *	@01T05 B
 /var/log/weekly.log			640  5	   1	$W6D0 JN
 /var/log/xferlog			600  7	   100	*     JC
diff --git a/etc/rc.d/var b/etc/rc.d/var
index 2b41219..7aa9063 100755
--- a/etc/rc.d/var
+++ b/etc/rc.d/var
@@ -101,9 +101,11 @@ esac
 # Make sure we have /var/log/utx.lastlogin and /var/log/utx.log files
 if [ ! -f /var/log/utx.lastlogin ]; then
 	cp /dev/null /var/log/utx.lastlogin
-	chmod 644 /var/log/utx.lastlogin
+	chmod 660 /var/log/utx.lastlogin
+	chown root:utmp /var/log/utx.lastlogin
 fi
 if [ ! -f /var/log/utx.log ]; then
 	cp /dev/null /var/log/utx.log
-	chmod 644 /var/log/utx.log
+	chmod 660 /var/log/utx.log
+	chown root:utmp /var/log/utx.log
 fi
diff --git a/lib/libc/gen/pututxline.c b/lib/libc/gen/pututxline.c
index 98addee..c1f7199 100644
--- a/lib/libc/gen/pututxline.c
+++ b/lib/libc/gen/pututxline.c
@@ -179,10 +179,13 @@
 	int fd;

 	/* Initialize utx.active with a single BOOT_TIME record. */
-	fd =3D _open(_PATH_UTX_ACTIVE, O_CREAT|O_RDWR|O_TRUNC, 0644);
+	fd =3D _open(_PATH_UTX_ACTIVE, O_CREAT|O_RDWR|O_TRUNC, 0660);
 	if (fd < 0)
 		return;
-	_write(fd, fu, sizeof(*fu));
+	if (fchown(fd, 0, _UTMP_GID) < 0)
+		warnx("Unable to set root:utmp on " _PATH_UTX_ACTIVE);
+	else
+		_write(fd, fu, sizeof(*fu));
 	_close(fd);
 }

@@ -269,13 +272,18 @@
 	vec[1].iov_len =3D l;
 	l =3D htobe16(l);

-	fd =3D _open(_PATH_UTX_LOG, O_CREAT|O_WRONLY|O_APPEND, 0644);
+	fd =3D _open(_PATH_UTX_LOG, O_CREAT|O_WRONLY|O_APPEND, 0660);
 	if (fd < 0)
 		return (-1);
-	if (_writev(fd, vec, 2) =3D=3D -1)
+	if (fchown(fd, 0, _UTMP_GID) < 0) {
+		warnx("Unable to set root:utmp on " _PATH_UTX_LOG);
 		error =3D errno;
-	else
-		error =3D 0;
+	} else {
+		if (_writev(fd, vec, 2) =3D=3D -1)
+			error =3D errno;
+		else
+			error =3D 0;
+	}
 	_close(fd);
 	errno =3D error;
 	return (error =3D=3D 0 ? 0 : 1);
diff --git a/lib/libc/gen/utxdb.h b/lib/libc/gen/utxdb.h
index d9ebc93..25a5164 100644
--- a/lib/libc/gen/utxdb.h
+++ b/lib/libc/gen/utxdb.h
@@ -34,6 +34,7 @@
 #define	_PATH_UTX_ACTIVE	"/var/run/utx.active"
 #define	_PATH_UTX_LASTLOGIN	"/var/log/utx.lastlogin"
 #define	_PATH_UTX_LOG		"/var/log/utx.log"
+#define	_UTMP_GID		45

 /*
  * Entries in struct futx are ordered by how often they are used.  In
diff --git a/usr.bin/last/Makefile b/usr.bin/last/Makefile
index fd63cd9..c18adbd 100644
--- a/usr.bin/last/Makefile
+++ b/usr.bin/last/Makefile
@@ -2,6 +2,8 @@
 # $FreeBSD$

 PROG=3D	last
+BINGRP=3D utmp
+BINMODE=3D2555

 NO_WFORMAT=3D

diff --git a/usr.bin/last/last.c b/usr.bin/last/last.c
index e587def..025f948 100644
--- a/usr.bin/last/last.c
+++ b/usr.bin/last/last.c
@@ -57,6 +57,10 @@
 #include <unistd.h>
 #include <utmpx.h>
 #include <sys/queue.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/sysctl.h>

 #define	NO	0				/* false/no */
 #define	YES	1				/* true/yes */
@@ -193,6 +197,42 @@ struct idtab {
 }

 /*
+ * Return whether or not the given user can see all entries or not
+ */
+static int
+is_user_restricted(struct passwd *pw, int see_other_uids)
+{
+	int restricted =3D 1; /* Default to restricted access */
+	gid_t *groups;
+	int ngroups, gid, cnt;
+	long ngroups_max;
+	struct group *group;
+
+	if (geteuid() =3D=3D 0 || see_other_uids)
+		restricted =3D 0;
+	else {
+		/* Check if the user is in a privileged group */
+		ngroups_max =3D sysconf(_SC_NGROUPS_MAX) + 1;
+		if ((groups =3D malloc(sizeof(gid_t) * (ngroups_max))) =3D=3D NULL)
+			err(1, "malloc");
+		ngroups =3D ngroups_max;
+		(void) getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups);
+		for (cnt =3D 0; cnt < ngroups; ++cnt) {
+			gid =3D groups[cnt];
+			group =3D getgrgid(gid);
+			/* User is in utmp or wheel group, they can see all */
+			if (strncmp("utmp", group->gr_name, 4) =3D=3D 0 || strncmp("wheel",
group->gr_name, 5) =3D=3D 0) {
+				restricted =3D 0;
+				break;
+			}
+		}
+		free(groups);
+	}
+
+	return (restricted);
+}
+
+/*
  * wtmp --
  *	read through the utx.log file
  */
@@ -205,6 +245,9 @@ struct idtab {
 	time_t t;
 	char ct[80];
 	struct tm *tm;
+	struct passwd *pw =3D NULL;
+	int restricted, see_other_uids;
+	size_t len;

 	LIST_INIT(&idlist);
 	(void)time(&t);
@@ -212,7 +255,30 @@ struct idtab {
 	/* Load the last entries from the file. */
 	if (setutxdb(UTXDB_LOG, file) !=3D 0)
 		err(1, "%s", file);
+
+	/* drop setgid now that the db is open */
+	setgid(getgid());
+
+	/* Lookup current user information */
+	pw =3D getpwuid(getuid());
+
+	len =3D sizeof(see_other_uids);
+	if (sysctlbyname("security.bsd.see_other_uids", &see_other_uids, &len,
NULL, 0))
+		see_other_uids =3D 0;
+	restricted =3D is_user_restricted(pw, see_other_uids);
+
 	while ((ut =3D getutxent()) !=3D NULL) {
+		/* Skip this entry if the invoking user is not permitted
+		 * to see it */
+		if (restricted &&
+			!(ut->ut_type =3D=3D BOOT_TIME ||
+				ut->ut_type =3D=3D SHUTDOWN_TIME ||
+				ut->ut_type =3D=3D OLD_TIME ||
+				ut->ut_type =3D=3D NEW_TIME ||
+				ut->ut_type =3D=3D INIT_PROCESS) &&
+			strncmp(ut->ut_user, pw->pw_name, sizeof(ut->ut_user)))
+			continue;
+
 		if (amount % 128 =3D=3D 0) {
 			buf =3D realloc(buf, (amount + 128) * sizeof *ut);
 			if (buf =3D=3D NULL)


--------------enig5FAF18043A57B2C0A76F8C09
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: OpenPGP digital signature
Content-Disposition: attachment; filename="signature.asc"

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQIcBAEBAgAGBQJPzBJxAAoJEG54KsA8mwz5CbYP/iU9/srJSr0OHGhNaqfKJtqx
SHB3KPVbchR/pwg5YgRYYIvVd1lPy2nd8KZHZOXqbK4H56B8QnMHyNMlk5RCZN/4
HC5AJGQ197m2ynSAwNOqE28qzhaxLWlz9JdRuZePYJuoaykYEPtMgXg2enNAuW0L
ZOs0RSeNgMcuRv6TH1GxyXOmfZLMG9YumJHlvR6nGF60vkajwvRYsvRXsvbOmhYl
EpVXSpvHKdhzdkwj7bJa4/Oj2zX0NqXO13vMMynGrdvfpwJa35m98B87Qo5iOABX
XbrsmUnAptr0tic/3r7z1ipBnmPIbZbKrVG5AaiRKcnTmusoJ/ANFSZRORNN4Gwe
dpwRCsm95cbbr4m+kq/x4xeIRXpbp/jOaWq17/76YQRof4o0MbAcK/Vz2UZNFTTW
/NsWX7tEszwSRvtNt0DDdMtqRWpoi2sUXTFuynrm5Y/NB7Uy5YKfomjB6uIcrRak
a7iHybd10evC+tnuLZ0HI/MF5yWn+779wpR1OR7Drx7tGUle25GD7Cz622GKgiNy
kIk1LRlZdCxWOBWn0uox9XE78xjar3odfy062IkZkxMJ3MTWqjdDuv851LoMZMy9
PPk85dWYz7Wf7SO4X9gFNpChBAndkPx4X3sEuEBURHZD+wLALXVhK/dtszJ55ERu
0tICfWmWj+/H92RzTnnd
=o2eb
-----END PGP SIGNATURE-----

--------------enig5FAF18043A57B2C0A76F8C09--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?4FCC126C.1020600>