interactive fiction

How Infidel Hid a Wild Pointer in the Desert

How Infidel Hid a Wild Pointer in the Desert

Drop a pickaxe in Infidel's desert, walk east, then west, and it is waiting for you. The map feels enormous, but the game is quietly reusing one room to represent every stretch of sand beyond the camp. That elegant trick hides a memory-corruption bug: a compiler sends the game to the wrong address, and ordinary play barely notices.

Infidel is a 1983 work of interactive fiction, a text-driven game controlled through typed commands. Its source was written in ZIL, short for Zork Implementation Language, and compiled into a story file for the Z-machine, a small virtual computer designed to run the same game logic on different home computers. The affected Macintosh update is release 22, serial 840522; the earlier release contains the same mistake at different addresses.

The desert is one room pretending to be many

The mapped part of the game contains a small grid of desert locations. Once you wander outside it, the game funnels you into ENDLESS-DESERT, a reusable room. Your changing latitude and longitude create the illusion that you are crossing a much larger wilderness.

That illusion becomes convincing when you drop an object. A portable object is one marked as takeable, such as the pickaxe or shovel. When you leave the endless desert, the game removes those objects from the room and records each one in DESERT-TABLE, an array: a continuous block of memory divided into numbered slots.

The basic idea looks like this:

for each portable object in ENDLESS_DESERT:
 slot = next_empty_pair(DESERT_TABLE)
 DESERT_TABLE[slot] = desert_coordinate
 DESERT_TABLE[slot + 1] = object_number
 remove object

Each pair stores a location and an object number. When you return to those coordinates, another routine scans the pairs and moves the matching objects back into the shared room. The feature is a nice example of getting more geography out of less memory.

The number that looked like an address

The trouble begins with a local variable named TBL. The routine intends TBL to contain the address of DESERT-TABLE, so every later read and write uses the correct array.

In the source, that default looks harmless: TBL starts as DESERT-TABLE. But this is a Version 3 Z-machine program, and Version 3 routines store their initial local values in a small header as fixed 16-bit numbers. A global variable is not a fixed number. It is a shared value stored elsewhere in the machine's writable memory.

The compiler confused the global variable's slot number with the value stored in that slot. In this release, DESERT-TABLE lives at byte address 11129, but its global-variable number is 30.

intended:
 TBL = 11129 # the table's actual memory address

buggy output:
 TBL = 30 # the number used to refer to the global variable

That distinction is the whole bug. The routine does not begin with a pointer to the table. It begins with the number 30, then treats that number as an array base address.

A wrong write can look correct for a long time

The Z-machine uses byte addresses, meaning each address identifies one byte of memory. Its ordinary data values are words, which occupy two bytes. Writing the first pair through the bad base address therefore touches bytes 30 and 32; the next pair touches 34 and 36, then 38 and 40.

None of those writes reaches DESERT-TABLE at address 11129. They march across the beginning of the story file instead. In this Version 3 layout, much of the region from address 30 through the low sixties is unused or unimportant header space, so the first few dropped objects leave no visible trace.

After roughly nine stored objects, the writes reach the game's abbreviation data. The Z-machine compresses common words by replacing them with short codes that point into this area. Once those entries are damaged, printed text can contain strange substitutions, run into unrelated strings, or send the interpreter into a crash.

The especially devious part is that the routine which brings objects back uses the same bad default. It reads from the same accidental memory region where the first routine wrote. The two mistakes partially cancel each other, allowing the pickaxe to disappear and reappear as though the system worked correctly.

Reproducing the failure is awkward for another reason: objects dropped in the endless desert can be buried by sand before they are recorded. Most players also have little reason to leave nine portable objects in a place that contains no useful destination. The bug needed an unusual experiment, not an ordinary solution path.

The disassembler tells a different story

A disassembler is a tool that turns compiled instructions back into readable operation names. Looking at the routine after compilation reveals the important sequence:

load_word TBL, CNT
store_word TBL, CNT, coordinate

The instructions are perfectly capable of reading and writing an array. They are not checking whether TBL points to the right array, though. By the time these instructions run, TBL already contains 30, so the machine faithfully performs the wrong operation.

This is why the defect is better described as a wild pointer bug. The Z-machine does not have C-style pointer types, but the failure mode is familiar: a value intended to name one memory region is used as an address for another. The resulting corruption is silent until it reaches data that matters.

The fix is small; the lesson is not

The safe repair is to load the global value at runtime instead of placing it in the routine's constant-initialization header:

<SET TBL,DESERT-TABLE>

Passing DESERT-TABLE as an ordinary argument would also work. A stricter compiler could have rejected the original default because it was not a true constant, or generated an explicit load instruction automatically.

The larger lesson reaches beyond Infocom and ZIL. A memory bug does not need to crash at the moment it is created. It can hide behind a feature that appears to work, especially when both sides of a read-and-write process share the same mistake. In Infidel, a clever illusion kept players looking at the desert while the real story was unfolding in the first few dozen bytes of memory.

The pickaxe returns because the data path is consistent, not because it is correct. Once the addresses become visible, the endless desert stops being a mystery and becomes a tidy little map of compiler assumptions, overwritten tables, and a bug that waited decades for someone to look underneath the sand.

ahsan

ahsan

Hello! I am Mr Ahsan, the writer of the Website. I am from Netherland. I like to write about technology and the news around it.

Comments (0)

No comments yet. Be the first to respond!

Leave a Comment

Your comment will be visible after review.