classification
Title: Readline module loading in interactive mode
Type: security Stage:
Components: Versions: Python 3.3, Python 3.2, Python 3.1, Python 2.7, Python 2.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Niels.Heinen, brett.cannon, eric.araujo, haypo, jcea, pitrou, r.david.murray
Priority: normal Keywords:

Created on 2011-06-02 11:57 by Niels.Heinen, last changed 2011-06-07 14:28 by r.david.murray.

Messages (9)
msg137473 - (view) Author: Niels Heinen (Niels.Heinen) Date: 2011-06-02 11:57
Running the python binary without a script or using the -i flag will
start the process in interactive mode. The interactive mode requires an
external module to be loaded: readline.

Per default behavior, Python also tries to load this module from the current working directory (see also trace below)

strcpy(0x7fff17609ed8, ".so")                  = 0x7fff17609ed8
fopen64("readline.so", "rb" <unfinished ...>
SYS_open("readline.so", 0, 0666)               = -2
<... fopen64 resumed> )                        = 0
strcpy(0x7fff17609ed8, "module.so")            = 0x7fff17609ed8
fopen64("readlinemodule.so", "rb" <unfinished ...>
SYS_open("readlinemodule.so", 0, 0666)

The module is imported in Modules/main.c line 663:

  if ((Py_InspectFlag || ......
    isatty(fileno(stdin))) {
      PyObject *v;
      v = PyImport_ImportModule("readline");


Why consider this a security bug: basically because you don't expect a
program to import a shared library from your current directory _unless_
you explicitly tell it to (e.g. import blah).

On a multi user system, someone could plant a malicious shared libraries
named "readline.so" in an attempt to hack a user that runs python in
interactive mode.

The risk obviously _very_ low but nevertheless worth to consider improving by, for example, loading readline with a more strict path? (e.g.  python lib directories only?)

Niels



AN EXAMPLE:
-----------
The code below is compiled to readline.so and stored in /tmp:

  void __attribute__ ((constructor)) _load();
  void _load() {
      printf("DING DONG!\n");

  }

foo@foo:/tmp$ ls -l /tmp/readline.so 
-rwxr-x--- 1 nnnnn nnn 7952 Mar 29 16:24 /tmp/readline.so
foo@foo:/tmp$ python
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
DING DONG!
>>>
msg137475 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2011-06-02 12:38
This is a general principle of how Python runs in interactive mode and is not confined to loading readline.  The same would be true for any module loaded during startup, and there are quite a few that are so loaded.  Since loading modules from the current working directory is an important feature of using python in interactive mode, this is not something that is likely to be changed.
msg137529 - (view) Author: Éric Araujo (eric.araujo) * (Python committer) Date: 2011-06-03 15:42
+1 to what David said.  See also #5753.
msg137741 - (view) Author: Niels Heinen (Niels.Heinen) Date: 2011-06-06 15:09
Hi Eric, David,

This means that you cannot type "python" and press <enter> in any shared directory without the risk of a malicious readlinemodule.so being imported and executed.  

I think this is different from a scenario where someone explicitly runs a script or imports a module in interactive mode where it is also reasonable that such a person understands the importing mechanism.

Thanks for the quick responses btw!

Niels
msg137752 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2011-06-06 16:58
I've done a little poking around, and it looks like you are correct and I'm wrong. It appears that readline.so is or should be a special case.  I've added some people to nosy to see what they think.

Specifically, it appears that if I put a file that should shadow a library module that is imported at python startup time (eg: os.py) into my current working directory I still get the os.py from the appropriate lib directory, even though '' is first in my sys.path.  This is not how I thought it worked, but it is my observation.  I tested this on 2.6.6, 2.7.1 and 3.3 tip.
msg137804 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2011-06-07 11:08
I don't think readline is "special-cased":

$ echo "1/0" > logging.py
$ cpython/default/python
Python 3.3a0 (default:d8502fee4638+, Jun  6 2011, 19:13:58) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import logging
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "logging.py", line 1, in <module>
    1/0
ZeroDivisionError: division by zero
msg137821 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2011-06-07 13:13
Python 3.3a0 (default:7323a865457a+, Jun  5 2011, 19:22:38) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.modules['logging']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'logging'
>>> sys.modules['os']
<module 'os' from '/home/rdmurray/python/p33/Lib/os.py'>

The difference is that logging is not imported at startup. So, however os (and friends, there are a lot of modules in sys.modules at startup) is imported, it is different from how readline.so is imported.
msg137824 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2011-06-07 13:21
> The difference is that logging is not imported at startup. So, however
> os (and friends, there are a lot of modules in sys.modules at startup)
> is imported, it is different from how readline.so is imported.

For the record, os is imported by the _io module:

    /* put os in the module state */
    state->os_module = PyImport_ImportModule("os");
    if (state->os_module == NULL)
        goto fail;

(in Modules/_io/_iomodule.c)

This probably happens before sys.path is
adjusted/tweaked/fixed/garbled/whatever.
msg137828 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2011-06-07 14:28
Yeah, that would be my guess.  And readline.so is imported in main at a point where it has decided we are going into interactive mode, which is presumably after all other initialization has taken place, including the path munging.

Thus my suggestion that that particular import of readline.so should be special cased...
History
Date User Action Args
2011-06-07 14:28:12r.david.murraysetmessages: + msg137828
2011-06-07 13:21:53pitrousetmessages: + msg137824
2011-06-07 13:13:00r.david.murraysetmessages: + msg137821
2011-06-07 11:08:03pitrousetmessages: + msg137804
2011-06-06 16:58:41r.david.murraysetnosy: + brett.cannon, pitrou, haypo

messages: + msg137752
versions: + Python 3.1, Python 2.7, Python 3.2, Python 3.3
2011-06-06 15:09:15Niels.Heinensetmessages: + msg137741
2011-06-03 15:42:37eric.araujosetnosy: + eric.araujo
messages: + msg137529
2011-06-02 15:38:18jceasetnosy: + jcea
2011-06-02 12:38:20r.david.murraysetnosy: + r.david.murray
messages: + msg137475
2011-06-02 11:57:39Niels.Heinencreate