• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Gallopsled / pwntools / 13600950642

01 Mar 2025 04:10AM UTC coverage: 74.211% (+3.2%) from 71.055%
13600950642

Pull #2546

github

web-flow
Merge 77df40314 into 60cff2437
Pull Request #2546: ssh: Allow passing `disabled_algorithms` keyword argument from `ssh` to paramiko

3812 of 6380 branches covered (59.75%)

0 of 1 new or added line in 1 file covered. (0.0%)

1243 existing lines in 37 files now uncovered.

13352 of 17992 relevant lines covered (74.21%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.29
/pwnlib/context/__init__.py
1
# -*- coding: utf-8 -*-
2
"""
3
Implements context management so that nested/scoped contexts and threaded
4
contexts work properly and as expected.
5
"""
6
from __future__ import absolute_import
1✔
7
from __future__ import division
1✔
8

9
import atexit
1✔
10
import collections
1✔
11
import errno
1✔
12
import functools
1✔
13
import logging
1✔
14
import os
1✔
15
import os.path
1✔
16
import platform
1✔
17
import shutil
1✔
18
import socket
1✔
19
import string
1✔
20
import sys
1✔
21
import tempfile
1✔
22
import threading
1✔
23
import time
1✔
24

25
import socks
1✔
26

27
from pwnlib.config import register_config
1✔
28
from pwnlib.device import Device
1✔
29
from pwnlib.timeout import Timeout
1✔
30

31
try:
1✔
32
    from collections.abc import Iterable
1✔
UNCOV
33
except ImportError:
×
34
    from collections import Iterable
×
35

36
__all__ = ['context', 'ContextType', 'Thread']
1✔
37

38
_original_socket = socket.socket
1✔
39

40
class _devnull(object):
1✔
41
    name = None
1✔
42
    def write(self, *a, **kw): pass
1!
43
    def read(self, *a, **kw):  return ''
1!
44
    def flush(self, *a, **kw): pass
1✔
45
    def close(self, *a, **kw): pass
1✔
46

47
class _defaultdict(dict):
1✔
48
    """
49
    Dictionary which loads missing keys from another dictionary.
50

51
    This is neccesary because the ``default_factory`` method of
52
    :class:`collections.defaultdict` does not provide the key.
53

54
    Examples:
55

56
        >>> a = {'foo': 'bar'}
57
        >>> b = pwnlib.context._defaultdict(a)
58
        >>> b['foo']
59
        'bar'
60
        >>> 'foo' in b
61
        False
62
        >>> b['foo'] = 'baz'
63
        >>> b['foo']
64
        'baz'
65
        >>> del b['foo']
66
        >>> b['foo']
67
        'bar'
68

69
        >>> a = {'foo': 'bar'}
70
        >>> b = pwnlib.context._defaultdict(a)
71
        >>> b['baz'] #doctest: +ELLIPSIS
72
        Traceback (most recent call last):
73
        ...
74
        KeyError: 'baz'
75
    """
76
    def __init__(self, default=None):
1✔
77
        super(_defaultdict, self).__init__()
1✔
78
        if default is None:
1!
UNCOV
79
            default = {}
×
80

81
        self.default = default
1✔
82

83

84
    def __missing__(self, key):
1✔
85
        return self.default[key]
1✔
86

87
class _DictStack(object):
1✔
88
    """
89
    Manages a dictionary-like object, permitting saving and restoring from
90
    a stack of states via :func:`push` and :func:`pop`.
91

92
    The underlying object used as ``default`` must implement ``copy``, ``clear``,
93
    and ``update``.
94

95
    Examples:
96

97
        >>> t = pwnlib.context._DictStack(default={})
98
        >>> t['key'] = 'value'
99
        >>> t
100
        {'key': 'value'}
101
        >>> t.push()
102
        >>> t
103
        {'key': 'value'}
104
        >>> t['key'] = 'value2'
105
        >>> t
106
        {'key': 'value2'}
107
        >>> t.pop()
108
        >>> t
109
        {'key': 'value'}
110
    """
111
    def __init__(self, default):
1✔
112
        self._current = _defaultdict(default)
1✔
113
        self.__stack  = []
1✔
114

115
    def push(self):
1✔
116
        self.__stack.append(self._current.copy())
1✔
117

118
    def pop(self):
1✔
119
        self._current.clear()
1✔
120
        self._current.update(self.__stack.pop())
1✔
121

122
    def copy(self):
1✔
123
        return self._current.copy()
1✔
124

125
    # Pass-through container emulation routines
126
    def __len__(self):              return self._current.__len__()
1!
127
    def __delitem__(self, k):       return self._current.__delitem__(k)
1✔
128
    def __getitem__(self, k):       return self._current.__getitem__(k)
1✔
129
    def __setitem__(self, k, v):    return self._current.__setitem__(k, v)
1✔
130
    def __contains__(self, k):      return self._current.__contains__(k)
1✔
131
    def __iter__(self):             return self._current.__iter__()
1!
132
    def __repr__(self):             return self._current.__repr__()
1!
133
    def __eq__(self, other):        return self._current.__eq__(other)
1!
134

135
    # Required for keyword expansion operator ** to work
136
    def keys(self):                 return self._current.keys()
1!
137
    def values(self):               return self._current.values()
1!
138
    def items(self):                return self._current.items()
1!
139

140

141
class _Tls_DictStack(threading.local, _DictStack):
1✔
142
    """
143
    Per-thread implementation of :class:`_DictStack`.
144

145
    Examples:
146

147
        >>> t = pwnlib.context._Tls_DictStack({})
148
        >>> t['key'] = 'value'
149
        >>> print(t)
150
        {'key': 'value'}
151
        >>> def p(): print(t)
152
        >>> thread = threading.Thread(target=p)
153
        >>> _ = (thread.start(), thread.join())
154
        {}
155
    """
156
    pass
1✔
157

158

159
def _validator(validator):
1✔
160
    """
161
    Validator that is tightly coupled to the implementation
162
    of the classes here.
163

164
    This expects that the object has a ._tls property which
165
    is of type _DictStack.
166
    """
167

168
    name = validator.__name__
1✔
169
    doc  = validator.__doc__
1✔
170

171
    def fget(self):
1✔
172
        return self._tls[name]
1✔
173

174
    def fset(self, val):
1✔
175
        self._tls[name] = validator(self, val)
1✔
176

177
    def fdel(self):
1✔
UNCOV
178
        self._tls._current.pop(name,None)
×
179

180
    return property(fget, fset, fdel, doc)
1✔
181

182
class Thread(threading.Thread):
1✔
183
    """
184
    Instantiates a context-aware thread, which inherit its context when it is
185
    instantiated. The class can be accessed both on the context module as
186
    `pwnlib.context.Thread` and on the context singleton object inside the
187
    context module as `pwnlib.context.context.Thread`.
188

189
    Threads created by using the native :class`threading`.Thread` will have a
190
    clean (default) context.
191

192
    Regardless of the mechanism used to create any thread, the context
193
    is de-coupled from the parent thread, so changes do not cascade
194
    to child or parent.
195

196
    Saves a copy of the context when instantiated (at ``__init__``)
197
    and updates the new thread's context before passing control
198
    to the user code via ``run`` or ``target=``.
199

200
    Examples:
201

202
        >>> context.clear()
203
        >>> context.update(arch='arm')
204
        >>> def p():
205
        ...     print(context.arch)
206
        ...     context.arch = 'mips'
207
        ...     print(context.arch)
208
        >>> # Note that a normal Thread starts with a clean context
209
        >>> # (i386 is the default architecture)
210
        >>> t = threading.Thread(target=p)
211
        >>> _=(t.start(), t.join())
212
        i386
213
        mips
214
        >>> # Note that the main Thread's context is unchanged
215
        >>> print(context.arch)
216
        arm
217
        >>> # Note that a context-aware Thread receives a copy of the context
218
        >>> t = pwnlib.context.Thread(target=p)
219
        >>> _=(t.start(), t.join())
220
        arm
221
        mips
222
        >>> # Again, the main thread is unchanged
223
        >>> print(context.arch)
224
        arm
225

226
    Implementation Details:
227

228
        This class implemented by hooking the private function
229
        :func:`threading.Thread._Thread_bootstrap`, which is called before
230
        passing control to :func:`threading.Thread.run`.
231

232
        This could be done by overriding ``run`` itself, but we would have to
233
        ensure that all uses of the class would only ever use the keyword
234
        ``target=`` for ``__init__``, or that all subclasses invoke
235
        ``super(Subclass.self).set_up_context()`` or similar.
236
    """
237
    def __init__(self, *args, **kwargs):
1✔
238
        super(Thread, self).__init__(*args, **kwargs)
1✔
239
        self.old = context.copy()
1✔
240

241
    def __bootstrap(self):
1✔
242
        """
243
        Implementation Details:
244
            This only works because the class is named ``Thread``.
245
            If its name is changed, we have to implement this hook
246
            differently.
247
        """
UNCOV
248
        context.update(**self.old)
×
249
        sup = super(Thread, self)
×
250
        bootstrap = getattr(sup, '_bootstrap', None)
×
251
        if bootstrap is None:
×
252
            sup.__bootstrap()
×
253
        else:
UNCOV
254
            bootstrap()
×
255
    _bootstrap = __bootstrap
1✔
256

257
def _longest(d):
1✔
258
    """
259
    Returns an OrderedDict with the contents of the input dictionary ``d``
260
    sorted by the length of the keys, in descending order.
261

262
    This is useful for performing substring matching via ``str.startswith``,
263
    as it ensures the most complete match will be found.
264

265
    >>> data = {'a': 1, 'bb': 2, 'ccc': 3}
266
    >>> pwnlib.context._longest(data) == data
267
    True
268
    >>> for i in pwnlib.context._longest(data):
269
    ...     print(i)
270
    ccc
271
    bb
272
    a
273
    """
274
    return collections.OrderedDict((k,d[k]) for k in sorted(d, key=len, reverse=True))
1✔
275

276
class ContextType(object):
1✔
277
    r"""
278
    Class for specifying information about the target machine.
279
    Intended for use as a pseudo-singleton through the global
280
    variable :data:`.context`, available via
281
    ``from pwn import *`` as ``context``.
282

283
    The context is usually specified at the top of the Python file for clarity. ::
284

285
        #!/usr/bin/env python
286
        context.update(arch='i386', os='linux')
287

288
    Currently supported properties and their defaults are listed below.
289
    The defaults are inherited from :data:`pwnlib.context.ContextType.defaults`.
290

291
    Additionally, the context is thread-aware when using
292
    :class:`pwnlib.context.Thread` instead of :class:`threading.Thread`
293
    (all internal ``pwntools`` threads use the former).
294

295
    The context is also scope-aware by using the ``with`` keyword.
296

297
    Examples:
298

299
        >>> context.clear()
300
        >>> context.update(os='linux') # doctest: +ELLIPSIS
301
        >>> context.os == 'linux'
302
        True
303
        >>> context.arch = 'arm'
304
        >>> vars(context) == {'arch': 'arm', 'bits': 32, 'endian': 'little', 'os': 'linux', 'newline': b'\n'}
305
        True
306
        >>> context.endian
307
        'little'
308
        >>> context.bits
309
        32
310

311
    .. doctest::
312
        :options: +POSIX +TODO
313

314
        >>> def nop():
315
        ...   print(enhex(pwnlib.asm.asm('nop')))
316
        >>> nop()
317
        00f020e3
318
        >>> with context.local(arch = 'i386'):
319
        ...   nop()
320
        90
321
        >>> from pwnlib.context import Thread as PwnThread
322
        >>> from threading      import Thread as NormalThread
323
        >>> with context.local(arch = 'mips'):
324
        ...     pwnthread = PwnThread(target=nop)
325
        ...     thread    = NormalThread(target=nop)
326
        >>> # Normal thread uses the default value for arch, 'i386'
327
        >>> _=(thread.start(), thread.join())
328
        90
329
        >>> # Pwnthread uses the correct context from creation-time
330
        >>> _=(pwnthread.start(), pwnthread.join())
331
        00000000
332
        >>> nop()
333
        00f020e3
334
    """
335

336
    #
337
    # Use of 'slots' is a heavy-handed way to prevent accidents
338
    # like 'context.architecture=' instead of 'context.arch='.
339
    #
340
    # Setting any properties on a ContextType object will throw an
341
    # exception.
342
    #
343
    __slots__ = '_tls',
1✔
344

345
    #: Default values for :class:`pwnlib.context.ContextType`
346
    defaults = {
1✔
347
        'adb_host': 'localhost',
348
        'adb_port': 5037,
349
        'arch': 'i386',
350
        'aslr': True,
351
        'binary': None,
352
        'bits': 32,
353
        'buffer_size': 4096,
354
        'cache_dir_base': os.environ.get(
355
            'XDG_CACHE_HOME',
356
            os.path.join(os.path.expanduser('~'), '.cache')
357
        ),
358
        'cyclic_alphabet': string.ascii_lowercase.encode(),
359
        'cyclic_size': 4,
360
        'delete_corefiles': False,
361
        'device': os.getenv('ANDROID_SERIAL', None) or None,
362
        'encoding': 'auto',
363
        'endian': 'little',
364
        'gdbinit': "",
365
        'gdb_binary': "",
366
        'kernel': None,
367
        'local_libcdb': "/var/lib/libc-database",
368
        'log_level': logging.INFO,
369
        'log_file': _devnull(),
370
        'log_console': sys.stdout,
371
        'randomize': False,
372
        'rename_corefiles': True,
373
        'newline': b'\n',
374
        'throw_eof_on_incomplete_line': None,
375
        'noptrace': False,
376
        'os': 'linux',
377
        'proxy': None,
378
        'ssh_session': None,
379
        'signed': False,
380
        'terminal': tuple(),
381
        'timeout': Timeout.maximum,
382
    }
383

384
    unix_like    = {'newline': b'\n'}
1✔
385
    windows_like = {'newline': b'\r\n'}
1✔
386

387
    #: Keys are valid values for :meth:`pwnlib.context.ContextType.os`
388
    oses = _longest({
1✔
389
        'linux':     unix_like,
390
        'freebsd':   unix_like,
391
        'windows':   windows_like,
392
        'cgc':       unix_like,
393
        'android':   unix_like,
394
        'baremetal': unix_like,
395
        'darwin':    unix_like,
396
    })
397

398
    big_32    = {'endian': 'big', 'bits': 32}
1✔
399
    big_64    = {'endian': 'big', 'bits': 64}
1✔
400
    little_8  = {'endian': 'little', 'bits': 8}
1✔
401
    little_16 = {'endian': 'little', 'bits': 16}
1✔
402
    little_32 = {'endian': 'little', 'bits': 32}
1✔
403
    little_64 = {'endian': 'little', 'bits': 64}
1✔
404

405
    #: Keys are valid values for :meth:`pwnlib.context.ContextType.arch`.
406
    #
407
    #: Values are defaults which are set when
408
    #: :attr:`pwnlib.context.ContextType.arch` is set
409
    architectures = _longest({
1✔
410
        'aarch64':   little_64,
411
        'alpha':     little_64,
412
        'avr':       little_8,
413
        'amd64':     little_64,
414
        'arm':       little_32,
415
        'cris':      little_32,
416
        'i386':      little_32,
417
        'ia64':      big_64,
418
        'm68k':      big_32,
419
        'mips':      little_32,
420
        'mips64':    little_64,
421
        'msp430':    little_16,
422
        'powerpc':   big_32,
423
        'powerpc64': big_64,
424
        'riscv32':   little_32,
425
        'riscv64':   little_64,
426
        'loongarch64':   little_64,
427
        's390':      big_32,
428
        'sparc':     big_32,
429
        'sparc64':   big_64,
430
        'thumb':     little_32,
431
        'vax':       little_32,
432
        'none':      {},
433
    })
434

435
    #: Valid values for :attr:`endian`
436
    endiannesses = _longest({
1✔
437
        'be':     'big',
438
        'eb':     'big',
439
        'big':    'big',
440
        'le':     'little',
441
        'el':     'little',
442
        'little': 'little'
443
    })
444

445
    #: Valid string values for :attr:`signed`
446
    signednesses = {
1✔
447
        'unsigned': False,
448
        'no':       False,
449
        'yes':      True,
450
        'signed':   True
451
    }
452

453
    valid_signed = sorted(signednesses)
1✔
454

455
    def __init__(self, **kwargs):
1✔
456
        """
457
        Initialize the ContextType structure.
458

459
        All keyword arguments are passed to :func:`update`.
460
        """
461
        self._tls = _Tls_DictStack(_defaultdict(self.defaults))
1✔
462
        self.update(**kwargs)
1✔
463

464

465
    def copy(self):
1✔
466
        r"""copy() -> dict
467
        Returns a copy of the current context as a dictionary.
468

469
        Examples:
470

471
            >>> context.clear()
472
            >>> context.os   = 'linux'
473
            >>> vars(context) == {'os': 'linux', 'newline': b'\n'}
474
            True
475
        """
476
        return self._tls.copy()
1✔
477

478

479
    @property
1✔
480
    def __dict__(self):
1✔
481
        return self.copy()
1✔
482

483
    def update(self, *args, **kwargs):
1✔
484
        """
485
        Convenience function, which is shorthand for setting multiple
486
        variables at once.
487

488
        It is a simple shorthand such that::
489

490
            context.update(os = 'linux', arch = 'arm', ...)
491

492
        is equivalent to::
493

494
            context.os   = 'linux'
495
            context.arch = 'arm'
496
            ...
497

498
        The following syntax is also valid::
499

500
            context.update({'os': 'linux', 'arch': 'arm'})
501

502
        Arguments:
503
          kwargs: Variables to be assigned in the environment.
504

505
        Examples:
506

507
            >>> context.clear()
508
            >>> context.update(arch = 'i386', os = 'linux')
509
            >>> context.arch, context.os
510
            ('i386', 'linux')
511
        """
512
        for arg in args:
1!
UNCOV
513
            self.update(**arg)
×
514

515
        for k,v in kwargs.items():
1✔
516
            setattr(self,k,v)
1✔
517

518
    def __repr__(self):
1✔
519
        v = sorted("%s = %r" % (k,v) for k,v in self._tls._current.items())
1✔
520
        return '%s(%s)' % (self.__class__.__name__, ', '.join(v))
1✔
521

522
    def local(self, function=None, **kwargs):
1✔
523
        """local(**kwargs) -> context manager
524

525
        Create a context manager for use with the ``with`` statement.
526

527
        For more information, see the example below or PEP 343.
528

529
        Arguments:
530
          kwargs: Variables to be assigned in the new environment.
531

532
        Returns:
533
          ContextType manager for managing the old and new environment.
534

535
        Examples:
536

537
            >>> context.clear()
538
            >>> context.timeout = 1
539
            >>> context.timeout == 1
540
            True
541
            >>> print(context.timeout)
542
            1.0
543
            >>> with context.local(timeout = 2):
544
            ...     print(context.timeout)
545
            ...     context.timeout = 3
546
            ...     print(context.timeout)
547
            2.0
548
            3.0
549
            >>> print(context.timeout)
550
            1.0
551
        """
552
        class LocalContext(object):
1✔
553
            def __enter__(a):
1✔
554
                self._tls.push()
1✔
555
                self.update(**{k:v for k,v in kwargs.items() if v is not None})
1✔
556
                return self
1✔
557

558
            def __exit__(a, *b, **c):
1✔
559
                self._tls.pop()
1✔
560

561
            def __call__(self, function, *a, **kw):
1✔
562
                @functools.wraps(function)
1✔
563
                def inner(*a, **kw):
1✔
564
                    with self:
1✔
565
                        return function(*a, **kw)
1✔
566
                return inner
1✔
567

568
        return LocalContext()
1✔
569

570
    @property
1✔
571
    def silent(self, function=None):
1✔
572
        """Disable all non-error logging within the enclosed scope.
573
        """
574
        return self.local(function, log_level='error')
1✔
575

576
    @property
1✔
577
    def quiet(self, function=None):
1✔
578
        """Disables all non-error logging within the enclosed scope,
579
        *unless* the debugging level is set to 'debug' or lower.
580

581
        Example:
582

583
            Let's assume the normal situation, where log_level is INFO.
584

585
            >>> context.clear(log_level='info')
586

587
            Note that only the log levels below ERROR do not print anything.
588

589
            >>> with context.quiet:
590
            ...     log.debug("DEBUG")
591
            ...     log.info("INFO")
592
            ...     log.warn("WARN")
593

594
            Next let's try with the debugging level set to 'debug' before we
595
            enter the context handler:
596

597
            >>> with context.local(log_level='debug'):
598
            ...     with context.quiet:
599
            ...         log.debug("DEBUG")
600
            ...         log.info("INFO")
601
            ...         log.warn("WARN")
602
            [...] DEBUG
603
            [...] INFO
604
            [...] WARN
605
        """
606
        level = 'error'
1✔
607
        if context.log_level <= logging.DEBUG:
1✔
608
            level = None
1✔
609
        return self.local(function, log_level=level)
1✔
610

611
    def quietfunc(self, function):
1✔
612
        """Similar to :attr:`quiet`, but wraps a whole function.
613

614
        Example:
615

616
            Let's set up two functions, which are the same but one is
617
            wrapped with :attr:`quietfunc`.
618

619
            >>> def loud(): log.info("Loud")
620
            >>> @context.quietfunc
621
            ... def quiet(): log.info("Quiet")
622

623
            If we set the logging level to 'info', the loud function
624
            prints its contents.
625

626
            >>> with context.local(log_level='info'): loud()
627
            [*] Loud
628

629
            However, the quiet function does not, since :attr:`quietfunc`
630
            silences all output unless the log level is DEBUG.
631

632
            >>> with context.local(log_level='info'): quiet()
633

634
            Now let's try again with debugging enabled.
635

636
            >>> with context.local(log_level='debug'): quiet()
637
            [*] Quiet
638
        """
639
        @functools.wraps(function)
1✔
640
        def wrapper(*a, **kw):
1✔
641
            level = 'error'
1✔
642
            if context.log_level <= logging.DEBUG:
1✔
643
                level = None
1✔
644
            with self.local(function, log_level=level):
1✔
645
                return function(*a, **kw)
1✔
646
        return wrapper
1✔
647

648

649
    @property
1✔
650
    def verbose(self):
1✔
651
        """Enable all logging within the enclosed scope.
652

653
        This is the opposite of :attr:`.quiet` and functionally equivalent to:
654

655
        .. code-block:: python
656

657
            with context.local(log_level='debug'):
658
                ...
659

660
        Example:
661

662
            Note that the function does not emit any information by default
663

664
            >>> context.clear()
665
            >>> def func(): log.debug("Hello")
666
            >>> func()
667

668
            But if we put it inside a :attr:`.verbose` context manager, the
669
            information is printed.
670

671
            >>> with context.verbose: func()
672
            [...] Hello
673

674
        """
675
        return self.local(log_level='debug')
1✔
676

677
    def clear(self, *a, **kw):
1✔
678
        """
679
        Clears the contents of the context.
680
        All values are set to their defaults.
681

682
        Arguments:
683

684
            a: Arguments passed to ``update``
685
            kw: Arguments passed to ``update``
686

687
        Examples:
688

689
            >>> # Default value
690
            >>> context.clear()
691
            >>> context.arch == 'i386'
692
            True
693
            >>> context.arch = 'arm'
694
            >>> context.arch == 'i386'
695
            False
696
            >>> context.clear()
697
            >>> context.arch == 'i386'
698
            True
699
        """
700
        self._tls._current.clear()
1✔
701

702
        if a or kw:
1✔
703
            self.update(*a, **kw)
1✔
704

705
    @property
1✔
706
    def native(self):
1✔
707
        if context.os in ('android', 'baremetal', 'cgc'):
1!
UNCOV
708
            return False
×
709

710
        arch = context.arch
1✔
711
        with context.local(arch = platform.machine()):
1✔
712
            platform_arch = context.arch
1✔
713

714
            if arch in ('i386', 'amd64') and platform_arch in ('i386', 'amd64'):
1✔
715
                return True
1✔
716

717
            return arch == platform_arch
1✔
718

719
    @_validator
1✔
720
    def arch(self, arch):
1✔
721
        """
722
        Target binary architecture.
723

724
        Allowed values are listed in :attr:`pwnlib.context.ContextType.architectures`.
725

726
        Side Effects:
727

728
            If an architecture is specified which also implies additional
729
            attributes (e.g. 'amd64' implies 64-bit words, 'powerpc' implies
730
            big-endian), these attributes will be set on the context if a
731
            user has not already set a value.
732

733
            The following properties may be modified.
734

735
            - :attr:`bits`
736
            - :attr:`endian`
737

738
        Raises:
739
            AttributeError: An invalid architecture was specified
740

741
        Examples:
742

743
            >>> context.clear()
744
            >>> context.arch == 'i386' # Default architecture
745
            True
746

747
            >>> context.arch = 'mips'
748
            >>> context.arch == 'mips'
749
            True
750

751
            >>> context.arch = 'doge' #doctest: +ELLIPSIS
752
            Traceback (most recent call last):
753
             ...
754
            AttributeError: arch must be one of ['aarch64', ..., 'thumb']
755

756
            >>> context.arch = 'ppc'
757
            >>> context.arch == 'powerpc' # Aliased architecture
758
            True
759

760
            >>> context.clear()
761
            >>> context.bits == 32 # Default value
762
            True
763
            >>> context.arch = 'amd64'
764
            >>> context.bits == 64 # New value
765
            True
766

767
            Note that expressly setting :attr:`bits` means that we use
768
            that value instead of the default
769

770
            >>> context.clear()
771
            >>> context.bits = 32
772
            >>> context.arch = 'amd64'
773
            >>> context.bits == 32
774
            True
775

776
            Setting the architecture can override the defaults for
777
            both :attr:`endian` and :attr:`bits`
778

779
            >>> context.clear()
780
            >>> context.arch = 'powerpc64'
781
            >>> vars(context) == {'arch': 'powerpc64', 'bits': 64, 'endian': 'big'}
782
            True
783
        """
784
        # Lowercase
785
        arch = arch.lower()
1✔
786

787
        # Attempt to perform convenience and legacy compatibility transformations.
788
        # We have to make sure that x86_64 appears before x86 for this to work correctly.
789
        transform = [('ppc64', 'powerpc64'),
1✔
790
                     ('ppc', 'powerpc'),
791
                     ('x86-64', 'amd64'),
792
                     ('x86_64', 'amd64'),
793
                     ('x86', 'i386'),
794
                     ('i686', 'i386'),
795
                     ('armv7l', 'arm'),
796
                     ('armeabi', 'arm'),
797
                     ('arm64', 'aarch64'),
798
                     ('rv32', 'riscv32'),
799
                     ('rv64', 'riscv64'),
800
                     ('loong64', 'loongarch64'),
801
                     ('la64', 'loongarch64')]
802
        for k, v in transform:
1✔
803
            if arch.startswith(k):
1✔
804
                arch = v
1✔
805
                break
1✔
806

807
        try:
1✔
808
            defaults = self.architectures[arch]
1✔
809
        except KeyError:
1✔
810
            raise AttributeError('AttributeError: arch (%r) must be one of %r' % (arch, sorted(self.architectures)))
1✔
811

812
        for k,v in defaults.items():
1✔
813
            if k not in self._tls:
1✔
814
                self._tls[k] = v
1✔
815

816
        return arch
1✔
817

818
    @_validator
1✔
819
    def aslr(self, aslr):
1✔
820
        """
821
        ASLR settings for new processes.
822

823
        If :const:`False`, attempt to disable ASLR in all processes which are
824
        created via ``personality`` (``setarch -R``) and ``setrlimit``
825
        (``ulimit -s unlimited``).
826

827
        The ``setarch`` changes are lost if a ``setuid`` binary is executed.
828
        """
829
        return bool(aslr)
1✔
830

831
    @_validator
1✔
832
    def kernel(self, arch):
1✔
833
        """
834
        Target machine's kernel architecture.
835

836
        Usually, this is the same as ``arch``, except when
837
        running a 32-bit binary on a 64-bit kernel (e.g. i386-on-amd64).
838

839
        Even then, this doesn't matter much -- only when the the segment
840
        registers need to be known
841
        """
842
        with self.local(arch=arch):
1✔
843
            return self.arch
1✔
844

845
    @_validator
1✔
846
    def bits(self, bits):
1✔
847
        """
848
        Target machine word size, in bits (i.e. the size of general purpose registers).
849

850
        The default value is ``32``, but changes according to :attr:`arch`.
851

852
        Examples:
853

854
            >>> context.clear()
855
            >>> context.bits == 32
856
            True
857
            >>> context.bits = 64
858
            >>> context.bits == 64
859
            True
860
            >>> context.bits = -1 #doctest: +ELLIPSIS
861
            Traceback (most recent call last):
862
            ...
863
            AttributeError: bits must be > 0 (-1)
864
        """
865
        bits = int(bits)
1✔
866

867
        if bits <= 0:
1✔
868
            raise AttributeError("bits must be > 0 (%r)" % bits)
1✔
869

870
        return bits
1✔
871

872
    @_validator
1✔
873
    def binary(self, binary):
1✔
874
        """
875
        Infer target architecture, bit-with, and endianness from a binary file.
876
        Data type is a :class:`pwnlib.elf.ELF` object.
877

878
        Examples:
879

880
        .. doctest::
881
            :options: +POSIX +TODO
882

883
            >>> context.clear()
884
            >>> context.arch, context.bits
885
            ('i386', 32)
886
            >>> context.binary = '/bin/bash'
887
            >>> context.arch, context.bits
888
            ('amd64', 64)
889
            >>> context.binary
890
            ELF('/bin/bash')
891

892
        """
893
        # Cyclic imports... sorry Idolf.
894
        from pwnlib.elf     import ELF
1✔
895

896
        if not isinstance(binary, ELF):
1✔
897
            binary = ELF(binary)
1✔
898

899
        self.arch   = binary.arch
1✔
900
        self.bits   = binary.bits
1✔
901
        self.endian = binary.endian
1✔
902
        self.os     = binary.os
1✔
903

904
        return binary
1✔
905

906
    @property
1✔
907
    def bytes(self):
1✔
908
        """
909
        Target machine word size, in bytes (i.e. the size of general purpose registers).
910

911
        This is a convenience wrapper around ``bits // 8``.
912

913
        Examples:
914

915
            >>> context.bytes = 1
916
            >>> context.bits == 8
917
            True
918

919
            >>> context.bytes = 0 #doctest: +ELLIPSIS
920
            Traceback (most recent call last):
921
            ...
922
            AttributeError: bits must be > 0 (0)
923
        """
924
        return self.bits // 8
1✔
925
    @bytes.setter
1✔
926
    def bytes(self, value):
1✔
927
        self.bits = value*8
1✔
928

929
    @_validator
1✔
930
    def encoding(self, charset):
1✔
UNCOV
931
        if charset == 'auto':
×
932
            return charset
×
933

UNCOV
934
        if (  b'aA'.decode(charset) != 'aA'
×
935
            or 'aA'.encode(charset) != b'aA'):
UNCOV
936
            raise ValueError('Strange encoding!')
×
937

UNCOV
938
        return charset
×
939

940
    @_validator
1✔
941
    def endian(self, endianness):
1✔
942
        """
943
        Endianness of the target machine.
944

945
        The default value is ``'little'``, but changes according to :attr:`arch`.
946

947
        Raises:
948
            AttributeError: An invalid endianness was provided
949

950
        Examples:
951

952
            >>> context.clear()
953
            >>> context.endian == 'little'
954
            True
955

956
            >>> context.endian = 'big'
957
            >>> context.endian
958
            'big'
959

960
            >>> context.endian = 'be'
961
            >>> context.endian == 'big'
962
            True
963

964
            >>> context.endian = 'foobar' #doctest: +ELLIPSIS
965
            Traceback (most recent call last):
966
             ...
967
            AttributeError: endian must be one of ['be', 'big', 'eb', 'el', 'le', 'little']
968
        """
969
        endian = endianness.lower()
1✔
970

971
        if endian not in self.endiannesses:
1✔
972
            raise AttributeError("endian must be one of %r" % sorted(self.endiannesses))
1✔
973

974
        return self.endiannesses[endian]
1✔
975

976

977
    @_validator
1✔
978
    def log_level(self, value):
1✔
979
        """
980
        Sets the verbosity of ``pwntools`` logging mechanism.
981

982
        More specifically it controls the filtering of messages that happens
983
        inside the handler for logging to the screen. So if you want e.g. log
984
        all messages to a file, then this attribute makes no difference to you.
985

986
        Valid values are specified by the standard Python ``logging`` module.
987

988
        Default value is set to ``INFO``.
989

990
        Examples:
991

992
            >>> context.log_level = 'error'
993
            >>> context.log_level == logging.ERROR
994
            True
995
            >>> context.log_level = 10
996
            >>> context.log_level = 'foobar' #doctest: +ELLIPSIS
997
            Traceback (most recent call last):
998
            ...
999
            AttributeError: log_level must be an integer or one of ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
1000
        """
1001
        # If it can be converted into an int, success
1002
        try:                    return int(value)
1✔
1003
        except ValueError:  pass
1✔
1004

1005
        # If it is defined in the logging module, success
1006
        try:                    return getattr(logging, value.upper())
1✔
1007
        except AttributeError:  pass
1✔
1008

1009
        # Otherwise, fail
1010
        try:
1✔
1011
            level_names = logging._levelToName.values()
1✔
UNCOV
1012
        except AttributeError:
×
1013
            level_names = filter(lambda x: isinstance(x,str), logging._levelNames)
×
1014
        permitted = sorted(level_names)
1✔
1015
        raise AttributeError('log_level must be an integer or one of %r' % permitted)
1✔
1016

1017
    @_validator
1✔
1018
    def log_file(self, value):
1✔
1019
        r"""
1020
        Sets the target file for all logging output.
1021

1022
        Works in a similar fashion to :attr:`log_level`.
1023

1024
        Examples:
1025

1026

1027
            >>> foo_txt = tempfile.mktemp()
1028
            >>> bar_txt = tempfile.mktemp()
1029
            >>> context.log_file = foo_txt
1030
            >>> log.debug('Hello!')
1031
            >>> with context.local(log_level='ERROR'): #doctest: +ELLIPSIS
1032
            ...     log.info('Hello again!')
1033
            >>> with context.local(log_file=bar_txt):
1034
            ...     log.debug('Hello from bar!')
1035
            >>> log.info('Hello from foo!')
1036
            >>> open(foo_txt).readlines()[-3] #doctest: +ELLIPSIS
1037
            '...:DEBUG:...:Hello!\n'
1038
            >>> open(foo_txt).readlines()[-2] #doctest: +ELLIPSIS
1039
            '...:INFO:...:Hello again!\n'
1040
            >>> open(foo_txt).readlines()[-1] #doctest: +ELLIPSIS
1041
            '...:INFO:...:Hello from foo!\n'
1042
            >>> open(bar_txt).readlines()[-1] #doctest: +ELLIPSIS
1043
            '...:DEBUG:...:Hello from bar!\n'
1044
        """
1045
        if isinstance(value, (bytes, str)):
1!
1046
            # check if mode was specified as "[value],[mode]"
1047
            from pwnlib.util.packing import _need_text
1✔
1048
            value = _need_text(value)
1✔
1049
            if ',' not in value:
1!
1050
                value += ',a'
1✔
1051
            filename, mode = value.rsplit(',', 1)
1✔
1052
            value = open(filename, mode)
1✔
1053

UNCOV
1054
        elif not hasattr(value, "fileno"):
×
1055
            raise AttributeError('log_file must be a file')
×
1056

1057
        # Is this the same file we already have open?
1058
        # If so, don't re-print the banner.
1059
        if self.log_file and not isinstance(self.log_file, _devnull):
1✔
1060
            a = os.fstat(value.fileno()).st_ino
1✔
1061
            b = os.fstat(self.log_file.fileno()).st_ino
1✔
1062

1063
            if a == b:
1!
UNCOV
1064
                return self.log_file
×
1065

1066
        iso_8601 = '%Y-%m-%dT%H:%M:%S'
1✔
1067
        lines = [
1✔
1068
            '=' * 78,
1069
            ' Started at %s ' % time.strftime(iso_8601),
1070
            ' sys.argv = [',
1071
            ]
1072
        for arg in sys.argv:
1✔
1073
            lines.append('   %r,' % arg)
1✔
1074
        lines.append(' ]')
1✔
1075
        lines.append('=' * 78)
1✔
1076
        for line in lines:
1✔
1077
            value.write('=%-78s=\n' % line)
1✔
1078
        value.flush()
1✔
1079
        return value
1✔
1080

1081
    @_validator
1✔
1082
    def log_console(self, stream):
1✔
1083
        """
1084
        Sets the default logging console target.
1085

1086
        Examples:
1087

1088
            >>> context.log_level = 'warn'
1089
            >>> log.warn("Hello")
1090
            [!] Hello
1091
            >>> context.log_console=open(os.devnull, 'w')
1092
            >>> log.warn("Hello")
1093
            >>> context.clear()
1094
        """
1095
        if isinstance(stream, str):
1!
UNCOV
1096
            stream = open(stream, 'wt')
×
1097
        return stream
1✔
1098

1099
    @_validator
1✔
1100
    def local_libcdb(self, path):
1✔
1101
        """
1102
        Sets path to local libc-database, get more information for libc-database:
1103
        https://github.com/niklasb/libc-database
1104

1105
        Works in :attr:`pwnlib.libcdb` when searching by local database provider.
1106

1107
        The default value is ``/var/lib/libc-database``.
1108

1109
        Sets `context.local_libcdb` to empty string or `None` will turn off local libc-database integration.
1110

1111
        Examples:
1112

1113
            >>> context.local_libcdb = pwnlib.data.elf.path
1114
            >>> context.local_libcdb = 'foobar'
1115
            Traceback (most recent call last):
1116
            ...
1117
            AttributeError: 'foobar' does not exist, please download libc-database first
1118
        """
1119

1120
        if not os.path.isdir(path):
1✔
1121
            raise AttributeError("'%s' does not exist, please download libc-database first" % path)
1✔
1122

1123
        return path
1✔
1124

1125
    @property
1✔
1126
    def mask(self):
1✔
1127
        return (1 << self.bits) - 1
1✔
1128

1129
    @_validator
1✔
1130
    def os(self, os):
1✔
1131
        r"""
1132
        Operating system of the target machine.
1133

1134
        The default value is ``linux``.
1135

1136
        Allowed values are listed in :attr:`pwnlib.context.ContextType.oses`.
1137

1138
        Side Effects:
1139

1140
            If an os is specified some attributes will be set on the context
1141
            if a user has not already set a value.
1142

1143
            The following property may be modified:
1144

1145
            - :attr:`newline`
1146

1147
        Raises:
1148
            AttributeError: An invalid os was specified
1149

1150
        Examples:
1151

1152
            >>> context.clear()
1153
            >>> context.os == 'linux' # Default os
1154
            True
1155

1156
            >>> context.os = 'freebsd'
1157
            >>> context.os == 'freebsd'
1158
            True
1159

1160
            >>> context.os = 'foobar' #doctest: +ELLIPSIS
1161
            Traceback (most recent call last):
1162
            ...
1163
            AttributeError: os must be one of ['android', 'baremetal', 'cgc', 'freebsd', 'linux', 'windows']
1164

1165
            >>> context.clear()
1166
            >>> context.newline == b'\n' # Default value
1167
            True
1168
            >>> context.os = 'windows'
1169
            >>> context.newline == b'\r\n' # New value
1170
            True
1171

1172
            Note that expressly setting :attr:`newline` means that we use
1173
            that value instead of the default
1174

1175
            >>> context.clear()
1176
            >>> context.newline = b'\n'
1177
            >>> context.os = 'windows'
1178
            >>> context.newline == b'\n'
1179
            True
1180

1181
            Setting the os can override the default for :attr:`newline`
1182

1183
            >>> context.clear()
1184
            >>> context.os = 'windows'
1185
            >>> vars(context) == {'os': 'windows', 'newline': b'\r\n'}
1186
            True
1187
        """
1188
        os = os.lower()
1✔
1189

1190
        try:
1✔
1191
            defaults = self.oses[os]
1✔
1192
        except KeyError:
1✔
1193
            raise AttributeError("os must be one of %r" % sorted(self.oses))
1✔
1194

1195
        for k,v in defaults.items():
1✔
1196
            if k not in self._tls:
1✔
1197
                self._tls[k] = v
1✔
1198

1199
        return os
1✔
1200

1201
    @_validator
1✔
1202
    def randomize(self, r):
1✔
1203
        """
1204
        Global flag that lots of things should be randomized.
1205
        """
UNCOV
1206
        return bool(r)
×
1207

1208
    @_validator
1✔
1209
    def signed(self, signed):
1✔
1210
        """
1211
        Signed-ness for packing operation when it's not explicitly set.
1212

1213
        Can be set to any non-string truthy value, or the specific string
1214
        values ``'signed'`` or ``'unsigned'`` which are converted into
1215
        :const:`True` and :const:`False` correspondingly.
1216

1217
        Examples:
1218

1219
            >>> context.signed
1220
            False
1221
            >>> context.signed = 1
1222
            >>> context.signed
1223
            True
1224
            >>> context.signed = 'signed'
1225
            >>> context.signed
1226
            True
1227
            >>> context.signed = 'unsigned'
1228
            >>> context.signed
1229
            False
1230
            >>> context.signed = 'foobar' #doctest: +ELLIPSIS
1231
            Traceback (most recent call last):
1232
            ...
1233
            AttributeError: signed must be one of ['no', 'signed', 'unsigned', 'yes'] or a non-string truthy value
1234
        """
1235
        try:             signed = self.signednesses[signed]
1✔
1236
        except KeyError: pass
1✔
1237

1238
        if isinstance(signed, str):
1✔
1239
            raise AttributeError('signed must be one of %r or a non-string truthy value' % sorted(self.signednesses))
1✔
1240

1241
        return bool(signed)
1✔
1242

1243
    @_validator
1✔
1244
    def timeout(self, value=Timeout.default):
1✔
1245
        """
1246
        Default amount of time to wait for a blocking operation before it times out,
1247
        specified in seconds.
1248

1249
        The default value is to have an infinite timeout.
1250

1251
        See :class:`pwnlib.timeout.Timeout` for additional information on
1252
        valid values.
1253
        """
1254
        return Timeout(value).timeout
1✔
1255

1256
    @_validator
1✔
1257
    def terminal(self, value):
1✔
1258
        """
1259
        Default terminal used by :meth:`pwnlib.util.misc.run_in_new_terminal`.
1260
        Can be a string or an iterable of strings.  In the latter case the first
1261
        entry is the terminal and the rest are default arguments.
1262
        """
1263
        if isinstance(value, (bytes, str)):
1!
UNCOV
1264
            return [value]
×
1265
        return value
1✔
1266

1267
    @property
1✔
1268
    def abi(self):
1✔
UNCOV
1269
        return self._abi
×
1270

1271
    @_validator
1✔
1272
    def proxy(self, proxy):
1✔
1273
        """
1274
        Default proxy for all socket connections.
1275

1276
        Accepts either a string (hostname or IP address) for a SOCKS5 proxy on
1277
        the default port, **or** a ``tuple`` passed to ``socks.set_default_proxy``,
1278
        e.g. ``(socks.SOCKS4, 'localhost', 1234)``.
1279

1280
        >>> context.proxy = 'localhost' #doctest: +ELLIPSIS
1281
        >>> r=remote('google.com', 80)
1282
        Traceback (most recent call last):
1283
        ...
1284
        ProxyConnectionError: Error connecting to SOCKS5 proxy localhost:1080: [Errno 111] Connection refused
1285

1286
        >>> context.proxy = None
1287
        >>> r=remote('google.com', 80, level='error')
1288
        """
1289

1290
        if not proxy:
1✔
1291
            socket.socket = _original_socket
1✔
1292
            return None
1✔
1293

1294
        if isinstance(proxy, str):
1!
1295
            proxy = (socks.SOCKS5, proxy)
1✔
1296

1297
        if not isinstance(proxy, Iterable):
1!
UNCOV
1298
            raise AttributeError('proxy must be a string hostname, or tuple of arguments for socks.set_default_proxy')
×
1299

1300
        socks.set_default_proxy(*proxy)
1✔
1301
        socket.socket = socks.socksocket
1✔
1302

1303
        return proxy
1✔
1304

1305
    @_validator
1✔
1306
    def noptrace(self, value):
1✔
1307
        """Disable all actions which rely on ptrace.
1308

1309
        This is useful for switching between local exploitation with a debugger,
1310
        and remote exploitation (without a debugger).
1311

1312
        This option can be set with the ``NOPTRACE`` command-line argument.
1313
        """
UNCOV
1314
        return bool(value)
×
1315

1316

1317
    @_validator
1✔
1318
    def adb_host(self, value):
1✔
1319
        """Sets the target host which is used for ADB.
1320

1321
        This is useful for Android exploitation.
1322

1323
        The default value is inherited from ANDROID_ADB_SERVER_HOST, or set
1324
        to the default 'localhost'.
1325
        """
UNCOV
1326
        return str(value)
×
1327

1328

1329
    @_validator
1✔
1330
    def adb_port(self, value):
1✔
1331
        """Sets the target port which is used for ADB.
1332

1333
        This is useful for Android exploitation.
1334

1335
        The default value is inherited from ANDROID_ADB_SERVER_PORT, or set
1336
        to the default 5037.
1337
        """
UNCOV
1338
        return int(value)
×
1339

1340
    @_validator
1✔
1341
    def device(self, device):
1✔
1342
        """Sets the device being operated on.
1343
        """
1344
        if isinstance(device, (bytes, str)):
1✔
1345
            device = Device(device)
1✔
1346
        if isinstance(device, Device):
1!
1347
            self.arch = device.arch or self.arch
1✔
1348
            self.bits = device.bits or self.bits
1✔
1349
            self.endian = device.endian or self.endian
1✔
1350
            self.os = device.os or self.os
1✔
1351
        elif device is not None:
×
1352
            raise AttributeError("device must be either a Device object or a serial number as a string")
×
1353

1354
        return device
1✔
1355

1356
    @property
1✔
1357
    def adb(self):
1✔
1358
        """Returns an argument array for connecting to adb.
1359

1360
        Unless ``$ADB_PATH`` is set, uses the default ``adb`` binary in ``$PATH``.
1361
        """
1362
        ADB_PATH = os.environ.get('ADB_PATH', 'adb')
1✔
1363

1364
        command = [ADB_PATH]
1✔
1365

1366
        if self.adb_host != self.defaults['adb_host']:
1!
1367
            command += ['-H', self.adb_host]
×
1368

1369
        if self.adb_port != self.defaults['adb_port']:
1!
1370
            command += ['-P', str(self.adb_port)]
×
1371

1372
        if self.device:
1!
1373
            command += ['-s', str(self.device)]
1✔
1374

1375
        return command
1✔
1376

1377
    @_validator
1✔
1378
    def buffer_size(self, size):
1✔
1379
        """Internal buffer size to use for :class:`pwnlib.tubes.tube.tube` objects.
1380

1381
        This is not the maximum size of the buffer, but this is the amount of data
1382
        which is passed to each raw ``read`` syscall (or equivalent).
1383
        """
1384
        return int(size)
1✔
1385

1386
    @_validator
1✔
1387
    def cache_dir_base(self, new_base):
1✔
1388
        """Base directory to use for caching content.
1389

1390
        Changing this to a different value will clear the :attr:`cache_dir` path
1391
        stored in TLS since a new path will need to be generated to respect the
1392
        new :attr:`cache_dir_base` value.
1393
        """
1394

UNCOV
1395
        if new_base != self.cache_dir_base:
×
1396
            del self._tls["cache_dir"]
×
1397
        if os.access(new_base, os.F_OK) and not os.access(new_base, os.W_OK):
×
1398
            raise OSError(errno.EPERM, "Cache base dir is not writable")
×
1399
        return new_base
×
1400

1401
    @property
1✔
1402
    def cache_dir(self):
1✔
1403
        """Directory used for caching data.
1404

1405
        Note:
1406
            May be either a path string, or :const:`None`.
1407
            Set to :const:`None` to disable caching.
1408
            Set to :const:`True` to generate the default cache directory path
1409
            based on :attr:`cache_dir_base` again.
1410

1411
        Example:
1412

1413
            >>> cache_dir = context.cache_dir
1414
            >>> cache_dir is not None
1415
            True
1416
            >>> os.chmod(cache_dir, 0o000)
1417
            >>> context.cache_dir = True
1418
            >>> context.cache_dir is None # doctest: +POSIX +TODO
1419
            True
1420
            >>> os.chmod(cache_dir, 0o755)
1421
            >>> cache_dir == context.cache_dir
1422
            True
1423
            >>> context.cache_dir = None
1424
            >>> context.cache_dir is None
1425
            True
1426
            >>> context.cache_dir = True
1427
            >>> context.cache_dir is not None
1428
            True
1429
        """
1430
        try:
1✔
1431
            # If the TLS already has a cache directory path, we return it
1432
            # without any futher checks since it must have been valid when it
1433
            # was set and if that has changed, hiding the TOCTOU here would be
1434
            # potentially confusing
1435
            return self._tls["cache_dir"]
1✔
1436
        except KeyError:
1✔
1437
            pass
1✔
1438

1439
        # Attempt to create a Python version specific cache dir and its parents
1440
        cache_dirname = '.pwntools-cache-%d.%d' % sys.version_info[:2]
1✔
1441
        cache_dirpath = os.path.join(self.cache_dir_base, cache_dirname)
1✔
1442
        try:
1✔
1443
            os.makedirs(cache_dirpath)
1✔
1444
        except OSError as exc:
1✔
1445
            # If we failed for any reason other than the cache directory
1446
            # already existing then we'll fall back to a temporary directory
1447
            # object which doesn't respect the `cache_dir_base`
1448
            if exc.errno != errno.EEXIST:
1!
UNCOV
1449
                try:
×
1450
                    cache_dirpath = tempfile.mkdtemp(prefix=".pwntools-tmp")
×
1451
                except IOError:
×
1452
                    # This implies no good candidates for temporary files so we
1453
                    # have to return `None`
UNCOV
1454
                    return None
×
1455
                else:
1456
                    # Ensure the temporary cache dir is cleaned up on exit. A
1457
                    # `TemporaryDirectory` would do this better upon garbage
1458
                    # collection but this is necessary for Python 2 support.
UNCOV
1459
                    atexit.register(shutil.rmtree, cache_dirpath)
×
1460
        # By this time we have a cache directory which exists but we don't know
1461
        # if it is actually writable. Some wargames e.g. pwnable.kr have
1462
        # created dummy directories which cannot be modified by the user
1463
        # account (owned by root).
1464
        if os.access(cache_dirpath, os.W_OK):
1✔
1465
            # Stash this in TLS for later reuse
1466
            self._tls["cache_dir"] = cache_dirpath
1✔
1467
            return cache_dirpath
1✔
1468
        else:
1469
            return None
1✔
1470

1471
    @cache_dir.setter
1✔
1472
    def cache_dir(self, v):
1✔
1473
        if v is True:
1✔
1474
            del self._tls["cache_dir"]
1✔
1475
        elif v is None or os.access(v, os.W_OK):
1!
1476
            # Stash this in TLS for later reuse
1477
            self._tls["cache_dir"] = v
1✔
1478

1479
    @_validator
1✔
1480
    def delete_corefiles(self, v):
1✔
1481
        """Whether pwntools automatically deletes corefiles after exiting.
1482
        This only affects corefiles accessed via :attr:`.process.corefile`.
1483

1484
        Default value is ``False``.
1485
        """
UNCOV
1486
        return bool(v)
×
1487

1488
    @_validator
1✔
1489
    def rename_corefiles(self, v):
1✔
1490
        """Whether pwntools automatically renames corefiles.
1491

1492
        This is useful for two things:
1493

1494
        - Prevent corefiles from being overwritten, if ``kernel.core_pattern``
1495
          is something simple like ``"core"``.
1496
        - Ensure corefiles are generated, if ``kernel.core_pattern`` uses ``apport``,
1497
          which refuses to overwrite any existing files.
1498

1499
        This only affects corefiles accessed via :attr:`.process.corefile`.
1500

1501
        Default value is ``True``.
1502
        """
UNCOV
1503
        return bool(v)
×
1504

1505
    @_validator
1✔
1506
    def newline(self, v):
1✔
1507
        """Line ending used for Tubes by default.
1508

1509
        This configures the newline emitted by e.g. ``sendline`` or that is used
1510
        as a delimiter for e.g. ``recvline``.
1511
        """
1512
        # circular imports
1513
        from pwnlib.util.packing import _need_bytes
1✔
1514
        return _need_bytes(v)
1✔
1515

1516
    @_validator
1✔
1517
    def throw_eof_on_incomplete_line(self, v):
1✔
1518
        """Whether to raise an :class:`EOFError` if an EOF is received before a newline in ``tube.recvline``.
1519

1520
        Controls if an :class:`EOFError` is treated as newline in ``tube.recvline`` and similar functions
1521
        and whether a warning should be logged about it.
1522

1523
        Possible values are:
1524

1525
        - ``True``: Raise an :class:`EOFError` if an EOF is received before a newline.
1526
        - ``False``: Return the data received so far if an EOF is received
1527
          before a newline without logging a warning.
1528
        - ``None``: Return the data received so far if an EOF is received
1529
          before a newline and log a warning.
1530

1531
        Default value is ``None``.
1532
        """
UNCOV
1533
        return v if v is None else bool(v)
×
1534

1535

1536
    @_validator
1✔
1537
    def gdbinit(self, value):
1✔
1538
        """Path to the gdbinit that is used when running GDB locally.
1539

1540
        This is useful if you want pwntools-launched GDB to include some additional modules,
1541
        like PEDA but you do not want to have GDB include them by default.
1542

1543
        The setting will only apply when GDB is launched locally since remote hosts may not have
1544
        the necessary requirements for the gdbinit.
1545

1546
        If set to an empty string, GDB will use the default `~/.gdbinit`.
1547

1548
        Default value is ``""``.
1549
        """
UNCOV
1550
        return str(value)
×
1551

1552
    @_validator
1✔
1553
    def gdb_binary(self, value):
1✔
1554
        """Path to the binary that is used when running GDB locally.
1555

1556
        This is useful when you have multiple versions of gdb installed or the gdb binary is
1557
        called something different.
1558

1559
        If set to an empty string, pwntools will try to search for a reasonable gdb binary from 
1560
        the path.
1561

1562
        Default value is ``""``.
1563
        """
UNCOV
1564
        return str(value)
×
1565

1566
    @_validator
1✔
1567
    def cyclic_alphabet(self, alphabet):
1✔
1568
        """Cyclic alphabet.
1569

1570
        Default value is `string.ascii_lowercase`.
1571
        """
1572

1573
        # Do not allow multiple occurrences
1574
        if len(set(alphabet)) != len(alphabet):
1!
UNCOV
1575
            raise AttributeError("cyclic alphabet cannot contain duplicates")
×
1576

1577
        return alphabet.encode()
1✔
1578

1579
    @_validator
1✔
1580
    def cyclic_size(self, size):
1✔
1581
        """Cyclic pattern size.
1582

1583
        Default value is `4`.
1584
        """
UNCOV
1585
        size = int(size)
×
1586

UNCOV
1587
        if size > self.bytes:
×
1588
            raise AttributeError("cyclic pattern size cannot be larger than word size")
×
1589

UNCOV
1590
        return size
×
1591

1592
    @_validator
1✔
1593
    def ssh_session(self, shell):
1✔
1594
        from pwnlib.tubes.ssh import ssh
1✔
1595

1596
        if not isinstance(shell, ssh):
1!
UNCOV
1597
            raise AttributeError("context.ssh_session must be an ssh tube")
×
1598

1599
        return shell
1✔
1600

1601
    #*************************************************************************
1602
    #                               ALIASES
1603
    #*************************************************************************
1604
    #
1605
    # These fields are aliases for fields defined above, either for
1606
    # convenience or compatibility.
1607
    #
1608
    #*************************************************************************
1609

1610
    def __call__(self, **kwargs):
1✔
1611
        """
1612
        Alias for :meth:`pwnlib.context.ContextType.update`
1613
        """
1614
        return self.update(**kwargs)
1✔
1615

1616
    def reset_local(self):
1✔
1617
        """
1618
        Deprecated.  Use :meth:`clear`.
1619
        """
1620
        self.clear()
1✔
1621

1622
    @property
1✔
1623
    def endianness(self):
1✔
1624
        """
1625
        Legacy alias for :attr:`endian`.
1626

1627
        Examples:
1628

1629
            >>> context.endian == context.endianness
1630
            True
1631
        """
1632
        return self.endian
1✔
1633
    @endianness.setter
1✔
1634
    def endianness(self, value):
1✔
1635
        self.endian = value
1✔
1636

1637

1638
    @property
1✔
1639
    def sign(self):
1✔
1640
        """
1641
        Alias for :attr:`signed`
1642
        """
1643
        return self.signed
1✔
1644

1645
    @sign.setter
1✔
1646
    def sign(self, value):
1✔
1647
        self.signed = value
1✔
1648

1649
    @property
1✔
1650
    def signedness(self):
1✔
1651
        """
1652
        Alias for :attr:`signed`
1653
        """
UNCOV
1654
        return self.signed
×
1655

1656
    @signedness.setter
1✔
1657
    def signedness(self, value):
1✔
UNCOV
1658
        self.signed = value
×
1659

1660

1661
    @property
1✔
1662
    def word_size(self):
1✔
1663
        """
1664
        Alias for :attr:`bits`
1665
        """
1666
        return self.bits
1✔
1667

1668
    @word_size.setter
1✔
1669
    def word_size(self, value):
1✔
1670
        self.bits = value
1✔
1671

1672
    Thread = Thread
1✔
1673

1674

1675
#: Global :class:`.ContextType` object, used to store commonly-used pwntools settings.
1676
#:
1677
#: In most cases, the context is used to infer default variables values.
1678
#: For example, :func:`.asm` can take an ``arch`` parameter as a
1679
#: keyword argument.
1680
#:
1681
#: If it is not supplied, the ``arch`` specified by ``context`` is used instead.
1682
#:
1683
#: Consider it a shorthand to passing ``os=`` and ``arch=`` to every single
1684
#: function call.
1685
context = ContextType()
1✔
1686

1687
# Inherit default ADB values
1688
if 'ANDROID_ADB_SERVER_HOST' in os.environ:
1!
UNCOV
1689
    context.adb_host = os.environ.get('ANDROID_ADB_SERVER_HOST')
×
1690

1691
if 'ANDROID_ADB_SERVER_PORT' in os.environ:
1!
UNCOV
1692
    context.adb_port = int(os.getenv('ANDROID_ADB_SERVER_PORT'))
×
1693

1694
def LocalContext(function):
1✔
1695
    """
1696
    Wraps the specified function on a context.local() block, using kwargs.
1697

1698
    Example:
1699

1700
        >>> context.clear()
1701
        >>> @LocalContext
1702
        ... def printArch():
1703
        ...     print(context.arch)
1704
        >>> printArch()
1705
        i386
1706
        >>> printArch(arch='arm')
1707
        arm
1708
    """
1709
    @functools.wraps(function)
1✔
1710
    def setter(*a, **kw):
1✔
1711
        with context.local(**{k:kw.pop(k) for k,v in tuple(kw.items()) if isinstance(getattr(ContextType, k, None), property)}):
1✔
1712
            arch = context.arch
1✔
1713
            bits = context.bits
1✔
1714
            endian = context.endian
1✔
1715

1716
            # Prevent the user from doing silly things with invalid
1717
            # architecture / bits / endianness combinations.
1718
            if (arch == 'i386' and bits != 32) \
1!
1719
              or (arch == 'amd64' and bits != 64):
UNCOV
1720
                raise AttributeError("Invalid arch/bits combination: %s/%s" % (arch, bits))
×
1721

1722
            if arch in ('i386', 'amd64') and endian == 'big':
1!
UNCOV
1723
                raise AttributeError("Invalid arch/endianness combination: %s/%s" % (arch, endian))
×
1724

1725
            return function(*a, **kw)
1✔
1726
    return setter
1✔
1727

1728
def LocalNoarchContext(function):
1✔
1729
    """
1730
    Same as LocalContext, but resets arch to :const:`'none'` by default
1731

1732
    Example:
1733

1734
        >>> @LocalNoarchContext
1735
        ... def printArch():
1736
        ...     print(context.arch)
1737
        >>> printArch()
1738
        none
1739
    """
1740
    @functools.wraps(function)
1✔
1741
    def setter(*a, **kw):
1✔
1742
        kw.setdefault('arch', 'none')
1✔
1743
        with context.local(**{k:kw.pop(k) for k,v in tuple(kw.items()) if isinstance(getattr(ContextType, k, None), property)}):
1✔
1744
            return function(*a, **kw)
1✔
1745
    return setter
1✔
1746

1747
# Read configuration options from the context section
1748
def update_context_defaults(section):
1✔
1749
    # Circular imports FTW!
UNCOV
1750
    from pwnlib.util import safeeval
×
1751
    from pwnlib.log import getLogger
×
1752
    log = getLogger(__name__)
×
1753
    for key, value in section.items():
×
1754
        if key not in ContextType.defaults:
×
1755
            log.warn("Unknown configuration option %r in section %r" % (key, 'context'))
×
1756
            continue
×
1757

UNCOV
1758
        default = ContextType.defaults[key]
×
1759

UNCOV
1760
        if isinstance(default, (str, int, tuple, list, dict)):
×
1761
            value = safeeval.expr(value)
×
1762
        else:
UNCOV
1763
            log.warn("Unsupported configuration option %r in section %r" % (key, 'context'))
×
1764

1765
        # Attempt to set the value, to see if it is value:
UNCOV
1766
        try:
×
1767
            with context.local(**{key: value}):
×
1768
                value = getattr(context, key)
×
1769
        except (ValueError, AttributeError) as e:
×
1770
            log.warn("Could not set context.%s=%s via pwn.conf (%s)", key, section[key], e)
×
1771
            continue
×
1772

UNCOV
1773
        ContextType.defaults[key] = value
×
1774

1775
register_config('context', update_context_defaults)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc