1
#include <stdio.h>
#include <stdlib.h>

int main()
{
    char buf[512];
    fgets(buf, 512, stdin);
    system("/bin/sh");
}

Compile with cc main.c

I would like a one-line command that makes this program run ls without it waiting for user input.

# This does not work - it prints nothing
(echo ; echo ls) | ./a.out

# This does work if you type ls manually
(echo ; cat) | ./a.out

I'm wondering:

  • Why doesn't the first example work?
  • What command would make the program run ls, without changing the source?

My question is shell and OS-agnostic but I would like it to work at least on bash 4.

Edit:

While testing out the answers, I found out that this works.

(python -c "print ''" ; echo ls) | ./a.out

Using strace:

$ (python -c "print ''" ; echo ls) | strace ./a.out
...
read(0, "\n", 4096)
...

This also works:

(echo ; sleep 0.1; echo ls) | ./a.out

It seems like the buffering is ignored. Is this due to the race condition?

Bilow
  • 2,194
  • 1
  • 19
  • 34
  • 1
    The problem is that stdio buffers its input. Even though `fgets()` only returns one line, it has put the rest of standard input into its internal input buffer, and it's no longer available for `/bin/sh` to read. – Barmar Mar 07 '18 at 20:36
  • See https://stackoverflow.com/questions/20342772/buffered-and-unbuffered-inputs-in-c for how to disable input buffering. – Barmar Mar 07 '18 at 20:39

1 Answers1

1

strace shows what's going on:

$ ( echo; echo ls; ) | strace ./foo
[...]
read(0, "\nls\n", 4096)                 = 4
[...]
clone(child_stack=NULL, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7ffdefc88b9c) = 9680

In other words, your program reads a whole 4096 byte buffer that includes both lines before it runs your shell. It's fine interactively, because by the time the read happens, there's only one line in the pipe buffer, so over-reading is not possible.

You instead need to stop reading after the first \n, and the only way to do that is to read byte by byte until you hit it. I don't know libc well enough to know if this kind of functionality is supported, but here it is with read directly:

#include <unistd.h>
#include <stdlib.h>

int main()
{
    char buf[1];
    while((read(0, buf, 1)) == 1 && buf[0] != '\n');
    system("/bin/sh");
}
that other guy
  • 116,971
  • 11
  • 170
  • 194
  • 1
    See https://stackoverflow.com/questions/20342772/buffered-and-unbuffered-inputs-in-c for how to disable buffering with stdio – Barmar Mar 07 '18 at 20:39
  • Nice catch for the buffering and race condition. Could you please check out my edit? – Bilow Mar 07 '18 at 21:00
  • @Bilow It's still a race condition, you're just rigging the race. `(python -c "print ''" ; echo ls) | (sleep 1; ./a.out)` makes it fail again. If you adjust the `1` down to a system specific value, maybe `0.01` or so, you can run it repeatedly and see that it sometimes works and sometimes fails. – that other guy Mar 07 '18 at 21:17
  • Since it is for buffer overflow exploitation it will do the trick. Now I understand what is happening. Thank you – Bilow Mar 07 '18 at 21:50