The Runtime Contract Behind a Roadblock

For FIRMENSTEIN, the runtime contract is the missing middle between “the firmware service started” and “the service is behaving usefully.” This chapter focuses on what a Linux-based service expects from its surroundings, how those expectations cross an API boundary, and why the distinction helps readers diagnose a failure instead of applying the same repair to every target.

The word contract does not mean that firmware comes with a tidy specification. Much of the relevant software is distributed across stripped binaries, libraries, configuration stores, device interfaces, and companion processes. The contract is an inferred relationship: when the service asks the environment for something, the response must have the value, side effect, and timing that the service’s own logic assumes. If one part is missing or semantically wrong, the next failure may appear far away from its cause.

The service is more than its executable

Rehosting aims to execute firmware on a general-purpose computer so that analysts can perform dynamic security analysis without relying on the original device. That substitution is useful only if the service still encounters a sufficiently faithful runtime. A Linux-based firmware service can depend on a root filesystem, network configuration, operating-system resources, proprietary kernel modules, device nodes, persistent configuration such as NVRAM, and other daemon processes. These are not decorative accessories around the program. They are sources of inputs and state transitions.

An API is the boundary through which those sources become visible to program logic. At the lowest level, a system call may open a device, read a pseudo-file, create a socket, or request an operation from the kernel. At a higher level, a library function may wrap several such actions and return a value that looks like an ordinary function result. In both cases, the service reacts to what happened, not just to the existence of the call. A return value can select a branch. A write into a caller-provided buffer can supply the data for a later comparison. A changed global or persistent resource can influence another function that never directly called the original API.

This is why a runtime mismatch becomes a control-flow problem. Imagine the service asks for a network address. If the emulated environment supplies an address that is syntactically valid but does not match the configured value the service expects, the helper may report success and the service may still exit. The API has done something plausible in isolation; it has failed the contract in context. Conversely, a forced success code may get past an immediate conditional while leaving an output structure empty. The next parser then fails, loops, or takes an error path for reasons that are consequences of the earlier omission.

The choice between full-system and user-space rehosting changes where this reconstruction occurs. Full-system emulation includes the guest kernel and a larger portion of the system stack. That can make dependent processes and operating-system behavior available in a more natural way, but it also increases configuration difficulty and runtime overhead. User-space emulation runs the target process more directly and is attractive for high-throughput dynamic analysis. Its efficiency comes with a responsibility: missing device behavior, files, network state, or daemon interactions must be supplied through targeted interventions.

Neither strategy should be treated as automatically faithful. Full-system emulation can still fail when a proprietary component is unavailable or a critical process cannot start. User-space emulation can be efficient while silently steering the service into a path it would not take on the device. The relevant question is not which mode has the more complete-looking diagram. It is whether the chosen mode produces the environmental outcomes that preserve the service’s important decisions.

State ownership makes the difference easier to see. Some state belongs to the service itself, some is held by the operating system or a device-facing interface, and some is maintained by a companion daemon or persistent configuration store. An API call crosses the boundary between those owners. Its return value is the part the caller checks immediately, but its side effects may be the part that transfers ownership of meaningful state into the service: a buffer is filled, a global is updated, or a resource becomes available for a later operation. An intervention that changes only the caller’s immediate control signal preserves one observable while breaking the larger exchange.

The timing of the exchange matters as well. A service may initialize a resource, query it through a wrapper, compare the result with configuration, and only then enter its request-processing path. A substitute that is present at launch but cannot participate in that sequence is not equivalent to a working dependency. The same is true of a daemon: making a socket endpoint exist does not establish that the process on the other side can answer with the state the service expects. Rehosting therefore has to preserve transitions, not just endpoints. The analyst needs to know what event moves the service from setup into usable interaction and what evidence would show that the move happened for the right reason.

This perspective also changes how failures should be investigated. The instruction that crashes is often only the first place where inconsistent state becomes impossible to ignore. Looking only at that location encourages a local patch, even when the state was created several calls earlier or in another process. Looking backward along the API exchange asks a more productive question: which environment-owned value or side effect first stopped matching the service’s assumptions? That question is what allows FIRMENSTEIN to separate a missing dependency from a semantically invalid result, even when both eventually look like the same early exit.

That standard also explains why a process-level success signal is weak evidence. Starting a process establishes an initial condition. Connecting to its service shows that some external interaction is possible. Neither observation proves that configuration updates, authentication, request parsing, stateful behavior, or error handling will follow the intended logic. For FIRMENSTEIN, fidelity is about preserving the behavior that later analysis needs to observe, not about making an emulator look quiet during startup.

Three ways the contract can break

FIRMENSTEIN calls an execution obstruction caused by an unsatisfied runtime dependency a roadblock. More specifically, a roadblock appears when an API’s outcome violates an internal constraint in the firmware’s logic. This definition points analysts toward the relationship between an environment-facing call and the later code that interprets its result. It also separates three mechanisms that can produce a similar symptom—an early exit, a crash, or a shallow execution path—but require different remedies.

