Ssh – SFTP client can’t write to own home directory when chrooted there

chrootsftpssh

This is a tougher variant of the previous question bad ownership or modes for chroot directory component.

In that other question, we learn that openssh refuses to chroot a user to its own home directory, if that home directory has the normal permissions. Instead you have to make root the owner of the user's home directory. This prevents the user from uploading to its own home directory.

I'm working with a program I'll call stupid_legacy_application or SLA for short, which wants to send me its output via sftp upload. And it insists on uploading to (what appears to the client to be) the root directory. The upload fails and leaves these messages in the server log:

Jun 17 18:05:53 sftphost sftp-server[29031]: open "/SLA\\SLA_Data_ 2018617_18551.zip" flags WRITE,CREATE,TRUNCATE mode 0666  
Jun 17 18:05:53 sftphost sftp-server[29031]: sent status Permission denied

Note the mix of slash and backslashes in the path. I guess it was meant to talk to some Windows sftp server, and /SLA is supposed to be a directory.

If I don't chroot the user, it tries to upload to the real root directory and fails. If I chroot the user and don't chown the directory to root, sshd doesn't allow the login. If I chroot the user and chown the directory to root, it fails again. If I add write permission for the user via group or world bits, sshd doesn't allow the login.

I even tried to fool it with an ACL, thinking that sshd wouldn't notice anything outside the traditional permissions, but I was only fooling myself (see this question for why that doesn't work).

Recompiling sshd with the directory permission check hacked out looks like the only way to fix this problem. Is there any other way? It doesn't need to be clean, since I only need this to work once, and the server is a disposable VM.

Best Answer

I found several working solutions to this problem, most of which were only usable because of my willingness to throw away the server after I got this one upload. But there's one that I think is pretty reasonable.

Ugliest solution first:

Bad idea #1 - Forget the chroot and chmod 777 /. Openssh only checks for sane permissions when you're chrooting.

Bad idea #2 - Allowing the client to write to the real root directory (like in bad idea #1) can be done more subtly, with group permissions or a user ACL.

Bad idea #3 - As I wrote in the question, it is possible to hack out the permission check from sshd. Just search for the error message bad ownership or modes for chroot in the source, change the nearby 022 to 002 and it won't notice loose group permissions and ACLs on the chroot any more.

Good idea(?) - That backslash is the source of the trouble. If the client would just upload to /SLA/SLA_Data_blah_blah.zip everything would be fine. So let's just fix the backslash at the point where it hits the filesystem, by wrapping the open syscall:

/* wrapopen.c */
#define _GNU_SOURCE
#include <string.h>
#include <sys/types.h>
#include <dlfcn.h>

static int (*libc_open)(const char *, int, mode_t);

/* Behave like normal open() but translate backslashes to slashes. */
int open(const char *fn, int flags, mode_t mode)
{
  char fn_fixed[strlen(fn)+1], *p;

  strcpy(fn_fixed, fn);
  for(p=fn_fixed;*p;++p)
    if(*p == '\\')
      *p = '/';

  if(!libc_open)
    libc_open = dlsym(RTLD_NEXT, "open");

  return libc_open(fn_fixed, flags, mode);
}

Usage:

$ gcc -fPIC -shared wrapopen.c -o wrapopen.so -ldl
$ /etc/init.d/ssh stop
$ LD_PRELOAD=$PWD/wrapopen.so /usr/sbin/sshd

Now when sshd tries to create SLA\SLA_Data_blah_blah it actually creates SLA/SLA_Data_blah_blah. The user can be chrooted, the chroot is owned by root mode 755, the SLA directory under the chroot is owned by the user, and the upload works.