# BinFHEContext::Decrypt accepts undersized ciphertexts, leading to an out-of-bounds read

**URL:** <https://openfhe.discourse.group/t/binfhecontext-decrypt-accepts-undersized-ciphertexts-leading-to-an-out-of-bounds-read/2353>\
**Category:** Bug Reports\
**Created:** [July 29, 2026, 12:43pm UTC](https://openfhe.discourse.group/t/binfhecontext-decrypt-accepts-undersized-ciphertexts-leading-to-an-out-of-bounds-read/2353 "2026-07-29T12:43:15Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![CCYJ](https://avatars.discourse-cdn.com/v4/letter/c/b5ac83/32.png) [@CCYJ](https://openfhe.discourse.group/u/CCYJ)\
**Post date:** [July 29, 2026, 12:43pm UTC](https://openfhe.discourse.group/t/binfhecontext-decrypt-accepts-undersized-ciphertexts-leading-to-an-out-of-bounds-read/2353/1 "2026-07-29T12:43:15Z")

</div>

Hi OpenFHE team,

I would like to report an input-validation issue in the BinFHE decryption path that can lead to an out-of-bounds read, and ask whether malformed ciphertext length is supposed to be validated at this API boundary.

# **Summary**

`BinFHEContext::Decrypt()` forwards a malformed `LWECiphertext` into `LWEEncryptionScheme::Decrypt()` without verifying that the ciphertext length matches the secret-key length.

The lower-level decryption loop uses the secret-key length as its loop bound while indexing the ciphertext vector.

With the `TOY` parameter set, replacing a valid ciphertext’s `a` vector with a one-element vector is sufficient to trigger an ASan heap-buffer-overflow in the decryption loop.

I reproduced this locally under ASan/UBSan on OpenFHE `1.5.1`.

# **Environment**

- OpenFHE version: `1.5.1`
- Compiler: `clang 14.0.0`
- Sanitizers: `AddressSanitizer`, `UndefinedBehaviorSanitizer`

# **Minimal reproduction**

The reproducer below starts from a valid ciphertext and then replaces its `a` vector with a one-element vector:

```cpp
#include "binfhecontext.h"
#include "lwe-ciphertext.h"

using namespace lbcrypto;

int main() {
    BinFHEContext cc;
    cc.GenerateBinFHEContext(TOY);

    auto sk = cc.KeyGen();
    auto ct = cc.Encrypt(sk, 1);

    NativeVector shortA(1, ct->GetModulus());
    shortA[0] = ct->GetA()[0];

    auto malformed =
        std::make_shared<LWECiphertextImpl>(std::move(shortA), ct->GetB(), ct->GetptModulus());

    LWEPlaintext result = 0;
    cc.Decrypt(sk, malformed, &result);

    return 0;
}

```

# **Expected behavior**

`Decrypt()` should reject the input if `ct->GetLength() != sk->GetLength()` before indexing `ct->GetA()`.

# **Actual behavior**

The process crashes with an ASan heap-buffer-overflow caused by an out-of-bounds read from the ciphertext’s `a` vector.

Relevant excerpt:

```plaintext
==...==ERROR: AddressSanitizer: heap-buffer-overflow
    #0 ... NativeIntegerT<unsigned long>::ModMulFast(...) .../ubintnat.h:1353
    #1 ... lbcrypto::LWEEncryptionScheme::Decrypt(...) .../lwe-pke.cpp:189
    #2 ... lbcrypto::BinFHEContext::Decrypt(...) .../binfhecontext.cpp:269

```

The sanitizer line numbers above refer to my local instrumented source tree; the stable `v1.5.1` source links are listed below.

# **Root cause analysis**

`BinFHEContext::Decrypt()` checks only whether `sk` and `ct` are null and then forwards the arguments. Neither the context-level function nor the lower-level decryption path validates that the ciphertext and secret key have matching lengths.

Inside `LWEEncryptionScheme::Decrypt()`, the loop bound is taken from the secret-key length:

```cpp
const auto& a = ct->GetA();
auto s = sk->GetElement();
uint32_t n = s.GetLength();
...
for (uint32_t i = 0; i < n; ++i) {
    inner += a[i].ModMulFast(s[i], q, mu);
}

```

The implementation already contains a TODO about checking secret-key and ciphertext parameter compatibility, but no dimension check is currently performed:

```cpp
// TODO in the future we should add a check to make sure sk parameters match
// the ct parameters

```

The loop bound `n` is derived from `s.GetLength()`, while the indexed vector `a` has length `ct->GetLength()`. No invariant connects these two lengths before the loop.

Therefore, whenever `ct->GetLength() < sk->GetLength()`, the loop eventually evaluates `a[i]` with `i >= a.GetLength()`.

# **Relevant source locations**

- `src/binfhe/lib/binfhecontext.cpp`
  - `BinFHEContext::Decrypt()`: lines 238-245
  - GitHub link: [https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/binfhe/lib/binfhecontext.cpp#L238-L245](https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/binfhe/lib/binfhecontext.cpp#L238-L245)

- `src/binfhe/lib/lwe-pke.cpp`
  - `LWEEncryptionScheme::Decrypt()`: lines 146-190
  - OOB read in the loop: line 170
  - GitHub link: [https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/binfhe/lib/lwe-pke.cpp#L146-L190](https://github.com/openfheorg/openfhe-development/blob/v1.5.1/src/binfhe/lib/lwe-pke.cpp#L146-L190)

# **Suggested direction**

It looks reasonable to check `ct->GetLength() == sk->GetLength()` before entering the decryption loop.

I think the most complete place for that check would be inside `LWEEncryptionScheme::Decrypt()` before the loop, with an optional matching early check in `BinFHEContext::Decrypt()`.

# **Question**

Is `BinFHEContext::Decrypt()` expected to reject malformed ciphertext dimensions, or is that currently considered a caller-side precondition only?

Reported by Jiang Chao, Beijing University of Posts and Telecommunications

---

<div class="post-metadata">

**Author:** ![ypolyakov](https://yyz1.discourse-cdn.com/flex031/user_avatar/openfhe.discourse.group/ypolyakov/32/47_2.png) [@ypolyakov](https://openfhe.discourse.group/u/ypolyakov)\
**Post date:** [August 3, 2026, 6:44pm UTC](https://openfhe.discourse.group/t/binfhecontext-decrypt-accepts-undersized-ciphertexts-leading-to-an-out-of-bounds-read/2353/2 "2026-08-03T18:44:25Z")

</div>

The answer is the same as for other similar issues. Our main focus is on the honest-but-curios thread model.