stateDiagram-v2 [*] --> EnvironmentAvailable EnvironmentAvailable : Environment available APICall : Environment-facing API call MissingDependency : Missing dependency InvalidResult : Semantically invalid result UnresolvedIPC : IPC dependency unresolved Intervention : Constraint-preserving intervention Connect : Connect state Interact : Interact state Abnormal : Abnormal exit or shallow path EnvironmentAvailable --> APICall APICall --> MissingDependency APICall --> InvalidResult APICall --> UnresolvedIPC MissingDependency --> Abnormal InvalidResult --> Abnormal UnresolvedIPC --> Abnormal MissingDependency --> Intervention InvalidResult --> Intervention UnresolvedIPC --> Intervention Intervention --> Connect Connect --> Interact

Missing runtime dependencies

The first mechanism is the most intuitive: the service accesses a resource that the emulated environment does not provide. The paper’s example uses a proprietary device node. A service opens that node and, if the open fails, takes an abnormal path. A conventional repair may create an empty device file so that the open succeeds, then force a subsequent device operation to return success.

That repair is incomplete if the operation is supposed to populate a request structure in memory. The return code answers one question—whether the operation reported success—but the structure answers another—what information the operation made available. If the structure remains uninitialized, the later rule parser cannot perform the work that made the device interaction necessary in the first place. The repair has changed the symptom while preserving the semantic roadblock.

This case also illustrates why side effects belong to the contract. The service owns the logic that interprets the structure, but the environment-facing operation owns the responsibility to produce it. A useful intervention must reproduce enough of that exchange for downstream code to see a coherent state. It need not recreate the physical device in every detail, but it cannot discard the particular state transition on which the path depends.

Semantic constraint violations

The second mechanism is more deceptive because the underlying calls can all succeed. A helper may retrieve a network interface address, write it into a buffer, and return a success code. The service then compares that address with a configured LAN value. If they differ, the service terminates even though no syscall has failed.

Here the environmental dependency is not simply “an interface exists.” It is “the value supplied by the interface is consistent with the configuration state used by this program.” The constraint is visible only when the helper’s output is followed into the later logic. A rehosting system that monitors failed syscalls can therefore miss the root cause entirely. It sees a successful call and an eventual exit, but not the semantic connection between them.

This category is especially important for high-fidelity analysis because wrong values can be more damaging than missing values. A missing resource may produce an obvious error and a useful trace. A plausible but inconsistent value can carry the program into a branch that looks legitimate while excluding the behavior the analyst hoped to test. The resulting execution is not merely incomplete; it can create false confidence about what the firmware would do.

Unresolved inter-process dependencies

The third mechanism involves a service that expects another process to participate in its runtime. Firmware commonly separates responsibilities among daemons, with communication mediated by inter-process communication. In the paper’s example, a service connects through a Unix-domain socket to a configuration manager. If the daemon is absent or has not been initialized, the connection fails and the service exits.

The remedy is not automatically to fake every message. If the dependent daemon is present in the firmware image and its behavior is important or complex, preparing the environment and launching that process may be the more faithful choice. If the interaction is simple and its relevant semantics can be isolated, execution intervention may model the API behavior instead. The mechanism of failure—unresolved IPC—identifies the dependency, but it does not by itself determine the intervention form.

These three categories are not a claim that every failure in every firmware image fits one of three boxes. They are a useful way to avoid collapsing different causes into “the emulator needs another heuristic.” Missing resources, inconsistent values, and absent processes all obstruct execution, but their contracts differ. A device operation may need to fill memory; a network helper may need to return a value consistent with configuration; a daemon-mediated API may need a live process or a sufficiently faithful model of its exchange.

The cases make “minimal” mean semantically sufficient

The paper’s concrete cases sharpen a distinction that otherwise sounds abstract: the smallest textual change is not necessarily the smallest faithful intervention. In the Tenda AC10 example, a web service checks local network configuration and attempts to connect to a configuration daemon before entering its request-listening loop. Greenhouse removes those checks so execution can continue regardless of their outcomes. But the patch also changes the control flow: the branch containing the listening and request-handling logic becomes dead code. The service appears to have fewer obstacles while losing the behavior the analysis was meant to reach.

FIRMENSTEIN instead synthesizes targeted interventions for the checks, preserving the essential logic and supplying outcomes that let the intended path remain live. The important contrast is not that one system modifies code and the other does not. Both may alter how the service encounters its environment. The contrast is whether the alteration respects the relationship between the check and the work that follows it.

The Netgear R6350 case makes the same point through returned content. A configuration lookup supplies a portal IP address to a global buffer. The service requires that value to be long enough before constructing a URL and invoking its web-listening function. If the lookup does not provide a valid initial value, the URL remains uninitialized and an assertion terminates execution. FIRMENSTEIN’s intervention supplies a value satisfying the downstream length constraint and populates the state that the later function consumes.

In both examples, “minimal” means enough semantics to preserve the intended transition. It does not mean “return success as early as possible” or “delete the conditional.” A repair that reproduces an entire physical subsystem may be unnecessarily expensive. A repair that suppresses a gate but omits the state created by the gated operation is too small. The useful middle ground is targeted: identify the condition that matters, provide the state and side effects that condition represents, and leave unrelated firmware logic in control.

That is the runtime contract FIRMENSTEIN must recover before it can generate code. It explains why later analysis cannot begin with a universal list of files to create or calls to bypass. The same API name can sit inside different dependency relationships, and the same abnormal exit can be the endpoint of a missing resource, a wrong value, or a daemon that never answered. The next problem is therefore one of visibility: how to trace those relationships across stripped binaries, libraries, shared state, and IPC so that a roadblock can be diagnosed at the level where its meaning is actually determined.

References