15

I created a Bash script which echoes "Hello World". I also created a test user, bob, using adduser.

Nobody has permission to execute that file as denoted by ls:

$ ls -l hello.sh 
-rw-r--r-- 1 george george 19 Mai 29 13:06 hello.sh

As we can see from the above the file's owner is george where he has only read and write access but no execute access. But logged in as george I am able to execute the script directly:

$ . hello.sh 
Hello World

To make matters worse, I log in as bob, where I have only read permission, but I am still able to execute the file:

$ su bob
Password: 
$ . /home/george/testdir/hello.sh 
Hello World

What's going on?

6
  • 1
    Regarding the broader conceptual issue: Is there any way to stop a user from creating executables and running them? Commented May 30, 2020 at 20:34
  • 1
    General rule: if the user has (r) permission on a file that isn't suid or sgid, the (x) permission is advisory. Ways can be found to make it run.
    – Joshua
    Commented May 31, 2020 at 16:49
  • This question might be better renamed something like "why can bash source files that do not have the execute bit set" Commented Jun 2, 2020 at 18:28
  • 2
    Could be @ThorSummoner. But I am thinking that If I knew about bash sourcing files maybe I wouldn't have that question. I literally learnt sourcing here. What I am trying to say is that it could be easier for an amateur (like me) to locate this question. I could be wrong though...
    – Themelis
    Commented Jun 2, 2020 at 20:58
  • 2
    @Themelis I think you are right about the current title being easier to find.
    – AdminBee
    Commented Jun 4, 2020 at 11:31

3 Answers 3

32

In your examples, you are not executing the files, but sourcing them.

Executing would be via

$ ./hello.sh

and for that, execution permission is necessary. In this case a sub-shell is opened in which the commands of the script file are executed.

Sourcing, i.e.

$ . hello.sh

(with a space in between) only reads the file, and the shell from which you have called the . hello.sh command then executes the commands directly as read, i.e. without opening a sub-shell. As the file is only read, the read permission is sufficient for the operation. (Also note that stating the script filename like that invokes a PATH search, so if there is another hello.sh in your PATH that will be sourced! Use explicit paths, as in . ./hello.sh to ensure you source "the right one".)

If you want to prevent that from happening, you have to remove the read permission, too, for any user who is not supposed to be using the script. This is reasonable anyway if you are really concerned about unauthorized use of the script, since

  • non-authorizeded users could easily bypass the missing execution permission by simply copy-and-pasting the script content into a new file to which they could give themselves execute permissions, and
  • as noted by Kusalananda, otherwise an unauthorized user could still comfortably use the script by calling it via
    sh ./hello.sh
    
    instead of
    ./hello.sh
    

because this also only requires read permissions on the script file (see this answer e.g.).

As a general note, keep in mind that there are subtle differences between sourcing and executing a script (see this question e.g.).

4
  • Could be worth mentioning ./script.sh vs. sh ./script.sh?
    – Kusalananda
    Commented May 29, 2020 at 21:17
  • 1
    @D.BenKnoble: Surprised me, too. I tested with . man which produces bash: .: /usr/bin/man: cannot execute binary file with Bash 5.0.16 on Arch GNU/Linux. Commented May 30, 2020 at 8:09
  • Hello from 2024, AdminBee. "Use explicit paths, as in . ./hello.sh to ensure you source "the right one". — At least in Zsh, . and source work differently. Would source ./hello.sh make more sense than source hello.sh nevertheless?
    – jsx97
    Commented Jun 25 at 15:23
  • 1
    @jsx97 Hello back. This answer focuses on the behavior of bash, because the OP had tagged the question with bash, so not all statements can be applied to other shells. For zsh, your linked answer implies that forcing to source the script in the current directory by prepending ./ is not necessary, and I can't come up with a good reason why it would still be better; however, for an authoritative answer I would refer you to Stephane Chazelas, who is an expert on these subtle differences. Maybe a question about this already exists; if not, you may want to ask you own.
    – AdminBee
    Commented Jun 25 at 15:33
14

The execute permission simply means that this file can be executed. However, when you source it (. hello.sh or source hello.sh) or when you pass it as an argument to a shell interpreter (sh hello.sh), you aren't executing the file, you're executing another command (. or sh) and passing the file as an argument to that command.

So to answer your question, the reason you can "execute" the file with . hello.sh is the same reason why you can run cat hello.sh: you are just reading the file, not executing it.

To illustrate:

$ ls -l
total 4.0K
-r--r--r-- 1 terdon terdon 21 May 29 12:29 foo.sh

$ cat ./foo.sh 
#!/bin/sh
echo Hello

$ ./foo.sh
bash: ./foo.sh: Permission denied

$ sh ./foo.sh
Hello

As you can see, I cannot actually execute the script, but I can read it—either with cat or with sh—perfectly well.

6
  • That means that terdon does not have the execute permission in your example but the cat programm does? And if yes that's because of the read permission?
    – Themelis
    Commented May 29, 2020 at 11:34
  • 1
    @Themelis I think you misunderstood. The cat call actually outputs only the content, without executing it.
    – AdminBee
    Commented May 29, 2020 at 11:35
  • 1
    @Themeli, yes pretty much (although "terdon" is my user, the script is foo.sh). The script doesn't have execute permission, but the program cat does. And it is cat that is being executed, not the script.
    – terdon
    Commented May 29, 2020 at 11:35
  • 1
    I've also removed read permission and I can see now that cat is not able to read the script thus cannot function. Thanks for the clarifications @AdminBee, @Terdon.
    – Themelis
    Commented May 29, 2020 at 11:41
  • 5
    Also, note that if the user has read access to the script, they can copy it, and then since they own the copy they can set whatever permissions they want (including execute access) on the copy. Commented May 29, 2020 at 21:11
8

The very short version is: you are not executing the file. You are reading it into the shell, which then executes it.

Note that some language interpreters check the execute permissions of a file they are being asked to execute and will refuse to do so if the user doesn't have appropriate permissions. But that is a completely discretionary check by the author of that particular interpreter, and is not enforced by the operating system.

Let's make a little thought experiment.

I think we can both agree, that if a program has executable permissions, I should be allowed to execute this program. I think we can also agree that this program should be allowed to print the string "Hello World" to the console.

Further, we can probably also agree that a program should be able to read a file as long as that file has read permissions. And it doesn't really matter what the content of that file is, as long as the file has the read permission bit set, the program should be allowed to read it.

Okay, so, since we have agreed that the program should be allowed to print "Hello World" to the console and we have agreed that the program should be allowed to read the file, you must logically also agree that the program should be allowed to read the file and print "Hello World" to the console. And we have also said that whether or not the program is allowed to read the file depends on the permissions, and not on the content, so it should also be allowed to read the file and print "Hello World" to the console if the content of the file is echo Hello World.

Now, can you also agree that since the program is allowed to read the file and since the program is allowed to print "Hello World" to the console, that it is also allowed to check the content of the file and only print "Hello World" to the console if the content of the file is echo Hello World?

Well, but then it is "interpreting", in other words executing the file, and you have just agreed with me on every step of the way that it should be allowed to do that!

And that is exactly what is happening here. The shell is simply reading a text file, which it is allowed to do because the text file has read permissions. And the shell is executing the instructions in the text file, which it is allowed to do because the shell has execute permissions.

3
  • Good example, thanks!
    – Themelis
    Commented May 29, 2020 at 22:14
  • 2
    Another point: even if the language interpreter enforces a file's execute permissions, if you can read the file, you can copy it and set the execute permissions on your own copy. In other words, the execute bit is really just a kind of convenience based on various conventions. There are always ways to execute the contents of a file which can be read.
    – jrw32982
    Commented Jun 3, 2020 at 17:20
  • @jrw32982supportsMonica: Agreed. In this instance, it's more a protection against your own stupidity, by e.g. making sure to mount untrusted filesystems with noexec. Commented Jun 3, 2020 at 17:34

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