sftp-server: add a chroot option
Dirk-Willem van Gulik
dirkx at webweaving.org
Wed Apr 1 20:15:54 AEDT 2026
FWIIW - would love this go in !
And this works cleanly for me on 14.3-RELEASE (fairly trivial setup where there is no shell or other access).
Dw
> On 1 Apr 2026, at 09:01, Eloi Benoist-Vanderbeken <eloi.benoist-vanderbeken at synacktiv.com> wrote:
>
> Hi list,
>
> Any news on this? It's a pretty simple patch and it should be harmless as it is very similar to ssh ChrootDirectory option.
> We can still think about the namespace option in the future but this implementation already offers a real security and usability advantage for most (all ?) of the platforms at almost no cost.
>
> Kind regards,
> --
> Eloi Benoist-Vanderbeken
> Synacktiv
> +33 (0)6 67 92 63 35
>
> Hi Jochen,
>
>> If I understand correctly, you have to create a "fully equipped" chroot
>> tree (with copies of all used libraries, $CHROOT/etc/passwd and
>> $CHROOT/etc/group for proper "ls -l" output, maybe a $CHROOT/dev/log
>> with the syslogd doing an extra LISTEN on it so as to have working
>> logging, yadda yadda), anyway.
>
> No, not at all, I call chroot when the process is initialized, so
> sftp-server already had the opportunity to open whatever it needs and now
> only sees what the sftp user should be able to access (and not the
> sftp-server executable nor /etc).
>
> It's almost the same than the ChrootDirectory option with internal-sftp.
> That's also why I proposed it.
> Am 25.02.26 um 12:31 schrieb Eloi Benoist-Vanderbeken:
>> [...] I would like to add an option to chroot the sftp-server.
>> I am well aware that I could use ChrootDirectory with internal-sftp
>> but that doesn't work for me. [...]
>
> If I understand correctly, you have to create a "fully equipped" chroot
> tree (with copies of all used libraries, $CHROOT/etc/passwd and
> $CHROOT/etc/group for proper "ls -l" output, maybe a $CHROOT/dev/log
> with the syslogd doing an extra LISTEN on it so as to have working
> logging, yadda yadda), anyway. If so, wouldn't wrapping the (unchanged)
> sftp-server executable/process with the OS' chroot(1) command do the
> trick already?
diff --git a/sftp-server.c b/sftp-server.c
index b98c3cd41..d7677d199 100644
--- a/sftp-server.c
+++ b/sftp-server.c
@@ -1888,7 +1888,8 @@ sftp_server_usage(void)
extern char *__progname;
fprintf(stderr,
- "usage: %s [-ehR] [-d start_directory] [-f log_facility] "
+ "usage: %s [-ehR] [-C chroot_directory] "
+ "[-d start_directory] [-f log_facility] "
"[-l log_level]\n\t[-P denied_requests] "
"[-p allowed_requests] [-u umask]\n"
" %s -Q protocol_feature\n",
@@ -1902,7 +1903,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
int i, r, in, out, ch, skipargs = 0, log_stderr = 0;
ssize_t len, olen;
SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
- char *cp, *homedir = NULL, uidstr[32], buf[4*4096];
+ char *cp, *homedir = NULL, *chrootdir = NULL, uidstr[32], buf[4*4096];
long mask;
extern char *optarg;
@@ -1914,7 +1915,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
pw = pwcopy(user_pw);
while (!skipargs && (ch = getopt(argc, argv,
- "d:f:l:P:p:Q:u:cehR")) != -1) {
+ "d:f:l:P:p:Q:u:C:cehR")) != -1) {
switch (ch) {
case 'Q':
if (strcasecmp(optarg, "requests") != 0) {
@@ -1937,6 +1938,9 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
*/
skipargs = 1;
break;
+ case 'C':
+ chrootdir = optarg;
+ break;
case 'e':
log_stderr = 1;
break;
@@ -2022,6 +2026,16 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
if ((oqueue = sshbuf_new()) == NULL)
fatal_f("sshbuf_new failed");
+ if (chrootdir != NULL) {
+ if (chdir(chrootdir) != 0) {
+ fatal_f("chdir to \"%s\" failed: %s", chrootdir,
+ strerror(errno));
+ }
+ if (chroot(chrootdir) != 0)
+ fatal_f("chroot to \"%s\" failed: %s", chrootdir,
+ strerror(errno));
+ }
+
if (homedir != NULL) {
if (chdir(homedir) != 0) {
error("chdir to \"%s\" failed: %s", homedir,
More information about the openssh-unix-dev
mailing list