ex_cmd() in usr.bin/vi/ex/ex.c computes the remaining command
length with unsigned arithmetic that underflows when the +cmd
inner loop consumes every byte and "discard" (escape pairs)
reaches save_cmd - cp:
ecp->clen = ((ecp->save_cmd - ecp->cp) - 1) - discard;
The resulting (size_t)-1 flows into argv_exp2() -> argv_fexp()
at ex_argv.c:305 which loops until cmd walks off mapped memory.
Minimum reproducer (7 bytes, \x16 = literal-next):
$ printf 'x\n' > t
$ printf 'e+"p\x16o\n' | MALLOC_OPTIONS=S vi -e t
Segmentation fault (core dumped)
Reproduced on OpenBSD 7.9 arm64, amd64 and i386. Found by
AFL++ fuzzing of ex(1).
OK?
Index: usr.bin/vi/ex/ex.c
===================================================================
RCS file: /cvs/src/usr.bin/vi/ex/ex.c,v
retrieving revision 1.23
diff -u -p -r1.23 ex.c
--- usr.bin/vi/ex/ex.c 23 Jun 2023 15:06:45 -0000 1.23
+++ usr.bin/vi/ex/ex.c
@@ -801,7 +801,8 @@ ex_cmd(SCR *sp)
ecp->cp = ecp->save_cmd;
ecp->save_cmd = p;
ecp->save_cmdlen = ecp->clen;
- ecp->clen = ((ecp->save_cmd - ecp->cp) - 1) - discard;
+ len = ecp->save_cmd - ecp->cp;
+ ecp->clen = (len > 1 + discard) ? len - 1 - discard : 0;
/*
* QUOTING NOTE: