]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-137044: Support large limit values in getrlimit() and setrlimit() (GH-13733...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Thu, 7 Aug 2025 09:37:02 +0000 (11:37 +0200)
committerGitHub <noreply@github.com>
Thu, 7 Aug 2025 09:37:02 +0000 (09:37 +0000)
* Return large limit values as positive integers instead of negative integers
  in resource.getrlimit().
* Accept large values and reject negative values (except RLIM_INFINITY)
  for limits in resource.setrlimit().
(cherry picked from commit baefaa6cba1d69efd2f930cdc56bca682c54b139)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Lib/test/test_resource.py
Misc/NEWS.d/next/Library/2025-08-03-13-16-39.gh-issue-137044.0hPVL_.rst [new file with mode: 0644]
Modules/clinic/resource.c.h
Modules/resource.c

index d23d3623235f38989b233da6ce8bfb930a28811d..fe05224828bd278acc98c5e45bb6a0f9ee0eb3b7 100644 (file)
@@ -14,89 +14,154 @@ class ResourceTest(unittest.TestCase):
 
     def test_args(self):
         self.assertRaises(TypeError, resource.getrlimit)
-        self.assertRaises(TypeError, resource.getrlimit, 42, 42)
+        self.assertRaises(TypeError, resource.getrlimit, 0, 42)
+        self.assertRaises(OverflowError, resource.getrlimit, 2**1000)
+        self.assertRaises(OverflowError, resource.getrlimit, -2**1000)
+        self.assertRaises(TypeError, resource.getrlimit, '0')
         self.assertRaises(TypeError, resource.setrlimit)
-        self.assertRaises(TypeError, resource.setrlimit, 42, 42, 42)
+        self.assertRaises(TypeError, resource.setrlimit, 0)
+        self.assertRaises(TypeError, resource.setrlimit, 0, 42)
+        self.assertRaises(TypeError, resource.setrlimit, 0, 42, 42)
+        self.assertRaises(OverflowError, resource.setrlimit, 2**1000, (42, 42))
+        self.assertRaises(OverflowError, resource.setrlimit, -2**1000, (42, 42))
+        self.assertRaises(ValueError, resource.setrlimit, 0, (42,))
+        self.assertRaises(ValueError, resource.setrlimit, 0, (42, 42, 42))
+        self.assertRaises(TypeError, resource.setrlimit, '0', (42, 42))
+        self.assertRaises(TypeError, resource.setrlimit, 0, ('42', 42))
+        self.assertRaises(TypeError, resource.setrlimit, 0, (42, '42'))
 
     @unittest.skipIf(sys.platform == "vxworks",
                      "setting RLIMIT_FSIZE is not supported on VxWorks")
+    @unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
     def test_fsize_ismax(self):
-        try:
-            (cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
-        except AttributeError:
-            pass
-        else:
-            # RLIMIT_FSIZE should be RLIM_INFINITY, which will be a really big
-            # number on a platform with large file support.  On these platforms,
-            # we need to test that the get/setrlimit functions properly convert
-            # the number to a C long long and that the conversion doesn't raise
-            # an error.
-            self.assertEqual(resource.RLIM_INFINITY, max)
-            resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
+        (cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
+        # RLIMIT_FSIZE should be RLIM_INFINITY, which will be a really big
+        # number on a platform with large file support.  On these platforms,
+        # we need to test that the get/setrlimit functions properly convert
+        # the number to a C long long and that the conversion doesn't raise
+        # an error.
+        self.assertEqual(resource.RLIM_INFINITY, max)
+        resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
 
+    @unittest.skipIf(sys.platform == "vxworks",
+                     "setting RLIMIT_FSIZE is not supported on VxWorks")
+    @unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
     def test_fsize_enforced(self):
+        (cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
+        # Check to see what happens when the RLIMIT_FSIZE is small.  Some
+        # versions of Python were terminated by an uncaught SIGXFSZ, but
+        # pythonrun.c has been fixed to ignore that exception.  If so, the
+        # write() should return EFBIG when the limit is exceeded.
+
+        # At least one platform has an unlimited RLIMIT_FSIZE and attempts
+        # to change it raise ValueError instead.
         try:
-            (cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
-        except AttributeError:
-            pass
-        else:
-            # Check to see what happens when the RLIMIT_FSIZE is small.  Some
-            # versions of Python were terminated by an uncaught SIGXFSZ, but
-            # pythonrun.c has been fixed to ignore that exception.  If so, the
-            # write() should return EFBIG when the limit is exceeded.
-
-            # At least one platform has an unlimited RLIMIT_FSIZE and attempts
-            # to change it raise ValueError instead.
             try:
+                resource.setrlimit(resource.RLIMIT_FSIZE, (1024, max))
+                limit_set = True
+            except ValueError:
+                limit_set = False
+            f = open(os_helper.TESTFN, "wb")
+            try:
+                f.write(b"X" * 1024)
                 try:
-                    resource.setrlimit(resource.RLIMIT_FSIZE, (1024, max))
-                    limit_set = True
-                except ValueError:
-                    limit_set = False
-                f = open(os_helper.TESTFN, "wb")
-                try:
-                    f.write(b"X" * 1024)
-                    try:
-                        f.write(b"Y")
+                    f.write(b"Y")
+                    f.flush()
+                    # On some systems (e.g., Ubuntu on hppa) the flush()
+                    # doesn't always cause the exception, but the close()
+                    # does eventually.  Try flushing several times in
+                    # an attempt to ensure the file is really synced and
+                    # the exception raised.
+                    for i in range(5):
+                        time.sleep(.1)
                         f.flush()
-                        # On some systems (e.g., Ubuntu on hppa) the flush()
-                        # doesn't always cause the exception, but the close()
-                        # does eventually.  Try flushing several times in
-                        # an attempt to ensure the file is really synced and
-                        # the exception raised.
-                        for i in range(5):
-                            time.sleep(.1)
-                            f.flush()
-                    except OSError:
-                        if not limit_set:
-                            raise
-                    if limit_set:
-                        # Close will attempt to flush the byte we wrote
-                        # Restore limit first to avoid getting a spurious error
-                        resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
-                finally:
-                    f.close()
-            finally:
+                except OSError:
+                    if not limit_set:
+                        raise
                 if limit_set:
+                    # Close will attempt to flush the byte we wrote
+                    # Restore limit first to avoid getting a spurious error
                     resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
-                os_helper.unlink(os_helper.TESTFN)
+            finally:
+                f.close()
+        finally:
+            if limit_set:
+                resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
+            os_helper.unlink(os_helper.TESTFN)
 
-    def test_fsize_toobig(self):
+    @unittest.skipIf(sys.platform == "vxworks",
+                     "setting RLIMIT_FSIZE is not supported on VxWorks")
+    @unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
+    def test_fsize_too_big(self):
         # Be sure that setrlimit is checking for really large values
         too_big = 10**50
+        (cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
+        try:
+            resource.setrlimit(resource.RLIMIT_FSIZE, (too_big, max))
+        except (OverflowError, ValueError):
+            pass
         try:
-            (cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
-        except AttributeError:
+            resource.setrlimit(resource.RLIMIT_FSIZE, (max, too_big))
+        except (OverflowError, ValueError):
             pass
+
+    @unittest.skipIf(sys.platform == "vxworks",
+                     "setting RLIMIT_FSIZE is not supported on VxWorks")
+    @unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
+    def test_fsize_not_too_big(self):
+        (cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
+        self.addCleanup(resource.setrlimit, resource.RLIMIT_FSIZE, (cur, max))
+
+        def expected(cur):
+            if resource.RLIM_INFINITY < 0:
+                return [(cur, max), (resource.RLIM_INFINITY, max)]
+            elif resource.RLIM_INFINITY < cur:
+                return [(resource.RLIM_INFINITY, max)]
+            else:
+                return [(cur, max)]
+
+        resource.setrlimit(resource.RLIMIT_FSIZE, (2**31-5, max))
+        self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**31-5, max))
+
+        try:
+            resource.setrlimit(resource.RLIMIT_FSIZE, (2**32, max))
+        except OverflowError:
+            resource.setrlimit(resource.RLIMIT_FSIZE, (2**31, max))
+            self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**31))
+            resource.setrlimit(resource.RLIMIT_FSIZE, (2**32-5, max))
+            self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**32-5))
         else:
+            self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**32))
+            resource.setrlimit(resource.RLIMIT_FSIZE, (2**31, max))
+            self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**31, max))
+            resource.setrlimit(resource.RLIMIT_FSIZE, (2**32-5, max))
+            self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**32-5, max))
+
+            resource.setrlimit(resource.RLIMIT_FSIZE, (2**63-5, max))
+            self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**63-5))
             try:
