NetHack / NetHack

Official NetHack Git Repository

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

out of bound access in uhpinc and ueninc

nhmall opened this issue · comments

As reported in closed pull request #1188 by @mkuoppal which stated

hpinc and ueninc are indexed by u.ulevel. ulevel can reach MAXULEV (30), which means that the 0-29 range of uhpinc and ueninc arrays is not enough and out of bounds by one will result:

exper.c:245:25: runtime error: index 30 out of bounds for type 'xint16 [30]'
exper.c:263:25: runtime error: index 30 out of bounds for type 'xint16 [30]'

Look into the indexing via [u.ulevel] vs [u.ulevel - 1]

Some references:

include/global.h:439 #define MAXULEV 30 /* max character experience level */
include/you.h:364:    int ulevel;         /* 1 to MAXULEV (30) */
include/you.h:466:    xint16 uhpinc[MAXULEV],  /* increases to uhpmax for each level gain */
include/you.h:467:          ueninc[MAXULEV];   /* increases to uenmax for each level gain */
u_init.c-946-    if (num_spells() && (u.uenmax < SPELL_LEV_PW(1)))
u_init.c:947:        u.uen = u.uenmax = u.uenpeak = u.ueninc[u.ulevel] = SPELL_LEV_PW(1);
src/u_init.c:947:        u.uen = u.uenmax = u.uenpeak = u.ueninc[u.ulevel] = SPELL_LEV_PW(1)
src/attrib.c-1079-    if (u.ulevel < MAXULEV) {
src/attrib.c-1080-        /* remember increment; future level drain could take it away again */
src/attrib.c:1081:        u.uhpinc[u.ulevel] = (xint16) hp;
polyself.c-376-    for (i = 0; i < oldlvl; i++)
polyself.c:377:        hpmax -= (int) u.uhpinc[i];
polyself.c-391-    for (i = 0; i < oldlvl; i++)
polyself.c:392:        enmax -= (int) u.ueninc[i];
src/exper.c-68-    if (u.ulevel < MAXULEV) {
src/exper.c-69-        /* remember increment; future level drain could take it away again */
src/exper.c:70:        u.ueninc[u.ulevel] = (xint16) en;
src/exper.c:245:    num = (int) u.uhpinc[u.ulevel];
src/exper.c:263:    num = (int) u.ueninc[u.ulevel];

Those references look perfectly safe to me without even delving into the actual code. The u_init.c one occurs when u.ulevel is 1. The attrib.c one, the polyself.c two, and the first exper.c one all test that the potential index is less that MAXULVL or oldlvl so won't access the array using 30.

The two out of bounds errors are definitely both bogus. For those, I did look at the code. Both of those expressions take place when losing an experience level, after
if (u.ulevel > 1) u.ulevel -= 1;
has executed. So the maximum potential index there is 29, not 30.

@mkuoppal did you actually encounter u.ulevel being 30 at runtime here?
(exper.c:245:25: runtime error: index 30 out of bounds for type 'xint16 [30]')
If so, do you have a backtrace available, or anything to pinpoint how to reproduce it?

I did #debugfuzzer and got that printout in stderr. I have now enabled option to get backtrace on ubsan errors so lets hope I can trigger it again.