From e6b642bf51173c21984c077a885e04ba421cfd67 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 9 Apr 2025 15:29:19 -0600 Subject: [PATCH 1/3] Add _PyModule_GetFilenameObject() and _PyModule_GetFilenameUTF8(). --- Include/internal/pycore_moduleobject.h | 6 +++ Objects/moduleobject.c | 66 ++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Include/internal/pycore_moduleobject.h b/Include/internal/pycore_moduleobject.h index 9bb282a13a9659..15ba44145d6983 100644 --- a/Include/internal/pycore_moduleobject.h +++ b/Include/internal/pycore_moduleobject.h @@ -47,6 +47,12 @@ static inline PyObject* _PyModule_GetDict(PyObject *mod) { return dict; // borrowed reference } +extern PyObject * _PyModule_GetFilenameObject(PyObject *); +extern Py_ssize_t _PyModule_GetFilenameUTF8( + PyObject *module, + char *buffer, + size_t maxlen); + PyObject* _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress); PyObject* _Py_module_getattro(PyObject *m, PyObject *name); diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 46dea1534cbcd6..acbdabb1bd7e92 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -607,32 +607,51 @@ PyModule_GetName(PyObject *m) } PyObject* -PyModule_GetFilenameObject(PyObject *mod) +_PyModule_GetFilenameObject(PyObject *mod) { + // We return None to indicate "not found" or "bogus". if (!PyModule_Check(mod)) { PyErr_BadArgument(); return NULL; } PyObject *dict = ((PyModuleObject *)mod)->md_dict; // borrowed reference if (dict == NULL) { - goto error; + // The module has been tampered with. + Py_RETURN_NONE; } PyObject *fileobj; - if (PyDict_GetItemRef(dict, &_Py_ID(__file__), &fileobj) <= 0) { - // error or not found - goto error; + int res = PyDict_GetItemRef(dict, &_Py_ID(__file__), &fileobj); + if (res < 0) { + return NULL; + } + if (res == 0) { + // __file__ isn't set. There are several reasons why this might + // be so, most of them valid reasons. If it's the __main__ + // module then we're running the REPL or with -c. Otherwise + // it's a namespace package or other module with a loader that + // isn't disk-based. It could also be that a user created + // a module manually but without manually setting __file__. + Py_RETURN_NONE; } if (!PyUnicode_Check(fileobj)) { Py_DECREF(fileobj); - goto error; + Py_RETURN_NONE; } return fileobj; +} -error: - if (!PyErr_Occurred()) { +PyObject* +PyModule_GetFilenameObject(PyObject *mod) +{ + PyObject *fileobj = _PyModule_GetFilenameObject(mod); + if (fileobj == NULL) { + return NULL; + } + if (fileobj == Py_None) { PyErr_SetString(PyExc_SystemError, "module filename missing"); + return NULL; } - return NULL; + return fileobj; } const char * @@ -648,6 +667,35 @@ PyModule_GetFilename(PyObject *m) return utf8; } +Py_ssize_t +_PyModule_GetFilenameUTF8(PyObject *mod, char *buffer, size_t maxlen) +{ + // We "return" an empty string for an invalid module + // and for a missing, empty, or invalid filename. + Py_ssize_t size = -1; + PyObject *filenameobj = _PyModule_GetFilenameObject(mod); + if (filenameobj == NULL) { + return -1; + } + if (filenameobj == Py_None) { + // It is missing or invalid. + buffer[0] = '\0'; + size = 0; + } + else { + const char *filename = PyUnicode_AsUTF8AndSize(filenameobj, &size); + if (size > maxlen) { + size = -1; + PyErr_SetString(PyExc_ValueError, "__file__ too long"); + } + else { + (void)strcpy(buffer, filename); + } + } + Py_DECREF(filenameobj); + return size; +} + PyModuleDef* PyModule_GetDef(PyObject* m) { From d034ec50a54fdcdc70cf56f69d958e140eedc962 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 25 Apr 2025 17:07:12 -0600 Subject: [PATCH 2/3] Fix a compiler warning. --- Objects/moduleobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index acbdabb1bd7e92..f85026e028b3a3 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -684,7 +684,7 @@ _PyModule_GetFilenameUTF8(PyObject *mod, char *buffer, size_t maxlen) } else { const char *filename = PyUnicode_AsUTF8AndSize(filenameobj, &size); - if (size > maxlen) { + if (size > PY_SIZE_MAX || (size_t)size > maxlen) { size = -1; PyErr_SetString(PyExc_ValueError, "__file__ too long"); } From fc6920d7ee9bf40360a286c8003f980e01eec7bb Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 25 Apr 2025 17:18:29 -0600 Subject: [PATCH 3/3] Fix the compiler warning a different way. --- Include/internal/pycore_moduleobject.h | 2 +- Objects/moduleobject.c | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_moduleobject.h b/Include/internal/pycore_moduleobject.h index 15ba44145d6983..b170d7bce702c6 100644 --- a/Include/internal/pycore_moduleobject.h +++ b/Include/internal/pycore_moduleobject.h @@ -51,7 +51,7 @@ extern PyObject * _PyModule_GetFilenameObject(PyObject *); extern Py_ssize_t _PyModule_GetFilenameUTF8( PyObject *module, char *buffer, - size_t maxlen); + Py_ssize_t maxlen); PyObject* _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress); PyObject* _Py_module_getattro(PyObject *m, PyObject *name); diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index f85026e028b3a3..c69a0bf5bbe314 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -668,10 +668,11 @@ PyModule_GetFilename(PyObject *m) } Py_ssize_t -_PyModule_GetFilenameUTF8(PyObject *mod, char *buffer, size_t maxlen) +_PyModule_GetFilenameUTF8(PyObject *mod, char *buffer, Py_ssize_t maxlen) { // We "return" an empty string for an invalid module // and for a missing, empty, or invalid filename. + assert(maxlen >= 0); Py_ssize_t size = -1; PyObject *filenameobj = _PyModule_GetFilenameObject(mod); if (filenameobj == NULL) { @@ -684,7 +685,8 @@ _PyModule_GetFilenameUTF8(PyObject *mod, char *buffer, size_t maxlen) } else { const char *filename = PyUnicode_AsUTF8AndSize(filenameobj, &size); - if (size > PY_SIZE_MAX || (size_t)size > maxlen) { + assert(size >= 0); + if (size > maxlen) { size = -1; PyErr_SetString(PyExc_ValueError, "__file__ too long"); }