17
echo 'main(){}' | gcc -xc - -o /dev/stdout | ???

Is there a way to run the output binary on a unix-like system?

EDIT: I needed it to run the output of g++ in a sandboxed environment where I can't write any file (nothing malicious, I promise).

2
  • I believe that basic security mechanisms must prevent this. But if your intension is to run C code on-the-fly, just use csh. Commented Oct 7, 2011 at 15:02
  • So, what's your final solution for "a sandboxed environment where I can't write any file"?
    – pmor
    Commented Apr 16 at 14:29

7 Answers 7

12

I don't believe this is possible. The exec(2) system call always requires a filename or absolute path (the filename is always a char*). posix_spawn also has similar requirements for a filename.

The closest you could do is pipe the output into a named pipe and try executing from the pipe. That may work, although the shell may refuse to execute any file that does not have the --x--x--x bits set. Create the pipe with mkfifo(1) and see if you can get it to work.

Another approach would be to write something that reads standard input, writes a file out to a temporay area, sets the --x bits on it, forks and execs then deletes the file. The inode and contents will remain until the program finishes executing but it won't be accessible through the file system. When the process terminates the inode will be released and storage will be returned to the free list.

EDIT: As Mat points out, the first approach won't work as the loader will attempt to demand-page in the executable, which will generate random seek traffic on the file, and this isn't possible on a pipe. This leaves some sort of approach like the second.

7
  • 3
    I'd be really surprised if the pipe trick worked - you can't do random seeks on a pipe, and can't mmap them - I'm pretty sure that will annoy the runtime loader/linker :) Your second suggestion seems good though, can't come up with anything without a temporary file.
    – Mat
    Commented Oct 7, 2011 at 14:05
  • @Mat - I think you're right. Demand paging in the executable would cause random access traffic which won't work on the pipe. Ironically it might have actually worked on SVR2.0 (the last version that didn't use demand paging) - Just to show my age I actually used to have an AT&T 3B2/400 once with SVR2.0 as the O/S. Commented Oct 7, 2011 at 15:49
  • Thinking about it some more, I'm pretty sure exe packers like UPX can do the decompression and execution on read-only media. Modify whatever stub they tack on to packed executables to read from a pipe rather than decompress, and ... might work.
    – Mat
    Commented Oct 7, 2011 at 16:05
  • @Mat packers already have an image loaded they don't start a new process. To do the similar I need to have one of processes to make an arbitrary jump into input data (which would be considered a security vulnerability).
    – Alex B
    Commented Oct 8, 2011 at 1:16
  • @Alex B: You are specifically asking how to make an arbitrary jump into input data. Why would you complain when it's suggested that you do exactly that? Is the purpose of the sandbox specifically to prevent what you're trying to do? Commented Oct 8, 2011 at 5:10
6

You could try tcc, which will compile and execute a program in one step, without writing any intermediate files. It's not gcc, which may be a problem for you, but it is spectacularly fast, so it may even bet better than gcc for your purposes.

6

A solution using memfd syscall: https://github.com/abbat/elfexec

It creates a named file descriptor in memory which could be used in exec. A pseudo-code:

#include <linux/memfd.h>
...
int memfd = syscall(SYS_memfd_create, "someName", 0);
...
write(memfd,... elf-content...);
...
fexecve(memfd, argv, environ);
2
  • 1
    You don't need the memfd.h header unless you want to use MFD_CLOEXEC (which will break #! /bin/sh scripts because of bugs in linux' fexecve()). That's not too complicated, you can include a 20-line working sample in your answer (eg. this git gist -- though that's not a drop-in replacement for your elfexec, since that will also allow you to specify argv[0], and will run a binary only from a pipe (UUoC mandated ;-))
    – user313992
    Commented Jan 6, 2019 at 13:36
  • So I don't get how this expected to work at all. gcc will write .o files to /tmp and die if it can't.
    – Joshua
    Commented Jul 8, 2019 at 18:06
2

This will automatically run the compilation of your code, but creates a file (temparaily) on the filesystem in order to do it.

echo 'main(){}' | gcc -xc -o /tmp/a.out && chmod u+x /tmp/a.out && /tmp/a.out && rm -f /tmp/a.out

(I'm currently testing this now, but I'm pretty sure this, or something close to it will work for you)

EDIT: If the goal of your piping is to cut physical disks out of the equation for speed, consider creating a ram disk to hold the intermediate file.

3
  • This will work of course, but it misses the main point of the question - to execute a binary code that is never written to disk. Commented Oct 7, 2011 at 14:47
  • @rozcietrzewiacz My hope was the it would be useful if the goal was to easily run a snippit of code on the fly without dealing with the required physical file.
    – dtyler
    Commented Oct 7, 2011 at 14:59
  • Yes, I understand. But for that, one could simply use csh. Commented Oct 7, 2011 at 15:03
1

In any case, gcc creates many temporary files when compiling C files into executables:

$ echo 'int main(){}' | strace -fe /open -o >(grep CREAT) gcc -xc -
96706 openat(AT_FDCWD, "/tmp/ccxiPEgx.s", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
96707 openat(AT_FDCWD, "/tmp/ccxiPEgx.s", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 0
96706 openat(AT_FDCWD, "/tmp/ccIBSUD4.o", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
96711 openat(AT_FDCWD, "/tmp/ccIBSUD4.o", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
96706 openat(AT_FDCWD, "/tmp/cciAP0FV.res", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
96712 openat(AT_FDCWD, "/tmp/ccfZa2PH.cdtor.c", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
96712 openat(AT_FDCWD, "/tmp/cc4LkCnx.cdtor.o", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
96713 openat(AT_FDCWD, "a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3

So you might as well create another one for the final executable.

If on Linux, it could be a pre-deleted one. For instance, with shells that still implement here-documents or here-strings as deleted temporary files such as zsh:

$ echo 'int main(){puts("Hello World");}' | { gcc -o /dev/fd/3 -x c  - && /dev/fd/3; } 3<<< ''
<stdin>: In function ‘main’:
<stdin>:1:12: warning: implicit declaration of function ‘puts’ [-Wimplicit-function-declaration]
<stdin>:1:1: note: include ‘<stdio.h>’ or provide a declaration of ‘puts’
Hello World
3
  • Extra question: can ld read object's content from stdin? Non-working example: echo "int main(void){}" | gcc -xc - -c -fPIC -o /dev/stdout | ld -shared - -o /dev/null.
    – pmor
    Commented Apr 12 at 13:05
  • @pmor, in my example, the openat(AT_FDCWD, "a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) is actually done by ld, which is run on the temporary .o's created by the compilation phase. Again, just use temp files or let gcc call ld itself and it will create temp files internally, so it is pointless to try and avoid those. Commented Apr 14 at 17:50
  • 1) I'm surprised that gcc creates many temporary files. As per your opinion, will it be useful for gcc to support an option to avoid creating temporary files (and produce an error if it's not possible to do so)? The use case is perhaps using gcc in read-only FS. 2) I'm also surprised that execl, execv, etc. don't have a version that takes void* data and size_t data_size.
    – pmor
    Commented Apr 16 at 14:27
0

Just like @TheQUUX suggested, I haven't tested it my self but you might want to try out cling - "the interactive C++ interpreter, built on top of LLVM and Clang libraries.".

Find more info here: https://cdn.rawgit.com/root-project/cling/master/www/index.html

0

From @kan answer and a perl implementation also done in this blog. This prints "Hello from C".

Requires: perl, base64, gunzip

#!/usr/bin/env bash
# From: https://unix.stackexchange.com/a/492559/257838
# And: https://magisterquis.github.io/2018/03/31/in-memory-only-elf-execution.html


execute_elf_string(){
  local bin_encoded=$1
  perl -e '
    require qw/syscall.ph/;

    # Create memfd
    my $name = "";
    my $fd = syscall(SYS_memfd_create(), $fn, 0);
    if (-1 == $fd) { die "memfd_create: $!"; }

    # Copy binary
    open(my $fh, ">&=".$fd) or die "open: $!";
    my $bin = `echo "'"$bin_encoded"'" | base64 -d | gunzip`;
    print $fh $bin;

    # Execute
    exec {"/proc/$$/fd/$fd"} "memfd";
  '
}

execute_elf_string "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"

To encode the binary to string, run

gzip -f < hi.so | base64

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .