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); //阻塞操作
   39   40   41   42   43   44   45   46   47   48   49