read

Chasing Synology's OpenSSH bugs

Trying to use a non-standard shell in your Synology DS212+ NAS may be an exercise of frustration.

I'm a bash-lover, and have a bunch of scripts to automate backups, so I wanted to set up default shell to /bin/bash.

It worked well with root, but when a regular user tries to log in, the connection is terminated with "Permission denied, please try again.".

Weird. Maybe /etc/shells? Nops. Maybe sshd_config? No. PAM modules, then? Also not. I even tried compiling bash from source (which took me a full day, just to get ARM GCC to cross compile properly). Nada.

In a moment of total despair, I tried setting my regular user to uid=0; if it's permissions, then this should work, right?

Sorry. Just uid=0 isn't enough. The user has to be called "root".

WHAAAT?

Thankfully, Synology uses a modified Linux distribution, so they publish the code under GPL.

After a couple of hours looking at the OpenSSH source code, things became pretty obvious. Here's the piece of code from their version of OpenSSH 5.8p1, file session.c:

void do_child(Session *s, const char *command)
{
...

#ifdef MY_ABC_HERE
   char szValue[8];
   int RunSSH = 0;
   SSH_CMD SSHCmd = REQ_UNKNOWN;

   if (1 == GetKeyValue("/etc/synoinfo.conf", "runssh", szValue, sizeof(szValue))) {
           if (strcasecmp(szValue, "yes") == 0) {
                   RunSSH = 1;
           }
   }

   if (IsSFTPReq(command)){
           SSHCmd = REQ_SFTP;
   } else if (IsRsyncReq(command)){
           SSHCmd = REQ_RSYNC;
   } else if (IsTimebkpRequest(command)){
           SSHCmd = REQ_TIMEBKP;
   } else if (RunSSH && IsAllowShell(pw)){
           SSHCmd = REQ_SHELL;
   } else {
           goto Err;
   }

   if (REQ_RSYNC == SSHCmd) {
           pw = SYNOChgValForRsync(pw);
   }
   if (!SSHCanLogin(SSHCmd, pw)) {
           goto Err;
   }
   goto Pass;

 Err:
   fprintf(stderr, "Permission denied, please try again.\n");
   exit(1);

 Pass:
   #endif /* MY_ABC_HERE */
...
}

And here is the IsAllowShell(pw):

static int IsAllowShell(const struct passwd *pw)
{
     struct passwd *pUnPrivilege = NULL;
     char *szUserName = NULL;
     if (!pw || !pw->pw_name) {
             return 0;
     }
     szUserName = pw->pw_name;
     if(!strcmp(szUserName, "root") || !strcmp(szUserName, "admin")){
             return 1;
     }
     if (NULL != (pUnPrivilege = getpwnam(szUserName))){
             if (!strcmp(pUnPrivilege->pw_shell, "/bin/sh") || 
                     !strcmp(pUnPrivilege->pw_shell, "/bin/ash")) {
                     return 1;
             }
     }
     return 0;
}

Basically synology created a heavily customized version of OpenSSH, including a handful of hacks - e.g., additional checking before accepting a login to see if the SSH service is enabled within the web interface, or stripping special chars (;, |, ') from rsync commands, and... wait for it... blocking anyone other than root or admin from using any shell other than /bin/sh or /bin/ash.

Once identified the cause, the fix was easy: just remove the call to IsAllowShell(). Cross-compile, upload to the NAS, reboot and voilĂ , regular users can now use bash normally.

The hardest part was to get cross-compiling working properly. If there's anyone interested, here's my modified version of the Makefile. This was tested with OpenSSH-5.8p1 from DSM4.1 source code, branch 2636, on models with Marvell Kirkwood mv6281/mv6282 CPU (like DS212+), under Ubuntu 12.10 x64.

Bottom line: Synology did exactly what companies should not do: deviate from standards, for no reason. The result is ugly and unmaintainable code, and creates all sorts of headaches for users.

Thankfully there's GPL to keep them open.


Blog Logo

Gui Ambros

Maker, engineer, ad:tech veteran. Incurable optimist. I was there when the web was born. Opinions here are my own. @GuiAmbros


Published

Image

wrgms.com

/dev/random rants about technology, electronics, startups.

Back to Overview