Page 42 - 《软件学报》2026年第7期
P. 42
胡明哲 等: Python 软件包库中 C/C++外部语言调用的安全性分析 2727
符串对从 Python 侧传入的 Python 对象实参进行解包和跨语言类型转换. 跨语言的类型一致性建立在 3 个要素的
一致性上: 格式化字符串的声明、Python 侧传入的对象、C/C++侧解包后接收参数的变量类型. 这种复杂性源自
Python 动态类型系统与 C/C++静态类型系统之间的本质差异, 并可能存在多种不匹配的漏洞.
如代码 6 所示是格式化字符串不匹配 (T2.1) 的漏洞测例, 在代码 6 的测例 1 中, 格式化字符串“i”要求传入参
数为 Python 整型对象并将其转换为 C 的 int 类型对象, 若 Python 侧传入其他类型对象, PyArg_ParseTuple 无法完
成类型转换, 返回 false 并设置 TypeError 异常. 在代码 6 的测例 2 中, 格式化字符串“ii”声明解析两个整型位置参
数, 调用者仅提供一个参数时, 跨语言传参在尝试解包并转换第 2 个变量时发生错误.
代码 6. 格式化字符串不匹配 (T2.1) 测例.
//测例 1: 格式字符串与实际参数类型不匹配 (要求整型, 传入其他类型)
static PyObject* wrong_format_int(PyObject *self, PyObject *args) {
int value;
//格式字符串声明为“i” (整型), 传入其他类型触发 TypeError
if (!PyArg_ParseTuple(args, “i”, &value)) return NULL;
return PyLong_FromLong(value * 2);
}
//测例 2: 参数数量不匹配 (要求 2 个位置参数, 实际 C/C++侧接收变量只有 1 个)
static PyObject* wrong_arg_count(PyObject *self, PyObject *args) {
int a;
//格式字符串声明为“ii” (两个整型位置参数), 但 C/C++侧只有一个变量 a 去接收}
if (!PyArg_ParseTuple(args, “ii”, &a)) return NULL;
return PyLong_FromLong(a);
}
在使用 Python/C API 编写扩展模块时, 每一个外部函数 (或方法) 都必须通过 PyMethodDef 结构体进行注册.
该结构体包含 4 个字段 (如图 2 中 message.c 第 15–18 行), 其中第 3 个字段是一个比特位域, 用于指明该外部函数
的调用约定. 该位域的值决定解释器在运行时如何封装参数并调用外部函数实现. 因此, 该位域的值必须与实际外
部函数实现的参数类型相匹配, 调用约定不匹配 (T2.2) 将导致调用栈破坏、参数传递错误等安全漏洞. 所有合法
的位域组合及其含义和约束如表 3 所示.
表 3 PyMethodDef 结构体调用约定位域标志及其适配的外部函数实现类型
标志值
标志组合 用法 参数数量 适配外部函数实现类型
(十六进制)
METH_VARARGS 0x0001 处理位置参数 (元组形式) 2 PyCFunction
METH_VARARGS | METH_KEYWORDS 0x0003 处理位置和关键字参数 3 PyCFunctionWithKeywords
METH_FASTCALL 0x0080 快速调用 (仅位置参数) 3 PyCFunctionFast
METH_FASTCALL | METH_KEYWORDS 0x0082 快速调用 (支持关键字参数) 4 PyCFunctionFastWithKeywords
METH_METHOD | METH_FASTCALL | 0x0282 支持定义式类方法 5 PyCMethod
METH_KEYWORDS
METH_NOARGS 0x0004 无参数 2 PyCFunction
METH_O 0x0008 单个对象参数 2 PyCFunction
METH_CLASS 0x0010 类方法 (绑定到类) 2 PyCFunction
METH_STATIC 0x0020 静态方法 (无实例绑定) 2 PyCFunction
如代码 7 所示, 测例 1 的外部函数实现 wrong_flags_noargs 使用 METH_NOARGS 标志, 表明该函数调用应仅
接受代表外部函数所属模块或外部方法所属类对应的实例对象 self 作为隐藏参数而不包含实际参数元组 args, 但

