Thursday, December 10, 2015

Portable Inline Assembly

This is a fun little tidbit.  Assembly language is, obviously, not portable.  If you use inline assembly code in a C program, you won't expect it to run on multiple processors without special handling for each one.  Well, there's an exception to that.

First, the trivial exception:


That's valid code (assuming the gcc compiler, as I will throughout this discussion).  But it doesn't do anything, so it's rather pointless.  The magic comes in when you specify the input and output parameters.  For output, I'll specify two registers, assigned to the variables 'a' and 'b'.  For input, I'll use the same registers.  The syntax GCC uses is that zero is the first output register, and so-forth.  Note that you specify the output first.

__asm__("" \
 :"=r"(a), "=r"(b) \
 :"0"(b),"1"(a) );

Now this does something!  We've told it that the input is two registers with the values from variables 'b' and 'a', and the output is the same two registers which are variables 'a' and 'b'.  Notice the order.  We've swapped two variables.  No intermediary storage.  No fancy xors.  No actual code.  What we've done is tell the compiler to swap it's notion of where 'a' and 'b' are stored (with both being registers).

We can make this into a fancy macro:

#define SWAP_ASM(_a,_b) __asm__("" \
    :"=r"(_a), "=r"(_b) \
    :"0"(_b),"1"(_a) );

And demonstrate that it works:

#include <stdio.h>

#define SWAP_ASM(_a,_b) __asm__("" \
    :"=r"(_a), "=r"(_b) \
    :"0"(_b),"1"(_a) );

int main(int argc,char *argv[])
   int a='a', b='b';
   (void)argc;(void)argv; // no "unused" warning
   printf("a contains '%c'\n"
          "b contains '%c'\n",a,b);
   return 0;

Now admittedly, this isn't very useful.  If the variables aren't in registers, the compiler has to generate load and store instructions before and after the swap.  Using inline assembly can mess up optimization (probably more so in older releases), so using a variable to do a swap with all your code being in C is likely a better choice.  Still, a neat little hack.

Thursday, December 3, 2015

Scripting ssh passwords

One of the most powerful communications tools available is ssh.  Pretty much the only version on Linux is OpenSSH, and most of the versions I've come across on other platforms are derived from it.  I assume you know that, and I assume you also know that the best way to use it is with pre-shared keys so that you don't have to worry about passwords.  Unfortunately, that isn't always an option.

Recently I was working on a script that needed to use ssh to connect to an embedded system.  There are no keys on the remote system, so you have to use a password.  You can look the password up in a database.  Asking the user to do that and type it in manually would be a pain, and in this case, would add no security.

Openssh does everything it can to make scripting passwords difficult, which is only reasonable from a security standpoint.  But for anyone who has been around Unix systems for a while knows, there's a program called "expect" that solves the problem.  Expect allocates a pseudo TTY device, spawns a target program, and controls it through the TTY.  It can watch for prompts and issue responses, and it can eventually return control to the parent TTY, allowing a user to interact manually.

So I set up a script using expect to enter the password for ssh, and all was good.  I invited everyone else in my department to use the same script.  Most people loved it.  Then I started getting complaints.  It seems that while I would expect expect to be installed everywhere, that expectation was flawed.  I could ask everyone to install expect (and I did), but it seems that everyone is managing their own Linux systems, and many developers don't really know much about doing so.

I needed a better solution.

I found a better solution.

Ssh has a feature where it can run a GUI program to ask for a password.  That is exactly where we'll get our scripted password inserted.  This requires two things:  Set the environment variable SSH_ASKPASS to an executable that will write the password to stdout, and have the ssh process not be connected to a tty.

The first part is easy.  Just create a script that echos the password:

   echo echo $PW > /tmp/pw.$$
   chmod 700 /tmp/pw.$$

The second part is a little more tricky.  Also, it means that we can't interact with ssh once it connects.  Well, maybe we could with some additional trickery, but fortunately, my use of ssh didn't require interacting with it once the connection was established--I was just forwarding ports.  The obvious solution of redirecting stdin from /dev/null doesn't work, and neither does outright closing stdin.  The tty is part of a process state independent of the file descriptors, so we have to actually detach it.  What we want here is a program called 'setsid.'  This is part of the util-linux package, and it seems to be on every system I've been able to find, including the ones that didn't have expect.

Now there's one problem with setsid.  It immediately runs the program in the background.  I wanted to check the exit status of ssh to verify that everything was good.  I thought I was in luck, seeing that there's an option to do just this:  --wait.  Then I found that most of the other developers have older systems from before this option was added (in 2013, apparently).  This required creating another temporary script and a temporary results file, with the parent script waiting in a loop for it to finish.  So in /tmp/dossh.$$, I put the ssh command, followed by saving of the status ($?) in /tmp/dossh.$$.result.  And to keep everything clean, the last line of the script removes the script itself from /tmp.

So the parent script runs 'setsid /tmp/dossh.$$' then sits in a loop:
   while ! [ -f /tmp/dossh.$$.result]; do sleep .1; done
Then the parent script can grab the result, remove the remaining temporary files, and make use of the ssh tunnel. No manual password entry required.

No expect required!  (And that's a good thing.  Expect is a pain to use, in large part due to TCL being a painful language, but also due to issues where subtle changes in software versions can break everything, such as if a prompt changes slightly.)

Everything works exactly as the user would expect it to.