From c9ef2103787ec00d07fe627bc8f8884f198bd515 Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 11 Dec 2023 21:04:12 -0800 Subject: [PATCH] fix #K4061 - wand with "interesting effect" Report was for an impossible "What an interesting effect (%d)" which the fuzzer turned into a panic. Monster on the drawbridge zapped toward the open portcullis, destroying the bridge and killing itself. Wand was made of wood and burned up in lava under the fallen span. Freeing the object zeroed it rather than leaving stale data, and the zap continued while referencing freed memory that looked like it had type STRANGE_OBJECT, triggering the impossible. This will make a monster-induced zap stop early if a drawbridge incident destroys the wand. That isn't the best possible fix because the zap should continue despite the wand's destruction, but at least it will now avoid triggering "intestsing effect". --- src/muse.c | 71 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/src/muse.c b/src/muse.c index a939cccde..83bafe2df 100644 --- a/src/muse.c +++ b/src/muse.c @@ -1,4 +1,4 @@ -/* NetHack 3.7 muse.c $NHDT-Date: 1701557157 2023/12/02 22:45:57 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.202 $ */ +/* NetHack 3.7 muse.c $NHDT-Date: 1702356860 2023/12/12 04:54:20 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.203 $ */ /* Copyright (C) 1990 by Ken Arromdee */ /* NetHack may be freely redistributed. See license for details. */ @@ -1598,16 +1598,16 @@ mbhitm(struct monst *mtmp, struct obj *otmp) */ static void mbhit( - struct monst *mon, /* monster shooting the wand */ - register int range, /* direction and range */ - int (*fhitm)(MONST_P, OBJ_P), - int (*fhito)(OBJ_P, OBJ_P), /* fns called when mon/obj hit */ - struct obj *obj) /* 2nd arg to fhitm/fhito */ + struct monst *mon, /* monster shooting the wand */ + int range, /* direction and range */ + int (*fhitm)(MONST_P, OBJ_P), /* must be non-Null */ + int (*fhito)(OBJ_P, OBJ_P), /* fns called when mon/obj hit */ + struct obj *obj) /* 2nd arg to fhitm/fhito */ { - register struct monst *mtmp; - register struct obj *otmp; - register uchar typ; - int ddx, ddy; + struct monst *mtmp; + struct obj *otmp; + uchar ltyp; + int ddx, ddy, otyp = obj->otyp; gb.bhitpos.x = mon->mx; gb.bhitpos.y = mon->my; @@ -1615,7 +1615,7 @@ mbhit( ddy = sgn(mon->muy - mon->my); while (range-- > 0) { - coordxy x, y; + coordxy x, y, dbx, dby; gb.bhitpos.x += ddx; gb.bhitpos.y += ddy; @@ -1627,11 +1627,6 @@ mbhit( gb.bhitpos.y -= ddy; break; } - if (find_drawbridge(&x, &y)) - switch (obj->otyp) { - case WAN_STRIKING: - destroy_drawbridge(x, y); - } if (u_at(gb.bhitpos.x, gb.bhitpos.y)) { (*fhitm)(&gy.youmonst, obj); range -= 3; @@ -1644,7 +1639,7 @@ mbhit( /* modified by GAN to hit all objects */ if (fhito) { int hitanything = 0; - register struct obj *next_obj; + struct obj *next_obj; for (otmp = gl.level.objects[gb.bhitpos.x][gb.bhitpos.y]; otmp; otmp = next_obj) { @@ -1655,9 +1650,32 @@ mbhit( if (hitanything) range--; } - typ = levl[gb.bhitpos.x][gb.bhitpos.y].typ; - if (IS_DOOR(typ) || typ == SDOOR) { - switch (obj->otyp) { + ltyp = levl[gb.bhitpos.x][gb.bhitpos.y].typ; + dbx = x, dby = y; + if (otyp == WAN_STRIKING && find_drawbridge(&dbx, &dby)) { + /* this might kill mon and destroy obj; mon will remain + accessible even if dead but obj could be deleted */ + destroy_drawbridge(dbx, dby); + for (otmp = mon->minvent; otmp; otmp = otmp->nobj) + if (otmp == obj) + break; + if (!otmp) { + for (otmp = gl.level.objects[x][y]; otmp; + otmp = otmp->nexthere) + if (otmp == obj) + break; + } + /* if otmp is Null, mon isn't carrying obj anymore and didn't + just drop it here; assume that it was destroyed and stop + the zap; not really correct behavior but we can't continue + without the responsible object because fhitm (mbhitm) and + fhito (bhito) will want it at forthcoming spots in zap path */ + if (!otmp) { + obj = NULL; + break; + } + } else if (IS_DOOR(ltyp) || ltyp == SDOOR) { + switch (otyp) { /* note: monsters don't use opening or locking magic at present, but keep these as placeholders */ case WAN_OPENING: @@ -1665,7 +1683,7 @@ mbhit( case WAN_STRIKING: if (doorlock(obj, gb.bhitpos.x, gb.bhitpos.y)) { if (gz.zap_oseen) - makeknown(obj->otyp); + makeknown(otyp); /* if a shop door gets broken, add it to the shk's fix list (no cost to player) */ if (levl[gb.bhitpos.x][gb.bhitpos.y].doormask == D_BROKEN @@ -1675,14 +1693,18 @@ mbhit( break; } } - if (!ZAP_POS(typ) - || (IS_DOOR(typ) && (levl[gb.bhitpos.x][gb.bhitpos.y].doormask - & (D_LOCKED | D_CLOSED)))) { + if (!ZAP_POS(ltyp) + || (IS_DOOR(ltyp) && (levl[gb.bhitpos.x][gb.bhitpos.y].doormask + & (D_LOCKED | D_CLOSED)))) { gb.bhitpos.x -= ddx; gb.bhitpos.y -= ddy; break; } } + /* 'obj' was set of Null when it couldn't be found, but isn't used again; + however, someday that might change, so we want it set to Null; give it + a fake use to pacify potential "set but not used"-type warnings */ + nhUse(obj); } /* Perform an offensive action for a monster. Must be called immediately @@ -1741,6 +1763,7 @@ use_offensive(struct monst *mtmp) mzapwand(mtmp, otmp, FALSE); gm.m_using = TRUE; mbhit(mtmp, rn1(8, 6), mbhitm, bhito, otmp); + /* note: 'otmp' might have been destroyed (drawbridge destruction) */ gm.m_using = FALSE; return 2; case MUSE_SCR_EARTH: {