-                resource.setrlimit(resource.RLIMIT_FSIZE, (too_big, max))
-            except (OverflowError, ValueError):
-                pass
-            try:
-                resource.setrlimit(resource.RLIMIT_FSIZE, (max, too_big))
-            except (OverflowError, ValueError):
+                resource.setrlimit(resource.RLIMIT_FSIZE, (2**63, max))
+            except ValueError:
+                # There is a hard limit on macOS.
                 pass
+            else:
+                self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**63))
+                resource.setrlimit(resource.RLIMIT_FSIZE, (2**64-5, max))
+                self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**64-5))
+
+    @unittest.skipIf(sys.platform == "vxworks",
+                     "setting RLIMIT_FSIZE is not supported on VxWorks")
+    @unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
+    def test_fsize_negative(self):
+        (cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
+        for value in -5, -2**31, -2**32-5, -2**63, -2**64-5, -2**1000:
+            with self.subTest(value=value):
+                # This test assumes that the values don't map to RLIM_INFINITY,
+                # though Posix doesn't guarantee it.
+                self.assertNotEqual(value, resource.RLIM_INFINITY)
+
+                self.assertRaises(ValueError, resource.setrlimit, resource.RLIMIT_FSIZE, (value, max))
+                self.assertRaises(ValueError, resource.setrlimit, resource.RLIMIT_FSIZE, (cur, value))
 
     @unittest.skipUnless(hasattr(resource, "getrusage"), "needs getrusage")
     def test_getrusage(self):
@@ -117,21 +182,18 @@ class ResourceTest(unittest.TestCase):
     # Issue 6083: Reference counting bug
     @unittest.skipIf(sys.platform == "vxworks",
                      "setting RLIMIT_CPU is not supported on VxWorks")
+    @unittest.skipUnless(hasattr(resource, 'RLIMIT_CPU'), 'requires resource.RLIMIT_CPU')
     def test_setrusage_refcount(self):
-        try:
-            limits = resource.getrlimit(resource.RLIMIT_CPU)
-        except AttributeError:
-            pass
-        else:
-            class BadSequence:
-                def __len__(self):
-                    return 2
-                def __getitem__(self, key):
-                    if key in (0, 1):
-                        return len(tuple(range(1000000)))
-                    raise IndexError
+        limits = resource.getrlimit(resource.RLIMIT_CPU)
+        class BadSequence:
+            def __len__(self):
+                return 2
+            def __getitem__(self, key):
+                if key in (0, 1):
+                    return len(tuple(range(1000000)))
+                raise IndexError
 
-            resource.setrlimit(resource.RLIMIT_CPU, BadSequence())
+        resource.setrlimit(resource.RLIMIT_CPU, BadSequence())
 
     def test_pagesize(self):
         pagesize = resource.getpagesize()
@@ -168,7 +230,8 @@ class ResourceTest(unittest.TestCase):
             def __len__(self):
                 return 2
             def __getitem__(self, key):
-                return limits[key] - 1  # new reference
+                lim = limits[key]
+                return lim - 1 if lim > 0 else lim + sys.maxsize*2  # new reference
 
         limits = resource.getrlimit(resource.RLIMIT_AS)
         self.assertEqual(resource.prlimit(0, resource.RLIMIT_AS, BadSeq()),
diff --git a/Misc/NEWS.d/next/Library/2025-08-03-13-16-39.gh-issue-137044.0hPVL_.rst b/Misc/NEWS.d/next/Library/2025-08-03-13-16-39.gh-issue-137044.0hPVL_.rst
new file mode 100644 (file)
index 0000000..f5f9626
--- /dev/null
@@ -0,0 +1,4 @@
+Return large limit values as positive integers instead of negative integers
+in :func:`resource.getrlimit`. Accept large values and reject negative
+values (except :data:`~resource.RLIM_INFINITY`) for limits in
+:func:`resource.setrlimit`.
index 9eda7de27532a17ce26efecdde6ac5d27ca17bd9..e4ef93900d17978975ac553ab5ebbc6bd002eba7 100644 (file)
@@ -2,6 +2,8 @@
 preserve
 [clinic start generated code]*/
 
+#include "pycore_modsupport.h"    // _PyArg_CheckPositional()
+
 #if defined(HAVE_GETRUSAGE)
 
 PyDoc_STRVAR(resource_getrusage__doc__,
@@ -66,7 +68,7 @@ PyDoc_STRVAR(resource_setrlimit__doc__,
 "\n");
 
 #define RESOURCE_SETRLIMIT_METHODDEF    \
-    {"setrlimit", (PyCFunction)(void(*)(void))resource_setrlimit, METH_FASTCALL, resource_setrlimit__doc__},
+    {"setrlimit", _PyCFunction_CAST(resource_setrlimit), METH_FASTCALL, resource_setrlimit__doc__},
 
 static PyObject *
 resource_setrlimit_impl(PyObject *module, int resource, PyObject *limits);
@@ -78,8 +80,7 @@ resource_setrlimit(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
     int resource;
     PyObject *limits;
 
-    if (nargs != 2) {
-        PyErr_Format(PyExc_TypeError, "setrlimit expected 2 arguments, got %zd", nargs);
+    if (!_PyArg_CheckPositional("setrlimit", nargs, 2, 2)) {
         goto exit;
     }
     resource = PyLong_AsInt(args[0]);
@@ -101,7 +102,7 @@ PyDoc_STRVAR(resource_prlimit__doc__,
 "\n");
 
 #define RESOURCE_PRLIMIT_METHODDEF    \
-    {"prlimit", (PyCFunction)(void(*)(void))resource_prlimit, METH_FASTCALL, resource_prlimit__doc__},
+    {"prlimit", _PyCFunction_CAST(resource_prlimit), METH_FASTCALL, resource_prlimit__doc__},
 
 static PyObject *
 resource_prlimit_impl(PyObject *module, pid_t pid, int resource,
@@ -115,12 +116,7 @@ resource_prlimit(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
     int resource;
     PyObject *limits = Py_None;
 
-    if (nargs < 2) {
-        PyErr_Format(PyExc_TypeError, "prlimit expected at least 2 arguments, got %zd", nargs);
-        goto exit;
-    }
-    if (nargs > 3) {
-        PyErr_Format(PyExc_TypeError, "prlimit expected at most 3 arguments, got %zd", nargs);
+    if (!_PyArg_CheckPositional("prlimit", nargs, 2, 3)) {
         goto exit;
     }
     pid = PyLong_AsPid(args[0]);
@@ -178,4 +174,4 @@ exit:
 #ifndef RESOURCE_PRLIMIT_METHODDEF
     #define RESOURCE_PRLIMIT_METHODDEF
 #endif /* !defined(RESOURCE_PRLIMIT_METHODDEF) */
-/*[clinic end generated code: output=e45883ace510414a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=8e905b2f5c35170e input=a9049054013a1b77]*/
index 3fe18e7c98e3d812d64a5e9a100052c365af58af..d34795d134af1c0a0a24c9c723cd2fef0398fbc7 100644 (file)
@@ -1,7 +1,5 @@
-// Need limited C API version 3.13 for PySys_Audit()
-#include "pyconfig.h"   // Py_GIL_DISABLED
-#ifndef Py_GIL_DISABLED
-#  define Py_LIMITED_API 0x030d0000
+#ifndef Py_BUILD_CORE_BUILTIN
+#  define Py_BUILD_CORE_MODULE 1
 #endif
 
 #include "Python.h"
@@ -9,6 +7,7 @@
 #include <string.h>
 #include <sys/resource.h>         // getrusage()
 #include <unistd.h>               // getpagesize()
+#include <internal/pycore_long.h> // _PyLong_IsNegative()
 
 /* On some systems, these aren't in any header file.
    On others they are, with inconsistent prototypes.
@@ -150,6 +149,35 @@ resource_getrusage_impl(PyObject *module, int who)
 }
 #endif
 
+static int
+py2rlim(PyObject *obj, rlim_t *out)
+{
+    obj = PyNumber_Index(obj);
+    if (obj == NULL) {
+        return -1;
+    }
+    int neg = _PyLong_IsNegative((const PyLongObject *)obj);
+    assert(neg >= 0);
+    Py_ssize_t bytes = PyLong_AsNativeBytes(obj, out, sizeof(*out),
+                                            Py_ASNATIVEBYTES_NATIVE_ENDIAN |
+                                            Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
+    Py_DECREF(obj);
+    if (bytes < 0) {
+        return -1;
+    }
+    else if (neg && (*out != RLIM_INFINITY || bytes > (Py_ssize_t)sizeof(*out))) {
+        PyErr_SetString(PyExc_ValueError,
+            "Cannot convert negative int");
+        return -1;
+    }
+    else if (bytes > (Py_ssize_t)sizeof(*out)) {
+        PyErr_SetString(PyExc_OverflowError,
+            "Python int too large to convert to C rlim_t");
+        return -1;
+    }
+    return 0;
+}
+
 static int
 py2rlimit(PyObject *limits, struct rlimit *rl_out)
 {
@@ -166,26 +194,13 @@ py2rlimit(PyObject *limits, struct rlimit *rl_out)
     }
     curobj = PyTuple_GetItem(limits, 0);  // borrowed
     maxobj = PyTuple_GetItem(limits, 1);  // borrowed
-#if !defined(HAVE_LARGEFILE_SUPPORT)
-    rl_out->rlim_cur = PyLong_AsLong(curobj);
-    if (rl_out->rlim_cur == (rlim_t)-1 && PyErr_Occurred())
-        goto error;
-    rl_out->rlim_max = PyLong_AsLong(maxobj);
-    if (rl_out->rlim_max == (rlim_t)-1 && PyErr_Occurred())
-        goto error;
-#else
-    /* The limits are probably bigger than a long */
-    rl_out->rlim_cur = PyLong_AsLongLong(curobj);
-    if (rl_out->rlim_cur == (rlim_t)-1 && PyErr_Occurred())
-        goto error;
-    rl_out->rlim_max = PyLong_AsLongLong(maxobj);
-    if (rl_out->rlim_max == (rlim_t)-1 && PyErr_Occurred())
+    if (py2rlim(curobj, &rl_out->rlim_cur) < 0 ||
+        py2rlim(maxobj, &rl_out->rlim_max) < 0)
+    {
         goto error;
-#endif
+    }
 
     Py_DECREF(limits);
-    rl_out->rlim_cur = rl_out->rlim_cur & RLIM_INFINITY;
-    rl_out->rlim_max = rl_out->rlim_max & RLIM_INFINITY;
     return 0;
 
 error:
@@ -193,15 +208,24 @@ error:
     return -1;
 }
 
+static PyObject*
+rlim2py(rlim_t value)
+{
+    if (value == RLIM_INFINITY) {
+        return PyLong_FromNativeBytes(&value, sizeof(value), -1);
+    }
+    return PyLong_FromUnsignedNativeBytes(&value, sizeof(value), -1);
+}
+
 static PyObject*
 rlimit2py(struct rlimit rl)
 {
-    if (sizeof(rl.rlim_cur) > sizeof(long)) {
-        return Py_BuildValue("LL",
-                             (long long) rl.rlim_cur,
-                             (long long) rl.rlim_max);
+    PyObject *cur = rlim2py(rl.rlim_cur);
+    if (cur == NULL) {
+        return NULL;
     }
-    return Py_BuildValue("ll", (long) rl.rlim_cur, (long) rl.rlim_max);
+    PyObject *max = rlim2py(rl.rlim_max);
+    return Py_BuildValue("NN", cur, max);
 }
 
 /*[clinic input]
@@ -495,14 +519,7 @@ resource_exec(PyObject *module)
     ADD_INT(module, RLIMIT_KQUEUES);
 #endif
 
-    PyObject *v;
-    if (sizeof(RLIM_INFINITY) > sizeof(long)) {
-        v = PyLong_FromLongLong((long long) RLIM_INFINITY);
-    } else
-    {
-        v = PyLong_FromLong((long) RLIM_INFINITY);
-    }
-    if (PyModule_Add(module, "RLIM_INFINITY", v) < 0) {
+    if (PyModule_Add(module, "RLIM_INFINITY", rlim2py(RLIM_INFINITY)) < 0) {
         return -1;
     }
     return 0;