Fix a very elusive PHOT reflection crash

get_normal_interp is given a PHOT and scans a line section starting from the PHOT, extended in the direction of its velocity, to determine the surface normal of the surface that is reflecting the PHOT. It uses is_boundary to detect "boundaries" on this line section, defined as one "blocking" cell with at least one "non-blocking" non-diagonal neighbour. It never actually makes sure the position it passes to is_boundary is within the simulation area, so I assume is_boundary is expected to handle this correctly.

Plot twist: it does not. It delegates checking whether a cell is "blocking" (defined as something PHOT would normally fail to move into as per eval_move, but GLAS and BLGA are handled specially) to is_blocking. is_blocking returns true for cells beyond the simulation area (as eval_move returns "no move" for such cells). Once is_boundary sees that the cell it's been given is blocking, it then proceeds to check whether any of its non-diagonal neighbours might be non-blocking. In most cases, non-diagonal neighbours of an out-of-bounds cell are also out of bounds and are thus blocking, but if the cell is_boundary is given is in the innermost layer of out-of-bounds cells, just beyond the simulation area, then it has a neighbour that is in bounds, and that one may not block PHOT. The takeaway is that out of bounds cells can indeed be boundaries, as far as is_boundary is concerned.

This is a problem because get_normal_interp's line section can easily reach beyond the simulation area if the PHOT's velocity is high enough, and if it finds a boundary along this line section, it immediately stops looking and passes its position to photoelectric_effect, which then uses this potentially out-of-bounds position to index pmap. A position with y = -1 causes photoelectric_effect to read from the last few slots of parts data that it then interprets as pmap entries, which then may direct it to particles to spark that are beyond parts. This eventually crashes.

This commit doesn't fix is_boundary's definition of boundaries, but it stops get_normal_interp looking at cells beyond the simulation area.

The crash is difficult to reproduce because there have to be many particles in the simulation for the very last slots of parts to be in use, and for them to point to memory that isn't accessible. PHOT also has to survive a try_move beyond the simulation area first (otherwise the reflection code isn't even run), which requires it to start from EHOLE. I have no idea why this is so. Reproduce the out of bounds read in photoelectric_effect with

	break Simulation.cpp:2934 if nx < 0

in gdb and executing the following Lua code:

	sim.clearSim()
	tpt.set_wallmap(55, 44, 12)
	local i = sim.partCreate(-1, 223, 178, 31)
	sim.partProperty(i, "vx", -300)
	sim.partProperty(i, "vy", 0)
	sim.framerender(1)
This commit is contained in:
Tamás Bálint Misius 2022-10-02 22:22:30 +02:00
parent 5d66533f23
commit b4462273b0
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2

View File

@ -3081,6 +3081,10 @@ int Simulation::get_normal_interp(int pt, float x0, float y0, float dx, float dy
for (i=0; i<NORMAL_INTERP; i++) { for (i=0; i<NORMAL_INTERP; i++) {
x = (int)(x0 + 0.5f); x = (int)(x0 + 0.5f);
y = (int)(y0 + 0.5f); y = (int)(y0 + 0.5f);
if (x < 0 || y < 0 || x >= XRES || y >= YRES)
{
return 0;
}
if (is_boundary(pt, x, y)) if (is_boundary(pt, x, y))
break; break;
x0 += dx; x0 += dx;