Page 44 - 《软件学报》2026年第7期
P. 44
胡明哲 等: Python 软件包库中 C/C++外部语言调用的安全性分析 2729
if (invalid_condition) {
PyErr_SetString(PyExc_ValueError, “参数无效”); //错误: 异常重复抛出
return NULL;
}
fclose(file);
Py_RETURN_NONE;
}
由于 C/C++可以使用返回值标识异常, 外部函数的返回 NULL 应与异常状态设置保持一致, 否则导致异常状
态与返回值不一致 (E1.3) 漏洞. 如代码 9 所示, 测例 1 中外部函数实现 return_null_without_error 返回 NULL 却未调
用抛出异常的 Python/C API, 可能引发运行时错误. 在代码 9 测例 2 的外部函数实现 set_error_but_return_object 中,
调用 PyErr_SetString 设置异常却返回非空对象 Py_NONE, 此时解释器误判函数成功执行, 导致异常状态丢失.
代码 9. 异常状态与返回值设置不一致测例.
//测例 1: 返回 NULL 但未设置异常 (E1.3)
static PyObject* return_null_without_error(PyObject *self) {
return NULL; //错误: 未设置异常返回 NULL
}
//测例 2: 设置异常返回非 NULL 对象 (E1.3)
static PyObject* set_error_but_return_object(PyObject *self) {
PyErr_SetString(PyExc_RuntimeError, “发生严重错误”);
Py_RETURN_NONE; //错误: 设置异常后返回有效对象
}
2.4 并发控制
Python 解释器通过全局解释器锁 (global interpreter lock, GIL) 实现对内部状态的并发控制 (不考虑 Python 3.13
新引入允许关闭 GIL 的实验特性). 然而, 该机制导致即便在多线程环境下, 同一时刻也仅有一个线程可执行
Python 字节码. 当在 C/C++外部扩展中执行诸如文件读写、网络通信或系统调用等可能发生阻塞的操作时, 若未
显式释放 GIL, 将导致当前线程在阻塞期间独占解释器资源, 从而阻碍其他线程的调度与执行, 形成 GIL 导致的临
界区阻塞 (C1.1). 该漏洞会显著限制 Python 多线程程序在 I/O 密集型任务上的并发性能.
如代码 10 所示, 外部函数实现 blocking_io 在执行阻塞操作 sleep(seconds) 时未使用 Python/C API 宏
Py_BEGIN_ALLOW_THREADS 和 Py_END_ALLOW_THREADS 释放 GIL, 导致当前线程在阻塞期间仍持有
GIL, 阻碍其他线程运行, 进而引发临界区阻塞.
代码 10. GIL 导致的临界区阻塞 (C1.1) 测例.
//在持有 GIL 期间执行阻塞操作
static PyObject* blocking_io(PyObject *self, PyObject *args) {
long seconds;
if (!PyArg_ParseTuple(args, “l”, &seconds)) return NULL;
//释放 GIL, 进入阻塞 I/O
//Py_BEGIN_ALLOW_THREADS
sleep(seconds); //阻塞操作

