Skip to content

Commit 052544d

Browse files
NoremosArtyom Abakumov
andauthored
Fix segfault when passing invalid include path in fbtrace config via fbtracemgr (#9091)
* Fix segfault when passing invalid `include` path in fbtrace config via fbtracemgr * Move config tests to existing file * Allow to skip test if test config cannot be created * Resolve path to `tmp` in config unit tests `include` does not work with symlinks --------- Co-authored-by: Artyom Abakumov <artyom.abakumov@red-soft.ru>
1 parent 1b42306 commit 052544d

6 files changed

Lines changed: 192 additions & 42 deletions

File tree

src/common/config/config_file.cpp

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ class MainStream : public ConfigFile::Stream
117117
class TextStream : public ConfigFile::Stream
118118
{
119119
public:
120+
inline static constexpr const char* STREAM_NAME = "Passed text";
121+
120122
explicit TextStream(const char* configText)
121123
: s(configText), l(0)
122124
{
@@ -275,7 +277,7 @@ ConfigFile::Stream::~Stream()
275277
* Parse line, taking quotes into account
276278
*/
277279

278-
ConfigFile::LineType ConfigFile::parseLine(const char* fileName, const String& inputPar, Parameter& par)
280+
ConfigFile::LineType ConfigFile::parseLine(const StreamName fileName, const String& inputPar, Parameter& par)
279281
{
280282
int inString = 0;
281283
String::size_type valStart = 0;
@@ -449,7 +451,7 @@ void ConfigFile::adjustMacroReplacePositions(const String& value, const String&
449451
to += getDirSeparatorLength(value, to);
450452
}
451453

452-
bool ConfigFile::macroParse(String& value, const char* fileName) const
454+
bool ConfigFile::macroParse(String& value, const StreamName fileName) const
453455
{
454456
String::size_type pos = 0;
455457
String::size_type subFrom;
@@ -506,7 +508,7 @@ bool ConfigFile::macroParse(String& value, const char* fileName) const
506508
* Find macro value
507509
*/
508510

509-
bool ConfigFile::translate(const char* fileName, const String& from, String& to) const
511+
bool ConfigFile::translate(const StreamName fileName, const String& from, String& to) const
510512
{
511513
if (from == "root")
512514
{
@@ -518,19 +520,20 @@ bool ConfigFile::translate(const char* fileName, const String& from, String& to)
518520
}
519521
else if (from == "this")
520522
{
521-
if (!fileName)
523+
if (!fileName.has_value())
522524
{
523525
return false;
524526
}
525527

526-
PathName tempPath(fileName);
528+
const char* fileNameData = fileName.value_or("");
529+
PathName tempPath(fileNameData);
527530

528531
#ifdef UNIX
529532
if (PathUtils::isSymLink(tempPath))
530533
{
531534
// If $(this) is a symlink, expand it.
532535
TEXT temp[MAXPATHLEN];
533-
const int n = readlink(fileName, temp, sizeof(temp));
536+
const int n = readlink(fileNameData, temp, sizeof(temp));
534537

535538
if (n != -1)
536539
{
@@ -539,7 +542,7 @@ bool ConfigFile::translate(const char* fileName, const String& from, String& to)
539542
if (PathUtils::isRelative(tempPath))
540543
{
541544
PathName parent;
542-
PathUtils::splitLastComponent(parent, tempPath, fileName);
545+
PathUtils::splitLastComponent(parent, tempPath, fileNameData);
543546
PathUtils::concatPath(tempPath, parent, temp);
544547
}
545548
}
@@ -640,9 +643,9 @@ const ConfigFile::Parameter* ConfigFile::findParameter(const KeyType& name, cons
640643
* Take into an account fault line
641644
*/
642645

643-
void ConfigFile::badLine(const char* fileName, const String& line)
646+
void ConfigFile::badLine(const StreamName fileName, const String& line)
644647
{
645-
(Arg::Gds(isc_conf_line) << (fileName ? fileName : "Passed text") << line).raise();
648+
(Arg::Gds(isc_conf_line) << fileName.value_or(TextStream::STREAM_NAME) << line).raise();
646649
}
647650

648651
/******************************************************************************
@@ -655,7 +658,7 @@ void ConfigFile::parse(Stream* stream)
655658
String inputLine;
656659
Parameter* previous = NULL;
657660
unsigned int line;
658-
const char* streamName = stream->getFileName();
661+
const StreamName streamName = stream->getFileName();
659662

660663
parameters.setSortMode(FB_ARRAY_SORT_MANUAL);
661664

@@ -756,24 +759,26 @@ void ConfigFile::parse(Stream* stream)
756759
* Parse include operator
757760
*/
758761

759-
void ConfigFile::include(const char* currentFileName, const PathName& parPath)
762+
void ConfigFile::include(const StreamName currentFileName, const PathName& parPath)
760763
{
764+
const auto fileNameForError = currentFileName.value_or(TextStream::STREAM_NAME);
765+
761766
#ifdef DEBUG_INCLUDES
762767
fprintf(stderr, "include into %s file(s) %s\n", currentFileName, parPath.c_str());
763768
#endif
764769
// We should better limit include depth
765770
AutoSetRestore<unsigned> depth(&includeLimit, includeLimit + 1);
766771
if (includeLimit > INCLUDE_LIMIT)
767772
{
768-
(Arg::Gds(isc_conf_include) << currentFileName << parPath << Arg::Gds(isc_include_depth)).raise();
773+
(Arg::Gds(isc_conf_include) << fileNameForError << parPath << Arg::Gds(isc_include_depth)).raise();
769774
}
770775

771776
// for relative paths first of all prepend with current path (i.e. path of current conf file)
772777
PathName path;
773778
if (PathUtils::isRelative(parPath))
774779
{
775780
PathName dummy;
776-
PathUtils::splitLastComponent(path, dummy, currentFileName);
781+
PathUtils::splitLastComponent(path, dummy, currentFileName.value_or(""));
777782
}
778783
PathUtils::concatPath(path, path, parPath);
779784

@@ -796,12 +801,12 @@ void ConfigFile::include(const char* currentFileName, const PathName& parPath)
796801
}
797802

798803
// analyze components for wildcards
799-
if (!wildCards(currentFileName, pathPrefix, components))
804+
if (!wildCards(pathPrefix, components))
800805
{
801806
// no matches found - check for presence of wild symbols in path
802807
if (!hadWildCards)
803808
{
804-
(Arg::Gds(isc_conf_include) << currentFileName << parPath << Arg::Gds(isc_include_miss)).raise();
809+
(Arg::Gds(isc_conf_include) << fileNameForError << parPath << Arg::Gds(isc_include_miss)).raise();
805810
}
806811
}
807812
}
@@ -814,7 +819,7 @@ void ConfigFile::include(const char* currentFileName, const PathName& parPath)
814819
* - returns true if some match was found
815820
*/
816821

817-
bool ConfigFile::wildCards(const char* currentFileName, const PathName& pathPrefix, FilesArray& components)
822+
bool ConfigFile::wildCards(const PathName& pathPrefix, FilesArray& components)
818823
{
819824
// Any change in directory can cause config change
820825
PathName prefix(pathPrefix);
@@ -855,7 +860,7 @@ bool ConfigFile::wildCards(const char* currentFileName, const PathName& pathPref
855860

856861
if (mustBeDir) // should be directory
857862
{
858-
found = wildCards(currentFileName, name, components) || found;
863+
found = wildCards(name, components) || found;
859864
}
860865
else
861866
{
@@ -962,4 +967,3 @@ bool ConfigFile::Parameter::asBoolean() const
962967
value.equalsNoCase("yes") ||
963968
value.equalsNoCase("y");
964969
}
965-

src/common/config/config_file.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include "../common/classes/objects_array.h"
2929
#include "../common/classes/fb_string.h"
3030
#include "../common/classes/auto.h"
31+
#include "../common/utils_proto.h"
32+
3133

3234
/**
3335
Since the original (isc.cpp) code wasn't able to provide powerful and
@@ -50,6 +52,8 @@ class ConfigCache;
5052

5153
class ConfigFile : public Firebird::AutoStorage, public Firebird::RefCounted
5254
{
55+
using StreamName = fb_utils::SafePointer<const char>;
56+
5357
public:
5458
// flags for config file
5559
static inline constexpr USHORT HAS_SUB_CONF = 0x01;
@@ -126,7 +130,7 @@ class ConfigFile : public Firebird::AutoStorage, public Firebird::RefCounted
126130
}
127131

128132
// Substitute macro values in a string
129-
bool macroParse(String& value, const char* fileName) const;
133+
bool macroParse(String& value, const StreamName fileName) const;
130134

131135
private:
132136
enum LineType {LINE_BAD, LINE_REGULAR, LINE_START_SUB, LINE_END_SUB, LINE_INCLUDE};
@@ -140,11 +144,11 @@ class ConfigFile : public Firebird::AutoStorage, public Firebird::RefCounted
140144
// utilities
141145
bool getLine(Stream* stream, String&, unsigned int&);
142146
void parse(Stream* stream);
143-
LineType parseLine(const char* fileName, const String& input, Parameter& par);
144-
bool translate(const char* fileName, const String& from, String& to) const;
145-
[[noreturn]] void badLine(const char* fileName, const String& line);
146-
void include(const char* currentFileName, const Firebird::PathName& path);
147-
bool wildCards(const char* currentFileName, const Firebird::PathName& pathPrefix, FilesArray& components);
147+
LineType parseLine(const StreamName fileName, const String& input, Parameter& par);
148+
bool translate(const StreamName fileName, const String& from, String& to) const;
149+
[[noreturn]] void badLine(const StreamName fileName, const String& line);
150+
void include(const StreamName currentFileName, const Firebird::PathName& path);
151+
bool wildCards(const Firebird::PathName& pathPrefix, FilesArray& components);
148152
bool substituteStandardDir(const String& from, String& to) const;
149153
void adjustMacroReplacePositions(const String& value, const String& macro, String::size_type& from, String::size_type& to) const;
150154
unsigned getDirSeparatorLength(const String& value, String::size_type subFrom) const;

src/common/tests/CommonFixtures.h

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,26 @@
22
#define COMMON_FIXTURES
33
#include "boost/test/unit_test.hpp"
44

5+
#include "CommonUtils.h"
6+
57
#include <filesystem>
6-
#include <random>
78

89
namespace TestsUtils
910
{
1011

1112
namespace fs = std::filesystem;
1213

13-
inline std::string generateRandomString(std::size_t length)
14-
{
15-
std::random_device rd;
16-
std::mt19937 generator(rd());
17-
18-
std::uniform_int_distribution<> distribution(0, 9);
19-
20-
std::string randomString;
21-
for (std::size_t i = 0; i < length; ++i)
22-
{
23-
randomString += '0' + distribution(generator);
24-
}
25-
26-
return randomString;
27-
}
28-
2914
struct TempPathFixture
3015
{
3116
fs::path tempPathFX;
3217

3318
TempPathFixture()
3419
{
35-
tempPathFX = fs::temp_directory_path() / (generateRandomString(10) + "_common_test.tmp");
20+
auto tempDir = fs::temp_directory_path();
21+
// Resolve symlink (/var on macos)
22+
tempDir = fs::canonical(tempDir);
23+
24+
tempPathFX = tempDir / (generateRandomString(10) + "_common_test.tmp");
3625
}
3726

3827
~TempPathFixture()

src/common/tests/CommonUtils.h

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#ifndef TEST_COMMON_UTILS
2+
#define TEST_COMMON_UTILS
3+
4+
#include "firebird.h"
5+
#include "fb_exception.h"
6+
7+
#include "boost/test/unit_test.hpp"
8+
9+
#include <string>
10+
#include <string_view>
11+
#include <random>
12+
13+
namespace TestsUtils
14+
{
15+
inline std::string generateRandomString(std::size_t length)
16+
{
17+
std::random_device rd;
18+
std::mt19937 generator(rd());
19+
20+
std::uniform_int_distribution<> distribution(0, 9);
21+
22+
std::string randomString;
23+
for (std::size_t i = 0; i < length; ++i)
24+
{
25+
randomString += '0' + distribution(generator);
26+
}
27+
28+
return randomString;
29+
}
30+
31+
// Use std::string because it works better with BOOST_TEST
32+
inline std::string getErrorMessage(const Firebird::status_exception& ex)
33+
{
34+
const ISC_STATUS* status = ex.value();
35+
36+
std::string buffer;
37+
TEXT temp[BUFFER_LARGE];
38+
while (fb_interpret(temp, sizeof(temp), &status))
39+
{
40+
buffer += temp;
41+
buffer += " ";
42+
}
43+
44+
if (!buffer.empty())
45+
buffer.resize(buffer.length() - 1);
46+
47+
return buffer;
48+
}
49+
50+
// Wrapper to pass const char array as template argument
51+
template<std::size_t N>
52+
struct ConstexprString
53+
{
54+
char value[N];
55+
56+
constexpr ConstexprString(const char (&str)[N])
57+
{
58+
std::copy_n(str, N, value);
59+
}
60+
61+
constexpr operator std::string_view() const
62+
{
63+
return std::string_view(value, N - 1); // Exclude '\0'
64+
}
65+
};
66+
67+
inline bool checkErrorMessage(const Firebird::status_exception& ex, const std::string_view expected)
68+
{
69+
const auto message = getErrorMessage(ex);
70+
BOOST_TEST_INFO(std::string("Expected exception: ") + expected.data());
71+
BOOST_TEST_INFO("Caught exception: " + message); // Space for alignment
72+
return message == expected;
73+
}
74+
75+
template<ConstexprString Expecter>
76+
inline bool checkErrorMessage(const Firebird::status_exception& ex)
77+
{
78+
return checkErrorMessage(ex, static_cast<std::string_view>(Expecter));
79+
}
80+
81+
}
82+
83+
#endif

src/common/tests/ConfigFileTest.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,53 @@ BOOST_FIXTURE_TEST_CASE(IncludeInUserSessionBug, TestsUtils::TempPathFixture)
2727
BOOST_CHECK_THROW(ConfigFile file({}, text.data(), ConfigFile::DENY_INCLUDE), Firebird::Exception);
2828
}
2929

30+
BOOST_AUTO_TEST_CASE(InvalidIncludeInTextStreamBug)
31+
{
32+
MemoryPool& pool = *getDefaultMemoryPool();
33+
34+
const std::string_view text = R"(
35+
database
36+
{
37+
enabled = true
38+
}
39+
include /a/b/c/d/f.d
40+
)";
41+
42+
43+
// Should be an exception, not a segfault
44+
BOOST_CHECK_EXCEPTION(ConfigFile file({}, text.data(), 0), Firebird::status_exception,
45+
TestsUtils::checkErrorMessage<"Invalid include operator in Passed text for </a/b/c/d/f.d> File to include not found">);
46+
}
47+
48+
BOOST_FIXTURE_TEST_CASE(RecursiveInclude, TestsUtils::TempPathFixture)
49+
{
50+
MemoryPool& pool = *getDefaultMemoryPool();
51+
const auto pathStr = tempPathFX.string();
52+
53+
std::string text = R"(
54+
database
55+
{
56+
enabled = true
57+
}
58+
include )";
59+
60+
text += pathStr;
61+
62+
std::ofstream out(pathStr);
63+
out << "include " + pathStr;
64+
out.close();
65+
66+
Firebird::string error;
67+
error.printf("Invalid include operator in %s for <%s> Include depth too big", pathStr.data(), pathStr.data());
68+
69+
// Should be an exception, not a segfault
70+
BOOST_CHECK_EXCEPTION(ConfigFile file({}, text.data(), 0), Firebird::status_exception,
71+
[&error](const Firebird::status_exception& ex)
72+
{
73+
return TestsUtils::checkErrorMessage(ex, error.data());
74+
});
75+
}
76+
3077

3178
BOOST_AUTO_TEST_SUITE_END() // AutoPtrFunctionalTests
3279
BOOST_AUTO_TEST_SUITE_END() // CommonClassesSuite

0 commit comments

Comments
 (0)