From b9b35a0def16110a1367a6c56f4524945d609cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 22 Jul 2021 21:54:31 +0200 Subject: [PATCH] Tests for allowed paths (current state) --- test/CMakeLists.txt | 1 + test/solc/CommandLineInterfaceAllowPaths.cpp | 536 +++++++++++++++++++ test/solc/Common.cpp | 19 +- test/solc/Common.h | 2 + 4 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 test/solc/CommandLineInterfaceAllowPaths.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f35660741..3135b53d5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -160,6 +160,7 @@ set(solcli_sources solc/Common.cpp solc/Common.h solc/CommandLineInterface.cpp + solc/CommandLineInterfaceAllowPaths.cpp solc/CommandLineParser.cpp ) detect_stray_source_files("${solcli_sources}" "solc/") diff --git a/test/solc/CommandLineInterfaceAllowPaths.cpp b/test/solc/CommandLineInterfaceAllowPaths.cpp new file mode 100644 index 000000000..ba71188ef --- /dev/null +++ b/test/solc/CommandLineInterfaceAllowPaths.cpp @@ -0,0 +1,536 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +/// Unit tests for solc/CommandLineInterface.h + +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +using namespace std; +using namespace solidity::frontend; +using namespace solidity::test; + +#define TEST_CASE_NAME (boost::unit_test::framework::current_test_case().p_name) + +namespace +{ + +struct ImportCheck +{ + enum class Result + { + Unknown, ///< Status is unknown due to a failure of the status check. + OK, ///< Passed compilation without errors. + FileNotFound, ///< Error was reported: file not found. + PathDisallowed, ///< Error was reported: file not allowed paths. + }; + + bool operator==(ImportCheck const& _other) const { return result == _other.result && message == _other.message; } + bool operator!=(ImportCheck const& _other) const { return !(*this == _other); } + + operator bool() const { return this->result == Result::OK; } + + static ImportCheck const OK() { return {Result::OK, ""}; } + static ImportCheck const FileNotFound() { return {Result::FileNotFound, ""}; } + static ImportCheck const PathDisallowed() { return {Result::PathDisallowed, ""}; } + static ImportCheck const Unknown(const string& _message) { return {Result::Unknown, _message}; } + + Result result; + std::string message; +}; + +ImportCheck checkImport( + string const& _import, + vector const& _cliOptions +) +{ + soltestAssert(regex_match(_import, regex{R"(import '[^']*')"}), ""); + for (string const& option: _cliOptions) + soltestAssert( + boost::starts_with(option, "--base-path") || + boost::starts_with(option, "--allow-paths") || + !boost::starts_with(option, "--"), + "" + ); + + vector commandLine = { + "solc", + "-", + "--no-color", + "--error-codes", + }; + commandLine += _cliOptions; + + string standardInputContent = + "// SPDX-License-Identifier: GPL-3.0\n" + "pragma solidity >=0.0;\n" + + _import + ";"; + + test::OptionsReaderAndMessages cliResult = test::parseCommandLineAndReadInputFiles( + commandLine, + standardInputContent, + true /* processInput */ + ); + if (cliResult.success) + return ImportCheck::OK(); + + static regex const sourceNotFoundErrorRegex{ + R"(^Error \(6275\): Source ".+" not found: (.*)\.\n)" + R"(\s*--> .*:\d+:\d+:\n)" + R"(\s*\|\n)" + R"(\d+\s*\| import '.+';\n)" + R"(\s*\| \^+\n\s*$)" + }; + + smatch submatches; + if (!regex_match(cliResult.stderrContent, submatches, sourceNotFoundErrorRegex)) + return ImportCheck::Unknown("Unexpected stderr content: '" + cliResult.stderrContent + "'"); + if (submatches[1] != "File not found" && submatches[1] != "File outside of allowed directories") + return ImportCheck::Unknown("Unexpected error message: '" + cliResult.stderrContent + "'"); + + if (submatches[1] == "File not found") + return ImportCheck::FileNotFound(); + else if (submatches[1] == "File outside of allowed directories") + return ImportCheck::PathDisallowed(); + else + return ImportCheck::Unknown("Unexpected error message '" + submatches[1].str() + "'"); +} + +class AllowPathsFixture +{ +protected: + AllowPathsFixture(): + m_tempDir({"code/", "work/"}, TEST_CASE_NAME), + m_tempWorkDir(m_tempDir.path() / "work"), + m_codeDir(m_tempDir.path() / "code"), + m_workDir(m_tempDir.path() / "work"), + m_portablePrefix(("/" / boost::filesystem::canonical(m_codeDir).relative_path()).generic_string()) + { + createFilesWithParentDirs( + { + m_codeDir / "a/b/c/d.sol", + m_codeDir / "a/b/c.sol", + m_codeDir / "a/b/X.sol", + m_codeDir / "a/X/c.sol", + m_codeDir / "X/b/c.sol", + + m_codeDir / "a/bc/d.sol", + m_codeDir / "X/bc/d.sol", + + m_codeDir / "x/y/z.sol", + m_codeDir / "contract.sol", + + m_workDir / "a/b/c/d.sol", + m_workDir / "a/b/c.sol", + m_workDir / "a/b/X.sol", + m_workDir / "a/X/c.sol", + m_workDir / "X/b/c.sol", + + m_workDir / "contract.sol", + }, + "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.0;" + ); + + if ( + !createSymlinkIfSupportedByFilesystem("b", m_codeDir / "a/b_sym", true) || + !createSymlinkIfSupportedByFilesystem("../x/y", m_codeDir / "a/y_sym", true) || + !createSymlinkIfSupportedByFilesystem("../../a/b/c.sol", m_codeDir / "a/b/c_sym.sol", false) || + !createSymlinkIfSupportedByFilesystem("../../x/y/z.sol", m_codeDir / "a/b/z_sym.sol", false) + ) + return; + + m_caseSensitiveFilesystem = boost::filesystem::create_directories(m_codeDir / "A/B/C"); + soltestAssert(boost::filesystem::equivalent(m_codeDir / "a/b/c", m_codeDir / "A/B/C") != m_caseSensitiveFilesystem, ""); + } + + TemporaryDirectory m_tempDir; + TemporaryWorkingDirectory m_tempWorkDir; + boost::filesystem::path const m_codeDir; + boost::filesystem::path const m_workDir; + string m_portablePrefix; + bool m_caseSensitiveFilesystem = true; +}; + +ostream& operator<<(ostream& _out, ImportCheck const& _value) +{ + switch (_value.result) + { + case ImportCheck::Result::Unknown: _out << "Unknown"; break; + case ImportCheck::Result::OK: _out << "OK"; break; + case ImportCheck::Result::FileNotFound: _out << "FileNotFound"; break; + case ImportCheck::Result::PathDisallowed: _out << "PathDisallowed"; break; + } + if (_value.message != "") + _out << "(" << _value.message << ")"; + return _out; +} + +} // namespace + +namespace boost::test_tools::tt_detail +{ + +// Boost won't find the << operator unless we put it in the std namespace which is illegal. +// The recommended solution is to overload print_log_value<> struct and make it use our operator. + +template<> +struct print_log_value +{ + void operator()(std::ostream& _out, ImportCheck const& _value) { ::operator<<(_out, _value); } +}; + +} // namespace boost::test_tools::tt_detail + +namespace solidity::frontend::test +{ + +BOOST_AUTO_TEST_SUITE(CommandLineInterfaceAllowPathsTest) + +BOOST_FIXTURE_TEST_CASE(allow_path_multiple_paths, AllowPathsFixture) +{ + string allowedPaths = + m_codeDir.generic_string() + "/a/b/X.sol," + + m_codeDir.generic_string() + "/X/," + + m_codeDir.generic_string() + "/z," + + m_codeDir.generic_string() + "/a/b"; + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", allowedPaths})); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", allowedPaths})); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"--allow-paths", allowedPaths}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"--allow-paths", allowedPaths})); +} + +BOOST_FIXTURE_TEST_CASE(allow_path_should_work_with_various_path_forms, AllowPathsFixture) +{ + string import = "import '" + m_portablePrefix + "/a/b/c.sol'"; + + // Without --allow-path + BOOST_TEST(checkImport(import, {}) == ImportCheck::PathDisallowed()); + + // Absolute paths allowed + BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string()})); + BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string() + "/a"})); + BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string() + "/a/"})); + BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string() + "/a/b"})); + BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string() + "/a/b/c.sol"})); + + // Relative paths allowed + BOOST_TEST(checkImport(import, {"--allow-paths=../code/a"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths=../code/a/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths=../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + + // Non-normalized paths allowed + BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/."}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/./"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/.."}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/../"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/./b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/../a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a///b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/b//"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths", "./../code/a/b///"}) == ImportCheck::PathDisallowed()); + + // Root path allowed + BOOST_TEST(checkImport(import, {"--allow-paths=/"})); + BOOST_TEST(checkImport(import, {"--allow-paths=///"})); + + // UNC paths should be treated differently from normal paths + soltestAssert(FileReader::isUNCPath("/" + m_portablePrefix), ""); + BOOST_TEST(checkImport(import, {"--allow-paths=//"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths=/" + m_portablePrefix}) == ImportCheck::PathDisallowed()); + + // Paths going beyond root allowed + BOOST_TEST(checkImport(import, {"--allow-paths=/../../"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths=/../.."}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths=/../../a/../"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport(import, {"--allow-paths=/../../" + m_portablePrefix}) == ImportCheck::PathDisallowed()); + + // File named like a directory + BOOST_TEST(checkImport(import, {"--allow-paths", m_codeDir.string() + "/a/b/c.sol/"})); +} + +BOOST_FIXTURE_TEST_CASE(allow_path_should_handle_empty_paths, AllowPathsFixture) +{ + // Work dir is base path + BOOST_TEST(checkImport("import 'a/../../work/a/b/c.sol'", {"--allow-paths", ""})); + BOOST_TEST(checkImport("import 'a/../../work/a/b/c.sol'", {"--allow-paths", "x,,y"})); + BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", ""})); + BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", "x,,y"})); + + // Work dir is not base path + BOOST_TEST(checkImport("import 'a/../../work/a/b/c.sol'", {"--allow-paths", "", "--base-path=../code/"})); + BOOST_TEST(checkImport("import 'a/../../work/a/b/c.sol'", {"--allow-paths", "x,,y", "--base-path=../code/"})); + BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", "", "--base-path=../code/"})); + BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", "x,,y", "--base-path=../code/"})); +} + +BOOST_FIXTURE_TEST_CASE(allow_path_case_sensitive, AllowPathsFixture) +{ + // Allowed paths are case-sensitive even on case-insensitive filesystems + BOOST_TEST( + checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", m_codeDir.string() + "/A/B/"}) == + ImportCheck::PathDisallowed() + ); +} + +BOOST_FIXTURE_TEST_CASE(allow_path_should_work_with_various_import_forms, AllowPathsFixture) +{ + // Absolute import paths + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", "../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"--allow-paths", "../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"--allow-paths", "../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", "../code/a/b/"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths", "../code/a"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"--allow-paths", "../code/a"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"--allow-paths", "../code/a"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"--allow-paths", "../code/a"}) == ImportCheck::PathDisallowed()); + + // Relative import paths + // NOTE: Base path is whitelisted by default so we need the 'a/../../code/' part to get + // outside of it. And it can't be just '../code/' because that would not be a direct import. + BOOST_TEST(checkImport("import 'a/../../code/a/b/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/../../code/X/b/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/../../code/a/X/c.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/../../code/a/b/X.sol'", {"--allow-paths", "../code/a/b"}) == ImportCheck::PathDisallowed()); + + // Non-normalized relative import paths + BOOST_TEST(checkImport("import 'a/../../code/a/./b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/../../code/a/../a/b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/../../code/a///b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + + // UNC paths in imports + string uncImportPath = "/" + m_portablePrefix + "/a/b/c.sol"; + soltestAssert(FileReader::isUNCPath(uncImportPath), ""); + BOOST_TEST(checkImport("import '" + uncImportPath + "'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); +} + +BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_input_files, AllowPathsFixture) +{ + // By default none of the files is whitelisted + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {}) == ImportCheck::PathDisallowed()); + + // Compiling a file whitelists its directory and subdirectories + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_codeDir.string() + "/a/b/c.sol"})); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {m_codeDir.string() + "/a/b/c.sol"})); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {m_codeDir.string() + "/a/b/c.sol"})); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {m_codeDir.string() + "/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {m_codeDir.string() + "/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {m_codeDir.string() + "/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + + // If only file name is specified, its parent dir path is empty. This should be equivalent to + // whitelisting the work dir. + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/contract.sol'", {"contract.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'contract.sol'", {"contract.sol"})); +} + +BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_remappings, AllowPathsFixture) +{ + // Adding a remapping whitelists target's parent directory and subdirectories + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {"x=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=/contract.sol"})); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=/contract.sol/"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "/a/b=../code/X/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "/a/b/=../code/X/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {m_portablePrefix + "/a/b=../code/X/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {m_portablePrefix + "/a/b/=../code/X/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "/a/b:y/z=x/w"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "/a/b:y/z=x/w"}) == ImportCheck::PathDisallowed()); + + // Adding a remapping whitelists the target and subdirectories when the target is a directory + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {"x=../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {"x=../code/a/c/"}) == ImportCheck::PathDisallowed()); + + // Adding a remapping whitelists target's parent directory and subdirectories when the target + // is a directory but does not have a trailing slash + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c/d.sol'", {"x=../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/X.sol'", {"x=../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {"x=../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/bc/d.sol'", {"x=../code/a/c"}) == ImportCheck::PathDisallowed()); + + // Adding a remapping to a relative target at VFS root whitelists the work dir + BOOST_TEST(checkImport("import '/../../x/y/z.sol'", {"x=contract.sol", "--base-path=../code/a/b/"})); + BOOST_TEST(checkImport("import '/../../../work/a/b/c.sol'", {"x=contract.sol", "--base-path=../code/a/b/"})); + + BOOST_TEST(checkImport("import '/../../x/y/z.sol'", {"x=contract.sol/", "--base-path=../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '/../../../work/a/b/c.sol'", {"x=contract.sol/", "--base-path=../code/a/b/"}) == ImportCheck::PathDisallowed()); + + // Adding a remapping with an empty target does not whitelist anything + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "="}) == ImportCheck::FileNotFound()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"../code/="})); + BOOST_TEST(checkImport("import '/../work/a/b/c.sol'", {"../code/=", "--base-path", m_portablePrefix})); + + // Adding a remapping that includes .. or . segments whitelists the parent dir and subdirectories + // of the resolved target + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=."})); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=."})); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=./"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=./"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=.."})); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=.."})); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/./.."}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/./.."}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/./.."}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/./../"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/./../"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/./../"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/./../b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/./../b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/./../b"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"x=../code/a/b/./../b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/X/c.sol'", {"x=../code/a/b/./../b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/X/b/c.sol'", {"x=../code/a/b/./../b/"}) == ImportCheck::PathDisallowed()); + + // If the target is just a file name, its parent dir path is empty. This should be equivalent to + // whitelisting the work dir. + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/contract.sol'", {"x=contract.sol"})); + BOOST_TEST(checkImport("import 'contract.sol'", {"x=contract.sol"})); +} + +BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_base_path, AllowPathsFixture) +{ + // Relative base path whitelists its content + BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path=../code/a"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path=../code/a"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path=../code/a"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path=../code/a"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path=../code/a/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path=../code/a/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path=../code/a/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path=../code/a/"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path=../code/."}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path=../code/./"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'code/a/b/c.sol'", {"--base-path=.."}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'code/a/b/c.sol'", {"--base-path=../"}) == ImportCheck::PathDisallowed()); + + // Absolute base path whitelists its content + BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path", m_codeDir.string() + "/a"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path", m_codeDir.string() + "/a"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path", m_codeDir.string() + "/a"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path", m_codeDir.string() + "/a"}) == ImportCheck::PathDisallowed()); +} + +BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_work_dir, AllowPathsFixture) +{ + // Work dir is only automatically whitelisted if it matches base path + BOOST_TEST(checkImport("import 'b/../../../work/a/b/c.sol'", {"--base-path=../code/a/"}) == ImportCheck::PathDisallowed()); + + // Compiling a file in the work dir whitelists it even if it's not in base path + BOOST_TEST(checkImport("import 'b/../../../work/a/b/c.sol'", {"--base-path", "../code/a/", "a/b/c.sol"})); + + // Work dir can also be whitelisted manually + BOOST_TEST(checkImport("import 'b/../../../work/a/b/c.sol'", {"--base-path", "../code/a/", "--allow-paths=."})); + + // Not setting base path whitelists the working directory + BOOST_TEST(checkImport("import 'a/b/c.sol'", {}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/b/c/d.sol'", {}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/b/X.sol'", {}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/X/c.sol'", {}) == ImportCheck::PathDisallowed()); + + // Setting base path to an empty value whitelists the working directory + BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path", ""}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/b/c/d.sol'", {"--base-path", ""}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/b/X.sol'", {"--base-path", ""}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import 'a/X/c.sol'", {"--base-path", ""}) == ImportCheck::PathDisallowed()); +} + +BOOST_FIXTURE_TEST_CASE(allow_path_symlinks_within_whitelisted_dir, AllowPathsFixture) +{ + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths=../code/a/b_sym/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b_sym/"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths=../code/a/b_sym"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b_sym"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c_sym.sol'", {"--allow-paths=../code/a/b/c.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"--allow-paths=../code/a/b/c_sym.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c_sym.sol'", {"--allow-paths=../code/a/b/c_sym.sol"}) == ImportCheck::PathDisallowed()); +} + +BOOST_FIXTURE_TEST_CASE(allow_path_symlinks_outside_whitelisted_dir, AllowPathsFixture) +{ + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/y_sym/z.sol'", {"--allow-paths=../code/a/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/y_sym/z.sol'", {"--allow-paths=../code/x/"}) == ImportCheck::PathDisallowed()); + + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/z_sym.sol'", {"--allow-paths=../code/a/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/z_sym.sol'", {"--allow-paths=../code/x/"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/z_sym.sol'", {"--allow-paths=../code/a/b/z_sym.sol"}) == ImportCheck::PathDisallowed()); + BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/z_sym.sol'", {"--allow-paths=../code/x/y/z.sol"}) == ImportCheck::PathDisallowed()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace solidity::frontend::test diff --git a/test/solc/Common.cpp b/test/solc/Common.cpp index 1bd209067..28263e21e 100644 --- a/test/solc/Common.cpp +++ b/test/solc/Common.cpp @@ -54,5 +54,22 @@ test::OptionsReaderAndMessages test::parseCommandLineAndReadInputFiles( if (success && _processInput) success = cli.processInput(); - return {success, cli.options(), cli.fileReader(), cli.standardJsonInput(), sout.str(), serr.str()}; + return { + success, + cli.options(), + cli.fileReader(), + cli.standardJsonInput(), + sout.str(), + stripPreReleaseWarning(serr.str()), + }; +} + +string test::stripPreReleaseWarning(string const& _stderrContent) +{ + static regex const preReleaseWarningRegex{ + R"(Warning( \(3805\))?: This is a pre-release compiler version, please do not use it in production\.\n)" + R"((\n)?)" + }; + + return regex_replace(_stderrContent, preReleaseWarningRegex, ""); } diff --git a/test/solc/Common.h b/test/solc/Common.h index 6d5e3875d..958d63054 100644 --- a/test/solc/Common.h +++ b/test/solc/Common.h @@ -50,4 +50,6 @@ OptionsReaderAndMessages parseCommandLineAndReadInputFiles( bool _processInput = false ); +std::string stripPreReleaseWarning(std::string const& _stderrContent); + } // namespace solidity::frontend::test