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
src/binfhe/lib/binfhecontext.cppBinFHEContext::SwitchCTtoqn(): lines 252-260- Incorrect guard: line 258
- GitHub link: https://github.com/openfheorg/openfhe-development/blob/main/src/binfhe/lib/binfhecontext.cpp#L252-L260
src/binfhe/lib/lwe-pke.cppLWEEncryptionScheme::SwitchCTtoqn(): lines 153-160LWEEncryptionScheme::KeySwitch(): lines 323-341- Crash-relevant read: line 336
- GitHub links:
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