将 C 结构体的指针传递给 Python 中的 ioctl

问题描述 投票:0回答:1

我正在编写一个需要使用

FS_IOC_ADD_ENCRYPTION_KEY
ioctl 的 Python 脚本。

此 ioctl 需要一个类型(指向)

fscrypt_add_key_arg
的参数,该参数在 Linux 内核头文件中具有以下定义:

struct fscrypt_add_key_arg {
    struct fscrypt_key_specifier key_spec;
    __u32 raw_size;
    __u32 key_id;
    __u32 __reserved[8];
    __u8 raw[];
};

#define FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR        1
#define FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER        2
#define FSCRYPT_KEY_DESCRIPTOR_SIZE             8
#define FSCRYPT_KEY_IDENTIFIER_SIZE             16

struct fscrypt_key_specifier {
    __u32 type;     /* one of FSCRYPT_KEY_SPEC_TYPE_* */
    __u32 __reserved;
    union {
            __u8 __reserved[32]; /* reserve some extra space */
            __u8 descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
            __u8 identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
    } u;
};

这是我的Python代码:

import fcntl
import struct

FS_IOC_ADD_ENCRYPTION_KEY = 0xc0506617

policy_data.key_descriptor: str = get_key_descriptor()
policy_key: bytes = get_policy_key(policy_data)

fscrypt_key_specifier = struct.pack(
    'II16s',
    0x2,
    0,
    bytes.fromhex(policy_data.key_descriptor)
)

fscrypt_add_key_arg = struct.pack(
    f'{len(fscrypt_key_specifier)}sII8I{len(policy_key)}s',
    fscrypt_key_specifier,
    len(policy_key),
    0,
    0, 0, 0, 0, 0, 0, 0, 0,
    policy_key
)

fd = os.open('/mnt/external', os.O_RDONLY)
res = fcntl.ioctl(fd, FS_IOC_ADD_ENCRYPTION_KEY, fscrypt_add_key_arg)

print(res)

当我执行此代码时,我得到一个

OSError
:

Traceback (most recent call last):
  File "/home/foo/fscryptdump/./main.py", line 101, in <module>
    res = fcntl.ioctl(fd, FS_IOC_ADD_ENCRYPTION_KEY, fscrypt_add_key_arg)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
OSError: [Errno 22] Invalid argument

我已经仔细检查了该 ioctl 的文档,我认为我传递的值是正确的,但它们的打包方式可能存在问题。

我该如何解决这个问题?

python c ioctl
1个回答
0
投票

将我的评论扩展为答案:

Python
struct
模块和 C
union
s

据我所知,

struct
模块没有针对C
union
的特殊规定。尽管我认为这不会干扰您在这种特殊情况下的工作,但使用
struct
打包或解包包含联合的结构是一个潜在的问题。结构尺寸和布局布局受每个成员(包括
union
)的尺寸和对齐要求的影响,因此要在包含
struct
的结构中使用
union
,您不仅需要手动考虑
union
' 自己的布局,以及它们对包含结构的影响。

union
是固定大小的数据类型,足以容纳其最大的成员。但它们也可以有尾部填充,因此
union
可以严格大于其最大成员。这取决于所使用的 C 语言实现。

联合尺寸

C

unions
固定大小 数据类型,足够大以包含其任何一个成员。因此,...

struct fscrypt_key_specifier {
    __u32 type;     /* one of FSCRYPT_KEY_SPEC_TYPE_* */
    __u32 __reserved;
    union {
            __u8 __reserved[32]; /* reserve some extra space */
            __u8 descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];
            __u8 identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
    } u;
};

... 至少有 32 个字节长,以便能够容纳成员

__reserved
。因此,正如 @TurePålsson 首先观察到的那样,格式
'II16s'
不可能是打包此类结构的正确格式。它将生成至少小 16 个字节的表示,但如果成员
u
实际上大于 32 个字节,则可能会更加不足。

实际上,根据代码注释和一般编程原则,我预计

FSCRYPT_KEY_DESCRIPTOR_SIZE
FSCRYPT_KEY_IDENTIFIER_SIZE
都不会超过 32,即
__reserved
成员的大小。此外,我还做出了一个有根据的猜测,包含三个字节数组(最大的包含 32 个字节)的联合不会使用任何尾随填充进行布局,因此其大小恰好是 32 个字节。

其他注意事项

标准。原生数据类型大小

内核的 __u32 和 __u8 数据类型是显式大小数据类型。前者不一定与宿主原生的

unsigned int
类型相匹配。这正是
struct
模块的“标准”数据类型大小旨在解决的情况,但您提供的格式指定“本机”数据类型大小(默认情况下)。也许这是一个没有区别的区别,但可以想象,您或继承您代码的人会遇到一个平台,其中相关的本机大小struct
的标准大小不匹配。

为了更正确地匹配 C 结构定义,您应该使用指定“标准”数据大小和本机字节顺序的格式。这是通过在其前面添加

=

 来实现的。您还需要选择与结构匹配的标准尺寸的格式代码,但在这种情况下不需要任何更改。

工会会员规模

编写联合的一种方法是使用一种格式来表达联合的完整大小(可能是 32 字节)的成员。但您似乎假设您正在编写的联合成员的大小是 16 个字节。在这种情况下,与您打算编写的结构最精确匹配的格式将使用该大小。但是,在这种情况下,您需要考虑联合的额外大小,您应该能够通过在格式中表达填充字节来实现这一点。这至少有一个优点,即您可以使用相同的格式来

un打包这种类型的结构,并使用相同的联合成员。

整体推荐

使用其中一种格式进行打包

fscrypt_key_specifier

  • '=II16s16x'

    (标准类型大小、本机字节顺序、16 字节联合成员、联合中成员末尾后的 16 字节)。 
    struct.pack()
     将对填充进行零填充。它还会根据需要截断或填充数据,使其恰好为 16 字节。 
    struct.unpack()
     将为成员读取 16 个字节,并提供恰好那么长的 
    bytes

  • '=II32s'

    (标准类型大小、本机字节顺序、32 字节联合成员)。 
    struct.pack()
     将根据需要截断或填充数据,使其恰好为 32 字节。 
    struct.unpack()
     将为成员读取 32 个字节,并提供恰好那么长的 
    bytes

  • 以上任何一个都没有前导

    =

    。这将为前两个成员使用本机数据类型大小(和字节顺序)。尽管其中任何一个都可能适合您,但严格来说,它们并未与目标结构类型正确匹配。

© www.soinside.com 2019 - 2024. All rights reserved.