[3.14] gh-137044: Support large limit values in getrlimit() and setrlimit() (GH-137338) (#137506)

gh-137044: Support large limit values in getrlimit() and setrlimit() (GH-137338)

* 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 baefaa6cba)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
Miss Islington (bot) 2025-10-07 20:43:12 +02:00 committed by GitHub
parent b414ad1043
commit c4be405fe9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 200 additions and 121 deletions

View file

@ -14,89 +14,154 @@ class ResourceTest(unittest.TestCase):
def test_args(self): def test_args(self):
self.assertRaises(TypeError, resource.getrlimit) 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)
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", @unittest.skipIf(sys.platform == "vxworks",
"setting RLIMIT_FSIZE is not supported on VxWorks") "setting RLIMIT_FSIZE is not supported on VxWorks")
@unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE')
def test_fsize_ismax(self): def test_fsize_ismax(self):
try: (cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE) # RLIMIT_FSIZE should be RLIM_INFINITY, which will be a really big
except AttributeError: # number on a platform with large file support. On these platforms,
pass # we need to test that the get/setrlimit functions properly convert
else: # the number to a C long long and that the conversion doesn't raise
# RLIMIT_FSIZE should be RLIM_INFINITY, which will be a really big # an error.
# number on a platform with large file support. On these platforms, self.assertEqual(resource.RLIM_INFINITY, max)
# we need to test that the get/setrlimit functions properly convert resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max))
# 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): 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: 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: 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: try:
resource.setrlimit(resource.RLIMIT_FSIZE, (1024, max)) f.write(b"Y")
limit_set = True f.flush()
except ValueError: # On some systems (e.g., Ubuntu on hppa) the flush()
limit_set = False # doesn't always cause the exception, but the close()
f = open(os_helper.TESTFN, "wb") # does eventually. Try flushing several times in
try: # an attempt to ensure the file is really synced and
f.write(b"X" * 1024) # the exception raised.
try: for i in range(5):
f.write(b"Y") time.sleep(.1)
f.flush() f.flush()
# On some systems (e.g., Ubuntu on hppa) the flush() except OSError:
# doesn't always cause the exception, but the close() if not limit_set:
# does eventually. Try flushing several times in raise
# 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:
if limit_set: 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)) 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 # Be sure that setrlimit is checking for really large values
too_big = 10**50 too_big = 10**50
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE)
try: try:
(cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE) resource.setrlimit(resource.RLIMIT_FSIZE, (too_big, max))
except AttributeError: except (OverflowError, ValueError):
pass pass
try:
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: 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: try:
resource.setrlimit(resource.RLIMIT_FSIZE, (too_big, max)) resource.setrlimit(resource.RLIMIT_FSIZE, (2**63, max))
except (OverflowError, ValueError): except ValueError:
pass # There is a hard limit on macOS.
try:
resource.setrlimit(resource.RLIMIT_FSIZE, (max, too_big))
except (OverflowError, ValueError):
pass 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") @unittest.skipUnless(hasattr(resource, "getrusage"), "needs getrusage")
def test_getrusage(self): def test_getrusage(self):
@ -117,21 +182,18 @@ def test_getrusage(self):
# Issue 6083: Reference counting bug # Issue 6083: Reference counting bug
@unittest.skipIf(sys.platform == "vxworks", @unittest.skipIf(sys.platform == "vxworks",
"setting RLIMIT_CPU is not supported on VxWorks") "setting RLIMIT_CPU is not supported on VxWorks")
@unittest.skipUnless(hasattr(resource, 'RLIMIT_CPU'), 'requires resource.RLIMIT_CPU')
def test_setrusage_refcount(self): def test_setrusage_refcount(self):
try: limits = resource.getrlimit(resource.RLIMIT_CPU)
limits = resource.getrlimit(resource.RLIMIT_CPU) class BadSequence:
except AttributeError: def __len__(self):
pass return 2
else: def __getitem__(self, key):
class BadSequence: if key in (0, 1):
def __len__(self): return len(tuple(range(1000000)))
return 2 raise IndexError
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): def test_pagesize(self):
pagesize = resource.getpagesize() pagesize = resource.getpagesize()
@ -168,7 +230,8 @@ class BadSeq:
def __len__(self): def __len__(self):
return 2 return 2
def __getitem__(self, key): 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) limits = resource.getrlimit(resource.RLIMIT_AS)
self.assertEqual(resource.prlimit(0, resource.RLIMIT_AS, BadSeq()), self.assertEqual(resource.prlimit(0, resource.RLIMIT_AS, BadSeq()),

View file

@ -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`.

View file

@ -2,6 +2,8 @@
preserve preserve
[clinic start generated code]*/ [clinic start generated code]*/
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
#if defined(HAVE_GETRUSAGE) #if defined(HAVE_GETRUSAGE)
PyDoc_STRVAR(resource_getrusage__doc__, PyDoc_STRVAR(resource_getrusage__doc__,
@ -66,7 +68,7 @@ PyDoc_STRVAR(resource_setrlimit__doc__,
"\n"); "\n");
#define RESOURCE_SETRLIMIT_METHODDEF \ #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 * static PyObject *
resource_setrlimit_impl(PyObject *module, int resource, PyObject *limits); 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; int resource;
PyObject *limits; PyObject *limits;
if (nargs != 2) { if (!_PyArg_CheckPositional("setrlimit", nargs, 2, 2)) {
PyErr_Format(PyExc_TypeError, "setrlimit expected 2 arguments, got %zd", nargs);
goto exit; goto exit;
} }
resource = PyLong_AsInt(args[0]); resource = PyLong_AsInt(args[0]);
@ -101,7 +102,7 @@ PyDoc_STRVAR(resource_prlimit__doc__,
"\n"); "\n");
#define RESOURCE_PRLIMIT_METHODDEF \ #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 * static PyObject *
resource_prlimit_impl(PyObject *module, pid_t pid, int resource, 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; int resource;
PyObject *limits = Py_None; PyObject *limits = Py_None;
if (nargs < 2) { if (!_PyArg_CheckPositional("prlimit", nargs, 2, 3)) {
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);
goto exit; goto exit;
} }
pid = PyLong_AsPid(args[0]); pid = PyLong_AsPid(args[0]);
@ -178,4 +174,4 @@ exit:
#ifndef RESOURCE_PRLIMIT_METHODDEF #ifndef RESOURCE_PRLIMIT_METHODDEF
#define RESOURCE_PRLIMIT_METHODDEF #define RESOURCE_PRLIMIT_METHODDEF
#endif /* !defined(RESOURCE_PRLIMIT_METHODDEF) */ #endif /* !defined(RESOURCE_PRLIMIT_METHODDEF) */
/*[clinic end generated code: output=e45883ace510414a input=a9049054013a1b77]*/ /*[clinic end generated code: output=8e905b2f5c35170e input=a9049054013a1b77]*/

View file

@ -1,7 +1,5 @@
// Need limited C API version 3.13 for PySys_Audit() #ifndef Py_BUILD_CORE_BUILTIN
#include "pyconfig.h" // Py_GIL_DISABLED # define Py_BUILD_CORE_MODULE 1
#ifndef Py_GIL_DISABLED
# define Py_LIMITED_API 0x030d0000
#endif #endif
#include "Python.h" #include "Python.h"
@ -150,6 +148,35 @@ resource_getrusage_impl(PyObject *module, int who)
} }
#endif #endif
static int
py2rlim(PyObject *obj, rlim_t *out)
{
obj = PyNumber_Index(obj);
if (obj == NULL) {
return -1;
}
int neg = PyLong_IsNegative(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 static int
py2rlimit(PyObject *limits, struct rlimit *rl_out) py2rlimit(PyObject *limits, struct rlimit *rl_out)
{ {
@ -166,26 +193,13 @@ py2rlimit(PyObject *limits, struct rlimit *rl_out)
} }
curobj = PyTuple_GetItem(limits, 0); // borrowed curobj = PyTuple_GetItem(limits, 0); // borrowed
maxobj = PyTuple_GetItem(limits, 1); // borrowed maxobj = PyTuple_GetItem(limits, 1); // borrowed
#if !defined(HAVE_LARGEFILE_SUPPORT) if (py2rlim(curobj, &rl_out->rlim_cur) < 0 ||
rl_out->rlim_cur = PyLong_AsLong(curobj); py2rlim(maxobj, &rl_out->rlim_max) < 0)
if (rl_out->rlim_cur == (rlim_t)-1 && PyErr_Occurred()) {
goto error; 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())
goto error;
#endif
Py_DECREF(limits); 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; return 0;
error: error:
@ -193,15 +207,24 @@ py2rlimit(PyObject *limits, struct rlimit *rl_out)
return -1; 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* static PyObject*
rlimit2py(struct rlimit rl) rlimit2py(struct rlimit rl)
{ {
if (sizeof(rl.rlim_cur) > sizeof(long)) { PyObject *cur = rlim2py(rl.rlim_cur);
return Py_BuildValue("LL", if (cur == NULL) {
(long long) rl.rlim_cur, return NULL;
(long long) rl.rlim_max);
} }
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] /*[clinic input]
@ -495,14 +518,7 @@ resource_exec(PyObject *module)
ADD_INT(module, RLIMIT_KQUEUES); ADD_INT(module, RLIMIT_KQUEUES);
#endif #endif
PyObject *v; if (PyModule_Add(module, "RLIM_INFINITY", rlim2py(RLIM_INFINITY)) < 0) {
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) {
return -1; return -1;
} }
return 0; return 0;