Hi SEAL team,
I would like to report a SEAL C API output-pointer validation bug that remains partially unresolved in the current v4.4.2 release.
Summary
I initially identified three SEAL C exports in v4.4.0 that write through caller-provided output pointers or output buffers without validating them:
ContextData_Parms
Modulus_Create2
Modulus_ConstRatio
I dynamically reproduced process-level null-pointer dereferences for all three entry points on v4.4.0.
I then rebuilt and reran the same reproducer against v4.4.2 to determine the current status. The issue is only partially addressed:
ContextData_Parms: still missing validation for parms
Modulus_Create2: still missing validation for small_modulus
Modulus_ConstRatio: now validates ratio with IfNullRet(ratio, E_POINTER)
So the current report concerns the two output-pointer checks that still remain missing in v4.4.2. The Modulus_ConstRatio result is retained below as part of the original v4.4.0 findings and to document the v4.4.2 partial fix.
Passing NULL to either remaining output pointer still reaches a direct write through that pointer instead of returning E_POINTER.
Environment
Dynamic reproduction on v4.4.0:
- SEAL release/tag:
v4.4.0
- Tested revision:
04d53b99ce745efc26bb4965be609b9894755227
- OS: Linux x86_64
- Compiler:
Clang 14.0.0
- Sanitizer instrumentation used during path confirmation:
AddressSanitizer, UndefinedBehaviorSanitizer
Dynamic reproduction on v4.4.2:
- SEAL release/tag:
v4.4.2
- Tested revision:
cbff8895f2307241e6fcc91fd791457227ffb2a2
- OS: Linux x86_64
- Compiler:
Clang 14.0.0
- Sanitizer instrumentation used during path confirmation:
AddressSanitizer, UndefinedBehaviorSanitizer
Status in v4.4.2
The three original v4.4.0 findings have the following status in v4.4.2.
ContextData_Parms — still affected
The function still validates only thisptr:
SEALContext::ContextData *cont_data = FromVoid<SEALContext::ContextData>(thisptr);
IfNullRet(cont_data, E_POINTER);
It then allocates an object and writes through parms without checking it:
EncryptionParameters *enc_params = new EncryptionParameters(cont_data->parms());
*parms = enc_params;
Modulus_Create2 — still affected
The function still validates only the source object:
Modulus *copypt = FromVoid<Modulus>(copy);
IfNullRet(copypt, E_POINTER);
It then writes through small_modulus without checking it:
Modulus *sm = new Modulus(*copypt);
*small_modulus = sm;
Modulus_ConstRatio — fixed in v4.4.2
The v4.4.2 implementation now includes:
IfNullRet(ratio, E_POINTER);
before the output copy. I also reran this case locally on v4.4.2, and it no longer crashes.
Minimal reproduction on v4.4.0 and v4.4.2
The reproducer below dynamically demonstrates the three conditions as they existed in v4.4.0, and it was rerun unchanged on v4.4.2.
The Modulus_ConstRatio mode is included for the historical v4.4.0 result and to confirm the v4.4.2 fix; only the first two modes still correspond to missing checks in v4.4.2.
#include "seal/c/contextdata.h"
#include "seal/c/modulus.h"
#include "seal/context.h"
#include "seal/encryptionparams.h"
#include "seal/modulus.h"
#include <cstdint>
#include <iostream>
#include <string>
using namespace seal;
namespace
{
int RunContextDataParmsNullOut()
{
EncryptionParameters parms(scheme_type::bfv);
parms.set_poly_modulus_degree(4096);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(4096));
parms.set_plain_modulus(PlainModulus::Batching(4096, 20));
SEALContext context(parms);
auto *context_data = const_cast<SEALContext::ContextData *>(context.first_context_data().get());
std::cout << "about to call ContextData_Parms with parms == NULL" << std::endl;
return static_cast<int>(ContextData_Parms(context_data, nullptr));
}
int RunModulusCreate2NullOut()
{
Modulus modulus(257);
std::cout << "about to call Modulus_Create2 with small_modulus == NULL" << std::endl;
return static_cast<int>(Modulus_Create2(&modulus, nullptr));
}
int RunModulusConstRatioNullOut()
{
Modulus modulus(257);
std::cout << "about to call Modulus_ConstRatio with ratio == NULL" << std::endl;
auto result = Modulus_ConstRatio(&modulus, 3, nullptr);
std::cout << "HRESULT=0x" << std::hex
<< static_cast<unsigned long>(result) << std::dec << std::endl;
return (result == E_POINTER) ? 0 : 1;
}
} // namespace
int main(int argc, char **argv)
{
if (argc != 2)
{
return 2;
}
std::string mode = argv[1];
if (mode == "contextdata-parms-null-out")
{
return Run
ContextDataParmsNullOut();
}
if (mode == "modulus-create2-null-out")
{
return RunModulusCreate2NullOut();
}
if (mode == "modulus-const-ratio-null-out")
{
return RunModulusConstRatioNullOut();
}
return 2;
}
Commands used:
./repro contextdata-parms-null-out
./repro modulus-create2-null-out
./repro modulus-const-ratio-null-out
Expected behavior
ContextData_Parms() and Modulus_Create2() should validate their required output pointers before allocation or assignment and return E_POINTER when those pointers are NULL.
This would match neighboring SEAL C exports such as ContextData_Qualifiers() and Modulus_Create1().
Modulus_ConstRatio() now follows this general pattern in v4.4.2 and is no longer included among the remaining affected exports.
Actual behavior on v4.4.0
On the tested v4.4.0 build, all three entry points terminated the process before returning an HRESULT.
Representative frames from my local v4.4.0 build for ContextData_Parms (local source line offsets omitted):
Program received signal SIGSEGV, Segmentation fault.
ContextData_Parms(...)
at native/src/seal/c/contextdata.cpp
RunContextDataParmsNullOut()
at repro.cpp
Representative frames from my local v4.4.0 build for Modulus_Create2 (local source line offsets omitted):
Program received signal SIGSEGV, Segmentation fault.
Modulus_Create2(...)
at native/src/seal/c/modulus.cpp
RunModulusCreate2NullOut()
at repro.cpp
Representative excerpt from my local ASan-instrumented v4.4.0 build for Modulus_ConstRatio (local source line offsets omitted):
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
Modulus_ConstRatio(...)
at native/src/seal/c/modulus.cpp
RunModulusConstRatioNullOut()
at repro.cpp
Actual behavior on v4.4.2
On the tested v4.4.2 build:
ContextData_Parms still writes through parms == NULL and terminates the process
Modulus_Create2 still writes through small_modulus == NULL and terminates the process
Modulus_ConstRatio no longer crashes; it now rejects ratio == NULL
Representative v4.4.2 UBSan/ASan output for ContextData_Parms (local source line offsets omitted):
about to call ContextData_Parms with parms == NULL
runtime error: store to null pointer of type 'void *'
AddressSanitizer: SEGV on unknown address 0x000000000000
#0 ContextData_Parms
at native/src/seal/c/contextdata.cpp
Representative v4.4.2 UBSan/ASan output for Modulus_Create2 (local source line offsets omitted):
about to call Modulus_Create2 with small_modulus == NULL
runtime error: store to null pointer of type 'void *'
AddressSanitizer: SEGV on unknown address 0x000000000000
#0 Modulus_Create2
at native/src/seal/c/modulus.cpp
For Modulus_ConstRatio on v4.4.2, the call now returns E_POINTER instead of crashing:
about to call Modulus_ConstRatio with ratio == NULL
HRESULT=0x80004003
Impact
The remaining confirmed impact in v4.4.2 is a process-level null-pointer dereference when a caller passes NULL for the required output pointer of ContextData_Parms() or Modulus_Create2().
This is primarily a C API robustness and availability issue. The caller must already be able to invoke the native export with an invalid output argument; serialized SEAL data alone does not trigger the condition.
In a managed/native boundary, plugin boundary, or another application path where output arguments can become malformed before reaching the C export, the condition can terminate the host process instead of returning a recoverable HRESULT.
Cause analysis
In v4.4.0, all three exports wrote through unchecked output arguments.
The first two missing checks remain present in v4.4.2. Modulus_ConstRatio() has since gained the corresponding IfNullRet(ratio, E_POINTER) guard.
ContextData_Parms — still affected in v4.4.2
SEAL_C_FUNC ContextData_Parms(void *thisptr, void **parms)
{
SEALContext::ContextData *cont_data = FromVoid<SEALContext::ContextData>(thisptr);
IfNullRet(cont_data, E_POINTER);
try
{
EncryptionParameters *enc_params = new EncryptionParameters(cont_data->parms());
*parms = enc_params;
return S_OK;
}
SEAL_C_CATCH_ALL
}
Modulus_Create2 — still affected in v4.4.2
SEAL_C_FUNC Modulus_Create2(void *copy, void **small_modulus)
{
Modulus *copypt = FromVoid<Modulus>(copy);
IfNullRet(copypt, E_POINTER);
try
{
Modulus *sm = new Modulus(*copypt);
*small_modulus = sm;
return S_OK;
}
SEAL_C_CATCH_ALL
}
Modulus_ConstRatio — affected in v4.4.0, fixed in v4.4.2
In v4.4.0, the function copied to ratio without validating it:
if (length != 3)
{
return E_INVALIDARG;
}
auto ratio_array = sm->const_ratio();
copy(ratio_array.begin(), ratio_array.end(), ratio);
In v4.4.2, it now performs:
IfNullRet(ratio, E_POINTER);
if (length != 3)
{
return E_INVALIDARG;
}
SEAL_C_CATCH_ALL can translate C++ exceptions into HRESULT values, but it does not convert a Linux SIGSEGV caused by writing through a null pointer into a recoverable return code. Modulus_ConstRatio() previously performed its output copy outside any such exception-translation block, but that entry point is now protected by the explicit null check in v4.4.2.
Relevant source locations
Original v4.4.0 findings:
Current v4.4.2 status:
Suggested direction
The remaining missing checks in v4.4.2 appear straightforward:
- add
IfNullRet(parms, E_POINTER) to ContextData_Parms() before allocating the new EncryptionParameters
- add
IfNullRet(small_modulus, E_POINTER) to Modulus_Create2() before allocating the new Modulus
Question
Should the two remaining exports, ContextData_Parms() and Modulus_Create2(), also return E_POINTER for NULL output pointers,
consistent with the checks already used by neighboring exports and now by Modulus_ConstRatio() in v4.4.2?
Reported by Jiang Chao, Beijing University of Posts and Telecommunications
Hi SEAL team,
I would like to report a SEAL C API output-pointer validation bug that remains partially unresolved in the current
v4.4.2release.Summary
I initially identified three SEAL C exports in
v4.4.0that write through caller-provided output pointers or output buffers without validating them:ContextData_ParmsModulus_Create2Modulus_ConstRatioI dynamically reproduced process-level null-pointer dereferences for all three entry points on
v4.4.0.I then rebuilt and reran the same reproducer against
v4.4.2to determine the current status. The issue is only partially addressed:ContextData_Parms: still missing validation forparmsModulus_Create2: still missing validation forsmall_modulusModulus_ConstRatio: now validatesratiowithIfNullRet(ratio, E_POINTER)So the current report concerns the two output-pointer checks that still remain missing in
v4.4.2. TheModulus_ConstRatioresult is retained below as part of the originalv4.4.0findings and to document thev4.4.2partial fix.Passing
NULLto either remaining output pointer still reaches a direct write through that pointer instead of returningE_POINTER.Environment
Dynamic reproduction on
v4.4.0:v4.4.004d53b99ce745efc26bb4965be609b9894755227Clang 14.0.0AddressSanitizer,UndefinedBehaviorSanitizerDynamic reproduction on
v4.4.2:v4.4.2cbff8895f2307241e6fcc91fd791457227ffb2a2Clang 14.0.0AddressSanitizer,UndefinedBehaviorSanitizerStatus in v4.4.2
The three original
v4.4.0findings have the following status inv4.4.2.ContextData_Parms— still affectedThe function still validates only
thisptr:It then allocates an object and writes through
parmswithout checking it:Modulus_Create2— still affectedThe function still validates only the source object:
It then writes through
small_moduluswithout checking it:Modulus *sm = new Modulus(*copypt); *small_modulus = sm;Modulus_ConstRatio— fixed inv4.4.2The
v4.4.2implementation now includes:before the output copy. I also reran this case locally on
v4.4.2, and it no longer crashes.Minimal reproduction on
v4.4.0andv4.4.2The reproducer below dynamically demonstrates the three conditions as they existed in
v4.4.0, and it was rerun unchanged onv4.4.2.The
Modulus_ConstRatiomode is included for the historicalv4.4.0result and to confirm thev4.4.2fix; only the first two modes still correspond to missing checks inv4.4.2.Commands used:
Expected behavior
ContextData_Parms()andModulus_Create2()should validate their required output pointers before allocation or assignment and returnE_POINTERwhen those pointers areNULL.This would match neighboring SEAL C exports such as
ContextData_Qualifiers()andModulus_Create1().Modulus_ConstRatio()now follows this general pattern inv4.4.2and is no longer included among the remaining affected exports.Actual behavior on
v4.4.0On the tested
v4.4.0build, all three entry points terminated the process before returning an HRESULT.Representative frames from my local
v4.4.0build forContextData_Parms(local source line offsets omitted):Representative frames from my local
v4.4.0build forModulus_Create2(local source line offsets omitted):Representative excerpt from my local ASan-instrumented
v4.4.0build forModulus_ConstRatio(local source line offsets omitted):Actual behavior on
v4.4.2On the tested
v4.4.2build:ContextData_Parmsstill writes throughparms == NULLand terminates the processModulus_Create2still writes throughsmall_modulus == NULLand terminates the processModulus_ConstRationo longer crashes; it now rejectsratio == NULLRepresentative
v4.4.2UBSan/ASan output forContextData_Parms(local source line offsets omitted):Representative
v4.4.2UBSan/ASan output forModulus_Create2(local source line offsets omitted):For
Modulus_ConstRatioonv4.4.2, the call now returnsE_POINTERinstead of crashing:Impact
The remaining confirmed impact in
v4.4.2is a process-level null-pointer dereference when a caller passesNULLfor the required output pointer ofContextData_Parms()orModulus_Create2().This is primarily a C API robustness and availability issue. The caller must already be able to invoke the native export with an invalid output argument; serialized SEAL data alone does not trigger the condition.
In a managed/native boundary, plugin boundary, or another application path where output arguments can become malformed before reaching the C export, the condition can terminate the host process instead of returning a recoverable HRESULT.
Cause analysis
In
v4.4.0, all three exports wrote through unchecked output arguments.The first two missing checks remain present in
v4.4.2.Modulus_ConstRatio()has since gained the correspondingIfNullRet(ratio, E_POINTER)guard.ContextData_Parms— still affected inv4.4.2Modulus_Create2— still affected inv4.4.2Modulus_ConstRatio— affected inv4.4.0, fixed inv4.4.2In
v4.4.0, the function copied toratiowithout validating it:In
v4.4.2, it now performs:SEAL_C_CATCH_ALLcan translate C++ exceptions into HRESULT values, but it does not convert a LinuxSIGSEGVcaused by writing through a null pointer into a recoverable return code.Modulus_ConstRatio()previously performed its output copy outside any such exception-translation block, but that entry point is now protected by the explicit null check inv4.4.2.Relevant source locations
Original
v4.4.0findings:ContextData_Parms():https://github.com/microsoft/SEAL/blob/v4.4.0/native/src/seal/c/contextdata.cpp#L62-L73
Modulus_Create2():https://github.com/microsoft/SEAL/blob/v4.4.0/native/src/seal/c/modulus.cpp#L32-L43
Modulus_ConstRatio():https://github.com/microsoft/SEAL/blob/v4.4.0/native/src/seal/c/modulus.cpp#L130-L144
Current
v4.4.2status:ContextData_Parms():https://github.com/microsoft/SEAL/blob/v4.4.2/native/src/seal/c/contextdata.cpp#L62-L73
ContextData_Qualifiers():https://github.com/microsoft/SEAL/blob/v4.4.2/native/src/seal/c/contextdata.cpp#L76-L88
Modulus_Create1():https://github.com/microsoft/SEAL/blob/v4.4.2/native/src/seal/c/modulus.cpp#L19-L29
Modulus_Create2():https://github.com/microsoft/SEAL/blob/v4.4.2/native/src/seal/c/modulus.cpp#L32-L43
Modulus_ConstRatio():https://github.com/microsoft/SEAL/blob/v4.4.2/native/src/seal/c/modulus.cpp#L130-L144
IfNullRetmacro:https://github.com/microsoft/SEAL/blob/v4.4.2/native/src/seal/c/stdafx.h#L18-L24
E_POINTER/SEAL_C_CATCH_ALL:https://github.com/microsoft/SEAL/blob/v4.4.2/native/src/seal/c/defines.h#L35-L92
Suggested direction
The remaining missing checks in
v4.4.2appear straightforward:IfNullRet(parms, E_POINTER)toContextData_Parms()before allocating the newEncryptionParametersIfNullRet(small_modulus, E_POINTER)toModulus_Create2()before allocating the newModulusQuestion
Should the two remaining exports,
ContextData_Parms()andModulus_Create2(), also returnE_POINTERforNULLoutput pointers,consistent with the checks already used by neighboring exports and now by
Modulus_ConstRatio()inv4.4.2?Reported by Jiang Chao, Beijing University of Posts and Telecommunications