bin/90384: chroot patch for sftp-server

Hideki SAKAMOTO sakamoto at tsnr.com
Wed Dec 14 03:00:18 PST 2005


>Number:         90384
>Category:       bin
>Synopsis:       chroot patch for sftp-server
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Wed Dec 14 11:00:13 GMT 2005
>Closed-Date:
>Last-Modified:
>Originator:     Hideki SAKAMOTO
>Release:        FreeBSD 6.0-RELEASE i386
>Organization:
TSNR.COM
>Environment:
System: FreeBSD xxx.tsnr.com 6.0-RELEASE FreeBSD 6.0-RELEASE #1: Tue Nov 29 17:50:40 JST 2
005 sakamoto at xxx.tsnr.com:/usr/obj/usr/src/sys/XXX i386

>Description:
        This patch enable sftp-server to chroot any directory just like ftpd(8).
        Mostly part of this patch code is copy of source of ftpd.c.
        Chroot user description file is /etc/ssh/sftpchroot and syntex is ftpchroot(5) com
patible. (ftpchroot(5) manual looks old version in 6.0-RELEASE. I think you need to copy f
rom 4.x manual.)
        I also test this patch on 4.11-RELEASE-p10 system.

        merit: No need to prepare bin/lib in chrooted directory tree.
        demerit: Need sftp-server binary to set suid bit and owned by root.
>How-To-Repeat:
        1. cd /usr/src and apply this patch.
        2. Set SFTP_CHROOT=yes in /etc/make.conf
        3. cd /usr/src/secure/libexec/sftp-server
        4. make & make install
>Fix:


*** secure/libexec/sftp-server/Makefile,old	Wed Dec 14 10:21:53 2005
--- secure/libexec/sftp-server/Makefile	Wed Dec 14 10:16:01 2005
***************
*** 8,13 ****
--- 8,19 ----
  DPADD=	${LIBSSH} ${LIBCRYPT} ${LIBCRYPTO} ${LIBZ}
  LDADD=	-lssh -lcrypt -lcrypto -lz
  
+ .if defined(SFTP_CHROOT)
+ CFLAGS+=-DSFTPCHROOT
+ BINOWN= root
+ BINMODE=4555
+ .endif
+ 
  .include <bsd.prog.mk>
  
  .PATH:	${SSHDIR}
*** crypto/openssh/pathnames.h,old	Wed Dec 14 09:25:10 2005
--- crypto/openssh/pathnames.h	Wed Dec 14 09:37:23 2005
***************
*** 43,48 ****
--- 43,50 ----
  /* Backwards compatibility */
  #define _PATH_DH_PRIMES			SSHDIR "/primes"
  
+ #define _PATH_SFTPCHROOT_FILE		SSHDIR "/sftpchroot"
+ 
  #ifndef _PATH_SSH_PROGRAM
  #define _PATH_SSH_PROGRAM		"/usr/bin/ssh"
  #endif
*** crypto/openssh/sftp-server.c,old	Wed Dec 14 09:24:59 2005
--- crypto/openssh/sftp-server.c	Wed Dec 14 17:47:36 2005
***************
*** 21,26 ****
--- 21,29 ----
  #include "getput.h"
  #include "log.h"
  #include "xmalloc.h"
+ #ifdef SFTPCHROOT
+ #include "pathnames.h"
+ #endif
  
  #include "sftp.h"
  #include "sftp-common.h"
***************
*** 1029,1040 ****
--- 1032,1136 ----
  		buffer_consume(&iqueue, msg_len - consumed);
  }
  
