Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[linux] how can psutil show exe if the linx is not readable? #2334

Open
calestyo opened this issue Dec 9, 2023 · 4 comments
Open

[linux] how can psutil show exe if the linx is not readable? #2334

calestyo opened this issue Dec 9, 2023 · 4 comments

Comments

@calestyo
Copy link

calestyo commented Dec 9, 2023

Summary

  • OS: Linux 6.5.13
  • Architecture: 64bit
  • Psutil version: 5.9.5
  • Python version: 3.11.7
  • Type: core

Description

I wondered how psutil the following can happen.

As a normal user:

$ id -u
1000
$ 
$ readlink /proc/3763/exe; echo $?
1
$ readlink /proc/4372/exe; echo $?
1

the exe information (and others) for these processes (which are Xorg and lightdm) cannot be read.

But their cmdline can:

$ hd /proc/3763/cmdline
00000000  2f 75 73 72 2f 6c 69 62  2f 78 6f 72 67 2f 58 6f  |/usr/lib/xorg/Xo|
00000010  72 67 00 3a 30 00 2d 73  65 61 74 00 73 65 61 74  |rg.:0.-seat.seat|
00000020  30 00 2d 61 75 74 68 00  2f 76 61 72 2f 72 75 6e  |0.-auth./var/run|
00000030  2f 6c 69 67 68 74 64 6d  2f 72 6f 6f 74 2f 3a 30  |/lightdm/root/:0|
00000040  00 2d 6e 6f 6c 69 73 74  65 6e 00 74 63 70 00 76  |.-nolisten.tcp.v|
00000050  74 37 00 2d 6e 6f 76 74  73 77 69 74 63 68 00     |t7.-novtswitch.|
0000005f
$ hd /proc/4372/cmdline
00000000  6c 69 67 68 74 64 6d 00  2d 2d 73 65 73 73 69 6f  |lightdm.--sessio|
00000010  6e 2d 63 68 69 6c 64 00  31 33 00 32 34 00        |n-child.13.24.|
0000001e

Now, when again as the same user (UID 1000), I do the following with psutil:

>>> import psutil
>>> p1=psutil.Process(pid=3763)
>>> p2=psutil.Process(pid=4372)
>>> p1.exe()
'/usr/lib/xorg/Xorg'

>>> p2.exe()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/psutil/__init__.py", line 671, in exe
    return guess_it(fallback=err)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/psutil/__init__.py", line 664, in guess_it
    raise fallback
  File "/usr/lib/python3/dist-packages/psutil/__init__.py", line 669, in exe
    exe = self._proc.exe()
          ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/psutil/_pslinux.py", line 1767, in exe
    raise AccessDenied(self.pid, self._name)
psutil.AccessDenied: (pid=4372)

>>>

So if exe is unreadable in both cases, how can it know the exe in the first case?

My assumption is, that it somehow takes cmdline, but perhaps only if that starts with an absolute path?

I tried to look it up in the code, but couldn't find anything at a first glance:
https://github.com/giampaolo/psutil/blob/master/psutil/_pslinux.py#L1763-L1773

If it would somehow use cmdline, that would IMO be a security hole. Processes can modify their own cmdline so when another process would e.g. trust that exe() returns the right executable, this might be wrong and so the user of exe() could be lead into doing bad things.

Any idea how your code does the magic to get the executable despite the symlink in /proc not being readable?

Thanks,
Chris.

@giampaolo
Copy link
Owner

Magic explained:

psutil/psutil/__init__.py

Lines 662 to 687 in 4407540

"""The process executable as an absolute path.
May also be an empty string.
The return value is cached after first call.
"""
def guess_it(fallback):
# try to guess exe from cmdline[0] in absence of a native
# exe representation
cmdline = self.cmdline()
if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'):
exe = cmdline[0] # the possible exe
# Attempt to guess only in case of an absolute path.
# It is not safe otherwise as the process might have
# changed cwd.
if (os.path.isabs(exe) and
os.path.isfile(exe) and
os.access(exe, os.X_OK)):
return exe
if isinstance(fallback, AccessDenied):
raise fallback
return fallback
if self._exe is None:
try:
exe = self._proc.exe()
except AccessDenied as err:
return guess_it(fallback=err)
. :)

@calestyo
Copy link
Author

Well but as I wrote quite lengthy, this is actually a security hole as any process may thereby fake it's executable to be something else.

So could you please reopen and ... well simply not do this?

@giampaolo
Copy link
Owner

Sorry, I was in a rush and only focused on the first part of your post.
I see your point. Reopening, but I'll have to think about this. Maybe we can add a guess=True argument.

@giampaolo giampaolo reopened this Dec 10, 2023
@calestyo
Copy link
Author

No worries.

Uhm, I would rather suggest to call it unsafe. That makes it much more of a heads up and people have a better chance to actually realise what's going on and make a proper choice.

And adding some warning box in the documentation, that when this is True processes might hide their own true executable, would be nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants