Skip to content

Commit ff39fd3

Browse files
FedeDPpoiana
authored andcommitted
fix(userspace/libsinsp): revert to old concatenate_paths helper function for perf reasons.
Also, added some small fixes and lots of new tests. Signed-off-by: Federico Di Pierro <[email protected]>
1 parent 6d57bde commit ff39fd3

File tree

3 files changed

+231
-29
lines changed

3 files changed

+231
-29
lines changed

userspace/libsinsp/test/sinsp_utils.ut.cpp

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ TEST(sinsp_utils_test, concatenate_paths)
2424
// Some tests were motivated by this resource:
2525
// https://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap04.html#tag_04_11
2626

27+
// PLEASE NOTE:
28+
// * current impl does not support unicode.
29+
// * current impl does not sanitize path1
30+
// * current impl expects path1 to end with '/'
31+
// * current impl skips path1 altogether if path2 is absolute
32+
2733
std::string path1, path2, res;
2834

2935
res = sinsp_utils::concatenate_paths("", "");
@@ -32,12 +38,57 @@ TEST(sinsp_utils_test, concatenate_paths)
3238
path1 = "";
3339
path2 = "../";
3440
res = sinsp_utils::concatenate_paths(path1, path2);
35-
EXPECT_EQ("..", res);
41+
EXPECT_EQ("", res);
42+
43+
path1 = "";
44+
path2 = "..";
45+
res = sinsp_utils::concatenate_paths(path1, path2);
46+
EXPECT_EQ("", res);
47+
48+
path1 = "/";
49+
path2 = "../";
50+
res = sinsp_utils::concatenate_paths(path1, path2);
51+
EXPECT_EQ("/", res);
52+
53+
path1 = "a";
54+
path2 = "../";
55+
res = sinsp_utils::concatenate_paths(path1, path2);
56+
EXPECT_EQ("a..", res); // since the helper does not add any "/" between path1 and path2, we end up with this.
57+
58+
path1 = "a/";
59+
path2 = "../";
60+
res = sinsp_utils::concatenate_paths(path1, path2);
61+
EXPECT_EQ("", res);
62+
63+
path1 = "";
64+
path2 = "/foo";
65+
res = sinsp_utils::concatenate_paths(path1, path2);
66+
EXPECT_EQ("/foo", res);
67+
68+
path1 = "foo/";
69+
path2 = "..//a";
70+
res = sinsp_utils::concatenate_paths(path1, path2);
71+
EXPECT_EQ("a", res); // path2 has been sanitized, plus we moved up a folder because of ".."
72+
73+
path1 = "/foo/";
74+
path2 = "..//a";
75+
res = sinsp_utils::concatenate_paths(path1, path2);
76+
EXPECT_EQ("/a", res); // path2 has been sanitized, plus we moved up a folder because of ".."
77+
78+
path1 = "heolo";
79+
path2 = "w////////////..//////.////////r.|";
80+
res = sinsp_utils::concatenate_paths(path1, path2);
81+
EXPECT_EQ("r.|", res); // since the helper does not add any "/" between path1 and path2, we end up with this.
82+
83+
path1 = "heolo";
84+
path2 = "w/////////////..//"; // heolow/////////////..// > heolow/..// -> /
85+
res = sinsp_utils::concatenate_paths(path1, path2);
86+
EXPECT_EQ("", res); // since the helper does not add any "/" between path1 and path2, we end up with this, ie a folder up from "heolow/"
3687

3788
path1 = "";
3889
path2 = "./";
3990
res = sinsp_utils::concatenate_paths(path1, path2);
40-
EXPECT_EQ(".", res);
91+
EXPECT_EQ("", res);
4192

4293
path1 = "";
4394
path2 = "dir/term";
@@ -92,27 +143,28 @@ TEST(sinsp_utils_test, concatenate_paths)
92143
path1 = "./app";
93144
path2 = "custom/term";
94145
res = sinsp_utils::concatenate_paths(path1, path2);
95-
EXPECT_EQ("app/custom/term", res);
146+
EXPECT_EQ("./appcustom/term", res); // since path1 is not '/' terminated, we expect a string concat without further path fields
96147

97148
path1 = "/app";
98149
path2 = "custom/term";
99150
res = sinsp_utils::concatenate_paths(path1, path2);
100-
EXPECT_EQ("/app/custom/term", res);
151+
EXPECT_EQ("/appcustom/term", res); // since path1 is not '/' terminated, we expect a string concat without further path fields
101152

102153
path1 = "app";
103154
path2 = "custom/term";
104155
res = sinsp_utils::concatenate_paths(path1, path2);
105-
EXPECT_EQ("app/custom/term", res);
156+
EXPECT_EQ("appcustom/term", res); // since path1 is not '/' terminated, we expect a string concat without further path fields
106157

107-
path1 = "app//";
158+
path1 = "app/";
108159
path2 = "custom/term";
109160
res = sinsp_utils::concatenate_paths(path1, path2);
110161
EXPECT_EQ("app/custom/term", res);
111162

163+
// We don't support sanitizing path1
112164
path1 = "app/////";
113165
path2 = "custom////term";
114166
res = sinsp_utils::concatenate_paths(path1, path2);
115-
EXPECT_EQ("app/custom/term", res);
167+
EXPECT_EQ("app/////custom/term", res);
116168

117169
path1 = "/";
118170
path2 = "/app/custom/dir/././././../../term/";
@@ -124,6 +176,7 @@ TEST(sinsp_utils_test, concatenate_paths)
124176
res = sinsp_utils::concatenate_paths(path1, path2);
125177
EXPECT_EQ("/app", res);
126178

179+
/* No unicode support
127180
path1 = "/root/";
128181
path2 = "../😉";
129182
res = sinsp_utils::concatenate_paths(path1, path2);
@@ -142,5 +195,5 @@ TEST(sinsp_utils_test, concatenate_paths)
142195
path1 = "/root";
143196
path2 = "c:/hello/world/";
144197
res = sinsp_utils::concatenate_paths(path1, path2);
145-
EXPECT_EQ("/root/c:/hello/world", res);
198+
EXPECT_EQ("/root/c:/hello/world", res); */
146199
}

userspace/libsinsp/utils.cpp

Lines changed: 168 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -613,36 +613,184 @@ std::filesystem::path workaround_win_root_name(std::filesystem::path p)
613613
return std::filesystem::path("./" + p.string());
614614
}
615615

616-
std::string sinsp_utils::concatenate_paths(std::string_view path1, std::string_view path2, size_t max_len)
616+
//
617+
// Helper function to move a directory up in a path string
618+
//
619+
static inline void rewind_to_parent_path(const char* targetbase, char** tc, const char** pc, uint32_t delta)
617620
{
618-
auto p1 = std::filesystem::path(path1, std::filesystem::path::format::generic_format);
619-
auto p2 = std::filesystem::path(path2, std::filesystem::path::format::generic_format);
621+
if(*tc <= targetbase + 1)
622+
{
623+
(*pc) += delta;
624+
return;
625+
}
620626

621-
#ifdef _WIN32
622-
// This is an ugly workaround to make sure we will not try to interpret root names (e.g. "c:/", "//?/") on Windows
623-
// since this function only deals with unix-like paths
624-
p1 = workaround_win_root_name(p1);
625-
p2 = workaround_win_root_name(p2);
626-
#endif // _WIN32
627+
(*tc)--;
628+
629+
while((*tc) >= targetbase + 1 && *((*tc) - 1) != '/')
630+
{
631+
(*tc)--;
632+
}
627633

628-
// note: if p2 happens to be an absolute path, p1 / p2 == p2
629-
auto path_concat = (p1 / p2).lexically_normal();
630-
std::string result = path_concat.generic_string();
634+
(*pc) += delta;
635+
}
631636

