CVE-2022-46280
A use of uninitialized pointer vulnerability exists in the PQS format pFormat functionality of Open Babel 3.1.1 and master commit 530dbfa3. A specially crafted malformed file can lead to arbitrary code execution. An attacker can provide a malicious file to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Open Babel 3.1.1
Open Babel master commit 530dbfa3
Open Babel - https://openbabel.org/
9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-824 - Access of Uninitialized Pointer
Open Babel is a popular library for converting chemical file formats, currently supporting about 130 different file formats. It implements bindings for several programming languages. Because of the nature of the library, and since there are many online chemical format converters and molecule viewers which might be using Open Babel in their backend for parsing and conversion, we consider this software as potentially accessible via network.
Open Babel ships a simple converter application called obabel
that can be used to trigger the issue described in this advisory. obabel
supports -i
and -o
parameters, which select the input and output formats to perform the conversion. obabel
supports multiple input and output files (as does the Open Babel library itself): this technically allows multiple vulnerabilities to trigger in sequence, which in turn could make some vulnerabilities easier to exploit. In this advisory, however, we focus on only one input file and a corresponding output file.
When a single input file and output file are supplied, obabel.cpp
records the input and output formats (if supplied) and calls OBConversion::FullConvert
in obconversion.cpp
. Inside this function, there’s a call to OpenAndSetFormat
, which uses FormatFromExt
to derive the input format from the filename extension if no -i
parameter was supplied. Similarly, OpenInAndOutFiles
can be used to derive both input and output formats from the filename extensions when none are supplied.
Depending on how the obabel
application is invoked, different paths could actually take place. Eventually, pInFormat
and pOutFormat
(of base class OBFormat
) objects are allocated, which are instances of the classes that implement the selected input and output formats.
The code then proceeds with a call to OBConversion::Convert
, which eventually leads to calling pInFormat->ReadMolecule
and pOutFormat->WriteMolecule
.
In this advisory, we describe an issue in the “Parallel Quantum Solutions” (PQS
) file format (formats/PQSformat.cpp.cpp
) when parsing an input file via ReadMolecule
.
bool PQSFormat::ReadMolecule(OBBase* pOb, OBConversion* pConv)
{
...
[1] const char* title = pConv->GetTitle();
[2] char buffer[BUFF_SIZE];
char coord_file[256];
char full_coord_path[256]="\0";
ifstream coordFileStream;
...
[3] while (!geom_found && ifs.getline(buffer,BUFF_SIZE))
{
lowerit(buffer); //look for geom except in title or text
if (strstr(buffer, "geom") != nullptr &&
(strncmp(buffer,"text",4)!=0 && strncmp(buffer,"titl",4)!=0))
{
...
[4] if (strstr(buffer, "file=") != nullptr)
{ //external geometry file
[5] strncpy(coord_file,strstr(buffer,"file=")+5, sizeof(coord_file));
coord_file[sizeof(coord_file) - 1] = '\0';
if (strrchr(coord_file, ' ') != nullptr)
*strrchr(coord_file,' ')='\0';
if (coord_file[0]!='/')
{
strncpy(full_coord_path,title, sizeof(full_coord_path));
full_coord_path[sizeof(full_coord_path)-1] = '\0';
if (strrchr(full_coord_path, '/') != nullptr)
*(strrchr(full_coord_path,'/')+1)='\0';
else
full_coord_path[0] = '\0';
}
[6] strcat(full_coord_path,coord_file);
full_coord_path[sizeof(full_coord_path) - 1] = '\0';
stringstream errorMsg;
errorMsg <<"External geometry file referenced: "<< \
full_coord_path<<endl;
obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obInfo);
At [1], the title
of the file is retrieved (this corresponds to the filename of the input file). Then, for each line in the input file [3], if the line contains the string “file=”, the condition at [4] matches. At [5] the coordinates file path is built, depending on what comes after “file=” (set to coord_file
). If coord_file
is a relative path, it is concatenated to the directory path of the input file, and the result is stored in full_coord_path
[6]. If coord_file
is an absolute path, full_coord_path
is simply set to coord_file
.
...
[7] coordFileStream.open(full_coord_path);
if (!coordFileStream)
{
obErrorLog.ThrowError(__FUNCTION__, "Cannot read external geometry file!", obError);
return(false);
// exit (-1);
}
else
[8] {
ifs.seekg(0, ios::end); //move .inp file pointer to the end of file
//New framework mods
OBConversion coordconv(&coordFileStream);
[9] OBFormat* pFormat;
if (strstr(buffer, "=car" ) != nullptr)
pFormat =OBConversion::FindFormat("BIOSYM");
if (strstr(buffer, "=hin" ) != nullptr)
pFormat = OBConversion::FindFormat("HIN");
if (strstr(buffer, "=pdb" ) != nullptr)
pFormat = OBConversion::FindFormat("PDB");
if (strstr(buffer, "=mop" ) != nullptr)
pFormat = OBConversion::FindFormat("MOPAC");
[10] return pFormat->ReadMolecule(&mol,&coordconv);
full_coord_path
is then opened [7], and if the open succeeds (it will succeed with any user-readable file or directory), we enter the block at [8].
At [9], a pFormat
is declared but not initialized. Then, a series of conditions try to match strings within the current line, setting pFormat
accordingly. Finally [10] pFormat
is used to call ReadMolecule
with the proper format conversion function. The issue is that, if none of the conditions between [9] and [10] succeed, pFormat
will be uninitialized, and at [10] an uninitialized pointer dereference will happen.
As ReadMolecule
is a virtual function referenced from pFormat
, this allows direct control of the control flow. The pointer is stored on the stack, and, depending on how the code is compiled and the stack is laid out, the pointer could be under full control of an attacker, who could in turn use this to execute arbitrary code.
$ ./bin/obabel -i pqs pformat.uninit.pqs -o sdf
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1280241==ERROR: AddressSanitizer: SEGV on unknown address 0x00000000 (pc 0xf4f51af1 bp 0xffffb818 sp 0xffff2b00 T0)
==1280241==The signal is caused by a READ memory access.
==1280241==Hint: address points to the zero page.
#0 0xf4f51af1 in OpenBabel::PQSFormat::ReadMolecule(OpenBabel::OBBase*, OpenBabel::OBConversion*) ./src/formats/PQSformat.cpp:263
#1 0xf751a915 in OpenBabel::OBMoleculeFormat::ReadChemObjectImpl(OpenBabel::OBConversion*, OpenBabel::OBFormat*) ./src/obmolecformat.cpp:102
#2 0xf63c358c in OpenBabel::OBMoleculeFormat::ReadChemObject(OpenBabel::OBConversion*) ./include/openbabel/obmolecformat.h:116
#3 0xf72a204e in OpenBabel::OBConversion::Convert() ./src/obconversion.cpp:545
#4 0xf72c717a in OpenBabel::OBConversion::Convert(std::istream*, std::ostream*) ./src/obconversion.cpp:481
#5 0xf72cf4f3 in OpenBabel::OBConversion::FullConvert(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >&) ./src/obconversion.cpp:1514
#6 0x565594ea in main ./tools/obabel.cpp:370
#7 0xf77923b4 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#8 0xf779247e in __libc_start_main_impl ../csu/libc-start.c:389
#9 0x5655c356 in _start (./bin/obabel+0x7356)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ./src/formats/PQSformat.cpp:263 in OpenBabel::PQSFormat::ReadMolecule(OpenBabel::OBBase*, OpenBabel::OBConversion*)
==1280241==ABORTING
Since the maintainer of this software did not release a patch during the 90 day window specified in our policy, we have now decided to release the information regarding this vulnerability, to make users of the software aware of this problem. See Cisco’s Coordinated Vulnerability Disclosure Policy for more information: https://tools.cisco.com/security/center/resources/vendor_vulnerability_policy.html
2022-12-20 - Initial Vendor Contact
2023-01-12 - Vendor Disclosure
2023-07-21 - Public Release
Discovered by Claudio Bozzato of Cisco Talos.