56

With Bash's source it is possible to execute a script without an execution bit set. This is documented and expected behaviour, but isn't this against the use of an execution bit?

I know, that source doesn't create a subshell.

  • 1
    The fact that chmod can let you set permissions (including `x) with an octal number gives some clue to what era it comes from. I wouldn't be surprised if it started as a quick and dirty "this is a binary file you can execute" indicator, from the days before she-bang was invented, but I have no evidence to that – infixed Jun 22 '16 at 17:49
  • 8
    Then again, when SUID comes into play, different levels of protection for different categories of users become more important. Go ahead and read my SUID program if you want. But if you execute it via simply reading it, the SUID powers are not coming along with it – infixed Jun 22 '16 at 17:54
  • @infixed The Linux program-loader won't even look at the shebang unless the execution bit is set. (To toot my own horn a bit: see here.) – Kyle Strand Jun 24 '16 at 16:05
  • You can also have +x-r, but that is a bit weird because it is usually possible to read the binary by injecting code into the running process – Niklas B. Jun 25 '16 at 15:54
  • @KyleStrand by "if you execute it via simply reading it, the SUID powers are not coming along with it " I was envisioning something along the lines of cp /sbin/suidexecutable /tmp/mycopy; /tmp/mycopy – infixed Jun 27 '16 at 12:51
34

Bash is an interpreter; it accepts input and does whatever it wants to. It doesn't need to heed the executable bit. In fact, Bash is portable, and can run on operating systems and filesystems that don't have any concept of an executable bit.

What does care about the executable bit is the operating system kernel. When the Linux kernel performs an exec, for example, it checks that the filesystem is not mounted with a noexec option, it checks the executable bit of the program file, and enforces any requirements imposed by security modules (such as SELinux or AppArmor).

Note that the executable bit is a rather discretionary kind of control. On a Linux x86-64 system, for example, you can bypass the kernel's verification of the executable bit by explicitly invoking /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 as the interpreter:

cp /bin/ls /tmp/
chmod -x /tmp/ls
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /tmp/ls

This is somewhat analogous to sourcing Bash source code in Bash, except that ld.so is the interpreter, and the code that it executes is machine code in ELF format.

  • 3
    Using the loader as an "interpreter" of "bytecode" that just happens to be a real executable binary.....awesome. Thank you for that. – Kyle Strand Jun 24 '16 at 16:07
  • @KyleStrand there may be several levels of indirection (the difference between the stored program and the loaded process and how the memory is mapped, various supervisors, VMs, etc) but in principle the machine code can be executed by CPU directly (no microcode, etc) and therefore the machine code is not interpreted: the hardware understands it as is—it is the native language—no interpreters are necessary. – jfs Jun 24 '16 at 20:00
  • 2
    @J.F.Sebastian Yes, we know that it is native code, hence "interpreter" is in quotes. The job of ld.so is to do dynamic linking. – 200_success Jun 24 '16 at 20:03
  • @J.F.Sebastian What 200_success said. Also, that's what I meant by "real executable binary," and I also put "bytecode" in quotes (since AFAIK "bytecode" is typically not used to refer to actual compiled natively-executable binary code). Also, nice username. – Kyle Strand Jun 24 '16 at 20:11
  • @J.F.Sebastian I do believe that modern x86-like CPUs are actually RISC internally, with the old-style CISC instruction set basically driving microcode execution of corresponding RISC instructions, where one CISC instruction can cause many RISC instructions to be executed. So in a sense, calling what the CPU does with the RISC machine code "interpreting" is, if not completely correct from a technical point of view, then at least a valid mental model. AES-NI is probably one of the more extreme examples: one CISC instruction per AES round! – a CVn Jun 25 '16 at 12:09
56

source or the equivalent but standard dot . do not execute the script, but read the commands from script file, then execute them, line by line, in current shell environment.

There's nothing against the use of execution bit, because the shell only need read permission to read the content of file.

The execution bit is only required when you run the script. Here the shell will fork() new process then using execve() function to create new process image from the script, which is required to be regular, executable file.

  • 5
    @alexis: I'm not sure I really follow. If you do bash < script, you get essentially the same result as source script. What protection does an execute bit check provide? – Conspicuous Compiler Jun 22 '16 at 17:34
  • 27
    @alexis, it's not an interpreter's job to check permissions of the scripts it interprets. Nothing does this -- not Python, not Ruby, not the Java virtual machine, not other random shells. The execute bit controls whether the OS execv* family of syscalls can be used with an executable, not whether an interpreter will run it. Why confuse people (and break ability to evaluate code streamed from non-file sources) by breaking conventions? – Charles Duffy Jun 22 '16 at 18:21
  • 14
    @alexis, ...moreover, there's a valuable meaning having a shell library that's not executable: It says "I'm a library, not a command -- source me, don't execute me". Otherwise you'd need to write code to detect and remediate misuse of code intended to only be sourced, but by not having the +x permission you can ensure that users can't (won't) misuse a library in such a way. – Charles Duffy Jun 22 '16 at 18:24
  • 6
    @alexis "it was a decision by the authors" Only in the sense that every other possible bit of code that wasn't included was a decision. The shell opens the file for reading, to read the commands out of it. I would not expect it to check for read permission, it just tries to open the file and fails if it can't. Likewise it doesn't check for write or execute permission, since those checks are not relevant for reading the file. – Randy Orrison Jun 22 '16 at 20:10
  • 2
    @alexis This is used in practice though, for example with python's virtualenv, the script to activate it is meant to be sourced, and not executed, and thus bin/activate doesn't have an executable bit. Scripts can totally be libraries or other things like that. I guess having .sh might be a signal as well, but having a minimum of foot-guns is good: it's nice that it isn't possible to run ./bin/activate by accident instead of . bin/activate – daboross Jun 23 '16 at 20:24
20

The executable bit (unlike the rest) on nonsetuid and nonsetguid files isn't much of a security mechanism. Anything you can read, you can run indirectly, and Linux will let you indirectly read anything you can run but not directly read (that should be enough to punch a hole in the concept of non-set(g)uid x-bit being a security measure).

It's more of a convenience thing: Let the system run it for me directly if the the bit is set, otherwise I need to do it indirectly (bash the_script; or some hacking to get the memory image of a no-read-permission executable).

You can set it for convenience if you intend to both in-source and execute your insourcable.

Apparently, however, many shared library implementers share your thinking and consequently, many systems do require that shared libraries, which are essentially the native equivalent of shell insourcables, be marked executable in order to be usable. See Why are shared libraries executable?.

  • 3
    @Motte001 They can execute those attachments if they really want to. It's a convenience. – PSkocik Jun 22 '16 at 16:11
  • 2
    The executable bit is not for security: If I own a file I am free to chmod it and make it executable. It is for sorting data from programs, so the OP's question is a reasonable one. – alexis Jun 22 '16 at 16:32
  • 1
    @Motte001 That's more of a security feature of the GUI file browser / mail application - it could easily decide that double-clicking any file whose name ends ".sh" be executed in a terminal (Windows-style), or conversely that double-clicking any file in the "downloaded attachments" directory will invoke a built-in sandboxed preview app. The x bit is just an extra place it can read/write a hint of what to do. – IMSoP Jun 22 '16 at 16:34
  • 3
    One reason we need the executable bit is to run setuid programs, particularly those owned by root. While you can certainly execute them on your own, you need the OS to run them so that they will have the necessary permissions. In some other cases it really is just a convenience. – Waleed Khan Jun 22 '16 at 17:56
  • 2
    "Linux will let you read anything you can run via /proc" -- Well, my stock Debian kernel doesn't: /tmp$ cp /bin/cat ./cat ; chmod a-rw ./cat ; ./cat & cp /proc/$!/exe /tmp/cat2 -> cp: cannot stat ‘/proc/16260/exe’: Permission denied – ilkkachu Jun 22 '16 at 18:30
9

That's a good question! Unix uses the executable bit to distinguish between programs and data. The OS does not require the execution bit, since a sourced script is not passed to the OS for execution as a new process. But the shell treats a sourced script as a program, and will look in $PATH for the file you want to source. So, the shell itself could have required execute permission for sourced files; but it didn't.

The question must have come up long ago. The design of the Bourne shell was the result of "a long sequence of modification, dialog, discussion" among the denizens of Bell Labs, and many design decisions were discussed by S.R. Bourne and others over the years. Unfortunately, my quick look didn't find any discussion of the source feature (in my defense, it's hard to google for it). What I did find is that the "." command does not appear in this early introduction to the shell by Bourne himself, but it is present in the more mature Version 7 version.

Absent authority, here's my own interpretation:

  1. The . command, aka source, is in effect textual inclusion (like #include in the C preprocessor) into the source code of the executing script or interactive session. As such, the included file is arguably not "executed".

  2. The Unix philosophy has always been to give the programmers enough rope to hang themselves. Too much hand-holding and arbitrary restrictions just get in the way. It is only rather recently that some distributions made rm -r / refuse to do what you ask. (This command tells rm to delete everything on your computer. Do not try it as root! Or better yet, not at all.) So, perhaps Bourne at al. just decided that when you try to source a file, you are assumed to know what you are doing. That also avoids unnecessary work, and cycles mattered a lot back then.

  • But one should definitely not do what @cat said. – TOOGAM Jun 25 '16 at 9:15
  • I'm surprised it hasn't been flagged and deleted @TOOGAM but I put in the /s! – cat Jun 25 '16 at 11:22
  • There, I don't know what @cat had written but I child-proofed the answer even more. (But come on, it was clear enough from the context already.) – alexis Jun 28 '16 at 10:31
4

As far as the OS is concerned, a file containing shell script is just data. If you pass the name of such a data file to the source command or pass it on the command line to an invocation of the bash shell, all the OS sees is a string that happens to coincide with the name of a file containing data.

How would the execute bit be at all relevant in that case?

3

The distinction is important because you may have a file of shell commands which is not useful as an executable, but only useful when sourced. For this file you can turn off the execute bit and then it will never be accessed unless explicitly in a source command. The reason for such a thing is to have side effects on the shell it is run from. For a specific example, I have a script called fix_path which looks at and modifies the path.

1

Just in case anyone is interested in further studies and/or clarification: In a nearly POSIX compliant shell implemented a while ago the internal workings of the 'exec_program()' and the 'builtin_source()' functions are very exemplary. In those functions you see exactly what is the difference between them:

https://github.com/rsenn/shish/blob/master/src/builtin/builtin_source.c

https://github.com/rsenn/shish/blob/master/src/exec/exec_program.c

basically sourcing can be seen as the shell temporarily redirecting its internal file descriptor where it parses shell script from (the terminal in interactive mode). so it is very similar to other redirections like <input_file.txt and >>append_to_something.list and these just need to open and close files.

so executing is handled by the execve() system call for which the execution bit is mandatory.

I recall seeing some systems which allow execution of ELF/a.out binaries, but via executing the "/lib/ld-dynamic-linker.so" and with the binary program (without exec bit) as first argument. I believe that was on some DEC Alpha or VAX machines (Could it have been SCO Unix?)

-2

Another point of view:

Sourced script basically consists of shell builtins and program calls. Shell builtins (with source among them) are parts of the shell and the shell must be executable in the first place. Every program called (that being ELF, another script with shebang, whatever) has to have execution bit set, otherwise it will not run.

So it is not against the use of an execution bit, because nothing without execution bit will run. The validation doesn't occur for the sourced script as a whole; it is performed for every part separately, but it is.

  • Shell buildins are not sourced from a shell script. Typically they are part of the shell executable. In ksh93, zsh and a couple of other shells they can be shell extensions. – fpmurphy Jun 23 '16 at 0:28
  • @fpmurphy1 Isn't this my sentence "shell builtins (...) are parts of the shell"? – Kamil Maciorowski Jun 23 '16 at 3:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.