Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/bashkit/docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ through configurable limits.
| Width-derived allocation (TM-DOS-103) | `printf x \| od -w1152921504606846976` | Reject widths above 65,536 bytes; stack-pad one numeric field; budget output capacity before fallible growth | MITIGATED |
| Pagination amplification (TM-DOS-137) | Long `pr -h` headers repeated on many short pages | Check the 16 MiB cap before every append; lease input, rows and output capacity before growth; borrow cells/page slices and retain date timestamps; charge input/work and poll cancellation/deadlines, including skipped pages | MITIGATED |
| Embedded CLI limit escalation (TM-DOS-139) | `markkit extract x --max-input-size 99999999999` | `markkit` limits can only be lowered below their defaults; `fetchkit` bodies capped by HTTP limits; `--ocr` refused | MITIGATED |
| Tree-walk memory (TM-DOS-140) | `find`, `du` or `rg` over a tree with millions of files | `FileSystem::walk` answers are bounded batches (10k entries) refilled as `find`/`du` descend; `rg` charges each file to the aggregate-input budget as it reads and stops at the first refusal | MITIGATED |
| Tree-walk memory (TM-DOS-140) | `find`, `du` or `rg` over a tree with millions of files | `FileSystem::walk` answers are bounded batches (10k entries) refilled as `find`/`du` descend; `rg` charges each file to the aggregate-input budget as it reads, stops at the first refusal and streams its walk; `ls -R` stops at the stdout limit | MITIGATED |
| Variable explosion (TM-DOS-003) | `x=$(cat /dev/urandom)` | /dev/urandom returns bounded 8KB | MITIGATED |
| Array growth (TM-DOS-004) | `arr+=(element)` in loop | Command limit | MITIGATED |

Expand Down
36 changes: 36 additions & 0 deletions crates/bashkit/src/builtins/ls/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ pub(super) struct LsOptions {
pub(super) columns: bool,
pub(super) directory: bool,
pub(super) quoting: QuotingStyle,
/// Stop generating once output passes this (`max_stdout_bytes` + 1):
/// the rest would be cut anyway, so `ls -R` over millions of files holds
/// at most this much (TM-DOS-140).
pub(super) output_limit: usize,
}

/// The ls builtin - list directory contents.
Expand Down Expand Up @@ -175,6 +179,11 @@ impl Builtin for Ls {
columns: matches.get_flag("C"),
directory: matches.get_flag("directory"),
quoting,
output_limit: ctx
.execution_extension::<crate::limits::ExecutionLimits>()
.and_then(|limits| limits.try_with(|limits| limits.max_stdout_bytes).ok())
.unwrap_or_else(|| crate::limits::ExecutionLimits::default().max_stdout_bytes)
.saturating_add(1),
};

// PATHS holds OsString values; convert to owned strings for the
Expand Down Expand Up @@ -245,6 +254,9 @@ impl Builtin for Ls {
// Output file arguments first (preserving path as given by user)
if opts.long {
for (path_str, path, metadata) in &file_args {
if output.len() > opts.output_limit {
break;
}
let mut entry =
format_long_entry(&quote_name(path_str, opts.quoting), metadata, opts.human);
push_link_target(&ctx, path, metadata, &mut entry).await;
Expand Down Expand Up @@ -272,6 +284,9 @@ impl Builtin for Ls {
output.push_str(&format_columns(&names, 80));
} else {
for name in &names {
if output.len() > opts.output_limit {
break;
}
output.push_str(name);
output.push('\n');
}
Expand All @@ -297,6 +312,13 @@ impl Builtin for Ls {
}
}

if output.len() > opts.output_limit {
let mut end = opts.output_limit;
while !output.is_char_boundary(end) {
end -= 1;
}
output.truncate(end);
}
Ok(ExecResult::ok(output))
}
}
Expand All @@ -312,6 +334,9 @@ async fn list_directory(
show_header: bool,
add_newline: bool,
) -> std::result::Result<(), String> {
if output.len() > opts.output_limit {
return Ok(());
}
if add_newline {
output.push('\n');
}
Expand Down Expand Up @@ -352,6 +377,11 @@ async fn list_directory(

if opts.long {
for entry in &filtered {
// Past the stdout limit nothing more is shown; stop before
// formatting (and link lookups) for the rest of a wide dir.
if output.len() > opts.output_limit {
break;
}
let mut line = format_long_entry(
&quote_name(&entry.name, opts.quoting),
&entry.metadata,
Expand Down Expand Up @@ -400,6 +430,9 @@ async fn list_directory(
output.push_str(&format_columns(&names, 80));
} else {
for name in &names {
if output.len() > opts.output_limit {
Comment thread
chaliy marked this conversation as resolved.
Resolved
break;
}
output.push_str(name);
output.push('\n');
}
Expand All @@ -409,6 +442,9 @@ async fn list_directory(
// Recursive listing
if opts.recursive {
for (subpath, display) in subdirs {
if output.len() > opts.output_limit {
Comment thread
chaliy marked this conversation as resolved.
Resolved
break;
}
// Box the future to avoid infinite recursion type size
Box::pin(list_directory(
ctx, listings, &subpath, &display, output, opts, true, true,
Expand Down
Loading
Loading