Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions src/time_zone_info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -418,18 +418,25 @@ inline FilePtr FOpen(const char* path) {
#endif
}

// Returns true if c separates path components. Windows accepts either
// form, so a "..\" component walks up a directory just like a "../" one.
inline bool IsPathSeparator(char c) {
#if defined(_WIN32)
return c == '/' || c == '\\';
#else
return c == '/';
#endif
}

// Returns true if the zone name starting at pos contains an unsafe path.
inline bool UnsafePath(const std::string& name, std::size_t pos) {
// Path traversal: exact match ".."
if (name.compare(pos, std::string::npos, "..") == 0) return true;
// Path traversal: leading component "../"
if (name.compare(pos, 3, "../") == 0) return true;
// Path traversal: interior component "/../"
if (name.find("/../", pos) != std::string::npos) return true;
// Path traversal: trailing component "/.."
if (name.size() - pos >= 3 &&
name.compare(name.size() - 3, 3, "/..") == 0) {
return true;
bool UnsafePath(const std::string& name, std::size_t pos) {
// Path traversal: a ".." component that is at the beginning or preceded
// by a separator, and at the end or followed by a separator.
for (auto i = pos; (i = name.find("..", i)) != std::string::npos; i += 2) {
if ((i == pos || IsPathSeparator(name[i - 1])) &&
(i == name.size() - 2 || IsPathSeparator(name[i + 2]))) {
return true;
}
}
return false;
}
Expand Down
14 changes: 13 additions & 1 deletion src/time_zone_lookup_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,22 @@ TEST(TimeZone, Failures) {
EXPECT_FALSE(load_time_zone("file:/../etc/passwd", &tz));
EXPECT_FALSE(load_time_zone("file:America/../America/Los_Angeles", &tz));

// Reject non-regular files and directories.
#if defined(_WIN32)
// Windows accepts '\' as a path separator, so these escape as well.
// If they were admitted, the second would resolve back into the zoneinfo
// directory and load, failing the test. Elsewhere '\' is an ordinary
// filename character, so these would only fail as nonexistent names.
EXPECT_FALSE(load_time_zone("file:..\\etc\\passwd", &tz));
EXPECT_FALSE(load_time_zone("file:America\\..\\America/Los_Angeles", &tz));
#endif

#if !defined(_MSC_VER)
// Reject non-regular files and directories. The check lives in the
// non-MSVC FOpen(), so only expect it there.
EXPECT_FALSE(load_time_zone("file:/dev/null", &tz));
EXPECT_FALSE(load_time_zone("file:/dev/stdin", &tz));
EXPECT_FALSE(load_time_zone("file:/tmp", &tz));
#endif
}

TEST(TimeZone, Equality) {
Expand Down