[Bug report] BinFHEContext::SwitchCTtoqn accepts malformed large-dimension ciphertexts when the modulus matches

Hi OpenFHE team,

I would like to report a possible memory-safety issue in BinFHEContext::SwitchCTtoqn() and ask whether the current input validation is intentional.

Summary

BinFHEContext::SwitchCTtoqn() uses a malformed-input guard that only rejects the ciphertext when both the dimension and modulus are wrong. A ciphertext with the correct large-dimension modulus Q but a one-element a vector passes the guard and later crashes inside KeySwitch().

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 large-dimension ciphertext, shrinks its a vector to length 1, and then calls SwitchCTtoqn():

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

using namespace lbcrypto;

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

    auto sk = cc.KeyGen();
    cc.BTKeyGen(sk);
    auto pk = cc.PubKeyGen(sk);

    auto ctLarge = cc.Encrypt(pk, 1, LARGE_DIM);

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

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

    auto out = cc.SwitchCTtoqn(cc.GetSwitchKey(), malformed);
    (void)out;

    return 0;
}

Expected behavior

SwitchCTtoqn() should reject the ciphertext immediately because its dimension is not the expected large N.

Actual behavior

The malformed ciphertext passes the front-end guard and later crashes with an ASan heap-buffer-overflow inside KeySwitch().

Relevant excerpt:

==...==ERROR: AddressSanitizer: heap-buffer-overflow
    #0  ... NativeIntegerT<unsigned long>::ConvertToInt(...) .../ubintnat.h:1652
    #1  ... lbcrypto::LWEEncryptionScheme::KeySwitch(...) .../lwe-pke.cpp:336
    #2  ... lbcrypto::LWEEncryptionScheme::SwitchCTtoqn(...) .../lwe-pke.cpp:158
    #3  ... lbcrypto::BinFHEContext::SwitchCTtoqn(...) .../binfhecontext.cpp:260

Cause analysis

The current guard is:

if ((ct->GetLength() != LWEParams->GetN()) && (ct->GetModulus() != LWEParams->GetQ()))
    OPENFHE_THROW("ciphertext dimension and modulus are not large N and Q");

This only throws when both invariants are violated at the same time.

As a result, a ciphertext with the wrong dimension but the correct modulus still passes validation and reaches:

NativeInteger::Integer atmp(ctQN->GetA()[i].ConvertToInt());

inside the for (uint32_t i = 0; i < N; ++i) loop in KeySwitch().

Relevant source locations

Suggested direction

It looks like the front-end guard should use || rather than &&.

It may also be worth validating the shape of the modulus-switched intermediate ciphertext before entering KeySwitch().

Question

Is the current && guard intentional here, or would you consider this a bug in the input validation logic?

Reported by Jiang Chao, Beijing University of Posts and Telecommunications