Monday, April 27, 2026

Re: Perhaps those who have hacked vi(1) can lend me a hand.

On Sat, Apr 25, 2026 at 07:29:27PM -0600, Todd C. Miller wrote: > On Mon, 20 Apr 2026 10:15:47 +0200, Walter Alejandro Iglesias wrote: > > > With the other diff I posted for this issue the segfault still happened > > in some scenarios, plus some regressions. With this one, I haven't had > > any problem so far. > > Nice work. I started to try debugging this when it was first > reported but didn't get very far. It's a wormhole. :-) None of what I posted are solutions. I found more ways of reproducing the segfault whatever, wherever I do with sp->ep->refcnt. Reloading *ecp avoids the write-after-free aborts but it's still a way to dodge the real issue which, may be (still not sure), has a common root. If the hours I spent with gdb (from ports) didn't lie to me, this is what happens. Both *sp and *ecp get screwed after ex_visual() is called by ex_cmd() (ex/ex.c:1372): /* * Call the underlying function for the ex command. * * XXX * Interrupts behave like errors, for now. */ if (ecp->cmd->fn(sp, ecp) || INTERRUPTED(sp)) { if (F_ISSET(gp, G_SCRIPTED)) F_SET(sp, SC_EXIT_FORCE); goto err; } The problem comes here (ex/ex_visual.c:122): if (F_ISSET(sp, SC_EX_GLOBAL)) { /* * When the vi screen(s) exit, we don't want to lose our hold * on this screen or this file, otherwise we're going to fail * fairly spectacularly. */ ++sp->refcnt; ++sp->ep->refcnt; /* * Fake up a screen pointer -- vi doesn't get to change our * underlying file, regardless. */ tsp = sp; HERE -> if (vi(&tsp)) return (1); /* * !!! * Historically, if the user exited the vi screen(s) using an * ex quit command (e.g. :wq, :q) ex/vi exited, it was only if * they exited vi using the Q command that ex continued. Some * early versions of nvi continued in ex regardless, but users * didn't like the semantic. * * Reset the screen. */ if (ex_init(sp)) return (1); /* Move out of the vi screen. */ (void)ex_puts(sp, "\n"); Is that "fake up a screen pointer" a fake assumption? Does nvi end up using the same SCR for everything? Anyway, when vi() (vi/vi.c:63) takes care of tsp, sp is cleansed. It happens at this point (vi/vi.c:391): /* * If the current screen is still displayed, it will * need a new status line. */ F_SET(sp, SC_STATUS); /* Switch screens, change focus. */ sp = sp->nextdisp; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); /* Refresh so we can display messages. */ HERE -> if (vs_refresh(sp, 1)) return (1); } When this whole walk ends and we return to ex_cmd(), *sp and *ecp are filled with garbage. How can we create a real separate screen for global-visual, I mean creating and destroying it as a separate process? I don't know how to do this, even when I made a big progress in understanding the code I'm still lost. > > - todd -- Walter

No comments:

Post a Comment