From d8cb1f184f9acaae02bb4d15ce1e00ffbeeeac88 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Sun, 10 Feb 2008 22:40:12 +1100 Subject: [PATCH] - djm@cvs.openbsd.org 2008/02/08 23:24:07 [servconf.c servconf.h session.c sftp-server.c sftp.h sshd_config] [sshd_config.5] add sshd_config ChrootDirectory option to chroot(2) users to a directory and tweak internal sftp server to work with it (no special files in chroot required). ok markus@ --- Makefile.in | 6 +-- servconf.c | 12 ++++- servconf.h | 4 +- session.c | 106 +++++++++++++++++++++++++++++++++++++++------ sftp-server-main.c | 50 +++++++++++++++++++++ sftp-server.c | 10 ++--- sftp.h | 6 ++- sshd_config | 3 +- sshd_config.5 | 54 ++++++++++++++++++++++- 9 files changed, 220 insertions(+), 31 deletions(-) create mode 100644 sftp-server-main.c diff --git a/Makefile.in b/Makefile.in index 2486edc95..1f78ea9cc 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,4 @@ -# $Id: Makefile.in,v 1.285 2007/06/11 04:01:42 djm Exp $ +# $Id: Makefile.in,v 1.286 2008/02/10 11:40:12 djm Exp $ # uncomment if you run a non bourne compatable shell. Ie. csh #SHELL = @SH@ @@ -156,8 +156,8 @@ ssh-keysign$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keysign.o ssh-keyscan$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keyscan.o $(LD) -o $@ ssh-keyscan.o $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS) -sftp-server$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-common.o sftp-server.o - $(LD) -o $@ sftp-server.o sftp-common.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) +sftp-server$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-common.o sftp-server.o sftp-server-main.o + $(LD) -o $@ sftp-server.o sftp-common.o sftp-server-main.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-common.o sftp-glob.o progressmeter.o $(LD) -o $@ progressmeter.o sftp.o sftp-client.o sftp-common.o sftp-glob.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(LIBEDIT) diff --git a/servconf.c b/servconf.c index 19c286c18..d38d0bfb1 100644 --- a/servconf.c +++ b/servconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.c,v 1.175 2008/01/01 09:27:33 dtucker Exp $ */ +/* $OpenBSD: servconf.c,v 1.176 2008/02/08 23:24:08 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved @@ -122,6 +122,7 @@ initialize_server_options(ServerOptions *options) options->permit_tun = -1; options->num_permitted_opens = -1; options->adm_forced_command = NULL; + options->chroot_directory = NULL; } void @@ -291,7 +292,7 @@ typedef enum { sHostbasedUsesNameFromPacketOnly, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2, sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel, - sMatch, sPermitOpen, sForceCommand, + sMatch, sPermitOpen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sDeprecated, sUnsupported } ServerOpCodes; @@ -403,6 +404,7 @@ static struct { { "match", sMatch, SSHCFG_ALL }, { "permitopen", sPermitOpen, SSHCFG_ALL }, { "forcecommand", sForceCommand, SSHCFG_ALL }, + { "chrootdirectory", sChrootDirectory, SSHCFG_ALL }, { NULL, sBadOption, 0 } }; @@ -1147,6 +1149,7 @@ parse_flag: case sBanner: charptr = &options->banner; goto parse_filename; + /* * These options can contain %X options expanded at * connect time, so that you can specify paths like: @@ -1255,6 +1258,10 @@ parse_flag: options->adm_forced_command = xstrdup(cp + len); return 0; + case sChrootDirectory: + charptr = &options->chroot_directory; + goto parse_filename; + case sDeprecated: logit("%s line %d: Deprecated option %s", filename, linenum, arg); @@ -1363,6 +1370,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth) if (preauth) return; M_CP_STROPT(adm_forced_command); + M_CP_STROPT(chroot_directory); } #undef M_CP_INTOPT diff --git a/servconf.h b/servconf.h index 8a5b950ea..81a68be89 100644 --- a/servconf.h +++ b/servconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.h,v 1.80 2007/02/19 10:45:58 dtucker Exp $ */ +/* $OpenBSD: servconf.h,v 1.81 2008/02/08 23:24:08 djm Exp $ */ /* * Author: Tatu Ylonen @@ -141,6 +141,8 @@ typedef struct { int permit_tun; int num_permitted_opens; + + char *chroot_directory; } ServerOptions; void initialize_server_options(ServerOptions *); diff --git a/session.c b/session.c index a1319b384..1768c8c2f 100644 --- a/session.c +++ b/session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: session.c,v 1.225 2008/02/04 21:53:00 markus Exp $ */ +/* $OpenBSD: session.c,v 1.226 2008/02/08 23:24:07 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved @@ -84,6 +84,7 @@ #include "sshlogin.h" #include "serverloop.h" #include "canohost.h" +#include "misc.h" #include "session.h" #include "kex.h" #include "monitor_wrap.h" @@ -93,6 +94,9 @@ #include #endif +/* Magic name for internal sftp-server */ +#define INTERNAL_SFTP_NAME "internal-sftp" + /* func */ Session *session_new(void); @@ -130,7 +134,7 @@ extern Buffer loginmsg; const char *original_command = NULL; /* data */ -#define MAX_SESSIONS 10 +#define MAX_SESSIONS 20 Session sessions[MAX_SESSIONS]; #define SUBSYSTEM_NONE 0 @@ -688,13 +692,17 @@ do_exec(Session *s, const char *command) if (options.adm_forced_command) { original_command = command; command = options.adm_forced_command; - if (s->is_subsystem) + if (strcmp(INTERNAL_SFTP_NAME, command) == 0) + s->is_subsystem = SUBSYSTEM_INT_SFTP; + else if (s->is_subsystem) s->is_subsystem = SUBSYSTEM_EXT; debug("Forced command (config) '%.900s'", command); } else if (forced_command) { original_command = command; command = forced_command; - if (s->is_subsystem) + if (strcmp(INTERNAL_SFTP_NAME, command) == 0) + s->is_subsystem = SUBSYSTEM_INT_SFTP; + else if (s->is_subsystem) s->is_subsystem = SUBSYSTEM_EXT; debug("Forced command (key option) '%.900s'", command); } @@ -710,7 +718,6 @@ do_exec(Session *s, const char *command) PRIVSEP(audit_run_command(shell)); } #endif - if (s->ttyfd != -1) do_exec_pty(s, command); else @@ -1293,6 +1300,61 @@ do_nologin(struct passwd *pw) } } +/* + * Chroot into a directory after checking it for safety: all path components + * must be root-owned directories with strict permissions. + */ +static void +safely_chroot(const char *path, uid_t uid) +{ + const char *cp; + char component[MAXPATHLEN]; + struct stat st; + + if (*path != '/') + fatal("chroot path does not begin at root"); + if (strlen(path) >= sizeof(component)) + fatal("chroot path too long"); + + /* + * Descend the path, checking that each component is a + * root-owned directory with strict permissions. + */ + for (cp = path; cp != NULL;) { + if ((cp = strchr(cp, '/')) == NULL) + strlcpy(component, path, sizeof(component)); + else { + cp++; + memcpy(component, path, cp - path); + component[cp - path] = '\0'; + } + + debug3("%s: checking '%s'", __func__, component); + + if (stat(component, &st) != 0) + fatal("%s: stat(\"%s\"): %s", __func__, + component, strerror(errno)); + if (st.st_uid != 0 || (st.st_mode & 022) != 0) + fatal("bad ownership or modes for chroot " + "directory %s\"%s\"", + cp == NULL ? "" : "component ", component); + if (!S_ISDIR(st.st_mode)) + fatal("chroot path %s\"%s\" is not a directory", + cp == NULL ? "" : "component ", component); + + } + + if (chdir(path) == -1) + fatal("Unable to chdir to chroot path \"%s\": " + "%s", path, strerror(errno)); + if (chroot(path) == -1) + fatal("chroot(\"%s\"): %s", path, strerror(errno)); + if (chdir("/") == -1) + fatal("%s: chdir(/) after chroot: %s", + __func__, strerror(errno)); + verbose("Changed root directory to \"%s\"", path); +} + /* Set login name, uid, gid, and groups. */ void do_setusercontext(struct passwd *pw) @@ -1324,7 +1386,7 @@ do_setusercontext(struct passwd *pw) } # endif /* USE_PAM */ if (setusercontext(lc, pw, pw->pw_uid, - (LOGIN_SETALL & ~LOGIN_SETPATH)) < 0) { + (LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETUSER))) < 0) { perror("unable to set user context"); exit(1); } @@ -1347,13 +1409,13 @@ do_setusercontext(struct passwd *pw) exit(1); } endgrent(); -#ifdef GSSAPI +# ifdef GSSAPI if (options.gss_authentication) { temporarily_use_uid(pw); ssh_gssapi_storecreds(); restore_uid(); } -#endif +# endif # ifdef USE_PAM /* * PAM credentials may take the form of supplementary groups. @@ -1367,15 +1429,33 @@ do_setusercontext(struct passwd *pw) # endif /* USE_PAM */ # if defined(WITH_IRIX_PROJECT) || defined(WITH_IRIX_JOBS) || defined(WITH_IRIX_ARRAY) irix_setusercontext(pw); -# endif /* defined(WITH_IRIX_PROJECT) || defined(WITH_IRIX_JOBS) || defined(WITH_IRIX_ARRAY) */ +# endif /* defined(WITH_IRIX_PROJECT) || defined(WITH_IRIX_JOBS) || defined(WITH_IRIX_ARRAY) */ # ifdef _AIX aix_usrinfo(pw); # endif /* _AIX */ -#ifdef USE_LIBIAF +# ifdef USE_LIBIAF if (set_id(pw->pw_name) != 0) { exit(1); } -#endif /* USE_LIBIAF */ +# endif /* USE_LIBIAF */ +#endif + + if (options.chroot_directory != NULL && + strcasecmp(options.chroot_directory, "none") != 0) { + char *chroot_path; + + chroot_path = percent_expand(options.chroot_directory, + "h", pw->pw_dir, "u", pw->pw_name, (char *)NULL); + safely_chroot(chroot_path, pw->pw_uid); + free(chroot_path); + } + +#ifdef HAVE_LOGIN_CAP + if (setusercontext(lc, pw, pw->pw_uid, LOGIN_SETUSER) < 0) { + perror("unable to set user context (setuser)"); + exit(1); + } +#else /* Permanently switch to the desired uid. */ permanently_set_uid(pw); #endif @@ -1625,7 +1705,7 @@ do_child(Session *s, const char *command) argv[i] = NULL; optind = optreset = 1; __progname = argv[0]; - exit(sftp_server_main(i, argv)); + exit(sftp_server_main(i, argv, s->pw)); } if (options.use_login) { @@ -1900,7 +1980,7 @@ session_subsystem_req(Session *s) if (strcmp(subsys, options.subsystem_name[i]) == 0) { prog = options.subsystem_command[i]; cmd = options.subsystem_args[i]; - if (!strcmp("internal-sftp", prog)) { + if (!strcmp(INTERNAL_SFTP_NAME, prog)) { s->is_subsystem = SUBSYSTEM_INT_SFTP; } else if (stat(prog, &st) < 0) { error("subsystem: cannot stat %s: %s", prog, diff --git a/sftp-server-main.c b/sftp-server-main.c new file mode 100644 index 000000000..1d2ea6220 --- /dev/null +++ b/sftp-server-main.c @@ -0,0 +1,50 @@ +/* $OpenBSD: */ +/* + * Copyright (c) 2008 Markus Friedl. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include "log.h" +#include "sftp.h" +#include "misc.h" + +void +cleanup_exit(int i) +{ + sftp_server_cleanup_exit(i); +} + +int +main(int argc, char **argv) +{ + struct passwd *user_pw; + + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ + sanitise_stdfd(); + + if ((user_pw = getpwuid(getuid())) == NULL) { + fprintf(stderr, "No user found for uid %lu", (u_long)getuid()); + return 1; + } + + return (sftp_server_main(argc, argv, user_pw)); +} diff --git a/sftp-server.c b/sftp-server.c index 373bd5eda..44ccefff8 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-server.c,v 1.76 2008/02/04 21:53:00 markus Exp $ */ +/* $OpenBSD: sftp-server.c,v 1.77 2008/02/08 23:24:07 djm Exp $ */ /* * Copyright (c) 2000-2004 Markus Friedl. All rights reserved. * @@ -1219,7 +1219,7 @@ sftp_server_usage(void) } int -sftp_server_main(int argc, char **argv) +sftp_server_main(int argc, char **argv, struct passwd *user_pw) { fd_set *rset, *wset; int in, out, max, ch, skipargs = 0, log_stderr = 0; @@ -1277,11 +1277,7 @@ sftp_server_main(int argc, char **argv) } else client_addr = xstrdup("UNKNOWN"); - if ((pw = getpwuid(getuid())) == NULL) { - error("No user found for uid %lu", (u_long)getuid()); - sftp_server_cleanup_exit(255); - } - pw = pwcopy(pw); + pw = pwcopy(user_pw); logit("session opened for local user %s from [%s]", pw->pw_name, client_addr); diff --git a/sftp.h b/sftp.h index 12b9cc056..0835da6ed 100644 --- a/sftp.h +++ b/sftp.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp.h,v 1.6 2008/02/04 21:53:00 markus Exp $ */ +/* $OpenBSD: sftp.h,v 1.7 2008/02/08 23:24:07 djm Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -91,5 +91,7 @@ #define SSH2_FX_OP_UNSUPPORTED 8 #define SSH2_FX_MAX 8 -int sftp_server_main(int, char **); +struct passwd; + +int sftp_server_main(int, char **, struct passwd *); void sftp_server_cleanup_exit(int) __dead; diff --git a/sshd_config b/sshd_config index c7094e775..ddfbbe91e 100644 --- a/sshd_config +++ b/sshd_config @@ -1,4 +1,4 @@ -# $OpenBSD: sshd_config,v 1.76 2007/08/23 03:22:16 djm Exp $ +# $OpenBSD: sshd_config,v 1.77 2008/02/08 23:24:07 djm Exp $ # This is the sshd server system-wide configuration file. See # sshd_config(5) for more information. @@ -102,6 +102,7 @@ Protocol 2 #PidFile /var/run/sshd.pid #MaxStartups 10 #PermitTunnel no +#ChrootDirectory none # no default banner path #Banner none diff --git a/sshd_config.5 b/sshd_config.5 index aa6720dc3..2f83bf2e1 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -34,8 +34,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: sshd_config.5,v 1.79 2008/01/01 09:27:33 dtucker Exp $ -.Dd $Mdocdate: January 1 2008 $ +.\" $OpenBSD: sshd_config.5,v 1.80 2008/02/08 23:24:07 djm Exp $ +.Dd $Mdocdate: February 8 2008 $ .Dt SSHD_CONFIG 5 .Os .Sh NAME @@ -173,6 +173,45 @@ All authentication styles from are supported. The default is .Dq yes . +.It Cm ChrootDirectory +Specifies a path to +.Xr chroot 2 +to after authentication. +This path, and all its components, must be root-owned directories that are +not writable by any other user or group. +.Pp +The path may contain the following tokens that are expanded at runtime once +the connecting user has been authenticated: %% is replaced by a literal '%', +%h is replaced by the home directory of the user being authenticated, and +%u is replaced by the username of that user. +.Pp +The +.Cm ChrootDirectory +must contain the necessary files and directories to support the +users' session. +For an interactive session this requires at least a shell, typically +.Xr sh 1 , +and basic +.Pa /dev +nodes such as +.Xr null 4 , +.Xr zero 4 , +.Xr stdin 4 , +.Xr stdout 4 , +.Xr stderr 4 , +.Xr arandom 4 +and +.Xr tty 4 +devices. +For file transfer sessions using +.Dq sftp , +no additional configuration of the environment is necessary if the +in-process sftp server is used (see +.Cm Subsystem +for details. +.Pp +The default is not to +.Xr chroot 2 . .It Cm Ciphers Specifies the ciphers allowed for protocol version 2. Multiple ciphers must be comma-separated. @@ -740,11 +779,22 @@ The default is Configures an external subsystem (e.g. file transfer daemon). Arguments should be a subsystem name and a command (with optional arguments) to execute upon subsystem request. +.Pp The command .Xr sftp-server 8 implements the .Dq sftp file transfer subsystem. +.Pp +Alternately the name +.Dq internal-sftp +implements an in-process +.Dq sftp +server. +This may simplify configurations using +.Cm ChrootDirectory +to force a different filesystem root on clients. +.Pp By default no subsystems are defined. Note that this option applies to protocol version 2 only. .It Cm SyslogFacility