嘿我有一个问题试图从python调用ioctl linux系统调用.
在C应用程序中运行以下行我设法获取给定linux命名空间文件描述符的父文件描述符.
#define NS_GET_PARENT _IO(NSIO, 0x2)
struct stat sb;
fd = open("/proc/1337/ns/user", O_RDONLY);
parent_fd = ioctl(fd, NS_GET_PARENT);
但是在python中运行相同的以下脚本会给我“设备的不适当的ioctl”
from fcntl import ioctl
NS_GET_PARENT = (0x7b << (4*2)) | 2
f = open('/proc/1337/ns/user')
fd = ioctl(f.fileno(),NS_GET_PARENT)
通过在两个脚本上运行strace,我发现它们都发出相同的系统调用
open("/proc/1337/ns/user", O_RDONLY) = 3
ioctl(3, _IOC(0, 0xb7, 0x02, 0x00), 0)
区别在于,当python代码返回时,C代码实际上将文件描述符返回到父名称空间
-1 ENOTTY (Inappropriate ioctl for device)
该问题在2台不同的计算机上复制(Linux Kernel 4.13.0-37),并且这两个脚本都在同一个用户上运行.
有谁知道可能导致这个问题的原因?
解决方法:
注意:现在有一个可用的Python 3库,它涵盖Linux名称空间ioctl:PyPi:linuxns-rel,GitHub:thediveo/linuxns_rel.
这是一个棘手的问题.有两个(三个)方面为什么你的例子不起作用,我花了一些时间来弄清楚它们并学习很多关于Linux命名空间的知识.
首先,它是NSIO = 0xb7(而不是NSIO = 0x7b),因为在你的Python示例中,NS_GET_PARENT的正确值是这样的(我最初完全错过了这个bug,并且只有在我从头开始工作PoC之后才会注意到,模仿Linux头文件宏定义非常接近):
NS_GET_PARENT = (0xb7 << (4*2)) | 2
其次,关于Python的ioctl()引发的异常的信息似乎有第二个错误,基于我在自己的Python 3实验中看到的:一个不合适的ioctl是ENOTTY(25). ENOTTY适合错误的NSIO 0x7b而不是0xb7.事实上,当我逐字尝试你的例子时,我得到了一个ENOTTY.那时我没有意识到这是无效请求代码的指示.
使用正确的NS_GET_PARENT请求代码,错误EPERM(1)将适合Linux内核不允许您查看父命名空间的情况,因为权限限制(即使您是超级用户,您可能只在另一个内部)用户用户空间等.
因此,这个更正的代码可以工作,受限于抛出OSError EPERMs的访问限制:
from fcntl import ioctl
NS_GET_PARENT = (0xb7 << (4*2)) | 2
with open('/proc/self/ns/user') as f:
fd = ioctl(f.fileno(), NS_GET_PARENT)
由于您没有提供有关系统用户命名空间设置的详细信息,因此我怀疑您感受到了我所感受到的相同陷阱的受害者:在“root”用户命名空间中询问其父用户工作空间的进程:这只会引发误导EPERM,但这对于ioctl_ns(2)的说法是正确的.
验证上述代码工作的一个好方法是首先启动Firefox,然后是sudo lsns -t user.这将显示一组在其自己的用户,ipc和网络命名空间中运行的Firefox子进程.获取其中一个Firefox子进程的PID,并使用它运行上面的示例:它现在应该成功,如果你返回fstat()文件描述符,它应该引用root用户命名空间.尝试获取root用户命名空间的父级应该再次失败并使用EPERM.