Ruby restricts method dispatch in Shell file tests
CVE-2019-16255 lets an untrusted Shell test selector invoke unintended Ruby methods. The fix limits named selectors to FileTest methods instead of allowing unrestricted dispatch.
A method intended to ask a question about a file should not let its caller choose an unrelated Ruby method to execute. CVE-2019-16255, disclosed October 1, concerns that boundary in the standard-library Shell class. Its [] and test methods permit code injection when their first argument is untrusted.
The advisory describes invocation of an arbitrary Ruby method. That mechanism is more specific than a general description of operating-system shell command injection. The vulnerable input is the file-test selector, and the unsafe operation is dynamic Ruby method dispatch. Understanding that distinction explains both why the method is vulnerable and what the patch changes.
I find Ruby's uniform object model one of its strengths. This defect shows the corresponding need to limit dynamic dispatch at an input boundary. A method name represented as a string can select behavior with much more authority than an ordinary descriptive value.
The selector chooses how to interpret a file
The Ruby 2.6.4 implementation shows the behavior inside the command processor. The file-test method expands its file arguments and converts a symbol selector to a string. It then distinguishes integer selectors, single-character strings, and longer strings.
The longer-string branch calls FileTest.send with the supplied selector and the expanded path arguments. That means the input does more than select among a fixed set of documented file questions. It reaches the object's dynamic method-dispatch mechanism without first establishing that the selected name belongs to the intended file-test operations.
The source comments show ordinary uses such as testing with the exists? selector. Those examples explain why an application author might interpret the method as a constrained interface. A caller wanting to test a pathname has no reason to expect that an arbitrary method name can expand the operation beyond that purpose.
The advisory makes that expectation part of the security judgment. It warns that untrusted input is dangerous for Shell methods generally, but treats this case as a vulnerability because the methods' purpose is file testing. The documented operation gives callers a narrower expectation than the implementation enforces.
The patch checks the dispatch target
The Ruby 2.6.5 implementation adds a check before the longer-string dispatch. It requires the selector to appear in FileTest.methods(false). If the selector is absent, the method raises an unsupported-command error instead of calling it.
The significant change is the allowed set of operations that the caller can select through this interface. The operation can still use dynamic dispatch internally, but the caller's selection must belong to the designated file-test methods. A string ceases to be an unrestricted instruction to the receiver and becomes a choice within the intended interface.
This also explains why changing only the file path would not address the defect. The selector and the path are different inputs. The path names the object of the test, while the selector determines the behavior performed. Normalizing a path can leave an unsafe behavior selector completely unchanged.
A source review needs to follow both values to their consumers. Seeing expand_path in the method could suggest that input handling has already occurred. That operation addresses pathname interpretation. The subsequent send introduces another interpretation step with a different rule, namely which method the caller is authorized to select.
Dynamic interfaces need a bounded vocabulary
A selector string can be useful when a program maps a small set of user choices to operations. The important requirement is that the mapping remain under application control. Accepting a choice called exists and mapping it to one fixed operation has a narrower meaning than accepting any Ruby method name.
The patch applies that principle inside the library. It checks the available file-test vocabulary before dispatch. A caller still needs to determine which file paths and test results are appropriate for its own use, but the library no longer treats the supplied method name as permission to invoke an unrelated method through that branch.
This distinction is also relevant to wrappers around the library. A wrapper should describe the operation it exposes, rather than merely forwarding arbitrary selector strings. The wrapper's input contract can then remain smaller than the full Ruby object interface of its implementation. That makes it possible to inspect which actions a request can cause without enumerating every inherited method.
I prefer explicit mappings for externally selected behavior because they make the permitted operations visible in one place. Ruby's flexibility remains useful inside the implementation. The external input does not need to inherit all of that flexibility to express a limited file-test request.
Affected releases and fixed artifacts are separate evidence
The advisory lists Ruby 2.3 and earlier, Ruby 2.4.7 and earlier, Ruby 2.5.6 and earlier, Ruby 2.6.4 and earlier, and Ruby 2.7.0 preview 1 as affected. Those ranges identify interpreter distributions containing the vulnerable library. They do not establish that every application running one of those distributions passes untrusted selectors to the method.
The Ruby 2.6.5 release announcement explicitly lists CVE-2019-16255 among its security fixes. That supplies the release-level evidence corresponding to the changed implementation. The advisory defines the vulnerability, while the release identifies an available corrected artifact for that branch.
For an application assessment, the relevant path is from untrusted input to the selector argument. A program can call file tests with a fixed selector while accepting a variable pathname, which is a different relationship from accepting the selector itself. Both inputs need an appropriate policy, but the code-injection mechanism in this disclosure follows the first argument into method dispatch.
The fixed implementation narrows that relationship at the library boundary. Long selector names must identify the intended FileTest operations before dispatch proceeds. That restriction preserves the useful file-testing interface while preventing the selector from reaching the broader Ruby method vocabulary that caused the vulnerability.