commit - 57176d45cacb98f1968daa8f8b2efd2735da2731
commit + b9f813fb4b7de1042370b529b9ccc036b208465b
blob - 313a4aa4f065a48741d91ffc8571f831898a4ff6
blob + c578e5c402a0b0258a157edccf97b8984e5ba8df
--- src/daemon.rs
+++ src/daemon.rs
//! 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;
.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
};
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(
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;
+ let mut line = String::new();
- for line in reader.lines() {
- let line = line?;
+ 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) => {
blob - 302bd5893e29d915799aaae103fbcb444edfa327
blob + 45fb919d90f70615db9b181e4c17eebe0fcc696d
--- src/ops.rs
+++ src/ops.rs
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)?;
blob - 50b32b17e3b00963bb84e866831305096f51d354
blob + 8bfe9792c1bc71763b585a56873a8a23890fb059
--- src/paste.rs
+++ src/paste.rs
.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)
}
}
blob - 18a7641ac411d880b5f3701315fc6a049084a4bb
blob + 98c54817e574fabaf0b4650ad6da24831a411efc
--- src/store.rs
+++ src/store.rs
// ── 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
}
}
+/// 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 {
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(())
}