[vim/vim] [Windows] Silent Crash/Access Violation on i9-13900K during xchacha20v2 decryption (Issue #19248)

9 views
Skip to first unread message

smss2022

unread,
Jan 24, 2026, 4:05:41 PM (20 hours ago) Jan 24
to vim/vim, Subscribed
smss2022 created an issue (vim/vim#19248)

Steps to reproduce

Description: I am reporting a reproducible silent crash in Vim 9.1 on Windows 11 using the xchacha20v2 cryptmethod. The crash occurs on an Intel i9-13900K processor immediately upon providing the correct password for a file.

Analysis: The crash produces no entry in the Windows Event Log. I have verified that:

  1. The file is valid (it decrypts correctly using vimcrypt on Linux).
    
  2. The crash persists even when CPU affinity is set to a single core.
    
  3. The crash occurs in both Nightly and Stable builds.
    

Observations on Encrypted Header: A hex dump of a file encrypted with this build shows anomalous values for opslimit and memlimit. For example: A8 9C 8C E9 (~3.9 billion ops) 55 EC 01 F5 (~4.1 GB mem) This suggests the issue may start during the encryption/save phase, where uninitialized memory or a pointer offset is being written into the Argon2id parameter fields.

Expected behaviour

Upon providing the correct password, the decrypted file is loaded in Vim.

Version of Vim

9.1.1825, 9.1.2006

Environment

Operating system: Microsoft Windows 11 [Version 10.0.22631.6199]
Terminal: Windows Terminal Version: 1.23.13503.0

Logs and stack traces


Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248@github.com>

Christian Brabandt

unread,
6:06 AM (6 hours ago) 6:06 AM
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#19248)

What do you mean with the file is valid? If those parameters are wrong, then surely this is not valid? Then what do you mean with vimcrypt? And finally can you reproduce how this has happened? Can you share that file?


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796445242@github.com>

smss2022

unread,
6:18 AM (6 hours ago) 6:18 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)

What do you mean with the file is valid?

The encrypted file (using xchacha20v2 in vim/Windows) can't be decrypted on the same machine, as vim crashes upon entering the correct password. I investigated the possibility that the encryption proceess, using the admittedly experimental xchacha20v2, might have corrupted something (header, content etc). I moved the file to a machine, running Debian Trixie on an old processor (i5-6200U) and the file crashed again upon entering the correct password - which reinforced my suspicion that some corruption had occured at encryption time. However, to my surprise, vimcrypt was able to decrypt the file as intended, which negated my initial hypothesis and lead me to a different one (silent exit usually means segmentation fault).

Can you share that file?

I wish I could, but it's got some really sensitive info inside.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796457855@github.com>

smss2022

unread,
7:19 AM (5 hours ago) 7:19 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)

I can provide you with some other info you may find useful.

I ran the following script to check the magic numbers for the encrypted file:

import struct

file_path = input("Enter path to file: ")
with open(file_path, 'rb') as f:
data = f.read(64) # Just read the header area

header = data[:12]
opslimit = struct.unpack('<I', data[28:32])[0]
memlimit = struct.unpack('<I', data[32:36])[0]

print(f"Header: {header}")
print(f"Raw Opslimit: {opslimit}")
print(f"Raw Memlimit: {memlimit} (Bytes)")
print(f"Memory requested: {memlimit / 1024 / 1024:.2f} MiB")

The script extracted some really anomalous values from the encrypted file:

OpsLimit=3198306472, MemLimit=4110543957 (4 Gigabytes of RAM just to attempt to derive the key?! OpsLimit over 3 Billion?! that would take years to process on a home computer!)


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796538611@github.com>

smss2022

unread,
7:21 AM (5 hours ago) 7:21 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)

(Continued)

I then ran another script that looked through the first 100 bytes of the encrypted file, to find where the Argon2 limits actually live by looking for "reasonable" values.

def scan_vim_header(file_path):


with open(file_path, 'rb') as f:

data = f.read(100)

print("--- Header Scan Results ---")
# We are looking for an OpsLimit (usually 1-10) 
# and a MemLimit (usually 64MB to 512MB in bytes)
for i in range(12, 60):
    # Read 4 bytes as a Little-Endian integer
    val = struct.unpack('<I', data[i:i+4])[0]
    
    # Check if this looks like a reasonable Argon2 MemLimit (between 1MB and 1GB)
    if 1000000 < val < 1073741824:
        print(f"Possible MemLimit found at offset {i}: {val} bytes ({val/1024/1024:.2f} MiB)")
        # Usually OpsLimit is the 4 bytes immediately BEFORE MemLimit
        ops = struct.unpack('<I', data[i-4:i])[0]
        print(f"Associated OpsLimit (at offset {i-4}): {ops}")
        print("-" * 20)

