4

This link explains the benefits of using noexec option when using mount. However it lists one limitation - if I have a Perl/Python/shell script or a file that starts with #! and will try to execute it - I will be able to do so whether I supplied the option or not.

Is there a way to prevent such execution? I.e. is there an additional option to noexec which I can give and the execution of the scripts will not be possible?

Kamil Maciorowski
  • 69,815
  • 22
  • 136
  • 202
Igor
  • 265
  • 1
  • 4
  • 12
  • "and will try to execute it" How? By running it directly or by passing it to the interpreter? – Ignacio Vazquez-Abrams Nov 21 '17 at 22:18
  • @IgnacioVazquez-Abrams, both ways. I can try to click the script, or I can just type it in the terminal. – Igor Nov 21 '17 at 22:20
  • Neither of those ways answers my question. – Ignacio Vazquez-Abrams Nov 21 '17 at 22:24
  • @IgnacioVazquez-Abrams, well, then I guess I don't understand the question. Could you please clarify? Because I presumed by second you meant just typing `cd && python my_script.py`. – Igor Nov 21 '17 at 22:27
  • 1
    That is the second, yes. And that will not execute it so `noexec` won't affect it. – Ignacio Vazquez-Abrams Nov 21 '17 at 22:28
  • @IgnacioVazquez-Abrams, OK, I guess I misunderstood the answer that question. But lets say I have a script that do `cat /etc/passwd`. Then when I try to run it it will print the content of that file to the stdout, right? And sorry for misinterpreting that answer. – Igor Nov 21 '17 at 23:25
  • That depends on how you "run" it. – Ignacio Vazquez-Abrams Nov 21 '17 at 23:26
  • @IgnacioVazquez-Abrams, the second way. See my answer above. Just presume I do `cd && python my_script.py` and the script will print /etc/passwd file. I am not very familiar with python so can't put this script on. Or just imagine I have a bash script. – Igor Nov 21 '17 at 23:28
  • 1
    Passing it to the interpreter doesn't execute the script, it only reads it. – Ignacio Vazquez-Abrams Nov 21 '17 at 23:29
  • @IgnacioVazquez-Abrams, so thats what `noexec` will do in this case? It will just print `cat /etc/passwd`, but not the actual file? Also what is the first way you were asking in the first reply? – Igor Nov 21 '17 at 23:31
  • `noexec` will do nothing. The interpreter will read the script and do whatever the script tells it to. – Ignacio Vazquez-Abrams Nov 21 '17 at 23:31
  • 1
    The first way is running `path/to/script.py`. This *will* execute the script, if possible. – Ignacio Vazquez-Abrams Nov 21 '17 at 23:32
  • @IgnacioVazquez-Abrams, so the content of the file /etc/passwd will be printed in that case? I don't want that and would like to avoid that. – Igor Nov 21 '17 at 23:33
  • @IgnacioVazquez-Abrams, but the first way will be prevented by `noexec`. – Igor Nov 21 '17 at 23:34
  • `noexec` will prevent the script from being executed, but not read. – Ignacio Vazquez-Abrams Nov 21 '17 at 23:35
  • Let us [continue this discussion in chat](http://chat.stackexchange.com/rooms/69089/discussion-between-igor-and-ignacio-vazquez-abrams). – Igor Nov 21 '17 at 23:36
  • @IgnacioVazquez-Abrams, I don't care about reading for now, at least. I just need to prevent executing the binary and the script. So if I run the script with `python test_script.py` as in the sample above will it print the content of the script or the content of /etc/passwd? – Igor Nov 21 '17 at 23:57
  • @IgnacioVazquez-Abrams It turned out (in my opinion) there was more beneath the surface. Check my answer, you may find it interesting. – Kamil Maciorowski Dec 13 '17 at 22:25

1 Answers1

12

There's a major misunderstanding here. Let's make these things clear.

First of all, the limitation you refer to, as it is stated, is not true:

However, when a script (a text file that begins with she-bang line; i.e., a line that begins with #!) is given to some shells (bash), it will run the executable named on that line (e.g., /usr/bin/perl) and connect the content of the script file to the stdin of that executable, which may not be on that drive.

Surprisingly it seems to explain the ability to execute, despite noexec. I think the asker there got it all wrong in the first place and it wasn't his or her fault! One wrong assumption in the question caused another wrong assumption in the answer.

What's wrong then?

1. Bind mount is specific

To get some context let's see what happens when you try to bind mount as read-only. There's this question: Why doesn't mount respect the read only option for bind mounts? The conclusion is:

To achieve the desired result one needs to run two commands:

mount SRC DST -o bind
mount DST -o remount,ro,bind

Newer versions of mount (util-linux >=2.27) do this automatically when one runs

mount SRC DST -o bind,ro

But when you try to use noexec instead of ro, you still need two commands! In my Kubuntu I have util-linux 2.27.1-6ubuntu3.3 and this command:

mount SRC DST -o bind,noexec

ignores noexec, I need to remount. It's the same if mounting is via /etc/fstab. You can experiment. At any time check with plain mount command what the actual options are.

I bet the asker thought the mount was with noexec option, but actually it was not. He or she was able to execute a script from within allegedly noexec mountpoint. It was strange, hence the question.

Then the answer author interpreted this, as if it was the shell what reads shebang, calls another executable and doesn't worry about noexec for the script. If the mountpoint was really noexec then this would be a reasonable speculation.

But…

2. It's a common myth that shells read shebangs; kernel does

Read How does the #! shebang work? and notice one of the answers there had originally followed the myth, then it was corrected.

So if you have:

  • a mountpoint /mnt/foo/ with noexec option,
  • a script /mnt/foo/script.py which is otherwise executable (e.g. chmod -x … was invoked),
  • a shebang like #!/usr/bin/python as the first line in the script

and you run it like this

/mnt/foo/script.py

then your Linux kernel won't let you because of noexec. It would have happened in this other question if the mounting was actually noexec there; but I believe it was not.

3. Still, there are two ways to "execute" a script

From comments:

"and will try to execute it" How? By running it directly or by passing it to the interpreter?

Running it directly means:

 /mnt/foo/script.py

This will honor noexec as elaborated above. The executable is script.py.

Passing it to the interpreter means:

python /mnt/foo/script.py

In this case the executable is python. It doesn't matter if foo/ is mounted with noexec; it doesn't matter if script.py is executable at all; it doesn't matter what the shebang is. The point is script.py is not executed, it's read.

As long as user can read a file and run proper interpreter, there's no way to prevent passing the file to the interpreter; but it's not the file that is executed.

Kamil Maciorowski
  • 69,815
  • 22
  • 136
  • 202