Skip to content

Commit 4273d46

Browse files
authored
Merge pull request ClickHouse#30452 from ClickHouse/backport/21.8/30309
Backport ClickHouse#30309 to 21.8: Fix symlinks in file table function
2 parents f0650b5 + 262cc6e commit 4273d46

File tree

7 files changed

+53
-17
lines changed

7 files changed

+53
-17
lines changed

src/Common/filesystemHelpers.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ bool pathStartsWith(const std::filesystem::path & path, const std::filesystem::p
118118
return absolute_path.starts_with(absolute_prefix_path);
119119
}
120120

121-
bool symlinkStartsWith(const std::filesystem::path & path, const std::filesystem::path & prefix_path)
121+
bool fileOrSymlinkPathStartsWith(const std::filesystem::path & path, const std::filesystem::path & prefix_path)
122122
{
123123
/// Differs from pathStartsWith in how `path` is normalized before comparison.
124124
/// Make `path` absolute if it was relative and put it into normalized form: remove
@@ -139,13 +139,14 @@ bool pathStartsWith(const String & path, const String & prefix_path)
139139
return pathStartsWith(filesystem_path, filesystem_prefix_path);
140140
}
141141

142-
bool symlinkStartsWith(const String & path, const String & prefix_path)
142+
bool fileOrSymlinkPathStartsWith(const String & path, const String & prefix_path)
143143
{
144144
auto filesystem_path = std::filesystem::path(path);
145145
auto filesystem_prefix_path = std::filesystem::path(prefix_path);
146146

147-
return symlinkStartsWith(filesystem_path, filesystem_prefix_path);
147+
return fileOrSymlinkPathStartsWith(filesystem_path, filesystem_prefix_path);
148148
}
149+
149150
}
150151

151152

src/Common/filesystemHelpers.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ bool pathStartsWith(const std::filesystem::path & path, const std::filesystem::p
3535
/// Returns true if path starts with prefix path
3636
bool pathStartsWith(const String & path, const String & prefix_path);
3737

38-
bool symlinkStartsWith(const String & path, const String & prefix_path);
38+
/// Same as pathStartsWith, but without canonization, i.e. allowed to check symlinks.
39+
/// (Path is made absolute and normalized.)
40+
bool fileOrSymlinkPathStartsWith(const String & path, const String & prefix_path);
3941

4042
}
4143

src/Dictionaries/FileDictionarySource.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ FileDictionarySource::FileDictionarySource(
3030
, sample_block{sample_block_}
3131
, context(context_)
3232
{
33-
if (created_from_ddl && !pathStartsWith(filepath, context->getUserFilesPath()))
34-
throw Exception(ErrorCodes::PATH_ACCESS_DENIED, "File path {} is not inside {}", filepath, context->getUserFilesPath());
33+
auto user_files_path = context->getUserFilesPath();
34+
if (created_from_ddl && !fileOrSymlinkPathStartsWith(filepath, user_files_path))
35+
throw Exception(ErrorCodes::PATH_ACCESS_DENIED, "File path {} is not inside {}", filepath, user_files_path);
3536
}
3637

3738

src/Dictionaries/LibraryDictionarySource.cpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,9 @@ LibraryDictionarySource::LibraryDictionarySource(
4141
, sample_block{sample_block_}
4242
, context(Context::createCopy(context_))
4343
{
44-
bool path_checked = false;
45-
if (fs::is_symlink(path))
46-
path_checked = symlinkStartsWith(path, context->getDictionariesLibPath());
47-
else
48-
path_checked = pathStartsWith(path, context->getDictionariesLibPath());
49-
50-
if (created_from_ddl && !path_checked)
51-
throw Exception(ErrorCodes::PATH_ACCESS_DENIED, "File path {} is not inside {}", path, context->getDictionariesLibPath());
44+
auto dictionaries_lib_path = context->getDictionariesLibPath();
45+
if (created_from_ddl && !fileOrSymlinkPathStartsWith(path, dictionaries_lib_path))
46+
throw Exception(ErrorCodes::PATH_ACCESS_DENIED, "File path {} is not inside {}", path, dictionaries_lib_path);
5247

5348
if (!fs::exists(path))
5449
throw Exception(ErrorCodes::FILE_DOESNT_EXIST, "LibraryDictionarySource: Can't load library {}: file doesn't exist", path);

src/Storages/StorageFile.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <Common/escapeForFileName.h>
2323
#include <Common/typeid_cast.h>
2424
#include <Common/parseGlobs.h>
25+
#include <Common/filesystemHelpers.h>
2526
#include <Storages/ColumnsDescription.h>
2627
#include <Storages/StorageInMemoryMetadata.h>
2728

@@ -120,8 +121,8 @@ void checkCreationIsAllowed(ContextPtr context_global, const std::string & db_di
120121
return;
121122

122123
/// "/dev/null" is allowed for perf testing
123-
if (!startsWith(table_path, db_dir_path) && table_path != "/dev/null")
124-
throw Exception("File is not inside " + db_dir_path, ErrorCodes::DATABASE_ACCESS_DENIED);
124+
if (!fileOrSymlinkPathStartsWith(table_path, db_dir_path) && table_path != "/dev/null")
125+
throw Exception(ErrorCodes::DATABASE_ACCESS_DENIED, "File `{}` is not inside `{}`", table_path, db_dir_path);
125126

126127
if (fs::exists(table_path) && fs::is_directory(table_path))
127128
throw Exception("File must not be a directory", ErrorCodes::INCORRECT_FILE_NAME);
@@ -136,7 +137,10 @@ Strings StorageFile::getPathsList(const String & table_path, const String & user
136137
fs_table_path = user_files_absolute_path / fs_table_path;
137138

138139
Strings paths;
139-
const String path = fs::weakly_canonical(fs_table_path);
140+
/// Do not use fs::canonical or fs::weakly_canonical.
141+
/// Otherwise it will not allow to work with symlinks in `user_files_path` directory.
142+
String path = fs::absolute(fs_table_path);
143+
path = fs::path(path).lexically_normal(); /// Normalize path.
140144
if (path.find_first_of("*?{") == std::string::npos)
141145
{
142146
std::error_code error;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OK
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env bash
2+
# Tags: no-fasttest, no-parallel
3+
4+
CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
5+
# shellcheck source=../shell_config.sh
6+
. "$CUR_DIR"/../shell_config.sh
7+
8+
# See 01658_read_file_to_string_column.sh
9+
user_files_path=$(clickhouse-client --query "select _path,_file from file('nonexist.txt', 'CSV', 'val1 char')" 2>&1 | grep Exception | awk '{gsub("/nonexist.txt","",$9); print $9}')
10+
11+
FILE_PATH="${user_files_path}/file/"
12+
mkdir -p ${FILE_PATH}
13+
chmod 777 ${FILE_PATH}
14+
15+
FILE="test_symlink_${CLICKHOUSE_DATABASE}"
16+
17+
symlink_path=${FILE_PATH}/${FILE}
18+
file_path=$CUR_DIR/${FILE}
19+
20+
touch ${file_path}
21+
ln -s ${file_path} ${symlink_path}
22+
chmod ugo+w ${symlink_path}
23+
24+
function cleanup()
25+
{
26+
rm ${symlink_path} ${file_path}
27+
}
28+
trap cleanup EXIT
29+
30+
${CLICKHOUSE_CLIENT} --query="insert into table function file('${symlink_path}', 'Values', 'a String') select 'OK'";
31+
${CLICKHOUSE_CLIENT} --query="select * from file('${symlink_path}', 'Values', 'a String')";
32+

0 commit comments

Comments
 (0)