Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/Crypto/Util/_raw_api.py: 48%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

252 statements  

1# =================================================================== 

2# 

3# Copyright (c) 2014, Legrandin <helderijs@gmail.com> 

4# All rights reserved. 

5# 

6# Redistribution and use in source and binary forms, with or without 

7# modification, are permitted provided that the following conditions 

8# are met: 

9# 

10# 1. Redistributions of source code must retain the above copyright 

11# notice, this list of conditions and the following disclaimer. 

12# 2. Redistributions in binary form must reproduce the above copyright 

13# notice, this list of conditions and the following disclaimer in 

14# the documentation and/or other materials provided with the 

15# distribution. 

16# 

17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 

18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 

19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 

20# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 

21# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 

22# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 

23# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 

24# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 

25# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 

26# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 

27# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 

28# POSSIBILITY OF SUCH DAMAGE. 

29# =================================================================== 

30 

31from __future__ import annotations 

32 

33import _thread 

34import abc 

35import os 

36import sys 

37import threading 

38import weakref 

39from importlib import machinery 

40from typing import Any, Optional, Union 

41 

42from Crypto.Util._file_system import pycryptodome_filename 

43 

44# List of file suffixes for Python extensions 

45extension_suffixes = machinery.EXTENSION_SUFFIXES 

46 

47# Which types with buffer interface we support (apart from byte strings) 

48_buffer_type = (bytearray, memoryview) 

49 

50# While a C function runs, the GIL is released, so another thread could 

51# resize a bytearray, or release a memoryview, whose memory the C function 

52# is using. CPython prevents that for as long as the object returned by 

53# c_uint8_ptr() is alive, but PyPy may not: PyPy 7.3 cannot lock any buffer, 

54# PyPy 8.0 can lock a bytearray but not a memoryview. 

55# So, on PyPy, if another thread exists, C functions get a private copy of 

56# the buffers that PyPy cannot lock (see c_uint8_ptr and c_uint8_ptr_out). 

57_is_pypy = "__pypy__" in sys.builtin_module_names 

58 

59# The buffer types that this PyPy keeps locked (found when cffi is loaded) 

60_pypy_locked_types: tuple = () 

61 

62 

63def _must_copy(data: Any) -> bool: 

64 """Whether C code must get a private copy of the bytearray or 

65 memoryview data, instead of its memory (see above). 

66 

67 With one thread only, nothing can change data while a C function runs: 

68 C code never starts Python threads, and signal handlers only run 

69 when it returns. 

70 """ 

71 return _is_pypy and not isinstance(data, _pypy_locked_types) and _thread._count() > 0 

72 

73 

74class _BytesOutput: 

75 """A new bytes object that C code writes into (see create_output_buffer)""" 

76 

77 __slots__ = ("obj", "ptr") 

78 

79 def __init__(self, obj: bytes, ptr: Any) -> None: 

80 self.obj = obj 

81 self.ptr = ptr 

82 

83 def __len__(self) -> int: 

84 return len(self.obj) 

85 

86 

87class _VoidPointer: 

88 @abc.abstractmethod 

89 def get(self) -> Any: 

90 """Return the memory location we point to""" 

91 return 

92 

93 @abc.abstractmethod 

94 def address_of(self) -> Any: 

95 """Return a raw pointer to this pointer""" 

96 return 

97 

98 

99try: 

100 # Starting from v2.18, pycparser (used by cffi for in-line ABI mode) 

101 # stops working correctly when PYOPTIMIZE==2 or the parameter -OO is 

102 # passed. In that case, we fall back to ctypes. 

103 # Note that PyPy ships with an old version of pycparser so we can keep 

104 # using cffi there. 

105 # See https://github.com/Legrandin/pycryptodome/issues/228 

106 if "__pypy__" not in sys.builtin_module_names and sys.flags.optimize == 2: 

107 raise ImportError("CFFI with optimize=2 fails due to pycparser bug.") 

108 

109 # cffi < 1.16.0 crashes on CPython 3.12+ for Windows due to a removed C API. 

110 # See https://groups.google.com/g/python-cffi/c/oZkOIZ_zi5k 

111 import cffi 

112 

113 if ( 

114 sys.version_info >= (3, 12) 

115 and os.name == "nt" 

116 and sys.implementation.name == "cpython" 

117 and cffi.__version_info__ < (1, 16) 

118 ): 

119 raise ImportError("CFFI 1.16.0+ is required with CPython 3.12+ on Windows") 

120 

121 # ffi.from_buffer() with a type (used by c_uint8_ptr) requires cffi 1.12+ 

122 if cffi.__version_info__ < (1, 12): 

123 raise ImportError("CFFI 1.12.0+ is required") 

124 

125 ffi: Any = cffi.FFI() 

126 null_pointer: Any = ffi.NULL 

127 

128 _Array = ffi.new("uint8_t[1]").__class__.__bases__ 

129 

130 def _find_pypy_locked_types() -> tuple: 

131 """Return the buffer types that cannot be resized or released 

132 while ffi.from_buffer() uses them""" 

133 

134 locked: list[type] = [] 

135 data = bytearray(1) 

136 ptr = ffi.from_buffer("uint8_t[]", data) 

137 try: 

138 data.append(0) 

139 except BufferError: 

140 locked.append(bytearray) 

141 data = bytearray(1) 

142 view = memoryview(data) 

143 ptr = ffi.from_buffer("uint8_t[]", view) 

144 try: 

145 view.release() 

146 except BufferError: 

147 try: 

148 data.append(0) 

149 except BufferError: 

150 locked.append(memoryview) 

151 del ptr 

152 return tuple(locked) 

153 

154 if _is_pypy: 

155 _pypy_locked_types = _find_pypy_locked_types() 

156 

157 # Declarations already passed to ffi.cdef(), and the lock that 

158 # protects it when modules are imported from several threads 

159 _declared: set = set() 

160 _declared_lock = threading.Lock() 

161 

162 def load_lib(name: str, cdecl: str) -> Any: 

163 """Load a shared library and return a handle to it. 

164 

165 @name, either an absolute path or the name of a library 

166 in the system search path. 

167 

168 @cdecl, the C function declarations. 

169 """ 

170 

171 if hasattr(ffi, "RTLD_DEEPBIND") and not os.getenv("PYCRYPTODOME_DISABLE_DEEPBIND"): 

172 lib = ffi.dlopen(name, ffi.RTLD_DEEPBIND) 

173 else: 

174 lib = ffi.dlopen(name) 

175 # Several libraries can export the same functions (for instance, 

176 # a portable and an optimized build of the same code), but 

177 # cffi rejects a second declaration of a function. 

178 with _declared_lock: 

179 if cdecl not in _declared: 

180 ffi.cdef(cdecl) 

181 _declared.add(cdecl) 

182 return lib 

183 

184 def c_ulong(x: int) -> Any: 

185 """Convert a Python integer to unsigned long""" 

186 return x 

187 

188 c_ulonglong = c_ulong 

189 c_uint = c_ulong 

190 c_ubyte = c_ulong 

191 

192 def c_size_t(x: int) -> Any: 

193 """Convert a Python integer to size_t""" 

194 return x 

195 

196 def create_string_buffer(init_or_size: Union[bytes, int], size: Optional[int] = None) -> Any: 

197 """Allocate the given amount of bytes (initially set to 0)""" 

198 

199 if isinstance(init_or_size, bytes): 

200 size = max(len(init_or_size) + 1, size or 0) 

201 result = ffi.new("uint8_t[]", size) 

202 result[:] = init_or_size 

203 else: 

204 if size: 

205 raise ValueError("Size must be specified once only") 

206 result = ffi.new("uint8_t[]", init_or_size) 

207 return result 

208 

209 def get_c_string(c_string: Any) -> bytes: 

210 """Convert a C string into a Python byte sequence""" 

211 return ffi.string(c_string) 

212 

213 def get_raw_buffer(buf: Any) -> bytes: 

214 """Convert a C buffer into a Python byte sequence""" 

215 if isinstance(buf, _BytesOutput): 

216 return buf.obj 

217 return ffi.buffer(buf)[:] 

218 

219 def c_uint8_ptr(data: Union[bytes, memoryview, bytearray]) -> Any: 

220 """Pass data to C code, which only reads it""" 

221 if isinstance(data, _buffer_type): 

222 if _is_pypy and _must_copy(data): 

223 return bytes(data) 

224 # The returned object holds the buffer of data, which cannot 

225 # be resized or freed (e.g. by another thread, while the GIL 

226 # is released during a C call) for as long as it is alive. 

227 return ffi.from_buffer("uint8_t[]", data) 

228 elif isinstance(data, (bytes, _Array)): 

229 return data 

230 else: 

231 raise TypeError("Object type %s cannot be passed to C code" % type(data)) 

232 

233 def _c_uint8_ptr_into_bytes(obj: bytes) -> Any: 

234 # On CPython, it points to the content of obj (no copy) 

235 return ffi.from_buffer("uint8_t[]", obj) 

236 

237 class VoidPointer_cffi(_VoidPointer): 

238 """Model a newly allocated pointer to void""" 

239 

240 def __init__(self): 

241 self._pp = ffi.new("void *[1]") 

242 

243 def get(self): 

244 return self._pp[0] 

245 

246 def address_of(self): 

247 return self._pp 

248 

249 def VoidPointer() -> _VoidPointer: 

250 return VoidPointer_cffi() 

251 

252 backend: str = "cffi" 

253 

254except ImportError: 

255 import ctypes 

256 from ctypes import ( # type: ignore[assignment] 

257 CDLL, 

258 byref, 

259 c_size_t, 

260 c_uint, 

261 c_ulong, 

262 c_ulonglong, 

263 c_void_p, 

264 create_string_buffer, 

265 ) 

266 from ctypes import Array as _Array # type: ignore[assignment] 

267 from ctypes.util import find_library 

268 

269 null_pointer = None 

270 cached_architecture: list[str] = [] 

271 

272 def c_ubyte(c: int) -> Any: # type: ignore[misc] 

273 if not (0 <= c < 256): 

274 raise OverflowError() 

275 return ctypes.c_ubyte(c) 

276 

277 def load_lib(name: str, cdecl: str) -> Any: 

278 if not cached_architecture: 

279 # platform.architecture() creates a subprocess, so caching the 

280 # result makes successive imports faster. 

281 import platform 

282 

283 cached_architecture[:] = platform.architecture() 

284 _bits, linkage = cached_architecture 

285 if "." not in name and not linkage.startswith("Win"): 

286 full_name = find_library(name) 

287 if full_name is None: 

288 raise OSError("Cannot load library '%s'" % name) 

289 name = full_name 

290 return CDLL(name) 

291 

292 def get_c_string(c_string: Any) -> bytes: 

293 return c_string.value 

294 

295 def get_raw_buffer(buf: Any) -> bytes: 

296 if isinstance(buf, _BytesOutput): 

297 return buf.obj 

298 return buf.raw 

299 

300 # ---- Get raw pointer --- 

301 

302 _c_ssize_t = ctypes.c_ssize_t 

303 

304 _PyBUF_SIMPLE = 0 

305 _PyObject_GetBuffer = ctypes.pythonapi.PyObject_GetBuffer 

306 _PyBuffer_Release = ctypes.pythonapi.PyBuffer_Release 

307 _py_object = ctypes.py_object 

308 _c_ssize_p = ctypes.POINTER(_c_ssize_t) 

309 

310 # See Include/object.h for CPython 

311 # and https://github.com/pallets/click/blob/master/src/click/_winconsole.py 

312 class _Py_buffer(ctypes.Structure): 

313 _fields_ = [ 

314 ("buf", c_void_p), 

315 ("obj", ctypes.py_object), 

316 ("len", _c_ssize_t), 

317 ("itemsize", _c_ssize_t), 

318 ("readonly", ctypes.c_int), 

319 ("ndim", ctypes.c_int), 

320 ("format", ctypes.c_char_p), 

321 ("shape", _c_ssize_p), 

322 ("strides", _c_ssize_p), 

323 ("suboffsets", _c_ssize_p), 

324 ("internal", c_void_p), 

325 ] 

326 

327 def c_uint8_ptr(data: Union[bytes, memoryview, bytearray]) -> Any: 

328 if isinstance(data, (bytes, _Array)): 

329 return data 

330 elif isinstance(data, bytearray): 

331 # Fast path: the array holds the buffer of data until it is 

332 # garbage collected, so data cannot be resized or freed 

333 # (e.g. by another thread, while the GIL is released during 

334 # a C call). 

335 # The memoryview locks the buffer and reads its length at once: 

336 # with len(data) first, another thread could shrink data 

337 # before from_buffer() locks it. 

338 view = memoryview(data) 

339 return (ctypes.c_ubyte * view.nbytes).from_buffer(view) 

340 elif isinstance(data, _buffer_type): 

341 # memoryview objects can be read-only, which from_buffer() 

342 # does not accept 

343 obj = _py_object(data) 

344 buf = _Py_buffer() 

345 _PyObject_GetBuffer(obj, byref(buf), _PyBUF_SIMPLE) 

346 try: 

347 buffer_type = ctypes.c_ubyte * buf.len 

348 result = buffer_type.from_address(buf.buf) 

349 # Hold the buffer of data until the returned array is 

350 # garbage collected: until then, data cannot be resized 

351 # or freed (e.g. by another thread, while the GIL is 

352 # released during a C call). 

353 weakref.finalize(result, _PyBuffer_Release, byref(buf)) 

354 except BaseException: 

355 _PyBuffer_Release(byref(buf)) 

356 raise 

357 return result 

358 else: 

359 raise TypeError("Object type %s cannot be passed to C code" % type(data)) 

360 

361 def _c_uint8_ptr_into_bytes(obj: bytes) -> Any: 

362 return (ctypes.c_ubyte * len(obj)).from_address(_PyBytes_AsString(obj)) 

363 

364 # --- 

365 

366 class VoidPointer_ctypes(_VoidPointer): 

367 """Model a newly allocated pointer to void""" 

368 

369 def __init__(self): 

370 self._p = c_void_p() 

371 

372 def get(self): 

373 return self._p 

374 

375 def address_of(self): 

376 return byref(self._p) 

377 

378 def VoidPointer() -> _VoidPointer: 

379 return VoidPointer_ctypes() 

380 

381 backend = "ctypes" 

382 

383 

384# Only on CPython, C code can write the output directly into a new bytes 

385# object, which is then returned without copying it. This is what C extensions 

386# do with PyBytes_FromStringAndSize(NULL, size): the content is undefined, 

387# and it must be filled in before anything else gets the object. 

388# Private prototypes, so that the ones in ctypes.pythonapi are not changed. 

389_PyBytes_FromStringAndSize: Any = None 

390_PyBytes_AsString: Any = None 

391if sys.implementation.name == "cpython": 

392 try: 

393 import ctypes as _ctypes 

394 

395 _PyBytes_FromStringAndSize = _ctypes.PYFUNCTYPE( 

396 _ctypes.py_object, _ctypes.c_void_p, _ctypes.c_ssize_t 

397 )(("PyBytes_FromStringAndSize", _ctypes.pythonapi)) 

398 _PyBytes_AsString = _ctypes.PYFUNCTYPE(_ctypes.c_void_p, _ctypes.py_object)( 

399 ("PyBytes_AsString", _ctypes.pythonapi) 

400 ) 

401 except (ImportError, AttributeError): 

402 _PyBytes_FromStringAndSize = None 

403 

404# For shorter outputs, the usual copy is cheap 

405_MIN_BYTES_OUTPUT = 64 * 1024 

406 

407 

408def create_output_buffer(size: int) -> Any: 

409 """Allocate the memory where C code writes the output of a function 

410 into, of the given size:: 

411 

412 out = create_output_buffer(size) 

413 with c_uint8_ptr_out(out) as ptr: 

414 lib.function(..., ptr, ...) 

415 return get_raw_buffer(out) 

416 

417 Usually, it is create_string_buffer(size), which get_raw_buffer() copies. 

418 On CPython, for long outputs, it is a new bytes object instead, which C code 

419 writes directly into, and which get_raw_buffer() returns as it is. 

420 Nothing else gets that bytes object until the C code has written it. 

421 """ 

422 

423 # The empty bytes object is a shared singleton 

424 if _PyBytes_FromStringAndSize is None or size < max(_MIN_BYTES_OUTPUT, 1): 

425 return create_string_buffer(size) 

426 obj = _PyBytes_FromStringAndSize(None, size) 

427 return _BytesOutput(obj, _c_uint8_ptr_into_bytes(obj)) 

428 

429 

430def c_uint8_ptr_len(data: Union[bytes, memoryview, bytearray]) -> tuple[Any, int]: 

431 """Like c_uint8_ptr(), but also return the length of the memory that 

432 C code gets. Pass C code that length, never len(data): another thread 

433 could resize data in the meantime, but not the memory that C code gets, 

434 for as long as the returned pointer is in use.""" 

435 

436 ptr = c_uint8_ptr(data) 

437 return ptr, len(ptr) 

438 

439 

440class c_uint8_ptr_out: 

441 """Pass a buffer to C code that writes into it:: 

442 

443 with c_uint8_ptr_out(buf) as ptr: 

444 lib.function(..., ptr, ...) 

445 

446 Usually, ptr is just c_uint8_ptr(buf). If the C code must not get the 

447 memory of buf (see _must_copy), it gets a private copy instead, which is 

448 copied back into buf when the C function returns (without an exception). 

449 """ 

450 

451 def __init__(self, data: Any) -> None: 

452 self._data = data 

453 self._copy: Any = None 

454 

455 def __enter__(self) -> Any: 

456 if isinstance(self._data, _BytesOutput): 

457 return self._data.ptr 

458 if _is_pypy and isinstance(self._data, _buffer_type) and _must_copy(self._data): 

459 self._copy = create_string_buffer(len(self._data)) 

460 self._copy[0 : len(self._data)] = bytes(self._data) 

461 return self._copy 

462 return c_uint8_ptr(self._data) 

463 

464 def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: 

465 if self._copy is not None and exc_type is None: 

466 self._data[:] = get_raw_buffer(self._copy) 

467 

468 

469class SmartPointer: 

470 """Class to hold a non-managed piece of memory""" 

471 

472 def __init__(self, raw_pointer: Any, destructor: Any) -> None: 

473 self._raw_pointer = raw_pointer 

474 self._destructor = destructor 

475 

476 def get(self) -> Any: 

477 return self._raw_pointer 

478 

479 def release(self) -> Any: 

480 rp, self._raw_pointer = self._raw_pointer, None 

481 return rp 

482 

483 def __del__(self): 

484 try: 

485 if self._raw_pointer is not None: 

486 self._destructor(self._raw_pointer) 

487 self._raw_pointer = None 

488 except AttributeError: 

489 pass 

490 

491 

492def load_pycryptodome_raw_lib(name: str, cdecl: str) -> Any: 

493 """Load a shared library and return a handle to it. 

494 

495 @name, the name of the library expressed as a PyCryptodome module, 

496 for instance Crypto.Cipher._raw_cbc. 

497 

498 @cdecl, the C function declarations. 

499 """ 

500 

501 split = name.split(".") 

502 dir_comps, basename = split[:-1], split[-1] 

503 attempts = [] 

504 for ext in extension_suffixes: 

505 try: 

506 filename = basename + ext 

507 full_name = pycryptodome_filename(dir_comps, filename) 

508 if not os.path.isfile(full_name): 

509 attempts.append("Not found '%s'" % filename) 

510 continue 

511 return load_lib(full_name, cdecl) 

512 except OSError as exp: 

513 attempts.append("Cannot load '%s': %s" % (filename, str(exp))) 

514 raise OSError("Cannot load native module '%s': %s" % (name, ", ".join(attempts))) 

515 

516 

517def is_buffer(x: Any) -> bool: 

518 """Return True if object x supports the buffer interface""" 

519 return isinstance(x, (bytes, bytearray, memoryview)) 

520 

521 

522def is_writeable_buffer(x: Any) -> bool: 

523 return isinstance(x, bytearray) or (isinstance(x, memoryview) and not x.readonly)