The reason is you are using scanf
which reads from the stdin stream. It uses buffers, so it will try to read one buffer's worth of data and only then checks to see how much of that data it really needs. Since your ls
command is immediately available, it will get read into the buffer too, waiting for another call to scanf (or any other buffered stdio function operating on stdin) to get used. So when sh
then tries to read from stdin, there is nothing left since it does not see the buffer which is internal to your C program.
There are at least two ways to solve this.
You make sure the "ls" command is not echoed to stdin before scanf has completed reading. This is a bit tricky since you would need to wait for your program to output evidence of having gotten past that point (not trivial), or then using some fixed delay and hope system will never stall at that point and make the delay too short (i.e. this is a brittle solution). The latter would look something like: (echo 3 ; sleep 1 ; echo ls) | myprogram
i.e. the three commands are executed in order and all provide input to myprogram
.
You use functions to read from stdin - without buffers - only the minimum number of characters needed. For example the read
function does not use buffers. You could write a helper function like
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
int unbuffered_scanf(const char *fmt, ...) {
char buffer[100]; // maximum line length
int i;
int ret;
for(i=0; i<sizeof(buffer)-1; ++i) {
if (!read(0, &buffer[i], 1)) break;
if (buffer[i] == '\n') break;
}
buffer[i] = '\0';
va_list ap;
va_start(ap, fmt);
ret = vsscanf(buffer, fmt, ap);
va_end(ap);
return ret;
}
int main()
{
int iRetval = 0;
unsigned int uiNum;
printf("Enter number: ");
fflush(stdout);
iRetval = unbuffered_scanf("%u", &uiNum);
printf("\nThe number is %u, Retval: %i\n", uiNum, iRetval);
fflush(stdout);
if( iRetval > 0)
system("/bin/sh");
else
printf("Goodbye!\n");
}
For more info on unbuffered functions, read the info pages for example here: https://www.gnu.org/software/libc/manual/html_node/I_002fO-Primitives.html#I_002fO-Primitives
EDIT: Apparently there is a way to disable the buffering done by scanf
and others using e.g. the setbuf
function - it probably works internally exactly as my example above:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int iRetval = 0;
unsigned int uiNum;
setbuf(stdin, NULL);
printf("Enter number: ");
fflush(stdout);
iRetval = scanf("%u", &uiNum);
printf("\nThe number is %u, Retval: %i\n", uiNum, iRetval);
fflush(stdout);
if( iRetval > 0)
system("/bin/sh");
else
printf("Goodbye!\n");
}
echo -e '3\nls\n' | stdbuf -i1 ./your_program
, ie force it to read its input byte-by-byte, otherwise it may bite more than it can chew, and not leave anything for the command run viasystem()
. That's whatbash
has to do too when reading a script from a pipe, in order to be able to also use itsread
builtin on the same stdin.fseek(stdin, 0, SEEK_CUR)
to sync the file pointer before thesystem("...")
, you will be able to use it with seekable files, as those created bybash
for here-strings:./your_program <<<$'3\nls\n'
.