RPC: Improve snapshot path sanitization

This commit is contained in:
Ivan Mironov 2021-02-20 03:15:32 +05:00 committed by Michael Vines
parent 5ae37b9675
commit 013daa8f47
3 changed files with 91 additions and 4 deletions

2
Cargo.lock generated
View File

@ -4311,6 +4311,7 @@ dependencies = [
"jsonrpc-http-server",
"jsonrpc-pubsub",
"jsonrpc-ws-server",
"libc",
"log 0.4.11",
"lru",
"matches",
@ -4359,6 +4360,7 @@ dependencies = [
"solana-version",
"solana-vote-program",
"spl-token",
"symlink",
"systemstat",
"tempfile",
"thiserror",

View File

@ -34,6 +34,7 @@ jsonrpc-derive = "17.0.0"
jsonrpc-http-server = "17.0.0"
jsonrpc-pubsub = "17.0.0"
jsonrpc-ws-server = "17.0.0"
libc = "0.2.81"
log = "0.4.11"
lru = "0.6.1"
miow = "0.2.2"
@ -89,6 +90,7 @@ matches = "0.1.6"
num_cpus = "1.13.0"
reqwest = { version = "0.10.8", default-features = false, features = ["blocking", "rustls-tls", "json"] }
serial_test = "0.4.0"
symlink = "0.1.0"
systemstat = "0.1.5"
[build-dependencies]

View File

@ -65,7 +65,7 @@ impl RpcRequestMiddleware {
Self {
ledger_path,
snapshot_archive_path_regex: Regex::new(
r"/snapshot-\d+-[[:alnum:]]+\.(tar|tar\.bz2|tar\.zst|tar\.gz)$",
r"^/snapshot-\d+-[[:alnum:]]+\.(tar|tar\.bz2|tar\.zst|tar\.gz)$",
)
.unwrap(),
snapshot_config,
@ -110,6 +110,26 @@ impl RpcRequestMiddleware {
}
}
#[cfg(unix)]
async fn open_no_follow(path: impl AsRef<Path>) -> std::io::Result<tokio_02::fs::File> {
// Stuck on tokio 0.2 until the jsonrpc crates upgrade
use tokio_02::fs::os::unix::OpenOptionsExt;
tokio_02::fs::OpenOptions::new()
.read(true)
.write(false)
.create(false)
.custom_flags(libc::O_NOFOLLOW)
.open(path)
.await
}
#[cfg(not(unix))]
async fn open_no_follow(path: impl AsRef<Path>) -> std::io::Result<tokio_02::fs::File> {
// TODO: Is there any way to achieve the same on Windows?
// Stuck on tokio 0.2 until the jsonrpc crates upgrade
tokio_02::fs::File::open(path).await
}
fn process_file_get(&self, path: &str) -> RequestMiddlewareAction {
let stem = path.split_at(1).1; // Drop leading '/' from path
let filename = {
@ -137,8 +157,7 @@ impl RpcRequestMiddleware {
RequestMiddlewareAction::Respond {
should_validate_hosts: true,
response: Box::pin(async {
// Stuck on tokio 0.2 until the jsonrpc crates upgrade
match tokio_02::fs::File::open(filename).await {
match Self::open_no_follow(filename).await {
Err(_) => Ok(Self::internal_server_error()),
Ok(file) => {
let stream =
@ -449,6 +468,7 @@ mod tests {
};
use solana_runtime::{bank::Bank, bank_forks::ArchiveFormat, snapshot_utils::SnapshotVersion};
use solana_sdk::{genesis_config::ClusterType, signature::Signer};
use std::io::Write;
use std::net::{IpAddr, Ipv4Addr};
#[test]
@ -566,15 +586,78 @@ mod tests {
assert!(rrm_with_snapshot_config
.is_file_get_path("/snapshot-100-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar"));
assert!(!rrm.is_file_get_path(
assert!(!rrm_with_snapshot_config.is_file_get_path(
"/snapshot-notaslotnumber-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar.bz2"
));
assert!(!rrm_with_snapshot_config.is_file_get_path("../../../test/snapshot-123-xxx.tar"));
assert!(!rrm.is_file_get_path("/"));
assert!(!rrm.is_file_get_path(".."));
assert!(!rrm.is_file_get_path("🎣"));
}
#[test]
fn test_process_file_get() {
let mut runtime = tokio_02::runtime::Runtime::new().unwrap();
let ledger_path = get_tmp_ledger_path!();
std::fs::create_dir(&ledger_path).unwrap();
let genesis_path = ledger_path.join("genesis.tar.bz2");
let rrm = RpcRequestMiddleware::new(
ledger_path.clone(),
None,
create_bank_forks(),
RpcHealth::stub(),
);
// File does not exist => request should fail.
let action = rrm.process_file_get("/genesis.tar.bz2");
if let RequestMiddlewareAction::Respond { response, .. } = action {
let response = runtime.block_on(response);
let response = response.unwrap();
assert_ne!(response.status(), 200);
} else {
panic!("Unexpected RequestMiddlewareAction variant");
}
{
let mut file = std::fs::File::create(&genesis_path).unwrap();
file.write_all(b"should be ok").unwrap();
}
// Normal file exist => request should succeed.
let action = rrm.process_file_get("/genesis.tar.bz2");
if let RequestMiddlewareAction::Respond { response, .. } = action {
let response = runtime.block_on(response);
let response = response.unwrap();
assert_eq!(response.status(), 200);
} else {
panic!("Unexpected RequestMiddlewareAction variant");
}
#[cfg(unix)]
{
std::fs::remove_file(&genesis_path).unwrap();
{
let mut file = std::fs::File::create(ledger_path.join("wrong")).unwrap();
file.write_all(b"wrong file").unwrap();
}
symlink::symlink_file("wrong", &genesis_path).unwrap();
// File is a symbolic link => request should fail.
let action = rrm.process_file_get("/genesis.tar.bz2");
if let RequestMiddlewareAction::Respond { response, .. } = action {
let response = runtime.block_on(response);
let response = response.unwrap();
assert_ne!(response.status(), 200);
} else {
panic!("Unexpected RequestMiddlewareAction variant");
}
}
}
#[test]
fn test_health_check_with_no_trusted_validators() {
let rm = RpcRequestMiddleware::new(