Prevent improper files (like /dev/random) from being used as file arguments (#10733)

* Implement check before adding path to files

* fix problem where directories were removed from args.files

* Revert "Implement check before adding path to files"

This reverts commit c123944d9b.

* Dissallow opening of irregular non-symlink files

* Fixed issue with creating new file from command line

* Fixed linting error.

* Optimized regularity check as suggested in review

* Created DocumentOpenError Sum Type to switch on in Application

* Forgot cargo fmt

* Update helix-term/src/application.rs

Accept suggestion in review.

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>

* Moved thiserror version configuration to the workspace instead of the individual packages.

---------

Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
This commit is contained in:
TiredTumblrina 2024-06-18 12:14:17 -04:00 committed by GitHub
parent d70f58da10
commit 94a9c81eb0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 59 additions and 25 deletions

1
Cargo.lock generated
View File

@ -1535,6 +1535,7 @@ dependencies = [
"serde_json",
"slotmap",
"tempfile",
"thiserror",
"tokio",
"tokio-stream",
"toml",

View File

@ -40,6 +40,7 @@ package.helix-term.opt-level = 2
tree-sitter = { version = "0.22" }
nucleo = "0.2.0"
slotmap = "1.0.7"
thiserror = "1.0"
[workspace.package]
version = "24.3.0"

View File

@ -20,8 +20,8 @@ anyhow = "1.0"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
thiserror.workspace = true
[dev-dependencies]
fern = "0.6"

View File

@ -26,9 +26,9 @@ log = "0.4"
lsp-types = { version = "0.95" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
tokio = { version = "1.38", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
tokio-stream = "0.1.15"
parking_lot = "0.12.3"
arc-swap = "1"
slotmap.workspace = true
thiserror.workspace = true

View File

@ -9,7 +9,7 @@
use helix_stdx::path::get_relative_path;
use helix_view::{
align_view,
document::DocumentSavedEventResult,
document::{DocumentOpenError, DocumentSavedEventResult},
editor::{ConfigEvent, EditorEvent},
graphics::Rect,
theme,
@ -186,9 +186,15 @@ pub fn new(args: Args, config: Config, lang_loader: syntax::Loader) -> Result<Se
Some(Layout::Horizontal) => Action::HorizontalSplit,
None => Action::Load,
};
let doc_id = editor
.open(&file, action)
.context(format!("open '{}'", file.to_string_lossy()))?;
let doc_id = match editor.open(&file, action) {
// Ignore irregular files during application init.
Err(DocumentOpenError::IrregularFile) => {
nr_of_files -= 1;
continue;
}
Err(err) => return Err(anyhow::anyhow!(err)),
Ok(doc_id) => doc_id,
};
// with Action::Load all documents have the same view
// NOTE: this isn't necessarily true anymore. If
// `--vsplit` or `--hsplit` are used, the file which is
@ -199,6 +205,11 @@ pub fn new(args: Args, config: Config, lang_loader: syntax::Loader) -> Result<Se
doc.set_selection(view_id, pos);
}
}
// if all files were invalid, replace with empty buffer
if nr_of_files == 0 {
editor.new_file(Action::VerticalSplit);
} else {
editor.set_status(format!(
"Loaded {} file{}.",
nr_of_files,
@ -208,6 +219,7 @@ pub fn new(args: Args, config: Config, lang_loader: syntax::Loader) -> Result<Se
// does not affect views without pos since it is at the top
let (view, doc) = current!(editor);
align_view(doc, view, Align::Center);
}
} else {
editor.new_file(Action::VerticalSplit);
}

View File

@ -69,6 +69,7 @@
use std::{
cmp::Ordering,
collections::{HashMap, HashSet},
error::Error,
fmt,
future::Future,
io::Read,

View File

@ -29,7 +29,7 @@
use helix_view::Editor;
use std::path::PathBuf;
use std::{error::Error, path::PathBuf};
pub fn prompt(
cx: &mut crate::commands::Context,

View File

@ -50,7 +50,7 @@ toml = "0.8"
log = "~0.4"
parking_lot = "0.12.3"
thiserror.workspace = true
[target.'cfg(windows)'.dependencies]
clipboard-win = { version = "5.3", features = ["std"] }

View File

@ -1,4 +1,4 @@
use anyhow::{anyhow, bail, Context, Error};
use anyhow::{anyhow, bail, Error};
use arc_swap::access::DynAccess;
use arc_swap::ArcSwap;
use futures_util::future::BoxFuture;
@ -12,6 +12,7 @@
use helix_lsp::util::lsp_pos_to_pos;
use helix_stdx::faccess::{copy_metadata, readonly};
use helix_vcs::{DiffHandle, DiffProviderRegistry};
use thiserror;
use ::parking_lot::Mutex;
use serde::de::{self, Deserialize, Deserializer};
@ -21,6 +22,7 @@
use std::collections::HashMap;
use std::fmt::Display;
use std::future::Future;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::{Arc, Weak};
@ -117,6 +119,14 @@ pub struct SavePoint {
revert: Mutex<Transaction>,
}
#[derive(Debug, thiserror::Error)]
pub enum DocumentOpenError {
#[error("path must be a regular file, simlink, or directory")]
IrregularFile,
#[error(transparent)]
IoError(#[from] io::Error),
}
pub struct Document {
pub(crate) id: DocumentId,
text: Rope,
@ -380,7 +390,7 @@ fn encode_from_utf8(
pub fn from_reader<R: std::io::Read + ?Sized>(
reader: &mut R,
encoding: Option<&'static Encoding>,
) -> Result<(Rope, &'static Encoding, bool), Error> {
) -> Result<(Rope, &'static Encoding, bool), io::Error> {
// These two buffers are 8192 bytes in size each and are used as
// intermediaries during the decoding process. Text read into `buf`
// from `reader` is decoded into `buf_out` as UTF-8. Once either
@ -523,7 +533,7 @@ fn read_and_detect_encoding<R: std::io::Read + ?Sized>(
reader: &mut R,
encoding: Option<&'static Encoding>,
buf: &mut [u8],
) -> Result<(&'static Encoding, bool, encoding::Decoder, usize), Error> {
) -> Result<(&'static Encoding, bool, encoding::Decoder, usize), io::Error> {
let read = reader.read(buf)?;
let is_empty = read == 0;
let (encoding, has_bom) = encoding
@ -685,11 +695,18 @@ pub fn open(
encoding: Option<&'static Encoding>,
config_loader: Option<Arc<ArcSwap<syntax::Loader>>>,
config: Arc<dyn DynAccess<Config>>,
) -> Result<Self, Error> {
) -> Result<Self, DocumentOpenError> {
// If the path is not a regular file (e.g.: /dev/random) it should not be opened.
if path
.metadata()
.map_or(false, |metadata| !metadata.is_file())
{
return Err(DocumentOpenError::IrregularFile);
}
// Open the file if it exists, otherwise assume it is a new file (and thus empty).
let (rope, encoding, has_bom) = if path.exists() {
let mut file =
std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
let mut file = std::fs::File::open(path)?;
from_reader(&mut file, encoding)?
} else {
let line_ending: LineEnding = config.load().default_line_ending.into();

View File

@ -1,6 +1,8 @@
use crate::{
align_view,
document::{DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint},
document::{
DocumentOpenError, DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint,
},
graphics::{CursorKind, Rect},
handlers::Handlers,
info::Info,
@ -1677,7 +1679,7 @@ pub fn new_file_from_stdin(&mut self, action: Action) -> Result<DocumentId, Erro
}
// ??? possible use for integration tests
pub fn open(&mut self, path: &Path, action: Action) -> Result<DocumentId, Error> {
pub fn open(&mut self, path: &Path, action: Action) -> Result<DocumentId, DocumentOpenError> {
let path = helix_stdx::path::canonicalize(path);
let id = self.document_by_path(&path).map(|doc| doc.id);