💾 Archived View for thrig.me › blog › 2023 › 01 › 12 › sans-shebang-script.gmi captured on 2024-07-09 at 00:56:51. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-11-14)
-=-=-=-=-=-=-
$ echo echo foo > bar $ chmod +x bar $ ./bar foo
In theory the above test implies that the #!/bin/sh line is optional in a shell script. In practice this is an inadequate test; the shell that is running the script adds complexity.
$ cfu 'system("./bar")' foo $ echo "puts [exec ./bar]" | tclsh8.6 foo $ ruby -e 'system "./bar"' foo $ perl -e 'system "./bar"' foo $ python2 -c 'import os;os.system("./bar")' foo
These tests are all bad.
The gist is that the system(3) call routes through /bin/sh, even if some folks are funny and call system exec instead of system. (cfu is a script that compiles and runs a C program, the rest can probably be found in your operating system, or its ports or package system.) We are still not avoiding the shell.
The other major interface goes by the name of exec (even if the TCL and PHP folks use that name for their system call), execv(3) or execve(2) or something like that. Quite often this is paired with fork(2) but need not be; exec wrappers do have various uses. Basically exec replaces the current process with something else.
$ cfu 'execl("./bar", "./bar", (char *)0)' $
We probably want some error checking, which will show a failure.
$ cfu 'execl("./bar", "./bar", (char *)0);err(1, "execl failed")' cfu: execl failed: Exec format error $ cfu 'execl("./bar", "./bar", (char *)0);err(1, "execl failed (%d)", errno)' > cfu: execl failed (8): Exec format error $ doas pkg_add moreutils ... $ errno 8 ENOEXEC 8 Exec format error
This on OpenBSD comes from somewhere in /usr/src/sys/kern/exec_script.c involving something about EXEC_SCRIPT_MAGIC and ENOEXEC. If we add appropriate script magic the file can be made to work under the exec interface.
$ ed bar 9 0a #!/bin/sh . wq 19 $ cfu 'execl("./bar", "./bar", (char *)0)' foo
So if you want something only to be runnable under the system but not exec interface, omit the shebang line. This is probably good for unix trivia night at the pub, and not much else. Maybe to detect when something is using a system call, without having to resort to ktrace or such?
Certainly the system(3) interface might look nicer,
system("./bar"); execl("./bar", "./bar", (char *)0); execve("./bar", (char *[]){"./bar", (char *)0}, NULL);
but there are various problems with system. For one, if the string is has been supplied by someone malicious, the system(3) call will mostly likely do bad things, while the execl will most likely fail.
// the "hello world" of security examples char *mystery_meat_string = "rm -rf /"; ... system(mystery_meat_string); execl(mystery_meat_string, mystery_meat_string, (char *)0);
Another problem is that various scripting languages can make it difficult to avoid system(3), for example when one does not want a random user-provided string to reach a shell. On the other hand, some folks do want their $VISUAL or $EDITOR environment variable to be able to contain spaces; these will generally not work with a program that uses execv(3) instead of system(3), unless that program implements shell word parsing itself. Probably there should have been a library call for this. Some programs will give you control over the word splitting, others maybe not so much.
$ exec zsh % EDITOR="ed -p =" % $EDITOR bar zsh: command not found: ed -p : % $=EDITOR bar 19 :q
I know, ed with a prompt, what is the world coming to.
system(3) shines when you just want a quick means to run some command, for example from a shell or editor which can already run anything anyways, and one that hopefully is not running under doas or some restricted security context or in a mail transport agent accepting who knows what strings from who knows where.
tags #sh #unix #c #zsh