if name == "main":
path = input("Enter file path: ").strip()
scan_vim_header(path)

The scanner gave 9 results (possible MemLimit found at offsets 15, 24, 31, 33, 44, 49, 55, 57, 58).


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796541285@github.com>

smss2022

unread,
7:27 AM (5 hours ago) 7:27 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)

(Continued)

Based on that output, I concentrated on offset 33, thinking that if the scanner found a result at offset 33, that was almost certainly the Memlimit, as n the standard Vim +sodium implementation:

Header: Bytes 0–11 (VimCrypt~05!)

Salt: Bytes 12–27 (16 bytes)

Opslimit: Bytes 28–31 (4 bytes)

Memlimit: Bytes 32–35 (4 bytes) — This starts at Offset 32 or 33 depending on how the scanner counts.

Based on those findings, I ran the following script:

import struct
import getpass
from nacl.secret import Aead
from nacl.pwhash import argon2id

def decrypt_vim_final(file_path, password, mem_offset=32):


with open(file_path, 'rb') as f:

data = f.read()

# Standard Vim Crypt ~05! Layout
# Header: 0-11
# Salt: 12-27
# Opslimit: 28-31
# Memlimit: 32-35 (Standard offset is 32)
# Nonce: 36-59
# Ciphertext: 60+

salt = data[12:28]
opslimit = struct.unpack('<I', data[mem_offset-4 : mem_offset])[0]
memlimit = struct.unpack('<I', data[mem_offset : mem_offset+4])[0]
nonce = data[mem_offset+4 : mem_offset+28]
ciphertext = data[mem_offset+28:]

print(f"[*] Attempting decryption with MemLimit at offset {mem_offset}...")
print(f"[*] Parameters: Ops={opslimit}, Mem={memlimit/1024/1024:.2f} MiB")

try:
    key = argon2id.kdf(
        Aead.KEY_SIZE,
        password.encode('utf-8'),
        salt,
        opslimit=opslimit,
        memlimit=memlimit
    )
    box = Aead(key)
    decrypted = box.decrypt(ciphertext, None, nonce)
    return decrypted.decode('utf-8')
except Exception as e:
    print(f"[-] Failed at offset {mem_offset}: {e}")
    return None

if name == "main":
path = input("File path: ").strip()
pwd = getpass.getpass("Password: ")

# Try the most likely offsets based on your scanner
# If 33 was your result, try 32 (0-indexed)
for offset in [32, 33]: 
    result = decrypt_vim_final(path, pwd, mem_offset=offset)
    if result:
        print("\n[SUCCESS] Content recovered:\n")
        print(result)
        break

This new script got stuck again, with high CPU activity. I thought the script was successfully running the Argon2id hashing algorithm, but the math was simply too heavy for the i5-6200U to complete in a reasonable timeframe.


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796558050@github.com>

smss2022

unread,
7:30 AM (4 hours ago) 7:30 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)

(Continued)

I then tried to force normal limits and ran a version of the script that ignores the file's header numbers and uses the standard defaults instead.

import struct
import getpass
from nacl.secret import Aead
from nacl.pwhash import argon2id

def bypass_decrypt(file_path, password):


with open(file_path, 'rb') as f:
data = f.read()

# We manually define the "Moderate" limits that Vim defaults to
# instead of reading the (likely misaligned) numbers from the file.
OPS_MODERATE = 3
MEM_MODERATE = 256 * 1024 * 1024 # 256 MiB

# Offsets for VimCrypt~05!
salt = data[12:28]
nonce = data[36:60]
ciphertext = data[60:]

print(f"[*] Forcing Moderate Limits (Ops: {OPS_MODERATE}, Mem: 256MB)")
print("[*] Deriving key...")

try:
    # This will take < 1 second on the i5-6200U
    key = argon2id.kdf(
        Aead.KEY_SIZE,
        password.encode('utf-8'),
        salt,
        opslimit=OPS_MODERATE,
        memlimit=MEM_MODERATE
    )
    box = Aead(key)
    decrypted = box.decrypt(ciphertext, None, nonce)
    return decrypted.decode('utf-8')