+ /*
+  * Check if a user is in the file "fname",
+  * return a pointer to a malloc'd string with the rest
+  * of the matching line in "residue" if not NULL.
+  */
+ #ifdef SFTPCHROOT
+ static int
+ checkuser(char *fname, char *name, gid_t pw_gid, char **residue)
+ {
+ 	FILE *fd;
+ 	int found = 0;
+ 	size_t len;
+ 	char *line, *mp, *p;
+ 
+ 	if ((fd = fopen(fname, "r")) != NULL) {
+ 		while (!found && (line = fgetln(fd, &len)) != NULL) {
+ 			/* skip comments */
+ 			if (line[0] == '#')
+ 				continue;
+ 			if (line[len - 1] == '\n') {
+ 				line[len - 1] = '\0';
+ 				mp = NULL;
+ 			} else {
+ 				if ((mp = malloc(len + 1)) == NULL)
+ 					fatal("Ran out of memory.");
+ 				memcpy(mp, line, len);
+ 				mp[len] = '\0';
+ 				line = mp;
+ 			}
+ 			/* avoid possible leading and trailing whitespace */
+ 			p = strtok(line, " \t");
+ 			/* skip empty lines */
+ 			if (p == NULL)
+ 				goto nextline;
+ 			/*
+ 			 * if first chr is '@', check group membership
+ 			 */
+ 			if (p[0] == '@') {
+ 				int i = 0;
+ 				struct group *grp;
+ 
+ 				if (p[1] == '\0') /* single @ matches anyone */
+ 					found = 1;
+ 				else {
+ 					if ((grp = getgrnam(p+1)) == NULL)
+ 						goto nextline;
+ 					/*
+ 					 * Check user's default group
+ 					 */
+ 					if (grp->gr_gid == pw_gid)
+ 						found = 1;
+ 					/*
+ 					 * Check supplementary groups
+ 					 */
+ 					while (!found && grp->gr_mem[i])
+ 						found = strcmp(name,
+ 							grp->gr_mem[i++])
+ 							== 0;
+ 				}
+ 			}
+ 			/*
+ 			 * Otherwise, just check for username match
+ 			 */
+ 			else
+ 				found = strcmp(p, name) == 0;
+ 			/*
+ 			 * Save the rest of line to "residue" if matched
+ 			 */
+ 			if (found && residue) {
+ 				if ((p = strtok(NULL, "")) != NULL)
+ 					p += strspn(p, " \t");
+ 				if (p && *p) {
+ 					if ((*residue = strdup(p)) == NULL)
+ 						fatal("Ran out of memory.");
+ 				} else
+ 					*residue = NULL;
+ 			}
+ nextline:
+ 			if (mp)
+ 				free(mp);
+ 		}
+ 		(void) fclose(fd);
+ 	}
+ 	return (found);
+ }
+ #endif	/* SFTPCHROOT */
+ 
  int
  main(int ac, char **av)
  {
  	fd_set *rset, *wset;
  	int in, out, max;
  	ssize_t len, olen, set_size;
+ #ifdef SFTPCHROOT
+ 	int dochroot;
+ 	char *chrootdir, *homedir, *residue;
+ 	uid_t uid;
+ 	struct passwd *pw;
+ #endif
  
  	/* XXX should use getopt */
  
***************
*** 1065,1070 ****
--- 1161,1243 ----
  	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
  	rset = (fd_set *)xmalloc(set_size);
  	wset = (fd_set *)xmalloc(set_size);
+ 
+ #ifdef	SFTPCHROOT
+ 	uid = getuid();
+ 	if ((pw = getpwuid(uid)) == NULL)
+ 		fatal("Cannot get user info");
+ 	residue = NULL;
+ 	dochroot =
+ 		checkuser(_PATH_SFTPCHROOT_FILE, pw->pw_name, pw->pw_gid, &residue);
+ 	chrootdir = NULL;
+ 	homedir = NULL;
+ 	/*
+ 	 * For a chrooted local user,
+ 	 * a) see whether ftpchroot(5) specifies a chroot directory,
+ 	 * b) extract the directory pathname from the line,
+ 	 * c) expand it to the absolute pathname if necessary.
+ 	 */
+ 	if (dochroot && residue &&
+ 	    (chrootdir = strtok(residue, " \t")) != NULL) {
+ 		if (chrootdir[0] != '/')
+ 			asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir);
+ 		else
+ 			chrootdir = strdup(chrootdir); /* make it permanent */
+ 		if (chrootdir == NULL)
+ 			fatal("Ran out of memory.");
+ 	}
+ 	if (dochroot) {
+ 		/*
+ 		 * If no chroot directory set yet, use the login directory.
+ 		 * Copy it so it can be modified while pw->pw_dir stays intact.
+ 		 */
+ 		if (chrootdir == NULL &&
+ 		    (chrootdir = strdup(pw->pw_dir)) == NULL)
+ 			fatal("Ran out of memory.");
+ 		/*
+ 		 * Check for the "/chroot/./home" syntax,
+ 		 * separate the chroot and home directory pathnames.
+ 		 */
+ 		if ((homedir = strstr(chrootdir, "/./")) != NULL) {
+ 			*(homedir++) = '\0';    /* wipe '/' */
+ 			homedir++;	      /* skip '.' */
+ 		} else {
+ 			/*
+ 			 * We MUST do a chdir() after the chroot. Otherwise
+ 			 * the old current directory will be accessible as "."
+ 			 * outside the new root!
+ 			 */
+ 			homedir = "/";
+ 		}
+ 		/*
+ 		 * Finally, do chroot()
+ 		 */
+ 		if (chroot(chrootdir) < 0) {
+ 			fatal("Can't change root.");
+ 		}
+ 	} else  /* real user w/o chroot */
+ 		homedir = pw->pw_dir;
+ 	/*
+ 	 * Set euid *before* doing chdir() so
+ 	 * a) the user won't be carried to a directory that he couldn't reach
+ 	 *    on his own due to no permission to upper path components,
+ 	 * b) NFS mounted homedirs w/restrictive permissions will be accessible
+ 	 *    (uid 0 has no root power over NFS if not mapped explicitly.)
+ 	 */
+ 	if (seteuid(pw->pw_uid) < 0) {
+ 		fatal("Can't set uid.");
+ 	}
+ 	if (chdir(homedir) < 0) {
+ 		if (dochroot) {
+ 			fatal("Can't change to base directory.");
+ 		} else {
+ 			if (chdir("/") < 0) {
+ 				fatal("Root is inaccessible.");
+ 			}
+ 			fatal("No directory! Logging in with home=/.");
+ 		}
+ 	}
+ #endif	/* SFTPCHROOT */
  
  	for (;;) {
  		memset(rset, 0, set_size);

>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list