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

Hi OpenFHE team,

I would like to report an input-validation issue in BinFHEContext::SwitchCTtoqn() that can lead to an out-of-bounds read inside KeySwitch(), and ask whether the current validation logic is intentional.

Summary

BinFHEContext::SwitchCTtoqn() uses a malformed-input guard that only rejects the ciphertext when both the dimension and modulus are wrong. As a result:

  • wrong dimension + expected modulus Q passes
  • expected dimension N + wrong modulus also passes

In the reproducer below, a ciphertext with the expected large 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 if either its dimension is not N or its modulus is not Q.

In this reproducer, it should be rejected because its dimension is 1 rather than 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

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

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.

Therefore, either a wrong dimension with modulus Q, or dimension N with the wrong modulus, can pass the guard.

The reproducer exercises the first case: wrong dimension with the expected modulus Q.

The modulus-switching step preserves the ciphertext vector length, so the one-element input reaches KeySwitch() as a one-element ciphertext.

KeySwitch() then uses params->GetN() as the fixed loop bound rather than the actual ciphertext length 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 &&, for example:

if ((ct->GetLength() != LWEParams->GetN()) ||
    (ct->GetModulus() != LWEParams->GetQ()))
    OPENFHE_THROW("ciphertext must have large dimension N and modulus Q");

It may also be worth enforcing the dimension invariant in the lower-level LWEEncryptionScheme::SwitchCTtoqn() path or immediately before the fixed-N indexing in 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

Added to issue Minor input validation issues · Issue #1234 · openfheorg/openfhe-development · GitHub