except Exception as e:
    print(f"[-] Decryption failed: {e}")
    return None

if name == "main":
path = input("File path: ").strip()
pwd = getpass.getpass("Password: ")

result = bypass_decrypt(path, pwd)
if result:
print("\n[SUCCESS]\n" + result)

The script ran successfully but the result was "Decryption failed".


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796570379@github.com>

smss2022

unread,
7:33 AM (4 hours ago) 7:33 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)

(Continued)

I then ran a script that probed the most likely byte-offsets. Instead of guessing, it tried the 4 most common structural alignments used by different versions of Vim's sodium integration.

import struct
import getpass
from nacl.secret import Aead
from nacl.pwhash import argon2id

def try_decrypt(data, password, salt_offset, ops_offset, mem_offset, nonce_offset, ct_offset):
try:
salt = data[salt_offset : salt_offset+16]
opslimit = struct.unpack('<I', data[ops_offset : ops_offset+4])[0]


memlimit = struct.unpack('<I', data[mem_offset : mem_offset+4])[0]

nonce = data[nonce_offset : nonce_offset+24]
ciphertext = data[ct_offset:]

    # Sanity check: If limits are crazy, skip this offset to avoid hanging
    if opslimit > 20 or memlimit > 1024*1024*1024: 
        return None

    key = argon2id.kdf(Aead.KEY_SIZE, password.encode(), salt, opslimit, memlimit)
    return Aead(key).decrypt(ciphertext, None, nonce).decode('utf-8')
except:
    return None

def recover_data(path, password):
with open(path, 'rb') as f:
data = f.read()

# Define common offset patterns [Salt, Ops, Mem, Nonce, CipherText]
patterns = [
    [12, 28, 32, 36, 60], # Standard (Vim source default)
    [12, 32, 36, 40, 64], # Padded / 64-bit aligned
    [16, 32, 36, 40, 64], # Shifted (VimCrypt~05! + 4 padding)
    [12, 29, 33, 37, 61], # The "Offset 33" pattern your scanner found
]

for i, p in enumerate(patterns):
    print(f"[*] Trying pattern {i+1}...")
    result = try_decrypt(data, password, *p)
    if result:
        return result
return None

if name == "main":
p = input("File path: ").strip()


pwd = getpass.getpass("Password: ")

final_text = recover_data(p, pwd)
if final_text:
print("\n[SUCCESS] Content recovered:\n" + final_text)
else:
print("\n[!] All standard patterns failed. My Vim build may use unique padding.")

Unfortunately, the output was: "All standard patterns failed. My Vim build may use unique padding."


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796579250@github.com>

smss2022

unread,
7:40 AM (4 hours ago) 7:40 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)

(Continued)

I then hex-dumped the first bytes of the encrypted file:

56 69 6D 43 72 79 70 74 7E 30 35 21 78 14 2A 7F FF 69 09 7B 48 F5 41 D1 88 FA 71 2E A8 9C 8C E9 55 EC 01 F5 02 00 00 00 00 00 00 [...]

Meaning:

56 69 6D 43 72 79 70 74 7E 30 35 21 Header "VimCrypt~05!"
78 14 2A 7F FF 69 09 7B 48 F5 41 D1 88 FA 71 2E Salt
A8 9C 8C E9 Opslimit 3,918,273,704 (Decimal)
55 EC 01 F5 Memlimit 4,110,543,957 (Decimal)

Again, the numbers stored for Opslimit and Memlimit are astronomical.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796591424@github.com>

smss2022

unread,
7:42 AM (4 hours ago) 7:42 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)

(Continued)

Let's run a script that ignores the crazy numbers in the encrypted file and forces the Standard Moderate Limits that Vim is supposed to use.

import struct
import getpass
from nacl.secret import Aead
from nacl.pwhash import argon2id

def final_rescue(file_path, password):
with open(file_path, 'rb') as f:
data = f.read()

# We manually extract only the Salt and Nonce, 
# ignoring the 'garbage' limits at bytes 28-35.
salt = data[12:28]
# In the hex dump, the Nonce starts after the 8 bytes of garbage limits
nonce = data[36:60]
ciphertext = data[60:]

# Force standard Vim defaults (Moderate)
OPS = 3
MEM = 256 * 1024 * 1024 # 256 MiB

