Skip to content

Commit cddf942

Browse files
Berthin TorresBerthin Torres
authored andcommitted
catch up with main
2 parents b4c22e9 + 621a8bd commit cddf942

File tree

15 files changed

+477
-662
lines changed

15 files changed

+477
-662
lines changed

Doc/library/netrc.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ the Unix :program:`ftp` program and other FTP clients.
2525
a :exc:`FileNotFoundError` exception will be raised.
2626
Parse errors will raise :exc:`NetrcParseError` with diagnostic
2727
information including the file name, line number, and terminating token.
28+
2829
If no argument is specified on a POSIX system, the presence of passwords in
2930
the :file:`.netrc` file will raise a :exc:`NetrcParseError` if the file
3031
ownership or permissions are insecure (owned by a user other than the user
3132
running the process, or accessible for read or write by any other user).
3233
This implements security behavior equivalent to that of ftp and other
33-
programs that use :file:`.netrc`.
34+
programs that use :file:`.netrc`. Such security checks are not available
35+
on platforms that do not support :func:`os.getuid`.
3436

3537
.. versionchanged:: 3.4 Added the POSIX permission check.
3638

Lib/netrc.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@
77
__all__ = ["netrc", "NetrcParseError"]
88

99

10+
def _can_security_check():
11+
# On WASI, getuid() is indicated as a stub but it may also be missing.
12+
return os.name == 'posix' and hasattr(os, 'getuid')
13+
14+
15+
def _getpwuid(uid):
16+
try:
17+
import pwd
18+
return pwd.getpwuid(uid)[0]
19+
except (ImportError, LookupError):
20+
return f'uid {uid}'
21+
22+
1023
class NetrcParseError(Exception):
1124
"""Exception raised on syntax errors in the .netrc file."""
1225
def __init__(self, msg, filename=None, lineno=None):
@@ -143,18 +156,12 @@ def _parse(self, file, fp, default_netrc):
143156
self._security_check(fp, default_netrc, self.hosts[entryname][0])
144157

145158
def _security_check(self, fp, default_netrc, login):
146-
if os.name == 'posix' and default_netrc and login != "anonymous":
159+
if _can_security_check() and default_netrc and login != "anonymous":
147160
prop = os.fstat(fp.fileno())
148-
if prop.st_uid != os.getuid():
149-
import pwd
150-
try:
151-
fowner = pwd.getpwuid(prop.st_uid)[0]
152-
except KeyError:
153-
fowner = 'uid %s' % prop.st_uid
154-
try:
155-
user = pwd.getpwuid(os.getuid())[0]
156-
except KeyError:
157-
user = 'uid %s' % os.getuid()
161+
current_user_id = os.getuid()
162+
if prop.st_uid != current_user_id:
163+
fowner = _getpwuid(prop.st_uid)
164+
user = _getpwuid(current_user_id)
158165
raise NetrcParseError(
159166
(f"~/.netrc file owner ({fowner}, {user}) does not match"
160167
" current user"))

Lib/test/test_hashlib.py

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,57 +1043,78 @@ def test_gil(self):
10431043

10441044
def test_sha256_gil(self):
10451045
gil_minsize = hashlib_helper.find_gil_minsize(['_sha2', '_hashlib'])
1046+
data = b'1' + b'#' * gil_minsize + b'1'
1047+
expected = hashlib.sha256(data).hexdigest()
1048+
10461049
m = hashlib.sha256()
10471050
m.update(b'1')
10481051
m.update(b'#' * gil_minsize)
10491052
m.update(b'1')
1050-
self.assertEqual(
1051-
m.hexdigest(),
1052-
'1cfceca95989f51f658e3f3ffe7f1cd43726c9e088c13ee10b46f57cef135b94'
1053-
)
1053+
self.assertEqual(m.hexdigest(), expected)
10541054

