March 5, 2019·4 min read·engineeringrubyrubygemssecurity

RubyGems fixes unsafe interpretation of package metadata

The March RubyGems advisories cover archive deletion, gemspec code injection, and terminal escape sequences. The common concern is metadata acquiring authority during installation or display.

Package metadata is often treated as descriptive information, but an installer can give that information operational authority. It becomes a destination path, generated source code, or terminal output. The March 5 RubyGems advisories describe failures at each of those boundaries. They disclose CVE-2019-8320 through CVE-2019-8325 and recommend RubyGems 3.0.3 or 2.7.8.

The Ruby project's corresponding notice identifies affected Ruby installations through the RubyGems versions bundled with them. The immediate subject is the package manager, rather than an application framework. An application need not call the vulnerable method itself if its installation workflow invokes the affected tooling.

I find Ruby's uniform object model pleasant to work with, but package installation needs a different kind of scrutiny from ordinary application code. The installer handles names and paths supplied by another party. Those values need defined limits before the installer uses its own authority to act on them.

A path check must precede the destructive operation

CVE-2019-8320 concerns archive decompression in RubyGems 2.7.6 through 3.0.2. The advisory says that the implementation deleted a target destination before creating directories or touching files. Symlink checks existed for the later operations, but a destination hidden behind a symlink could already have caused deletion elsewhere.

The order is the defect. Validating a destination before writing is insufficient if an earlier cleanup step has already followed an unsafe path. The whole sequence of filesystem actions has to respect the destination boundary, including actions that prepare the location for extraction.

This is an instructive distinction for archive handling. A review that focuses only on where file contents are written can miss deletion, replacement, or directory creation. Each operation uses a path with its own consequences. The extraction process must establish an acceptable destination before an operation can affect anything there.

The advisory says the attacker needs to guess paths and that damage occurs with the permissions of the installing user. Running installation with greater privileges consequently changes the possible damage. The package manager does not gain a new filesystem identity through the defect, but it can misuse the identity it already has.

The permissions context also determines which deployment environments need attention. A developer installing a gem locally and an automated build installing the same gem may expose different files to the operation. The vulnerable package-manager behavior can be the same while its consequences differ because the surrounding account has access to different directories.

A gem name becomes generated source

CVE-2019-8324 addresses crafted multiline gem names. The advisory explains that such a name can inject code into a gemspec stub line. RubyGems evaluates that stub during the ensure_loadable_spec preinstallation check. Metadata that should identify the package can therefore alter what the generated Ruby source does.

The distinction between a string and source code is easy to lose when code generation interpolates values. A name may be harmless while stored as data, then acquire a different meaning when inserted into a language construct. The safety requirement belongs at the point where the value crosses into that construct, as well as wherever the input format defines valid names.

This is also why the preinstallation timing matters. Delaying an application's require of an unfamiliar gem does not address a vulnerability triggered by the installation process. The unsafe interpretation occurs before the application decides which library files to load.

The advisory identifies RubyGems 2.6 through 3.0.2 for this issue. That is broader than the range stated for the archive-deletion problem. Treating the six identifiers as one undifferentiated version range would therefore make the assessment less precise. The appropriate range follows the individual operation being evaluated.

Displaying a response is another interpretation step

The remaining four disclosures concern terminal escape-sequence injection. They cover verbose output, gem owner, API-response handling, and error reporting. The advisories identify unescaped data reaching the terminal through those paths. In particular, gem owner can print an API response directly, and the response utility can do the same for other operations.

A terminal interprets control sequences rather than displaying every byte as an ordinary printable character. Data reaching that interpreter therefore needs a display policy. Calling a message informational does not stop it from containing bytes with terminal meaning.

These disclosures should not be described as equivalent to the gemspec arbitrary-code-execution vulnerability. The interpretation mechanism differs. Terminal escape injection concerns what happens when output reaches the terminal, while the gemspec issue concerns evaluating generated Ruby code. Keeping those mechanisms separate makes both the trigger and the relevant repair understandable.

I regard the error-reporting path as particularly instructive. An error message often repeats the input that caused the failure. If that input is untrusted, rejecting it in one operation does not make it safe to print without escaping in another. Diagnostics need their own output boundary even when they describe a rejected request.

The affected component determines the update

The RubyGems 3.0.3 release announcement lists all six identifiers as fixed and gives gem update --system as the update command. The security advisory also identifies the 2.7.8 release for the older line and provides a patch route for 2.6 installations that cannot move to those versions.

That separates the interpreter from the installer bundled with it. A Ruby version number can help identify the initial bundled component, but an installation may subsequently update RubyGems. The component actually executing the installation determines whether its vulnerable path is present. Inventorying only application gem dependencies leaves that part of the environment undescribed.

The fixed operations consume metadata in different ways. Archive metadata must remain inside its extraction destination before cleanup begins. A package name must not change the meaning of generated Ruby, and an API response must not acquire unintended terminal instructions when displayed. Each boundary needs the rule appropriate to its consumer, because validation for one use of a value does not automatically make its other uses safe.