July 6, 2018·6 min read·engineeringrustsecuritydocumentation

Rustdoc discloses an unsafe default plugin directory

A deprecated plugin loader can execute a library placed in a shared temporary directory. The advisory distinguishes this trust failure from the memory-safety guarantees of Rust.

A tool that loads executable extensions has to decide whose files it trusts. Rustdoc's deprecated plugin mechanism makes that decision incorrectly when it uses a shared temporary directory as its default source of libraries. Today's security advisory explains that another user can place a library in that location and have code executed through the plugin-loading path.

The advisory also restricts the exposure carefully. Exposure requires active use of rustdoc plugins. The team reports no known users of the feature and says its required library is not shipped in current Rust distributions. It describes the infrastructure as predating Rust 1.0 and unusable with current stable or nightly releases. Those qualifications are part of the security information, not details to omit in pursuit of a more dramatic story.

I find the incident instructive because my interest in Rust rests on its semantics, not a belief that a language can make every operational decision safe. Memory safety cannot determine whether the executable library selected by a program belongs to a trusted author. That remains a question about how the tool interprets its environment.

The directory is part of the authority decision

The Rust 1.27.0 rustdoc source contains the default path /tmp/rustdoc/plugins. When no plugin path is supplied, the tool constructs a plugin manager using that location. It then loads the requested plugins through the manager.

On most platforms, the advisory says, the directory is globally writable. That creates a mismatch between the person choosing to use a plugin and the person able to provide the file the tool finds. A name selected by one user does not establish ownership of the corresponding library in shared storage.

The resulting security boundary is local to the loading operation. If another actor can supply executable content at the expected path, the tool must have a reason to trust that content before loading it. Merely locating a file with the expected name does not provide such a reason. The path is therefore more than a convenience setting.

This is different from saying every use of a temporary directory is unsafe. Temporary storage has legitimate purposes. The problem here is selecting executable extension code from a location another actor can influence. The role of the file determines why the directory's write permissions matter so much.

The manager opens a dynamic library

The plugin manager implementation shows the next steps. It converts a plugin name into the platform's dynamic-library filename, joins that name to the prefix directory, and opens the resulting library. It then looks up rustdoc_plugin_entrypoint and stores a callback for later execution.

The manager also handles platform-specific filenames. Its source appends .dll on Windows and .dylib on macOS. On the other platforms covered by its fallback, it adds a lib prefix and a .so suffix. A shared logical plugin name therefore leads to a concrete library lookup determined by the platform.

This conversion does not establish who supplied the resulting file. Filename construction and trust are different properties. The tool can derive exactly the filename it intended and still open content written by another actor. Correctly implementing the naming convention is insufficient to make a globally writable lookup directory appropriate.

The source retains the opened library in the manager as well as the callback. Later, it runs the callbacks over the cleaned crate representation. These details make the mechanism concrete. The plugin is executable native code integrated into rustdoc's processing, rather than a passive configuration file or a Markdown document.

That distinction explains the stated consequence. A malicious library supplied at the selected path is given an opportunity to execute in the tool's process. The advisory describes another user's execution of that code as the impact. No memory corruption in the document parser is needed to explain the trust failure.

The loader also supports ordinary built-in passes through normal Rust functions. Their existence should not be confused with the external dynamic-plugin path. The security report concerns loading an attacker-supplied library from the writable directory. Treating every internal documentation pass as an affected plugin would broaden the claim beyond the mechanism disclosed.

The feature has to be used

The source distinguishes collecting plugin names from loading them. It processes the configured names and then calls the loader for those requests. The mere presence of the default-path string in rustdoc is not evidence that every invocation executes a library from that path.

This agrees with the advisory's statement that users not using rustdoc plugins are unaffected. A useful exposure description needs both the reachable operation and the actor's control over its input. Omitting the active-use condition would turn a deprecated-feature issue into an unsupported claim about routine documentation generation.

The missing library in current distributions narrows practical use further. The team describes the infrastructure as unusable in today's stable and nightly environments. That does not make the default directory a good design. It explains why removing the remaining behavior is expected to have little impact on users despite correcting a real security defect.

There is an important difference between an insecure mechanism and a widely reachable one. A mechanism can be wrong while its deployment conditions make exploitation uncommon. Reporting both properties allows a reader to understand the design flaw without inventing a larger affected population.

Memory safety does not authenticate plugins

The advisory explicitly calls the bug a logic error and warns against broad claims that Rust makes programs inherently secure. That is a useful technical distinction. A language can reject invalid memory access in safe code while a program deliberately loads and invokes the wrong library.

Authentication of an extension's origin is not something the borrow checker can infer from a pathname. The program needs an appropriate policy about who can place code there and how it is selected. If that policy grants authority to an untrusted file, memory-safe control flow can faithfully execute the wrong decision.

The unsafe operations visible inside the loader deserve attention in an implementation audit, but their presence alone is not the explanation for this report. The disclosed issue is that the source of executable content is globally writable. Replacing a particular unsafe expression would not establish that a selected library is trustworthy.

I regard that distinction as essential when evaluating Rust for systems work. Its guarantees address substantial classes of programming errors, while systems still require correct decisions about authority. Understanding the guarantees precisely makes the language easier to evaluate than treating any security advisory as either a refutation or an endorsement of all its claims.

Removal is the announced remedy

Red Hat reported the issue on July 3. The July 6 advisory says the behavior will be removed, with patches planned for the release channels. It names Rust 1.27.1 as the intended stable fix. As of this announcement, that patch is planned, and describing it as already released would get the sequence wrong.

The choice of removal follows the feature's status. The team says it knows of no users and does not expect removal to affect them. Keeping an obsolete loading path would preserve a trust decision that the current distributions do not need for supported plugin use. Deleting it removes the route described in the report.

This also clarifies the response for ordinary users reading the announcement. The source does not advise that every existing documentation output is compromised. It identifies an execution mechanism requiring a specific deprecated feature. The appropriate scope of the claim follows that mechanism, rather than the general fact that a tool processing source code has received an advisory.

Reporting can preserve the conditions of a flaw

The team says it followed its full security procedure despite the low reported impact and severity. The public account includes when the report arrived and what the planned fix changes. It also explains why disclosure occurs before the patch. Those facts let users distinguish the report, the announced response, and the eventual software update.

For maintainers of extension systems, the directly applicable design question is whether a trusted operation can select executable code supplied by a less-trusted actor. That question remains relevant even where a plugin interface is obscure or deprecated. The rustdoc source makes the answer visible through its directory default and dynamic loader. Removing that path addresses this particular issue without changing what Rust's memory-safety rules promise for ordinary application code.