1055-
m = hashlib.sha256(b'1' + b'#' * gil_minsize + b'1')
1056-
self.assertEqual(
1057-
m.hexdigest(),
1058-
'1cfceca95989f51f658e3f3ffe7f1cd43726c9e088c13ee10b46f57cef135b94'
1059-
)
1055+
@threading_helper.reap_threads
1056+
@threading_helper.requires_working_threading()
1057+
def test_threaded_hashing_fast(self):
1058+
# Same as test_threaded_hashing_slow() but only tests some functions
1059+
# since otherwise test_hashlib.py becomes too slow during development.
1060+
for name in ['md5', 'sha1', 'sha256', 'sha3_256', 'blake2s']:
1061+
if constructor := getattr(hashlib, name, None):
1062+
with self.subTest(name):
1063+
self.do_test_threaded_hashing(constructor, is_shake=False)
1064+
if shake_128 := getattr(hashlib, 'shake_128', None):
1065+
self.do_test_threaded_hashing(shake_128, is_shake=True)
10601066

1067+
@requires_resource('cpu')
10611068
@threading_helper.reap_threads
10621069
@threading_helper.requires_working_threading()
1063-
def test_threaded_hashing(self):
1070+
def test_threaded_hashing_slow(self):
1071+
for algorithm, constructors in self.constructors_to_test.items():
1072+
is_shake = algorithm in self.shakes
1073+
for constructor in constructors:
1074+
with self.subTest(constructor.__name__, is_shake=is_shake):
1075+
self.do_test_threaded_hashing(constructor, is_shake)
1076+
1077+
def do_test_threaded_hashing(self, constructor, is_shake):
10641078
# Updating the same hash object from several threads at once
10651079
# using data chunk sizes containing the same byte sequences.
10661080
#
10671081
# If the internal locks are working to prevent multiple
10681082
# updates on the same object from running at once, the resulting
10691083
# hash will be the same as doing it single threaded upfront.
1070-
hasher = hashlib.sha1()
1071-
num_threads = 5
1072-
smallest_data = b'swineflu'
1073-
data = smallest_data * 200000
1074-
expected_hash = hashlib.sha1(data*num_threads).hexdigest()
1075-
1076-
def hash_in_chunks(chunk_size):
1077-
index = 0
1078-
while index < len(data):
1079-
hasher.update(data[index:index + chunk_size])
1080-
index += chunk_size
1084+
1085+
# The data to hash has length s|M|q^N and the chunk size for the i-th
1086+
# thread is s|M|q^(N-i), where N is the number of threads, M is a fixed
1087+
# message of small length, and s >= 1 and q >= 2 are small integers.
1088+
smallest_size, num_threads, s, q = 8, 5, 2, 10
1089+
1090+
smallest_data = os.urandom(smallest_size)
1091+
data = s * smallest_data * (q ** num_threads)
1092+
1093+
h1 = constructor(usedforsecurity=False)
1094+
h2 = constructor(data * num_threads, usedforsecurity=False)
1095+
1096+
def update(chunk_size):
1097+
for index in range(0, len(data), chunk_size):
1098+
h1.update(data[index:index + chunk_size])
10811099

10821100
threads = []
1083-
for threadnum in range(num_threads):
1084-
chunk_size = len(data) // (10 ** threadnum)
1101+
for thread_num in range(num_threads):
1102+
# chunk_size = len(data) // (q ** thread_num)
1103+
chunk_size = s * smallest_size * q ** (num_threads - thread_num)
10851104
self.assertGreater(chunk_size, 0)
1086-
self.assertEqual(chunk_size % len(smallest_data), 0)
1087-
thread = threading.Thread(target=hash_in_chunks,
1088-
args=(chunk_size,))
1105+
self.assertEqual(chunk_size % smallest_size, 0)
1106+
thread = threading.Thread(target=update, args=(chunk_size,))
10891107
threads.append(thread)
10901108

10911109
for thread in threads:
10921110
thread.start()
10931111
for thread in threads:
10941112
thread.join()
10951113

1096-
self.assertEqual(expected_hash, hasher.hexdigest())
1114+
if is_shake:
1115+
self.assertEqual(h1.hexdigest(16), h2.hexdigest(16))
1116+
else:
1117+
self.assertEqual(h1.hexdigest(), h2.hexdigest())
10971118

10981119
def test_get_fips_mode(self):
10991120
fips_mode = self.is_fips_mode

Lib/test/test_netrc.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import netrc, os, unittest, sys, textwrap
22
from contextlib import ExitStack
3+
from test import support
34
from test.support import os_helper, subTests
45
from unittest import mock
56

6-
try:
7-
import pwd
8-
except ImportError:
9-
pwd = None
10-
117