632-
//
633-
// If the path ends with a separator, remove it, as the OS does.
634-
//
635-
if (result.length() > 1 && result.back() == '/')
637+
//
638+
// Args:
639+
// - target: the string where we are supposed to start copying
640+
// - targetbase: the base of the path, i.e. the furthest we can go back when
641+
// following parent directories
642+
// - path: the path to copy
643+
//
644+
static inline void copy_and_sanitize_path(char* target, char* targetbase, const char *path, char separator)
645+
{
646+
char* tc = target;
647+
const char* pc = path;
648+
g_invalidchar ic;
649+
const bool empty_base = target == targetbase;
650+
651+
while(true)
652+
{
653+
if(*pc == 0)
654+
{
655+
*tc = 0;
656+
657+
//
658+
// If the path ends with a separator, remove it, as the OS does.
659+
// Properly manage case where path is just "/".
660+
//
661+
if((tc > (targetbase + 1)) && (*(tc - 1) == separator))
662+
{
663+
*(tc - 1) = 0;
664+
}
665+
666+
return;
667+
}
668+
669+
if(ic(*pc))
670+
{
671+
//
672+
// Invalid char, substitute with a '.'
673+
//
674+
*tc = '.';
675+
tc++;
676+
pc++;
677+
}
678+
else
679+
{
680+
//
681+
// If path begins with '.' or '.' is the first char after a '/'
682+
//
683+
if(*pc == '.' && (tc == targetbase || *(tc - 1) == separator))
684+
{
685+
//
686+
// '../', rewind to the previous separator
687+
//
688+
if(*(pc + 1) == '.' && *(pc + 2) == separator)
689+
{
690+
rewind_to_parent_path(targetbase, &tc, &pc, 3);
691+
}
692+
//
693+
// '..', with no separator.
694+
// This is valid if we are at the end of the string, and in that case we rewind.
695+
//
696+
else if(*(pc + 1) == '.' && *(pc + 2) == 0)
697+
{
698+
rewind_to_parent_path(targetbase, &tc, &pc, 2);
699+
}
700+
//
701+
// './', just skip it
702+
//
703+
else if(*(pc + 1) == separator)
704+
{
705+
pc += 2;
706+
}
707+
//
708+
// '.', with no separator.
709+
// This is valid if we are at the end of the string, and in that case we rewind.
710+
//
711+
else if(*(pc + 1) == 0)
712+
{
713+
pc++;
714+
}
715+
//
716+
// Otherwise, we leave the string intact.
717+
//
718+
else
719+
{
720+
*tc = *pc;
721+
pc++;
722+
tc++;
723+
}
724+
}
725+
else if(*pc == separator)
726+
{
727+
//
728+
// separator:
729+
// * if the last char is already a separator, skip it
730+
// * if we are back at targetbase but targetbase was not empty before, it means we
731+
// fully rewinded back to targetbase and the string is now empty. Skip separator.
732+
// Example: "/foo/../a" -> "/a" BUT "foo/../a" -> "a"
733+
// -> Otherwise: "foo/../a" -> "/a"
734+
//
735+
if((tc > targetbase && *(tc - 1) == separator) || (tc == targetbase && !empty_base))
736+
{
737+
pc++;
738+
}
739+
else
740+
{
741+
*tc = *pc;
742+
tc++;
743+
pc++;
744+
}
745+
}
746+
else
747+
{
748+
//
749+
// Normal char, copy it
750+
//
751+
*tc = *pc;
752+
tc++;
753+
pc++;
754+
}
755+
}
756+
}
757+
}
758+
759+
/*
760+
* Return false if path2 is an absolute path.
761+
* path1 MUST be '/' terminated.
762+
* path1 is not sanitized.
763+
* If path2 is absolute, we only account for it.
764+
*/
765+
static inline bool concatenate_paths_(char* target, uint32_t targetlen, const char* path1, uint32_t len1,
766+
const char* path2, uint32_t len2)
767+
{
768+
if(targetlen < (len1 + len2 + 1))
636769
{
637-
result.pop_back();
770+
strlcpy(target, "/PATH_TOO_LONG", targetlen);
771+
return false;
638772
}
639773

640-
if (result.length() > max_len)
774+
if(len2 != 0 && path2[0] != '/')
775+
{
776+
memcpy(target, path1, len1);
777+
copy_and_sanitize_path(target + len1, target, path2, '/');
778+
return true;
779+
}
780+
else
641781
{
642-
return "/PATH_TOO_LONG";
782+
target[0] = 0;
783+
copy_and_sanitize_path(target, target, path2, '/');
784+
return false;
643785
}
786+
}
644787

645-
return result;
788+
std::string sinsp_utils::concatenate_paths(std::string_view path1, std::string_view path2)
789+
{
790+
char fullpath[SCAP_MAX_PATH_SIZE];
791+
concatenate_paths_(fullpath, SCAP_MAX_PATH_SIZE, path1.data(), (uint32_t)path1.length(), path2.data(),
792+
path2.size());
793+
return std::string(fullpath);
646794
}
647795

648796

userspace/libsinsp/utils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,11 @@ class sinsp_utils
9595

9696
//
9797
// Concatenate posix-style path1 and path2 up to max_len in size, normalizing the result.
98+
// path1 MUST be '/' terminated and is not sanitized.
9899
// If path2 is absolute, the result will be equivalent to path2.
99100
// If the result would be too long, the output will contain the string "/PATH_TOO_LONG" instead.
100101
//
101-
static std::string concatenate_paths(std::string_view path1, std::string_view path2, size_t max_len=SCAP_MAX_PATH_SIZE-1);
102+
static std::string concatenate_paths(std::string_view path1, std::string_view path2);
102103

103104
//
104105
// Determines if an IPv6 address is IPv4-mapped

0 commit comments

Comments
 (0)