Files
boringssl/SANDBOXING.md
T
David Benjamin 5f4f26f59c Retire RSA blinding support
The idea to do RSA blinding dates back to one of the first papers on
timing attacks in RSA in 1996. Our blinding code dates to around that
time (OpenSSL git history ends at 1998).
https://paulkocher.com/doc/TimingAttacks.pdf

In the last 30 years, we've gained a much better understanding of side
channel mitigations. In particular, blinding was added when OpenSSL's
RSA implementation was very far from constant-time. BoringSSL's RSA
implementation has now been constant-time for some time now, which
protects against cache and timing side channels more thoroughly than
blinding does. (Blinding of the base doesn't mask the exponent.)

With a constant-time implementation, RSA blinding is no longer
load-bearing. It is also surprisingly expensive. The mod-inverse step
costs a sizeable fraction of the signing operation itself, to the point
that even the original paper suggested caching the mod-inverse operation
and squaring to rerandomize. This mitigation has some issues:

- The blinding values are no longer independent secrets, so the
  mathematical grounds for the technique are shakier.

- Single-use RSA keys still pay for the full inversion.

- Long-lived RSA keys pay for cross-thread synchronization and cacheline
  contention.

Given it's redundant with a constant-time RSA signing implementation,
just remove it. Go's crypto/rsa package similarly dropped blinding when
they became constant-time.

A comparison on a Intel(R) Xeon(R) Gold 6154 CPU @ 3.00GHz: signing with
single-use RSA keys decreases significantly. Multiple-use RSA keys also
see a significant benefit because we remove a source of thread
contention.

Benchmark                                                     Time             CPU      Time Old      Time New       CPU Old       CPU New
------------------------------------------------------------------------------------------------------------------------------------------
BM_SpeedRSASign/2048/threads:1                             -0.0252         -0.0253        543443        529729        543420        529672
BM_SpeedRSASign/2048/threads:72                            -0.1449         -0.1598       1141309        975876       1131652        950843
BM_SpeedRSAImportKeyAndSign/2048/threads:1                 -0.3323         -0.3323        945512        631272        945414        631261
BM_SpeedRSAImportKeyAndSign/2048/threads:72                -0.2901         -0.3001       1567751       1112956       1515580       1060742
BM_SpeedRSASign/3072/threads:1                             -0.0497         -0.0494       1731748       1645764       1731232       1645727
BM_SpeedRSASign/3072/threads:72                            -0.1760         -0.1927       3590240       2958464       3540956       2858770
BM_SpeedRSAImportKeyAndSign/3072/threads:1                 -0.2063         -0.2063       2391296       1897868       2390855       1897622
BM_SpeedRSAImportKeyAndSign/3072/threads:72                -0.2224         -0.2465       4213323       3276466       4092405       3083547
BM_SpeedRSASign/4096/threads:1                             -0.0150         -0.0151       3715336       3659534       3714992       3658934
BM_SpeedRSASign/4096/threads:72                            -0.1192         -0.0854       8024291       7067945       7647429       6994248
BM_SpeedRSAImportKeyAndSign/4096/threads:1                 -0.1910         -0.1909       4905741       3968685       4904703       3968311
BM_SpeedRSAImportKeyAndSign/4096/threads:72                -0.1554         -0.1193       9182005       7755037       8483022       7471388

(Since this is the first output of the new benchmark comparison script,
the time is measured in nanoseconds, and the -0.3323 means a 33.23%
reduction in time per iteration.)

Change-Id: I4738fb8b0539408b86faafb6ae8751aa2265f831
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/85368
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
2025-12-08 11:14:35 -08:00

6.5 KiB

Using BoringSSL in a Sandbox

Sandboxes are a valuable tool for securing applications, so BoringSSL aims to support them. However, it is difficult to make concrete API guarantees with sandboxes. Sandboxes remove low-level OS resources and system calls, which breaks platform abstractions. A syscall-filtering sandbox may, for instance, be sensitive to otherwise non-breaking changes to use newer syscalls in either BoringSSL or the C library.

Some functions in BoringSSL, such as BIO_new_file, inherently need OS resources like the filesystem. We assume that sandboxed consumers either avoid those functions or make necessary resources available. Other functions like RSA_sign are purely computational, but still have some baseline OS dependencies.

Sandboxes which drop privileges partway through a process's lifetime are additionally sensitive to OS resources retained across the transitions. For instance, if a library function internally opened and retained a handle to the user's home directory, and then the application called chroot, that handle would be a sandbox escape.

This document attempts to describe these baseline OS dependencies and long-lived internal resources. These dependencies may change over time, but we aim to work with sandboxed consumers when they do. However, each sandbox imposes different constraints, so, above all, sandboxed consumers must have ample test coverage to detect issues as they arise.

Baseline dependencies

Callers must assume that any BoringSSL function may perform one of the following operations:

Memory allocation

Any BoringSSL function may allocate memory via malloc and related functions.

Thread synchronization

Any BoringSSL function may call into the platform's thread synchronization primitives, including read/write locks and the equivalent of pthread_once. These must succeed, or BoringSSL will abort the process. Callers, however, can assume that BoringSSL functions will not spawn internal threads, unless otherwise documented.

Syscall-filtering sandboxes should note that BoringSSL uses pthread_rwlock_t on POSIX systems, which is less common and may not be part of other libraries' syscall surface. Additionally, thread synchronization primitives usually have an atomics-based fast path. If a sandbox blocks a necessary pthreads syscall, it may not show up in testing without lock contention.

Standard error

Any BoringSSL function may write to stderr or file descriptor STDERR_FILENO (2), either via FILE APIs or low-level functions like write. Writes to stderr may fail, but there must some file at STDERR_FILENO which will tolerate error messages from BoringSSL. (The file descriptor must be allocated so calls to open do not accidentally open something else there.)

Note some C standard library implementations also log to stderr, so callers should ensure this regardless.

Entropy

Any BoringSSL function may draw entropy from the OS. On Windows, this uses RtlGenRandom and, on POSIX systems, this uses getrandom, getentropy, or a read from a file descriptor to /dev/urandom. These operations must succeed or BoringSSL will abort the process. BoringSSL only probes for getrandom support once and assumes support is consistent for the lifetime of the address space (and any copies made via fork). If a syscall-filtering sandbox is enabled partway through this lifetime and changes whether getrandom works, BoringSSL may abort the process. Sandboxes are recommended to allow getrandom.

Note even deterministic algorithms may require OS entropy, e.g. for blinding techniques.

Entropy gathering additionally has some initialization dependencies described in the following section.

Initialization

BoringSSL has some uncommon OS dependencies which are only used once to initialize some state. Sandboxes which drop privileges after some setup work may use CRYPTO_pre_sandbox_init to initialize this state ahead of time. Otherwise, callers must assume any BoringSSL function may depend on these resources, in addition to the operations above.

CPU capabilities

On Linux ARM platforms, BoringSSL depends on OS APIs to query CPU capabilities. 32-bit and 64-bit ARM both depend on the getauxval function. 32-bit ARM, to work around bugs in older Android devices, may additionally read /proc/cpuinfo.

On 64-bit Apple ARM platforms, BoringSSL needs to query hw.optional.* sysctls.

If querying CPU capabilities fails, BoringSSL will still function, but may not perform as well.

Entropy

On Linux systems without a working getrandom, drawing entropy from the OS additionally requires opening /dev/urandom. If this fails, BoringSSL will abort the process. BoringSSL retains the resulting file descriptor, even across privilege transitions.

Fork protection

On Linux, BoringSSL allocates a page and calls madvise with MADV_WIPEONFORK to protect single-use state from fork. This operation must not crash, but if it fails, BoringSSL will use alternate fork-safety strategies, potentially at a performance cost. If it succeeds, BoringSSL assumes MADV_WIPEONFORK is functional and relies on it for fork-safety. Sandboxes must not report success if they ignore the MADV_WIPEONFORK flag. As of writing, QEMU will ignore madvise calls and report success, so BoringSSL detects this by calling madvise with -1. Sandboxes must cleanly report an error instead of crashing.

Once initialized, this mechanism does not require system calls in the steady state, though note the configured page will be inherited across privilege transitions.

C and C++ standard library

BoringSSL depends on the C and C++ standard libraries which, themselves, do not make any guarantees about sandboxes. If it produces the correct answer and has no observable invalid side effects, it is possible, though unreasonable, for memcmp to create and close a socket.

BoringSSL assumes that functions in the C and C++ library only have the platform dependencies which would be "reasonable". For instance, a function in BoringSSL which aims not to open files will still freely call any libc memory and string functions.

Note some C functions, such as strerror, may read files relating to the user's locale. BoringSSL may trigger these paths and assumes the sandbox environment will tolerate this. BoringSSL additionally cannot make guarantees about which system calls are used by standard library's syscall wrappers. In some cases, the compiler may add dependencies. (Some C++ language features emit locking code.) Syscall-filtering sandboxes may need updates as these dependencies change.