128
class NetrcEnvironment:
139
"""Context manager for setting up an isolated environment to test
@@ -371,9 +367,14 @@ def test_comment_at_end_of_machine_line_pass_has_hash(self, make_nrc):
371367
machine bar.domain.com login foo password pass
372368
""", '#pass')
373369

370+
@unittest.skipUnless(support.is_wasi, 'WASI only test')
371+
def test_security_on_WASI(self):
372+
self.assertFalse(netrc._can_security_check())
373+
self.assertEqual(netrc._getpwuid(0), 'uid 0')
374+
self.assertEqual(netrc._getpwuid(123456), 'uid 123456')
374375

375376
@unittest.skipUnless(os.name == 'posix', 'POSIX only test')
376-
@unittest.skipIf(pwd is None, 'security check requires pwd module')
377+
@unittest.skipUnless(hasattr(os, 'getuid'), "os.getuid is required")
377378
@os_helper.skip_unless_working_chmod
378379
def test_security(self):
379380
# This test is incomplete since we are normally not run as root and

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,7 @@ Anton Kasyanov
943943
Lou Kates
944944
Makoto Kato
945945
Irit Katriel
946+
Kattni
946947
Hiroaki Kawai
947948
Dmitry Kazakov
948949
Brian Kearns
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`netrc`: skip security checks if :func:`os.getuid` is missing.
2+
Patch by Bénédikt Tran.

Modules/_hashopenssl.c

Lines changed: 34 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ py_hashentry_table_new(void) {
255255
return NULL;
256256
}
257257

258-
/* Module state */
258+
// --- Module state -----------------------------------------------------------
259+
259260
static PyModuleDef _hashlibmodule;
260261

261262
typedef struct {
@@ -277,35 +278,33 @@ get_hashlib_state(PyObject *module)
277278
return (_hashlibstate *)state;
278279
}
279280

281+
// --- Module objects ---------------------------------------------------------
282+
280283
typedef struct {
281-
PyObject_HEAD
284+
HASHLIB_OBJECT_HEAD
282285
EVP_MD_CTX *ctx; /* OpenSSL message digest context */
283-
// Prevents undefined behavior via multiple threads entering the C API.
284-
bool use_mutex;
285-
PyMutex mutex; /* OpenSSL context lock */
286286
} HASHobject;
287287

288288
#define HASHobject_CAST(op) ((HASHobject *)(op))
289289

290290
typedef struct {
291-
PyObject_HEAD
291+
HASHLIB_OBJECT_HEAD
292292
HMAC_CTX *ctx; /* OpenSSL hmac context */
293-
// Prevents undefined behavior via multiple threads entering the C API.
294-
bool use_mutex;
295-
PyMutex mutex; /* HMAC context lock */
296293
} HMACobject;
297294

298295
#define HMACobject_CAST(op) ((HMACobject *)(op))
299296

300-
#include "clinic/_hashopenssl.c.h"
297+
// --- Module clinic configuration --------------------------------------------
298+
301299
/*[clinic input]
302300
module _hashlib
303-
class _hashlib.HASH "HASHobject *" "((_hashlibstate *)PyModule_GetState(module))->HASH_type"
304-
class _hashlib.HASHXOF "HASHobject *" "((_hashlibstate *)PyModule_GetState(module))->HASHXOF_type"
305-
class _hashlib.HMAC "HMACobject *" "((_hashlibstate *)PyModule_GetState(module))->HMAC_type"
301+
class _hashlib.HASH "HASHobject *" "&PyType_Type"
302+
class _hashlib.HASHXOF "HASHobject *" "&PyType_Type"
303+
class _hashlib.HMAC "HMACobject *" "&PyType_Type"
306304
[clinic start generated code]*/
307-
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=eb805ce4b90b1b31]*/
305+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6b5c9ce5c28bdc58]*/
308306

307+
#include "clinic/_hashopenssl.c.h"
309308

310309
/* LCOV_EXCL_START */
311310

@@ -700,9 +699,9 @@ static int
700699
_hashlib_HASH_copy_locked(HASHobject *self, EVP_MD_CTX *new_ctx_p)
701700
{
702701
int result;
703-
ENTER_HASHLIB(self);
702+
HASHLIB_ACQUIRE_LOCK(self);
704703
result = EVP_MD_CTX_copy(new_ctx_p, self->ctx);
705-
LEAVE_HASHLIB(self);
704+
HASHLIB_RELEASE_LOCK(self);
706705
if (result == 0) {
707706
notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_copy));
708707
return -1;
@@ -802,27 +801,13 @@ _hashlib_HASH_update_impl(HASHobject *self, PyObject *obj)
802801
{
803802
int result;
804803
Py_buffer view;
805-
806804
GET_BUFFER_VIEW_OR_ERROUT(obj, &view);
807-
808-
if (!self->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) {
809-
self->use_mutex = true;
810-
}
811-
if (self->use_mutex) {
812-
Py_BEGIN_ALLOW_THREADS
813-
PyMutex_Lock(&self->mutex);
814-
result = _hashlib_HASH_hash(self, view.buf, view.len);
815-
PyMutex_Unlock(&self->mutex);
816-
Py_END_ALLOW_THREADS
817-
} else {
818-
result = _hashlib_HASH_hash(self, view.buf, view.len);
819-
}
820-
805+
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
806+
self, view.len,
807+
result = _hashlib_HASH_hash(self, view.buf, view.len)
808+
);
821809
PyBuffer_Release(&view);
822-
823-
if (result == -1)
824-
return NULL;
825-
Py_RETURN_NONE;
810+
return result < 0 ? NULL : Py_None;
826811
}
827812

828813
static PyMethodDef HASH_methods[] = {
@@ -1144,15 +1129,12 @@ _hashlib_HASH(PyObject *module, const char *digestname, PyObject *data_obj,
11441129
}
11451130

11461131
if (view.buf && view.len) {
1147-
if (view.len >= HASHLIB_GIL_MINSIZE) {
1148-
/* We do not initialize self->lock here as this is the constructor
1149-
* where it is not yet possible to have concurrent access. */
1150-
Py_BEGIN_ALLOW_THREADS
1151-
result = _hashlib_HASH_hash(self, view.buf, view.len);
1152-
Py_END_ALLOW_THREADS
1153-
} else {
1154-
result = _hashlib_HASH_hash(self, view.buf, view.len);
1155-
}
1132+
/* Do not use self->mutex here as this is the constructor
1133+
* where it is not yet possible to have concurrent access. */
1134+
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
1135+
view.len,
1136+
result = _hashlib_HASH_hash(self, view.buf, view.len)
1137+
);
11561138
if (result == -1) {
11571139
assert(PyErr_Occurred());
11581140
Py_CLEAR(self);
@@ -1813,9 +1795,9 @@ static int
18131795
locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self)
18141796
{
18151797
int result;
1816-
ENTER_HASHLIB(self);
1798+
HASHLIB_ACQUIRE_LOCK(self);
18171799
result = HMAC_CTX_copy(new_ctx_p, self->ctx);
1818-
LEAVE_HASHLIB(self);
1800+
HASHLIB_RELEASE_LOCK(self);
18191801
if (result == 0) {
18201802
notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy));
18211803
return -1;
@@ -1846,24 +1828,12 @@ _hmac_update(HMACobject *self, PyObject *obj)
18461828
Py_buffer view = {0};
18471829

18481830
GET_BUFFER_VIEW_OR_ERROR(obj, &view, return 0);
1849-
1850-
if (!self->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) {
1851-
self->use_mutex = true;
1852-
}
1853-
if (self->use_mutex) {
1854-
Py_BEGIN_ALLOW_THREADS
1855-
PyMutex_Lock(&self->mutex);
1856-
r = HMAC_Update(self->ctx,
1857-
(const unsigned char *)view.buf,
1858-
(size_t)view.len);
1859-
PyMutex_Unlock(&self->mutex);
1860-
Py_END_ALLOW_THREADS
1861-
} else {
1862-
r = HMAC_Update(self->ctx,
1863-
(const unsigned char *)view.buf,
1864-
(size_t)view.len);
1865-
}
1866-
1831+
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
1832+
self, view.len,
1833+
r = HMAC_Update(
1834+
self->ctx, (const unsigned char *)view.buf, (size_t)view.len
1835+
)
1836+
);
18671837
PyBuffer_Release(&view);
18681838

18691839
if (r == 0) {

0 commit comments

Comments
 (0)