print(f"[*] Bypassing corrupted header limits...")
print(f"[*] Forcing Ops={OPS}, Mem={MEM/1024/1024}MB")

try:
    key = argon2id.kdf(Aead.KEY_SIZE, password.encode(), salt, opslimit=OPS, memlimit=MEM)
    box = Aead(key)
    decrypted = box.decrypt(ciphertext, None, nonce)
    return decrypted.decode('utf-8')
except Exception as e:
    print(f"[-] Recovery failed: {e}")
    return None

if name == "main":
path = input("Enter path to file: ").strip()
pwd = getpass.getpass("Enter password: ")
content = final_rescue(path, pwd)
if content:
print("\n[SUCCESS] DATA RECOVERED:\n" + content)

Again: "Recovery failed. Decryption failed"


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796594091@github.com>

smss2022

unread,
7:54 AM (4 hours ago) 7:54 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)

(Continued)

At this point, the file content seemed irretrievable. But then, in a "Hail Mary" attempt, I ran vimcrypt and... the file was decrypted as expected!

Could the fact that I was running a nightly build be the culprit? let's switch to the stable version and try the encryption/decryption with it.

The stable version exhibited the same behavior trying to decrypt the encrypted file (i.e. instant crash, SefFault or Access violation - impossible to tell, as there was nothing written into the Event Log). Could it be the interaction between my i9-13900K and libsodium (SIGILL, libsodium attempts to use a specific instruction my Windows build/configuration doesn't like, and the CPU generates an Illegal Instruction signal?) Memory Protection (DEP/ASLR) (Vim's internal implementation tries to allocate this memory in a way that violates Data Execution Prevention (DEP), Windows kills the process silently)? Hybrid architecture" hiccup (of Windows tries to shift the Argon2 thread from a P-core to an E-core mid-calculation and the libsodium library isn't "thread-safe" for that specific jump the instruction pointer hits a wall)?


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796620813@github.com>

smss2022

unread,
7:58 AM (4 hours ago) 7:58 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)

(Continued)

I then ran another experiment: I ran vim setting its affinity to a single core, then tried to open the file previously encrypted with xchacha20v2. The same crash occured.

I interpreted that as: the crash isn't caused by a race condition or a multi-core synchronization error. Since it still fails even on a single core, we are dealing with a Direct Execution Failure within the libsodium library's interaction with the i9's architecture.

The lack of an Event Log entry combined with the silent exit points to a Hardware exception (Access violation) that is killing the process before Windows can even wrap it in an error report. (SIMD/AVX Instruction Set Mismatch? Memory Alignment/Guard Pages?)

At that point, I stopped experimenting/investigating. Hope the information above proves useful to you


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796625270@github.com>

Christian Brabandt

unread,
10:08 AM (2 hours ago) 10:08 AM
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#19248)

sorry, I don't follow, your messages are too chaotic and too much python scripts, that I don't know how it relates. Please give a concise summary of what you found out.

Please answer the following questions from my initial comment:

  • Again, what is vimcrypt?
  • how did you create that file and with what vim version?
  • can you reproduce the issue? If yes how?
  • can you share the file?

There haven't been any changes in the vim crypt implementation lately (for at least 2 years), so it is not a difference between vim nightly and stable builds.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796814004@github.com>

smss2022

unread,
10:54 AM (1 hour ago) 10:54 AM
to vim/vim, Subscribed
smss2022 left a comment (vim/vim#19248)
* Again, what is vimcrypt?

https://github.com/chrisbra/vimcrypt

* how did you create that file and with what vim version?

Standard text file, UTF-8, LF. Created as unencrypted file, initially.

* can you reproduce the issue? If yes how?

Yes. Change default encryption method from blowfish2 to xchacha20v2. Save file (previously encrypted with blowfish2) as a xchacha20v2 encrypted file. Experience crash.

* can you share the file?

As I was saying in my very first reply, no, sorry. Private/confidential info.

Sorry you're finding my investigative logic "chaotic". I thought I documented each step/script result with a conclusion.

Feel free to close this incident report if you want.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796862207@github.com>

Christian Brabandt

unread,
11:32 AM (27 minutes ago) 11:32 AM
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#19248)

Oh my vimcrypt? Hm, let me try to reproduce.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/19248/3796914887@github.com>

Reply all
Reply to author
Forward
0 new messages