Skip FZF_CURRENT_ITEM export for items larger than 64 KB
CodeQL / Analyze (go) (push) Has been cancelled
build / build (push) Has been cancelled
Test fzf on macOS / build (push) Has been cancelled

A huge item can overflow ARG_MAX and break exec with E2BIG, failing
preview and other child commands. (#4806)
This commit is contained in:
Junegunn Choi
2026-06-23 20:49:50 +09:00
parent 109057877b
commit f2e451596c
4 changed files with 32 additions and 2 deletions
+1
View File
@@ -12,6 +12,7 @@ CHANGELOG
--bind 'result:transform-header(echo result: $FZF_MATCH_COUNT),result-final:transform-footer(echo final: $FZF_MATCH_COUNT)'
```
- Bound `alt-left` to `backward-word` and `alt-right` to `forward-word` by default (#4833)
- Skip `$FZF_CURRENT_ITEM` export when the item is larger than 64 KB; a huge item can overflow `ARG_MAX` and break preview and other child commands with `E2BIG` (#4806)
0.73.1
------
+3
View File
@@ -1534,6 +1534,9 @@ fzf exports the following environment variables to its child processes.
.PP
.B FZF_CURRENT_ITEM
is omitted when the item contains a NUL byte, because exec(2) cannot pass it.
It is also omitted when the item is larger than 64 KB, so that a huge item
cannot overflow the environment size limit and break preview and other child
commands.
.SH EXTENDED SEARCH MODE
+8 -2
View File
@@ -68,6 +68,10 @@ const maxFocusEvents = 10000
// After this duration, users can press CTRL-C to terminate the command.
const blockDuration = 1 * time.Second
// Skip exporting FZF_CURRENT_ITEM when the item is larger than this, so a huge
// item cannot overflow ARG_MAX and break exec for preview and other commands.
const maxCurrentItemEnvSize = 64 * 1024
func init() {
placeholder = regexp.MustCompile(`\\?(?:{[+*sfr]*[0-9,-.]*}|{q(?::s?[0-9,-.]+)?}|{fzf:(?:query|action|prompt)}|{[+*]?f?nf?})`)
whiteSuffix = regexp.MustCompile(`\s*$`)
@@ -1444,8 +1448,10 @@ func (t *Terminal) environImpl(forPreview bool) []string {
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
env = append(env, fmt.Sprintf("FZF_POS=%d", min(t.merger.Length(), t.cy+1)))
if item := t.currentItem(); item != nil {
// Skip if the value contains a NUL byte; exec(2) would reject the env.
if s := item.AsString(t.ansi); !strings.ContainsRune(s, 0) {
// Skip if the value contains a NUL byte (exec(2) would reject the env)
// or is too large (a huge item can overflow ARG_MAX and break exec
// entirely for preview and other child commands).
if s := item.AsString(t.ansi); !strings.ContainsRune(s, 0) && len(s) <= maxCurrentItemEnvSize {
env = append(env, "FZF_CURRENT_ITEM="+s)
}
}
+20
View File
@@ -2346,6 +2346,26 @@ class TestCore < TestInteractive
end
end
def test_env_current_item_size_limit
preview = %[(echo START; env | grep '^FZF_CURRENT_ITEM='; echo END) > #{tempname}]
# Large item (> 64 KB) is omitted so it cannot overflow ARG_MAX and break exec
tmux.send_keys %(head -c 70000 /dev/zero | tr '\\0' a | #{FZF} --preview-window 0 --preview "#{preview}"), :Enter
wait do
content = File.exist?(tempname) ? File.read(tempname) : ''
assert_includes content, 'END'
refute_includes content, 'FZF_CURRENT_ITEM='
end
tmux.send_keys :Enter
FileUtils.rm_f(tempname)
# Smaller item is exported as usual
tmux.send_keys %(head -c 1000 /dev/zero | tr '\\0' a | #{FZF} --preview-window 0 --preview "#{preview}"), :Enter
wait do
content = File.exist?(tempname) ? File.read(tempname) : ''
assert_includes content, 'END'
assert_includes content, 'FZF_CURRENT_ITEM=' + ('a' * 1000)
end
end
def test_abort_action_chain
tmux.send_keys %(seq 100 | #{FZF} --bind 'load:accept+up+up' > #{tempname}), :Enter
wait do