python / cpython

The Python programming language
https://www.python.org
Other
63.15k stars 30.23k forks source link

POSIX semantics of PATH search in execvpe is not respected #64147

Open ea85c2d2-6c1b-404f-925b-b563a6edde5b opened 10 years ago

ea85c2d2-6c1b-404f-925b-b563a6edde5b commented 10 years ago
BPO 19948
Nosy @ericvsmith, @bitdancer, @ztane

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields: ```python assignee = None closed_at = None created_at = labels = ['type-bug', 'library'] title = 'POSIX semantics of PATH search in execvpe is not respected' updated_at = user = 'https://bugs.python.org/glondu' ``` bugs.python.org fields: ```python activity = actor = 'ztane' assignee = 'none' closed = False closed_date = None closer = None components = ['Library (Lib)'] creation = creator = 'glondu' dependencies = [] files = [] hgrepos = [] issue_num = 19948 keywords = [] message_count = 10.0 messages = ['205819', '205850', '205899', '205900', '205908', '205909', '205910', '279943', '281335', '281338'] nosy_count = 5.0 nosy_names = ['eric.smith', 'r.david.murray', 'ztane', 'glondu', 'Alex Samuel'] pr_nums = [] priority = 'normal' resolution = None stage = None status = 'open' superseder = None type = 'behavior' url = 'https://bugs.python.org/issue19948' versions = ['Python 2.7'] ```

ea85c2d2-6c1b-404f-925b-b563a6edde5b commented 10 years ago

Hello,

According to [1],

"In the cases where the other members of the exec family of functions would fail and set errno to [ENOEXEC], the execlp() and execvp() functions shall execute a command interpreter and the environment of the executed command shall be as if the process invoked the sh utility using execl() as follows:

execl(\<shell path>, arg0, file, arg1, ..., (char *)0);"

This is not the case with os.execvp which keeps looking in PATH for other executables. To reproduce:

  1. pick some executable that exists in /usr/bin (let's say "curl")
  2. prepend to PATH a directory where you put an executable file with name "curl" and some random shell commands, without the #! line
  3. run os.execvp("curl", ["curl"])

Instead of running the #!-less shell script, /usr/bin/curl is executed. With GNU libc's execvp(), the shell script is executed. According to my interpretation of POSIX, the shell script should be executed.

[1] http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_01_01

Cheers,

-- Stéphane

ericvsmith commented 10 years ago

What platform is this on? Looking quickly through posix.execve (which is what I think gets called), it looks like it just calls C's execve().

Also, what's your use case for this? I realize it might be a standard behavior, but it seems like a bad idea to me. (Not that that's a reason not to be standards compliant: I'm mostly just curious why you'd want this.)

ea85c2d2-6c1b-404f-925b-b563a6edde5b commented 10 years ago

What platform is this on?

I'm on Linux (Debian testing).

Looking quickly through posix.execve (which is what I think gets called), it looks like it just calls C's execve().

Yes, but I'm talking about os.execvp, here. With the search in PATH.

Also, what's your use case for this?

I discovered that by accident while investigating another bug...

I realize it might be a standard behavior, but it seems like a bad idea to me.

What is the bad idea? Keep looking in subsequent directories in PATH when you find a candidate for which execve() fails? Sorry, but I beg to differ, and POSIX is on my side.

ea85c2d2-6c1b-404f-925b-b563a6edde5b commented 10 years ago

What is the bad idea? Keep looking in subsequent directories in PATH when you find a candidate for which execve() fails? Sorry, but I beg to differ, and POSIX is on my side.

Sorry, I meant "Stop looking in subsequent [..]".

ericvsmith commented 10 years ago

os.execvp calls os._execvpe which calls posix.execv which calls execv. At least that's how I think it works.

ea85c2d2-6c1b-404f-925b-b563a6edde5b commented 10 years ago

os.execvp calls os._execvpe which calls posix.execv which calls execv. At least that's how I think it works.

I am not contesting that. This bug is about the "search the command in PATH" part. More precisely, the fact that os.execvp continues the search after execv fails.

ericvsmith commented 10 years ago

I know that. This is mostly just a note to myself so I can remember what I've looked at the next time I have a few moments to look at the bug. Or, anyone else who wants to track it down.

8e6d84da-a964-4bf2-a518-ae9fdccdaae6 commented 7 years ago

Here's a real-world case where this can cause unexpected results: A shell script has a typo in the shebang ("#/!bin/bash") but the execute bit set. It still runs via the C library's execvp() and also via bash (which uses execve() but reimplements the behavior) but not with Python's os.execvp().

Would there be interest in a patch that fixes this?

ff59cd45-ebe3-4b3e-9696-65dc59a38b8c commented 7 years ago

Someone on Stack Overflow just had a problem where their shell script would work in shell but get OSError: [Errno 8] Exec format error when calling it with subprocess.call. I'd say rather fix this (on POSIX platforms).

Why does Python do path resolving on its own anyway? Shouldn't it delegate these all to appropriate execv* variants.

ff59cd45-ebe3-4b3e-9696-65dc59a38b8c commented 7 years ago

While at it, another POSIX semantic that execvp doesn't support is the behaviour when PATH is not set, e.g. on Linux, the search path is set to '.', followed by confstr(_CS_PATH). It is debatable whether this is desired (having current directory first in search path doesn't exactly sound right, which is also acknowledged by Linux man pages:

NOTES On some other systems, the default path (used when the environment does not contain the variable PATH) has the current working direc‐ tory listed after /bin and /usr/bin, as an anti-Trojan-horse mea‐ sure. Linux uses here the traditional "current directory first" default path.