Skip to content

[Security] MacVim affected by GHSA-crm5-rh6j-2c7c — netrw NetrwBookHistSave() persistent code injection #1663

@dkgkdfg65

Description

@dkgkdfg65

[Security] MacVim affected by GHSA-crm5-rh6j-2c7c — netrw NetrwBookHistSave() code injection via crafted directory name (vim < 9.2.0495)

Summary

MacVim bundles the vim runtime at version 9.2 (patches 1-321 in the current build), which is
below the patched version 9.2.0495 that fixes a code injection vulnerability in the netrw
plugin's s:NetrwBookHistSave() function.

Vulnerability Details

  • GHSA: GHSA-crm5-rh6j-2c7c
  • Upstream fix: vim 9.2.0495 (commit f08ab2f4d7d2947c8dd6c179ae08ee6146a2694b)
  • Affected code: runtime/pack/dist/opt/netrw/autoload/netrw.vims:NetrwBookHistSave()
  • Vulnerability type: CWE-94 — Improper Control of Generation of Code (Code Injection)

Root Cause

In s:NetrwBookHistSave(), the directory history is serialized to ~/.vim/.netrwhist using
a single-quoted Vimscript string literal without escaping embedded single quotes:

" runtime/pack/dist/opt/netrw/autoload/netrw.vim line 2961 (macvim r183)
call setline(lastline,'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'")

This generates lines of the form:

let g:netrw_dirhist_1='/some/path'

If the directory name (stored in g:netrw_dirhist_{cnt}) contains a single quote ', the
generated Vimscript breaks out of the string literal. For example, a directory named:

x'|let g:injected=1|let y='z

would generate:

let g:netrw_dirhist_1='x'|let g:injected=1|let y='z'

When ~/.vim/.netrwhist is later sourced by netrw (at vim startup via the VimLeave
autocommand that calls NetrwBookHistRead), the injected Vimscript executes.

Attack Scenario

  1. An attacker creates a directory named with an embedded single-quote followed by
    Vimscript commands:
    mkdir -p "target/x'|call system('id > /tmp/pwned')|let y='z"
  2. The victim opens this directory in netrw (:Explore) inside MacVim and then quits vim.
  3. s:NetrwBookHistSave() writes the crafted path to ~/.vim/.netrwhist unescaped.
  4. The next time MacVim starts and opens netrw, NetrwBookHistRead() sources .netrwhist,
    executing the injected call system('id > /tmp/pwned') command.

This provides persistent arbitrary command execution — the payload is written once and
fires on every subsequent vim startup.

Affected MacVim Code

" netrw.vim line 2961 (macvim r183)
call setline(lastline,'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'")

The directory path g:netrw_dirhist_{cnt} is interpolated directly into a single-quoted
string without any escaping.

Affected MacVim Version

MacVim r183 (vim 9.2 patches 1-321) — current HEAD as of 2026-05-18.

The fix commit f08ab2f4d7d2947c8dd6c179ae08ee6146a2694b from vim/vim is not present
in the macvim-dev/macvim repository.

Suggested Fix

Merge or cherry-pick vim/vim patches up to at least 9.2.0495.

The fix replaces the unescaped string interpolation with Vimscript's built-in string()
function, which properly double-quotes the value and escapes embedded single quotes:

" Fixed (vim 9.2.0495):
call setline(lastline,'let g:netrw_dirhist_'.cnt.'='.string(g:netrw_dirhist_{cnt}))

string() produces a safely quoted Vimscript literal (e.g., "x'|cmd" for a path
containing '), so the value round-trips safely through source.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions