Subject: Re: per-user /tmp
To: Elad Efrat <elad@NetBSD.org>
From: Jason Thorpe <thorpej@shagadelic.org>
List: tech-security
Date: 02/03/2007 20:29:10
On Feb 3, 2007, at 9:23 AM, Elad Efrat wrote:

> hi,
>
> attached are some diffs & instructions on how we can add support for
> per-user /tmp directories. the motivation is of course to avoid /tmp
> races.

I like this in concept, but I think we can polish it up a little.   
Relying on magic is convenient, but not necessarily elegant :-)

Specifically, I think it would be good to formalize this in API  
somehow, perhaps even engaging with other open source OS projects to  
standardize on a "private temp area" API.  For example, perhaps  
mkstemp(3) and friends could be changed to understand some simple  
format strings, such as %T (that would expand to ${TMPDIR} if set, or  
some reasonable default like _PATH_TMP if not set).

That said, having the magic would be good for legacy programs...

So, first of all, I don't like littering the top-level directory with  
extra stuff.  So, let's pick a generic place to put the "real" temp  
dirs... like maybe /private/tmp (hey, there's prior art for this, at  
least :-)

It would be nice of setusercontext(3) could take care of making sure  
the private temp directory exists, rather than making login(1) do it.   
setusercontext(3) could also set TMPDIR in the environment -- this  
would make any well-behaved program DTRT out of the box.

Anyway, I don't have a lot of time to think about this right now, but  
there's some food for thought, anyway... "Discuss!" :-)

>
> 1. enable magic links:
>
> 	# sysctl -w vfs.generic.magiclinks=1
>
> 2. decide on a directory that should be the per-user /tmp base, and
>    set the symlink:
>
> 	# rmdir /tmp
> 	# mkdir /rtmp
> 	# ln -s /rtmp/@uid /tmp
>
>    note that /rtmp in this case should be the mount-point if /tmp is
>    a tmpfs or something.
>
> 3. each user using /tmp should have a directory under /rtmp named  
> after
>    the uid, set 0700. for example:
>
> 	# mkdir /rtmp/1000 -m 0700
> 	# chown user:user /rtmp/1000
>
>    the attached login.diff will help doing that automatically for  
> users
>    when they login. it will read a string variable 'per-user-tmp' from
>    login.conf during login, creating the directory (if it's not  
> already
>    there).
>
>    for example:
>
> 	# from login.conf:
> 	default:\
> 		:per-user-tmp=/rtmp:
>
>    will set /rtmp as the per-user /tmp base.
>
> 4. if the above goes in, it will of course be disabled by default.
>
> thoughts?
>
> -e.
> Index: login.c
> ===================================================================
> RCS file: /usr/cvs/src/usr.bin/login/login.c,v
> retrieving revision 1.94
> diff -u -p -r1.94 login.c
> --- login.c	17 Jan 2007 00:21:43 -0000	1.94
> +++ login.c	2 Feb 2007 06:22:14 -0000
> @@ -188,6 +188,7 @@ main(int argc, char *argv[])
>  #ifdef LOGIN_CAP
>  	char *shell = NULL;
>  	login_cap_t *lc = NULL;
> +	char *per_user_tmp;
>  #endif
>
>  	tbuf[0] = '\0';
> @@ -596,6 +597,22 @@ main(int argc, char *argv[])
>  		environ = envinit;
>
>  #ifdef LOGIN_CAP
> +	/* Create per-user temporary directories if needed. */
> +	per_user_tmp = login_getcapstr(lc, "per-user-tmp", NULL, NULL);
> +	if (per_user_tmp != NULL) {
> +		char *tmp_dir;
> +
> +		/* Ignore errors here. */
> +		if (asprintf(&tmp_dir, "%s/%u", per_user_tmp,
> +		    pwd->pw_uid) != -1) {
> +			(void)mkdir(tmp_dir, S_IRWXU);
> +			(void)chown(tmp_dir, pwd->pw_uid, pwd->pw_gid);
> +			free(tmp_dir);
> +		}
> +	}
> +#endif /* LOGIN_CAP */
> +
> +#ifdef LOGIN_CAP
>  	if (nested == NULL && setusercontext(lc, pwd, pwd->pw_uid,
>  	    LOGIN_SETLOGIN) != 0) {
>  		syslog(LOG_ERR, "setusercontext failed");
> Index: login_pam.c
> ===================================================================
> RCS file: /usr/cvs/src/usr.bin/login/login_pam.c,v
> retrieving revision 1.17
> diff -u -p -r1.17 login_pam.c
> --- login_pam.c	17 Apr 2006 16:29:44 -0000	1.17
> +++ login_pam.c	2 Feb 2007 06:20:21 -0000
> @@ -144,6 +144,7 @@ main(int argc, char *argv[])
>  	int status;
>  	char *saved_term;
>  	char **pamenv;
> +	char *per_user_tmp;
>
>  	tbuf[0] = '\0';
>  	pwprompt = NULL;
> @@ -643,6 +644,20 @@ skip_auth:
>  		free(pamenv);
>  	}
>
> +	/* Create per-user temporary directories if needed. */
> +	per_user_tmp = login_getcapstr(lc, "per-user-tmp", NULL, NULL);
> +	if (per_user_tmp != NULL) {
> +		char *tmp_dir;
> +
> +		/* Ignore errors here. */
> +		if (asprintf(&tmp_dir, "%s/%u", per_user_tmp,
> +		    pwd->pw_uid) != -1) {
> +			(void)mkdir(tmp_dir, S_IRWXU);
> +			(void)chown(tmp_dir, pwd->pw_uid, pwd->pw_gid);
> +			free(tmp_dir);
> +		}
> +	}
> +
>  	/* This drops root privs */
>  	if (setusercontext(lc, pwd, pwd->pw_uid,
>  	    (LOGIN_SETALL & ~LOGIN_SETLOGIN)) != 0) {

-- thorpej