Files
nethack/src/fountain.c
Michael Meyer 75ff2fa5fc Fix: use-after-free when fountain dipping
A potion of acid could be destroyed and freed by dipping into a
fountain, then dereferenced after the fact -- both when checking its
type immediately after the water_damage() call (as was noticed by
hackemslashem and amateurhour on IRC), and also in the later switch/case
a few lines further down in dipfountain().

I basically reversed the original 'er != ER_DESTROYED' test here: as it
was before this, I think the only thing that could hit it was a greased
potion of acid, which would survive the initial dip due to the grease.
Such a potion would be silently deleted.  Potions of acid which were
actually destroyed by water_damage, on the other hand, could be allowed
to continue down to the switch/case of further effects (and associated
dereferences).  I think this makes more sense in reverse, with potions
that were protected by grease actually being protected and producing
normal dip effects, and potions of acid which exploded causing an early
return with no further effects.  This effectively prevents the various
use-after-free scenarios that were possible, too.
2022-11-30 12:54:26 -08:00

655 lines
21 KiB
C

/* NetHack 3.7 fountain.c $NHDT-Date: 1646870844 2022/03/10 00:07:24 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.78 $ */
/* Copyright Scott R. Turner, srt@ucla, 10/27/86 */
/* NetHack may be freely redistributed. See license for details. */
/* Code for drinking from fountains. */
#include "hack.h"
static void dowatersnakes(void);
static void dowaterdemon(void);
static void dowaternymph(void);
static void gush(coordxy, coordxy, genericptr_t);
static void dofindgem(void);
static boolean watchman_warn_fountain(struct monst *);
DISABLE_WARNING_FORMAT_NONLITERAL
/* used when trying to dip in or drink from fountain or sink or pool while
levitating above it, or when trying to move downwards in that state */
void
floating_above(const char *what)
{
const char *umsg = "are floating high above the %s.";
if (u.utrap && (u.utraptype == TT_INFLOOR || u.utraptype == TT_LAVA)) {
/* when stuck in floor (not possible at fountain or sink location,
so must be attempting to move down), override the usual message */
umsg = "are trapped in the %s.";
what = surface(u.ux, u.uy); /* probably redundant */
}
You(umsg, what);
}
RESTORE_WARNING_FORMAT_NONLITERAL
/* Fountain of snakes! */
static void
dowatersnakes(void)
{
register int num = rn1(5, 2);
struct monst *mtmp;
if (!(gm.mvitals[PM_WATER_MOCCASIN].mvflags & G_GONE)) {
if (!Blind)
pline("An endless stream of %s pours forth!",
Hallucination ? makeplural(rndmonnam(NULL)) : "snakes");
else
You_hear("%s hissing!", something);
while (num-- > 0)
if ((mtmp = makemon(&mons[PM_WATER_MOCCASIN], u.ux, u.uy,
MM_NOMSG)) != 0
&& t_at(mtmp->mx, mtmp->my))
(void) mintrap(mtmp, NO_TRAP_FLAGS);
} else
pline_The("fountain bubbles furiously for a moment, then calms.");
}
/* Water demon */
static void
dowaterdemon(void)
{
struct monst *mtmp;
if (!(gm.mvitals[PM_WATER_DEMON].mvflags & G_GONE)) {
if ((mtmp = makemon(&mons[PM_WATER_DEMON], u.ux, u.uy,
MM_NOMSG)) != 0) {
if (!Blind)
You("unleash %s!", a_monnam(mtmp));
else
You_feel("the presence of evil.");
/* Give those on low levels a (slightly) better chance of survival
*/
if (rnd(100) > (80 + level_difficulty())) {
pline("Grateful for %s release, %s grants you a wish!",
mhis(mtmp), mhe(mtmp));
/* give a wish and discard the monster (mtmp set to null) */
mongrantswish(&mtmp);
} else if (t_at(mtmp->mx, mtmp->my))
(void) mintrap(mtmp, NO_TRAP_FLAGS);
}
} else
pline_The("fountain bubbles furiously for a moment, then calms.");
}
/* Water Nymph */
static void
dowaternymph(void)
{
register struct monst *mtmp;
if (!(gm.mvitals[PM_WATER_NYMPH].mvflags & G_GONE)
&& (mtmp = makemon(&mons[PM_WATER_NYMPH], u.ux, u.uy,
MM_NOMSG)) != 0) {
if (!Blind)
You("attract %s!", a_monnam(mtmp));
else
You_hear("a seductive voice.");
mtmp->msleeping = 0;
if (t_at(mtmp->mx, mtmp->my))
(void) mintrap(mtmp, NO_TRAP_FLAGS);
} else if (!Blind)
pline("A large bubble rises to the surface and pops.");
else
You_hear("a loud pop.");
}
/* Gushing forth along LOS from (u.ux, u.uy) */
void
dogushforth(int drinking)
{
int madepool = 0;
do_clear_area(u.ux, u.uy, 7, gush, (genericptr_t) &madepool);
if (!madepool) {
if (drinking)
Your("thirst is quenched.");
else
pline("Water sprays all over you.");
}
}
static void
gush(coordxy x, coordxy y, genericptr_t poolcnt)
{
register struct monst *mtmp;
register struct trap *ttmp;
if (((x + y) % 2) || u_at(x, y)
|| (rn2(1 + distmin(u.ux, u.uy, x, y))) || (levl[x][y].typ != ROOM)
|| (sobj_at(BOULDER, x, y)) || nexttodoor(x, y))
return;
if ((ttmp = t_at(x, y)) != 0 && !delfloortrap(ttmp))
return;
if (!((*(int *) poolcnt)++))
pline("Water gushes forth from the overflowing fountain!");
/* Put a pool at x, y */
levl[x][y].typ = POOL, levl[x][y].flags = 0;
/* No kelp! */
del_engr_at(x, y);
water_damage_chain(gl.level.objects[x][y], TRUE);
if ((mtmp = m_at(x, y)) != 0)
(void) minliquid(mtmp);
else
newsym(x, y);
}
/* Find a gem in the sparkling waters. */
static void
dofindgem(void)
{
if (!Blind)
You("spot a gem in the sparkling waters!");
else
You_feel("a gem here!");
(void) mksobj_at(rnd_class(DILITHIUM_CRYSTAL, LUCKSTONE - 1), u.ux, u.uy,
FALSE, FALSE);
SET_FOUNTAIN_LOOTED(u.ux, u.uy);
newsym(u.ux, u.uy);
exercise(A_WIS, TRUE); /* a discovery! */
}
static boolean
watchman_warn_fountain(struct monst *mtmp)
{
if (is_watch(mtmp->data) && couldsee(mtmp->mx, mtmp->my)
&& mtmp->mpeaceful) {
if (!Deaf) {
pline("%s yells:", Amonnam(mtmp));
verbalize("Hey, stop using that fountain!");
} else {
pline("%s earnestly %s %s %s!",
Amonnam(mtmp),
nolimbs(mtmp->data) ? "shakes" : "waves",
mhis(mtmp),
nolimbs(mtmp->data)
? mbodypart(mtmp, HEAD)
: makeplural(mbodypart(mtmp, ARM)));
}
return TRUE;
}
return FALSE;
}
void
dryup(coordxy x, coordxy y, boolean isyou)
{
if (IS_FOUNTAIN(levl[x][y].typ)
&& (!rn2(3) || FOUNTAIN_IS_WARNED(x, y))) {
if (isyou && in_town(x, y) && !FOUNTAIN_IS_WARNED(x, y)) {
struct monst *mtmp;
SET_FOUNTAIN_WARNED(x, y);
/* Warn about future fountain use. */
mtmp = get_iter_mons(watchman_warn_fountain);
/* You can see or hear this effect */
if (!mtmp)
pline_The("flow reduces to a trickle.");
return;
}
if (isyou && wizard) {
if (yn("Dry up fountain?") == 'n')
return;
}
/* FIXME: sight-blocking clouds should use block_point() when
being created and unblock_point() when going away, then this
glyph hackery wouldn't be necessary */
if (cansee(x, y)) {
int glyph = glyph_at(x, y);
if (!glyph_is_cmap(glyph) || glyph_to_cmap(glyph) != S_cloud)
pline_The("fountain dries up!");
}
/* replace the fountain with ordinary floor */
levl[x][y].typ = ROOM, levl[x][y].flags = 0;
levl[x][y].blessedftn = 0;
gl.level.flags.nfountains--;
/* The location is seen if the hero/monster is invisible
or felt if the hero is blind. */
newsym(x, y);
if (isyou && in_town(x, y))
(void) angry_guards(FALSE);
}
}
void
drinkfountain(void)
{
/* What happens when you drink from a fountain? */
register boolean mgkftn = (levl[u.ux][u.uy].blessedftn == 1);
register int fate = rnd(30);
if (Levitation) {
floating_above("fountain");
return;
}
if (mgkftn && u.uluck >= 0 && fate >= 10) {
int i, ii, littleluck = (u.uluck < 4);
pline("Wow! This makes you feel great!");
/* blessed restore ability */
for (ii = 0; ii < A_MAX; ii++)
if (ABASE(ii) < AMAX(ii)) {
ABASE(ii) = AMAX(ii);
gc.context.botl = 1;
}
/* gain ability, blessed if "natural" luck is high */
i = rn2(A_MAX); /* start at a random attribute */
for (ii = 0; ii < A_MAX; ii++) {
if (adjattrib(i, 1, littleluck ? -1 : 0) && littleluck)
break;
if (++i >= A_MAX)
i = 0;
}
display_nhwindow(WIN_MESSAGE, FALSE);
pline("A wisp of vapor escapes the fountain...");
exercise(A_WIS, TRUE);
levl[u.ux][u.uy].blessedftn = 0;
return;
}
if (fate < 10) {
pline_The("cool draught refreshes you.");
u.uhunger += rnd(10); /* don't choke on water */
newuhs(FALSE);
if (mgkftn)
return;
} else {
switch (fate) {
case 19: /* Self-knowledge */
You_feel("self-knowledgeable...");
display_nhwindow(WIN_MESSAGE, FALSE);
enlightenment(MAGICENLIGHTENMENT, ENL_GAMEINPROGRESS);
exercise(A_WIS, TRUE);
pline_The("feeling subsides.");
break;
case 20: /* Foul water */
pline_The("water is foul! You gag and vomit.");
morehungry(rn1(20, 11));
vomit();
break;
case 21: /* Poisonous */
pline_The("water is contaminated!");
if (Poison_resistance) {
pline("Perhaps it is runoff from the nearby %s farm.",
fruitname(FALSE));
losehp(rnd(4), "unrefrigerated sip of juice", KILLED_BY_AN);
break;
}
poison_strdmg(rn1(4, 3), rnd(10), "contaminated water",
KILLED_BY);
exercise(A_CON, FALSE);
break;
case 22: /* Fountain of snakes! */
dowatersnakes();
break;
case 23: /* Water demon */
dowaterdemon();
break;
case 24: { /* Maybe curse some items */
register struct obj *obj;
int buc_changed = 0;
pline("This water's no good!");
morehungry(rn1(20, 11));
exercise(A_CON, FALSE);
/* this is more severe than rndcurse() */
for (obj = gi.invent; obj; obj = obj->nobj)
if (obj->oclass != COIN_CLASS && !obj->cursed && !rn2(5)) {
curse(obj);
++buc_changed;
}
if (buc_changed)
update_inventory();
break;
}
case 25: /* See invisible */
if (Blind) {
if (Invisible) {
You("feel transparent.");
} else {
You("feel very self-conscious.");
pline("Then it passes.");
}
} else {
You_see("an image of someone stalking you.");
pline("But it disappears.");
}
HSee_invisible |= FROMOUTSIDE;
newsym(u.ux, u.uy);
exercise(A_WIS, TRUE);
break;
case 26: /* See Monsters */
if (monster_detect((struct obj *) 0, 0))
pline_The("%s tastes like nothing.", hliquid("water"));
exercise(A_WIS, TRUE);
break;
case 27: /* Find a gem in the sparkling waters. */
if (!FOUNTAIN_IS_LOOTED(u.ux, u.uy)) {
dofindgem();
break;
}
/*FALLTHRU*/
case 28: /* Water Nymph */
dowaternymph();
break;
case 29: /* Scare */
{
register struct monst *mtmp;
pline("This %s gives you bad breath!",
hliquid("water"));
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
if (DEADMONSTER(mtmp))
continue;
monflee(mtmp, 0, FALSE, FALSE);
}
break;
}
case 30: /* Gushing forth in this room */
dogushforth(TRUE);
break;
default:
pline("This tepid %s is tasteless.",
hliquid("water"));
break;
}
}
dryup(u.ux, u.uy, TRUE);
}
void
dipfountain(register struct obj *obj)
{
int er = ER_NOTHING;
if (Levitation) {
floating_above("fountain");
return;
}
if (obj->otyp == LONG_SWORD && u.ulevel >= 5 && !rn2(6)
/* once upon a time it was possible to poly N daggers into N swords */
&& obj->quan == 1L && !obj->oartifact
&& !exist_artifact(LONG_SWORD, artiname(ART_EXCALIBUR))) {
static const char lady[] = "Lady of the Lake";
if (u.ualign.type != A_LAWFUL) {
/* Ha! Trying to cheat her. */
pline("A freezing mist rises from the %s and envelopes the sword.",
hliquid("water"));
pline_The("fountain disappears!");
curse(obj);
if (obj->spe > -6 && !rn2(3))
obj->spe--;
obj->oerodeproof = FALSE;
exercise(A_WIS, FALSE);
livelog_printf(LL_ARTIFACT,
"was denied %s! The %s has deemed %s unworthy",
artiname(ART_EXCALIBUR), lady, uhim());
} else {
/* The lady of the lake acts! - Eric Backus */
/* Be *REAL* nice */
pline(
"From the murky depths, a hand reaches up to bless the sword.");
pline("As the hand retreats, the fountain disappears!");
obj = oname(obj, artiname(ART_EXCALIBUR),
ONAME_VIA_DIP | ONAME_KNOW_ARTI);
discover_artifact(ART_EXCALIBUR);
bless(obj);
obj->oeroded = obj->oeroded2 = 0;
obj->oerodeproof = TRUE;
exercise(A_WIS, TRUE);
livelog_printf(LL_ARTIFACT, "was given %s by the %s",
artiname(ART_EXCALIBUR), lady);
}
update_inventory();
levl[u.ux][u.uy].typ = ROOM, levl[u.ux][u.uy].flags = 0;
newsym(u.ux, u.uy);
gl.level.flags.nfountains--;
if (in_town(u.ux, u.uy))
(void) angry_guards(FALSE);
return;
} else {
er = water_damage(obj, NULL, TRUE);
if (er == ER_DESTROYED || (er != ER_NOTHING && !rn2(2))) {
return; /* no further effect */
}
}
switch (rnd(30)) {
case 16: /* Curse the item */
if (obj->oclass != COIN_CLASS && !obj->cursed) {
curse(obj);
}
break;
case 17:
case 18:
case 19:
case 20: /* Uncurse the item */
if (obj->cursed) {
if (!Blind)
pline_The("%s glows for a moment.", hliquid("water"));
uncurse(obj);
} else {
pline("A feeling of loss comes over you.");
}
break;
case 21: /* Water Demon */
dowaterdemon();
break;
case 22: /* Water Nymph */
dowaternymph();
break;
case 23: /* an Endless Stream of Snakes */
dowatersnakes();
break;
case 24: /* Find a gem */
if (!FOUNTAIN_IS_LOOTED(u.ux, u.uy)) {
dofindgem();
break;
}
/*FALLTHRU*/
case 25: /* Water gushes forth */
dogushforth(FALSE);
break;
case 26: /* Strange feeling */
pline("A strange tingling runs up your %s.", body_part(ARM));
break;
case 27: /* Strange feeling */
You_feel("a sudden chill.");
break;
case 28: /* Strange feeling */
pline("An urge to take a bath overwhelms you.");
{
long money = money_cnt(gi.invent);
struct obj *otmp;
if (money > 10) {
/* Amount to lose. Might get rounded up as fountains don't
* pay change... */
money = somegold(money) / 10;
for (otmp = gi.invent; otmp && money > 0; otmp = otmp->nobj)
if (otmp->oclass == COIN_CLASS) {
int denomination = objects[otmp->otyp].oc_cost;
long coin_loss =
(money + denomination - 1) / denomination;
coin_loss = min(coin_loss, otmp->quan);
otmp->quan -= coin_loss;
money -= coin_loss * denomination;
if (!otmp->quan)
delobj(otmp);
}
You("lost some of your gold in the fountain!");
CLEAR_FOUNTAIN_LOOTED(u.ux, u.uy);
exercise(A_WIS, FALSE);
}
}
break;
case 29: /* You see coins */
/* We make fountains have more coins the closer you are to the
* surface. After all, there will have been more people going
* by. Just like a shopping mall! Chris Woodbury */
if (FOUNTAIN_IS_LOOTED(u.ux, u.uy))
break;
SET_FOUNTAIN_LOOTED(u.ux, u.uy);
(void) mkgold((long) (rnd((dunlevs_in_dungeon(&u.uz) - dunlev(&u.uz)
+ 1) * 2) + 5),
u.ux, u.uy);
if (!Blind)
pline("Far below you, you see coins glistening in the %s.",
hliquid("water"));
exercise(A_WIS, TRUE);
newsym(u.ux, u.uy);
break;
default:
if (er == ER_NOTHING)
pline("Nothing seems to happen.");
break;
}
update_inventory();
dryup(u.ux, u.uy, TRUE);
}
void
breaksink(coordxy x, coordxy y)
{
if (cansee(x, y) || u_at(x, y))
pline_The("pipes break! Water spurts out!");
gl.level.flags.nsinks--;
levl[x][y].typ = FOUNTAIN, levl[x][y].looted = 0;
levl[x][y].blessedftn = 0;
SET_FOUNTAIN_LOOTED(x, y);
gl.level.flags.nfountains++;
newsym(x, y);
}
void
drinksink(void)
{
struct obj *otmp;
struct monst *mtmp;
if (Levitation) {
floating_above("sink");
return;
}
switch (rn2(20)) {
case 0:
You("take a sip of very cold %s.", hliquid("water"));
break;
case 1:
You("take a sip of very warm %s.", hliquid("water"));
break;
case 2:
You("take a sip of scalding hot %s.", hliquid("water"));
if (Fire_resistance) {
pline("It seems quite tasty.");
monstseesu(M_SEEN_FIRE);
} else
losehp(rnd(6), "sipping boiling water", KILLED_BY);
/* boiling water burns considered fire damage */
break;
case 3:
if (gm.mvitals[PM_SEWER_RAT].mvflags & G_GONE)
pline_The("sink seems quite dirty.");
else {
mtmp = makemon(&mons[PM_SEWER_RAT], u.ux, u.uy, MM_NOMSG);
if (mtmp)
pline("Eek! There's %s in the sink!",
(Blind || !canspotmon(mtmp)) ? "something squirmy"
: a_monnam(mtmp));
}
break;
case 4:
do {
otmp = mkobj(POTION_CLASS, FALSE);
if (otmp->otyp == POT_WATER) {
obfree(otmp, (struct obj *) 0);
otmp = (struct obj *) 0;
}
} while (!otmp);
otmp->cursed = otmp->blessed = 0;
pline("Some %s liquid flows from the faucet.",
Blind ? "odd" : hcolor(OBJ_DESCR(objects[otmp->otyp])));
otmp->dknown = !(Blind || Hallucination);
otmp->quan++; /* Avoid panic upon useup() */
otmp->fromsink = 1; /* kludge for docall() */
(void) dopotion(otmp);
obfree(otmp, (struct obj *) 0);
break;
case 5:
if (!(levl[u.ux][u.uy].looted & S_LRING)) {
You("find a ring in the sink!");
(void) mkobj_at(RING_CLASS, u.ux, u.uy, TRUE);
levl[u.ux][u.uy].looted |= S_LRING;
exercise(A_WIS, TRUE);
newsym(u.ux, u.uy);
} else
pline("Some dirty %s backs up in the drain.", hliquid("water"));
break;
case 6:
breaksink(u.ux, u.uy);
break;
case 7:
pline_The("%s moves as though of its own will!", hliquid("water"));
if ((gm.mvitals[PM_WATER_ELEMENTAL].mvflags & G_GONE)
|| !makemon(&mons[PM_WATER_ELEMENTAL], u.ux, u.uy, MM_NOMSG))
pline("But it quiets down.");
break;
case 8:
pline("Yuk, this %s tastes awful.", hliquid("water"));
more_experienced(1, 0);
newexplevel();
break;
case 9:
pline("Gaggg... this tastes like sewage! You vomit.");
morehungry(rn1(30 - ACURR(A_CON), 11));
vomit();
break;
case 10:
pline("This %s contains toxic wastes!", hliquid("water"));
if (!Unchanging) {
You("undergo a freakish metamorphosis!");
polyself(POLY_NOFLAGS);
}
break;
/* more odd messages --JJB */
case 11:
You_hear("clanking from the pipes...");
break;
case 12:
You_hear("snatches of song from among the sewers...");
break;
case 19:
if (Hallucination) {
pline("From the murky drain, a hand reaches up... --oops--");
break;
}
/*FALLTHRU*/
default:
You("take a sip of %s %s.",
rn2(3) ? (rn2(2) ? "cold" : "warm") : "hot",
hliquid("water"));
}
}
/*fountain.c*/