ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python sqlite3 安全修复:禁止删除 row_factory 与 text_factory 属性防止查询崩溃

Python sqlite3 安全修复:禁止删除 row_factory 与 text_factory 属性防止查询崩溃 Python sqlite3 安全修复禁止删除 row_factory 与 text_factory 属性防止查询崩溃【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython导读sqlite3是 Python 标准库中基于 SQLite 的数据库接口模块。本篇文章围绕 CPython 仓库中的一则变更对应 gh-issue-149738 变更记录展开新版本中连接Connection与游标Cursor对象的row_factory和text_factory属性将不再允许被删除del删除操作会抛出AttributeError从而避免后续查询时因工厂函数缺失而触发解释器崩溃。读完本文你将理解这两个工厂属性在 sqlite3 内部的作用机制、本次变更的源码级实现原理以及如何正确调整代码以适配这一行为变化。变更背景一次查询引发的崩溃在 SQLite 的 Python 封装_sqlite扩展模块中row_factory与text_factory是两个决定结果行如何被构造的核心属性row_factory接收原始元组并返回最终行对象的工厂函数例如sqlite3.Rowtext_factory决定 SQLiteTEXT列的值如何转换为 Python 对象默认是str。旧版本中用户可以通过del con.row_factory或del cur.text_factory这类操作把属性删除。删除后属性槽位被置空但查询执行路径仍会无条件地读取并使用这两个工厂导致在随后执行查询时访问到空指针使解释器直接崩溃crash而非抛出可捕获的 Python 异常。本次变更正是针对这一隐患从 CPython 3.16 起删除这两个属性会被明确禁止。源码实现如何在 C 层禁止删除属性属性描述符的 setter 拒绝 NULL在 CPython 中Connection.row_factory、Connection.text_factory与Cursor.row_factory都是通过成员描述符getset descriptor暴露给 Python 层的。当用户执行del obj.attr时解释器会调用描述符的 setter并传入value NULL。对应实现位于Modules/_sqlite/connection.c 中的connection_set_row_factory与connection_set_text_factoryModules/_sqlite/cursor.c 中的cursor_set_row_factory。三个 setter 的实现逻辑完全一致当检测到value NULL时直接通过PyErr_SetString(PyExc_AttributeError, cannot delete row_factory attribute)或 text_factory 对应的错误信息设置AttributeError并返回-1从而阻止删除其余情况则使用Py_XSETREF安全替换原有引用。// Modules/_sqlite/connection.c连接层 row_factory setter 核心逻辑 if (value NULL) { PyErr_SetString(PyExc_AttributeError, cannot delete row_factory attribute); return -1; } Py_XSETREF(self-row_factory, Py_NewRef(value));工厂属性在 C 层的默认值与存储连接对象创建时Modules/_sqlite/connection.c两个工厂属性会被初始化为安全的默认值self-row_factory Py_NewRef(Py_None); // 默认不使用行工厂 self-text_factory Py_NewRef(PyUnicode_Type); // 默认 TEXT 列转为 str也就是说text_factory默认指向PyUnicode_Typestrrow_factory默认是None。这两个槽位在对象生命周期中始终保存着有效引用并通过Py_VISITconnection.c与Py_CLEARconnection.c参与垃圾回收与销毁流程。为什么删除会崩溃查询路径对工厂的硬依赖text_factory 在列值转换中的读取游标在将 SQLite 结果集转换成 Python 元组时Modules/_sqlite/cursor.c的pysqlite_cursor_convert_arguments附近逻辑见 Modules/_sqlite/cursor.c会无条件读取self-connection-text_factory并按其类型分派// Modules/_sqlite/cursor.cSQLITE_TEXT 列值的转换分派 if (self-connection-text_factory (PyObject*)PyUnicode_Type) { converted PyUnicode_FromStringAndSize(text, nbytes); ... } else if (self-connection-text_factory (PyObject*)PyBytes_Type) { converted PyBytes_FromStringAndSize(text, nbytes); } else if (self-connection-text_factory (PyObject*)PyByteArray_Type) { converted PyByteArray_FromStringAndSize(text, nbytes); } else { converted PyObject_CallFunction(self-connection-text_factory, y#, text, nbytes); }可以看到对于常见类型str/bytes/bytearray走快速路径直接构造对象自定义工厂则通过PyObject_CallFunction(factory, y#, ...)回调执行。无论哪条路径都要求text_factory是一个有效对象引用——一旦被删除成空指针这里就会成为崩溃点。row_factory 在取行时的回调游标每次取出一行原始元组后pysqlite_cursor_iternextModules/_sqlite/cursor.c会检查row_factory并执行回调if (!Py_IsNone(self-row_factory)) { PyObject *factory self-row_factory; PyObject *args[] { op, row, }; PyObject *new_row PyObject_Vectorcall(factory, args, 2, NULL); Py_SETREF(row, new_row); }此外创建新游标时连接会把自身的row_factory复制给游标Modules/_sqlite/connection.c保证连接级配置能传播到每个游标。若该引用因del而被破坏Py_IsNone检查与后续PyObject_Vectorcall都会操作无效内存。行为变化与正确的迁移方式变化后的行为从本变更起CPython 3.16 开发分支参见 Include/patchlevel.h 中的版本定义以下操作均会抛出AttributeErrorimport sqlite3 con sqlite3.connect(:memory:) del con.row_factory # AttributeError: cannot delete row_factory attribute del con.text_factory # AttributeError: cannot delete text_factory attribute cur con.cursor() del cur.row_factory # AttributeError: cannot delete row_factory attribute正确的替代写法需要关闭工厂功能时请改为赋值为默认值而不是删除属性import sqlite3 con sqlite3.connect(:memory:) cur con.cursor() # 取消自定义行工厂回到默认的元组行为 cur.row_factory None # 恢复 TEXT 列的默认 str 转换 con.text_factory str # 将 TEXT 列转为 bytes 而不是 str con.text_factory bytes # 自定义工厂仍然可用 con.text_factory lambda data: data.decode(utf-8, replace) cur.row_factory sqlite3.Row需要特别注意的是row_factory None与row_factory sqlite3.Row在 C 层的存储上语义不同None表示不应用任何行工厂直接返回元组pysqlite_cursor_iternext中Py_IsNone检查短路而任何非None的可调用对象都会被当作工厂执行。测试用例的印证仓库中的回归测试覆盖了本次变更见 Lib/test/test_sqlite3/test_factory.pydef test_delete_connection_row_factory(self): # gh-149738: deleting row_factory should raise an exception with self.assertRaises(AttributeError): del self.con.row_factory def test_delete_connection_text_factory(self): # gh-149738: deleting text_factory should raise an exception with self.assertRaises(AttributeError): del self.con.text_factory def test_delete_cursor_row_factory(self): # gh-149738: deleting row_factory should raise an exception cur self.con.cursor() with self.assertRaises(AttributeError): del cur.row_factory # Executing a query here should succeed. self.assertEqual(tuple(cur.execute(select 1).fetchone()), (1,))注意test_delete_cursor_row_factory的收尾断言删除被拒绝后紧接着执行select 1查询仍然正常返回结果这正是本次修复的核心目标——用可捕获的AttributeError取代解释器崩溃。对既有代码的影响与建议本次变更属于向后兼容性收紧对绝大多数正常使用sqlite3的代码无影响因为实践中几乎不会有代码去del这两个属性。需要留意的场景包括动态反射 / 元编程代码使用delattr(con, row_factory)或遍历dir(con)后尝试删除属性的通用清理逻辑需要改为检查属性是否存在后再操作或直接跳过这两个属性依赖旧行为的测试代码如果此前有测试用del来重置工厂应改用con.row_factory None或con.text_factory strC 扩展开发者若在扩展中直接操作pysqlite_Connection/pysqlite_Cursor结构体的row_factory/text_factory槽位应确保始终持有有效引用并关注 Modules/_sqlite/connection.c 与 Modules/_sqlite/cursor.c 中成员描述符表如connection.c第 26742677 行的{row_factory, ...}、{text_factory, ...}定义的行为约束。总结本次 CPython 3.16 的变更为sqlite3模块补上了一块安全短板Connection.row_factory、Connection.text_factory与Cursor.row_factory三个属性从可删除变为禁止删除删除时抛出AttributeError。其本质是让C 层始终持有有效工厂引用的不变量不被 Python 层破坏从而将潜在的解释器崩溃收敛为标准的 Python 异常。如果你正在维护依赖sqlite3的库或测试套件只需记住一个迁移口诀想关闭工厂就赋默认值None/str不要del。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表