今天遇到了一个非常奇怪的问题:在root用户下,python的sys path中包含了/usr/local
路径下的site-packages目录,但是在非root用户下,却没有包含,导致该用户执行程序异常,报:ModuleNotFoundError
。
已经知道python程序在启动过程中会通过site.py
脚本动态修改python path,所以简单的看了下site.py
的代码,发现下面一段逻辑:
def addsitepackages(known_paths, prefixes=None):
"""Add site-packages to sys.path
'/usr/local' is included in PREFIXES if RPM build is not detected
to make packages installed into this location visible.
"""
if ENABLE_USER_SITE and 'RPM_BUILD_ROOT' not in os.environ:
PREFIXES.insert(0, "/usr/local")
for sitedir in getsitepackages(prefixes):
if os.path.isdir(sitedir):
addsitedir(sitedir, known_paths)
return known_paths
通过ENABLE_USER_SITE
这个全局变量来影响是否将/usr/local
加入到site packages的查询地址中。经过查阅,ENABLE_USER_SITE
这个变量是由下面这个方法决定的。
def check_enableusersite():
"""Check if user site directory is safe for inclusion
The function tests for the command line flag (including environment var),
process uid/gid equal to effective uid/gid.
None: Disabled for security reasons
False: Disabled by user (command line option)
True: Safe and enabled
"""
if sys.flags.no_user_site:
return False
if hasattr(os, "getuid") and hasattr(os, "geteuid"):
# check process uid == effective uid
if os.geteuid() != os.getuid():
return None
if hasattr(os, "getgid") and hasattr(os, "getegid"):
# check process gid == effective gid
if os.getegid() != os.getgid():
return None
return True
这里经过debug,返回了一个None
。
于是乎,今天的主角出场了,可以看到上面这段代码中会去判断euid
/egid
和uid
/gid
是否相等,如果不相等,就返回None
,None
是啥意思呢,根据官方给出的解释:
None means it was disabled for security reasons (mismatch between user or group id and effective id) or by an administrator.
我没有disable过呀,那就是user或者group的id和effective id不匹配导致的。
继续debug,终于确认是euid
和uid
不相同导致的,这俩是个什么货呢?
UID: 进程中获取的UID是指进程的创建者,直白点说就是这个进程是谁执行的。
EUID: 进程中的EUID是指进程对系统中文件的访问控制权限是哪个用户的权限。
通常这两个ID是相同的,在用户登陆的时候被设置为/etc/passwd
中的UID。
但是Linux提供了修改的机制,可以通过设定setuid
来实现。当某个可执行文件被设置了setuid后,会使得其他用户在执行该可执行文件时获得该可执行文件所有者的权限。有点绕,说简单点就是,如果一个root用户所属的可执行文件,被设置了setuid
后,其他用户执行时也能获得root权限。也就是上面的EUID
会变成root用户的ID,而UID
依然是执行用户的ID,这样两者就可能不再相同。
于是就明白了,问题所在:启动python进程的可执行文件肯定是被设置了setuid
。但是检查发现python代码文件并没有。难道问题原因找错了?当然不是,启动python进程的应该是python命令,而不是python脚本,所以应该去check python命令文件,果然/usr/bin/python3.6文件被设置了setuid
改回来一切又变得熟悉起来。