aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/daemon.rs34
-rw-r--r--src/ops.rs12
-rw-r--r--src/paste.rs2
-rw-r--r--src/store.rs26
4 files changed, 59 insertions, 15 deletions
diff --git a/src/daemon.rs b/src/daemon.rs
index 313a4aa..c578e5c 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -6,7 +6,7 @@
//! over a Unix socket using a line-oriented text protocol
//! (see [`crate::protocol`]).
-use std::io::{BufRead, BufReader, Write};
+use std::io::{BufRead, BufReader, Read, Write};
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
@@ -196,7 +196,7 @@ fn republish(node: &mut Node, store: &PasteStore) {
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
- let expires = paste.created_at + paste.ttl_secs;
+ let expires = paste.created_at.saturating_add(paste.ttl_secs);
let rem = expires.saturating_sub(now);
std::cmp::min(rem, u16::MAX as u64) as u16
};
@@ -259,6 +259,10 @@ pub fn run_unix_listener(
let _ = std::fs::remove_file(sock_path);
}
+/// Maximum protocol line size (128 KiB covers the 64 KiB paste
+/// limit after base58 expansion plus command overhead).
+const MAX_LINE_SIZE: usize = 128 * 1024;
+
/// Read requests line-by-line from a connected Unix socket
/// client, forwarding each to the daemon main loop via `tx`.
fn handle_client(
@@ -268,11 +272,29 @@ fn handle_client(
stream.set_nonblocking(false)?;
stream.set_read_timeout(Some(Duration::from_secs(60)))?;
- let reader = BufReader::new(&stream);
+ let mut reader = BufReader::new(&stream);
let mut writer = &stream;
-
- for line in reader.lines() {
- let line = line?;
+ let mut line = String::new();
+
+ loop {
+ line.clear();
+ // Limit read to MAX_LINE_SIZE to prevent a client from
+ // exhausting memory with an unbounded request line.
+ let n = (&mut reader).take(MAX_LINE_SIZE as u64).read_line(&mut line)?;
+ if n == 0 {
+ break;
+ }
+ if !line.ends_with('\n') && n >= MAX_LINE_SIZE {
+ let resp = protocol::format_response(&Response::Err(
+ "request too large".into(),
+ ));
+ writer.write_all(resp.as_bytes())?;
+ // Drain remaining bytes until newline
+ let mut discard = String::new();
+ let _ = reader.read_line(&mut discard);
+ continue;
+ }
+ let line = line.trim();
let cmd = match protocol::parse_request(&line) {
Ok(c) => c,
Err(e) => {
diff --git a/src/ops.rs b/src/ops.rs
index 302bd58..45fb919 100644
--- a/src/ops.rs
+++ b/src/ops.rs
@@ -118,7 +118,17 @@ pub fn get_paste(
if vals.is_empty() {
return Err(PasteError::NotFound);
}
- vals[0].clone()
+ // Verify DHT result: the content hash must match the
+ // requested key to prevent a malicious node from
+ // injecting arbitrary data.
+ match vals.iter().find(|v| {
+ Paste::from_bytes(v)
+ .map(|p| Paste::content_key(&p.content) == *hash)
+ .unwrap_or(false)
+ }) {
+ Some(v) => v.clone(),
+ None => return Err(PasteError::NotFound),
+ }
};
let paste = Paste::from_bytes(&data).ok_or(PasteError::InvalidKey)?;
diff --git a/src/paste.rs b/src/paste.rs
index 50b32b1..8bfe979 100644
--- a/src/paste.rs
+++ b/src/paste.rs
@@ -81,7 +81,7 @@ impl Paste {
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
- now > self.created_at + self.ttl_secs
+ now > self.created_at.saturating_add(self.ttl_secs)
}
}
diff --git a/src/store.rs b/src/store.rs
index 18a7641..98c5481 100644
--- a/src/store.rs
+++ b/src/store.rs
@@ -45,14 +45,12 @@ impl PasteStore {
// ── Paste CRUD ──────────────────────────────────
- /// Write a paste to disk. The key (32 bytes) is prepended
- /// to the file so [`original_keys`] can reconstruct it.
+ /// Write a paste to disk atomically (write-to-temp + rename).
+ /// The key (32 bytes) is prepended to the file so
+ /// [`original_keys`] can reconstruct it.
pub fn put_paste(&self, key: &[u8], value: &[u8]) -> std::io::Result<()> {
let path = self.paste_path(key);
- let mut f = fs::File::create(path)?;
- f.write_all(key)?;
- f.write_all(value)?;
- Ok(())
+ atomic_write(&path, &[key, value])
}
/// Read a paste from disk. Returns `None` if the paste
@@ -179,6 +177,20 @@ impl PasteStore {
}
}
+/// Write data to `path` atomically: write to a temporary file in
+/// the same directory, then rename over the target. This prevents
+/// corruption if the process is killed mid-write.
+fn atomic_write(path: &Path, chunks: &[&[u8]]) -> std::io::Result<()> {
+ let parent = path.parent().unwrap_or(Path::new("."));
+ let tmp = parent.join(format!(".tmp.{}", std::process::id()));
+ let mut f = fs::File::create(&tmp)?;
+ for chunk in chunks {
+ f.write_all(chunk)?;
+ }
+ f.sync_all()?;
+ fs::rename(&tmp, path)
+}
+
// ── tesseras-dht persistence traits ─────────────────
impl tesseras_dht::persist::RoutingPersistence for PasteStore {
@@ -198,7 +210,7 @@ impl tesseras_dht::persist::RoutingPersistence for PasteStore {
buf.extend_from_slice(id);
buf.extend_from_slice(addr_bytes);
}
- fs::write(&path, &buf).map_err(tesseras_dht::Error::Io)?;
+ atomic_write(&path, &[&buf]).map_err(tesseras_dht::Error::Io)?;
log::info!("store: persisted {} routing contacts", contacts.len());
Ok(())
}