diff --git a/plugins/dotenv/dotenv.plugin.zsh b/plugins/dotenv/dotenv.plugin.zsh index e3ad00661..d12f5b609 100644 --- a/plugins/dotenv/dotenv.plugin.zsh +++ b/plugins/dotenv/dotenv.plugin.zsh @@ -202,7 +202,7 @@ parse_dotenv() { # Fail if file is too large to avoid DoS zmodload -F zsh/stat b:zstat local -i file_size max_size=10485760 # 10MiB - if ! file_size=$(zstat -L +size "$filename" 2>/dev/null); then + if ! file_size=$(zstat +size "$filename" 2>/dev/null); then echo "dotenv: unable to determine size of file '$filename'" >&2 return 1 fi @@ -216,6 +216,39 @@ parse_dotenv() { _parse_dotenv_content "$content" "$mode" } +_dotenv_read_limited() { + local filename="$1" + local chunk content="" + local -i max_size=10485760 total=0 read_size=0 fd read_status + + zmodload zsh/system || return 1 + exec {fd}<"$filename" || return 1 + + while true; do + sysread -i $fd -s 65536 -c read_size chunk + read_status=$? + + if (( read_status == 5 )); then + break + elif (( read_status != 0 )); then + exec {fd}<&- + return 1 + fi + + (( total += read_size )) + if (( total > max_size )); then + exec {fd}<&- + echo "dotenv: file '$filename' is too large to parse (size: more than $max_size bytes)" >&2 + return 1 + fi + + content+="$chunk" + done + + exec {fd}<&- + REPLY="$content" +} + _dotenv_check_syntax() { local filename="$1" @@ -272,7 +305,8 @@ source_env() { local content if [[ -p "$ZSH_DOTENV_FILE" ]]; then - content="$(<"$ZSH_DOTENV_FILE")" || return 1 + _dotenv_read_limited "$ZSH_DOTENV_FILE" || return 1 + content="$REPLY" _dotenv_check_syntax "$ZSH_DOTENV_FILE" "$content" || return 1 setopt localoptions allexport diff --git a/plugins/dotenv/tests/basic-parsing.zunit b/plugins/dotenv/tests/basic-parsing.zunit index 3ec0378cd..7061cca4b 100644 --- a/plugins/dotenv/tests/basic-parsing.zunit +++ b/plugins/dotenv/tests/basic-parsing.zunit @@ -82,6 +82,45 @@ assert "$result" equals 'secret' } +@test 'source_env rejects oversized named pipes' { + run zsh -fc ' + source ./dotenv.plugin.zsh + + tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/dotenv.XXXXXX")" || exit 1 + fifo="$tmpdir/.env" + command mkfifo "$fifo" || exit 1 + + cleanup() { + kill $killer_pid 2>/dev/null || true + kill $writer_pid 2>/dev/null || true + wait $writer_pid 2>/dev/null || true + command rm -rf "$tmpdir" + } + trap cleanup EXIT + + ( + { + print -rn -- "BIG=" + command dd if=/dev/zero bs=10485761 count=1 2>/dev/null | tr "\0" a + } > "$fifo" + ) & + writer_pid=$! + + ( + sleep 2 + kill -0 $$ 2>/dev/null || exit 0 + kill $$ 2>/dev/null || exit 0 + ) & + killer_pid=$! + + ZSH_DOTENV_PROMPT=false + ZSH_DOTENV_FILE="$fifo" + source_env >/dev/null 2>&1 + ' + + assert $state equals 1 +} + @test 'parse basic variable assignment' { > "$fixture" <<'EOF' # Basic assignments