How to read file capabilities using Python?
Asked Answered
O

3

10

On Linux systems root privileges can be granted more selectively than adding the setuid bit using file capabilities. See capabilities(7) for details. These are attributes of files and can be read using the getcap program. How can these attributes be retrieved in Python?

Even though running the getcap program using e.g. subprocess for answering such a question is possible it is not desirable when retrieving very many capabilities.

It should be possible to devise a solution using ctypes. Are there alternatives to this approach or even libraries facilitating this task?

Oldtime answered 4/2, 2014 at 18:48 Comment(0)
B
7

Python 3.3 comes with os.getxattr. If not, yeah... one way would be using ctypes, at least to get the raw stuff, or maybe use pyxattr

For pyxattr:

>>> import xattr
>>> xattr.listxattr("/bin/ping")
(u'security.capability',)
>>> xattr.getxattr("/bin/ping", "security.capability")
'\x00\x00\x00\x02\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

For Python 3.3's version, it's essentially the same, just importing os, instead of xattr. ctypes is a bit more involved, though.

Now, we're getting the raw result, meaning that those two are most useful only retrieving textual attributes. But... we can use the same approach of getcap, through libcap itself [warning, this will work as-is only on Python 2, as written originally - read the note at the end for details]:

import ctypes

libcap = ctypes.cdll.LoadLibrary("libcap.so")
cap_t = libcap.cap_get_file('/bin/ping')
libcap.cap_to_text.restype = ctypes.c_char_p
libcap.cap_to_text(cap_t, None)

which gives me:

'= cap_net_raw+p'

probably more useful for you.

PS: note that cap_to_text returns a malloced string. It's your job to deallocate it using cap_free

Hint about the "binary gibberish":

>>> import struct
>>> caps = '\x00\x00\x00\x02\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> struct.unpack("<IIIII", caps)
(33554432, 8192, 0, 0, 0)

In that 8192, the only active bit is the 13th. If you go to linux/capability.h, you'll see that CAP_NET_RAW is defined at 13.

Now, if you want to write a module with all those constants, you can decode the info. But I'd say it's much more laborious than just using ctypes + libcap.

PS2: I notice another answer mentioning that there are problems with my invocation on the ctype solution. That's because I likely wrote that using Python 2.x back then in 2014 (as Python 3 offered os.getxattr).

The key here is reminding that back in Python 2.x there were no bytes objects, and str and unicode where separate entities. Python 3.x merged those two so that str is now basically unicode, and that may mess up with the C interface.

So, if using Python 3 (likely the case at this point), make sure to turn your strings into bytes (e.g., foo_bar.encode('utf-8')) before passing them to libcap, and the reverse operation with the results.

Bandmaster answered 4/2, 2014 at 19:31 Comment(10)
Could you give a concrete example? E.g. evaluating the capabilities of /bin/ping?Oldtime
Depends on which one you want to use... pyxattr comes with examples in the documentation.Dutiable
All of them? ;-) I can upvote multiple answers. Just make sure that the result is human readable.Oldtime
Btw, xtattr's documentation insists that getxattr is deprecated in favour of get, and so on, from version 0.4... but I installed version 0.7.3 and they're not there...Dutiable
The pyxattr based solution is useless, because all you get is binary gibberish. The ctypes solution is laborious, but gives a useful result, hence the upvote.Oldtime
It's not binary gibberish. It's a struct with data that you can decode using the #defines in capability.h. I'm afraid I'm not going to do that work for you.Dutiable
Make sure to use struct.unpack("<IIIII", caps), or it will fail horribly on big-endian architectures.Oldtime
I love the solution that uses libcap, could you also write an example of setcap?Wrist
@Wrist uh... it's been a loooong time since I wrote this and I'd have to look it up. Plus, it's out of the scope of the question. Would you post a separate one? You can reference this answer (if you link the new question here as well I'll try to answer if I have the time)Dutiable
Nevermind.. I found it: https://mcmap.net/q/1096003/-how-to-read-file-capabilities-using-pythonWrist
A
1

I tried the code from Ricardo Cárdenes's answer, but it did not work properly for me, because some details of the ctypes invocation incorrect. This issue caused a truncated path string to be passed to getxattr(...) inside of libcap, which thus returned the wrong capabilities list for the wrong item (the / directory, or other first path character, and not the actual path).

It is very important to remember and account for the difference between str and bytes in Python 3.X. This code works properly on Python 3.5/3.6:

#!/usr/bin/env python3

import ctypes
import os
import sys

# load shared library
libcap = ctypes.cdll.LoadLibrary('libcap.so')

class libcap_auto_c_char_p(ctypes.c_char_p):
    def __del__(self):
        libcap.cap_free(self)

# cap_t cap_get_file(const char *path_p)
libcap.cap_get_file.argtypes = [ctypes.c_char_p]
libcap.cap_get_file.restype  = ctypes.c_void_p

# char* cap_to_text(cap_t caps, ssize_t *length_p)
libcap.cap_to_text.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
libcap.cap_to_text.restype  = libcap_auto_c_char_p

def cap_get_file(path):
    cap_t = libcap.cap_get_file(path.encode('utf-8'))
    if cap_t is None:
        return ''
    else:
        return libcap.cap_to_text(cap_t, None).value.decode('utf-8')

print(cap_get_file('/usr/bin/traceroute6.iputils'))
print(cap_get_file('/usr/bin/systemd-detect-virt'))
print(cap_get_file('/usr/bin/mtr'))
print(cap_get_file('/usr/bin/tar'))
print(cap_get_file('/usr/bin/bogus'))

The output will look like this (anything nonexistent, or with no capabilities set just returns '':

= cap_net_raw+ep
= cap_dac_override,cap_sys_ptrace+ep
= cap_net_raw+ep
Asternal answered 25/4, 2018 at 18:45 Comment(0)
W
0

getcap.py

import ctypes, sys, glob

libcap = ctypes.cdll.LoadLibrary(glob.glob("/lib/libcap*")[0])
cap_t = libcap.cap_get_file(sys.argv[1])
libcap.cap_to_text.restype = ctypes.c_char_p
print libcap.cap_to_text(cap_t, None)

setcap.py (set all capabilities =ep)

import ctypes, sys, glob

libcap = ctypes.cdll.LoadLibrary(glob.glob("/lib/libcap*")[0])

libcap.cap_from_text.argtypes = [ctypes.c_char_p]
libcap.cap_from_text.restype = ctypes.c_void_p
libcap.cap_set_file.argtypes = [ctypes.c_char_p,ctypes.c_void_p]

cap = '=ep'
path = sys.argv[1]
print(path)
cap_t = libcap.cap_from_text(cap)
status = libcap.cap_set_file(path,cap_t)

if(status == 0):
    print (cap + " was successfully added to " + path)

Notes:

  • I search for the library because on some systems the library name changes.
  • Setcap sets all capabilies. You can change the code or add an argv to add any capability.
Wrist answered 6/12, 2023 at 0:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.