[Bug report] BinFHEContext::Decrypt reads past malformed short ciphertext vectors

Hi OpenFHE team,

I would like to report a possible memory-safety issue in the BinFHE decryption path 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. A one-element a vector is enough 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:

#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 malformed ciphertext and report a dimension mismatch before doing arithmetic on ct->GetA().

Actual behavior

The process crashes with an ASan heap-buffer-overflow.

Relevant excerpt:

==...==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

Cause analysis

BinFHEContext::Decrypt() only checks for null pointers and then forwards the ciphertext to LWEEncryptionScheme::Decrypt().

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

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);
}

So once ct->GetLength() < sk->GetLength(), the loop reads past the end of ct->GetA().

Relevant source locations

Suggested direction

It looks reasonable to validate:

  • ct->GetLength() == sk->GetLength()
  • and possibly modulus compatibility between ct, sk, and the active LWECryptoParams

before entering the decryption loop.

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