curl · Report #3973169
42: `VMS_STS` macro typo (`< 3` vs `<< 3`) turns curl failures into successful OpenVMS conditions
How it was found
Summary
VMS_STS in src/tool_vms.h line 46 uses a comparison (< 3) where a left-shift (<< 3) is intended. This causes VMSSTS_HIDE to evaluate to 0x10000001 instead of 0x10000000, setting bit 0 (the OpenVMS success bit). When curl runs under DCL with --silent and without --show-error, the mask is ORed into the mapped CURLcode condition before decc$exit(). Every mapped curl failure with a clear success bit becomes a successful DCL condition.
Affected versions
OpenVMS curl CLI 7.8.1 through 8.21.0, plus tested 8.22.0 prerelease/development versions.
Root cause
#define VMS_STS(c, f, e, s) \
((((c) & 0xF) << 28) | \
(((e) & 0x1FFF) < 3) | \
((s) & 7))
#define VMSSTS_HIDE VMS_STS(1, 0, 0, 0)
For VMSSTS_HIDE the event field e is zero. (0 & 0x1FFF) < 3 evaluates to 1 (true), setting bit 0. The intended expression (0 & 0x1FFF) << 3 evaluates to 0.
On OpenVMS, bit 0 of a condition value determines success: odd values are successful, even values are failures. Bit 28 (STS$V_INHIB_MSG) suppresses the exit message without changing severity. The current mask does both — suppresses the message and flips failures to successes.
The control flow is:
post_check_result()insrc/tool_operate.c:608-613assignsVMSSTS_HIDEtovms_showwhen--silentis set without--show-error.main()insrc/tool_main.c:200-201passesvms_showtovms_special_exit().vms_special_exit()insrc/tool_vms.c:103ORs the mask intovms_cond[code]and callsdecc$exit().
Concrete example with CURLE_PARTIAL_FILE (exit code 18):
| Step | Value | Bit 0 | VMS meaning |
|---|---|---|---|
vms_cond[18] = CURL_PARTIAL_FILE | 0x0F01809A | 0 | Error (severity 2) |
OR with buggy VMSSTS_HIDE 0x10000001 | 0x1F01809B | 1 | Success (severity 3) |
OR with fixed mask 0x10000000 | 0x1F01809A | 0 | Error preserved |
Security impact
A remote server can trigger a transfer failure while leaving output behind (e.g. declaring Content-Length: 10, sending three bytes, closing the connection). DCL automation that branches on curl's exit condition would enter the success path and process the incomplete output.
This requires all of the following:
- curl built for and run under OpenVMS DCL
--silentwithout--show-error- A server or network condition causes curl to fail
- Downstream automation trusts curl's condition code for a security-sensitive decision
The platform and option constraints make this low severity. I have verified the source-level arithmetic and control flow but have not executed on native OpenVMS.
Suggested fix
Replace < 3 with << 3 in src/tool_vms.h:
--- a/src/tool_vms.h
+++ b/src/tool_vms.h
@@ -43,7 +43,7 @@
#define VMS_STS(c, f, e, s) \
((((c) & 0xF) << 28) | \
- (((e) & 0x1FFF) < 3) | \
+ (((e) & 0x1FFF) << 3) | \
((s) & 7))
Reproduction steps
- Build an affected curl command-line tool for OpenVMS, starting with curl 7.8.1.
- Start an HTTP test server that advertises a response body larger than the data it actually sends, then closes the connection. For example, advertise ten bytes and send only three.
- Write a DCL procedure that invokes curl with
--silent, without--show-error, and saves the response to a file. - Immediately after curl exits, record the DCL
$STATUSand$SEVERITYvalues and branch on the normal OpenVMS success test. - Confirm that curl created an incomplete output file containing only the bytes sent before the server closed.
- Confirm through curl diagnostics or a control run that the underlying transfer result is
CURLE_PARTIAL_FILE, not a successful transfer. - Observe the vulnerable result under DCL: the condition returned by curl is odd, has informational severity, and is treated as success even though curl detected the partial-file error.
- Confirm that automation following the ordinary DCL success path would process the incomplete output.
- Repeat the failing request without
--silent, or add--show-error, and verify that the transfer is represented as a failure. This isolates the incorrect conversion to the message-hiding path. - Inspect the mapped OpenVMS condition for
CURLE_PARTIAL_FILEand confirm that it is an even failure value before the hide mask is applied. - Apply the
VMSSTS_HIDEmask generated by the affectedVMS_STSmacro and confirm that the< 3comparison contributes bit zero, converting the condition into an odd success value. - Replace the comparison with the intended left shift, changing
((e) & 0x1FFF) < 3to((e) & 0x1FFF) << 3. - Rebuild curl and repeat the same silent truncated transfer.
- Observe the corrected result: the message remains suppressed, but
$STATUSstays even,$SEVERITYreports failure, and the DCL procedure enters its failure branch. - If native OpenVMS testing is unavailable, build a portable validation harness against the affected
tool_vms.hand generated condition table, feed it a real failing curl exit code, and compare the affected and corrected numeric transformations. Treat this as arithmetic and control-flow evidence rather than a substitute for the final native DCL test.
Expected result
--silent may suppress diagnostic messages, but it must not change a failed transfer into a successful OpenVMS condition.
Vulnerable result
The malformed hide-mask expression sets the OpenVMS success bit. A genuine curl failure is returned to DCL as a successful informational condition, allowing automation to process partial or otherwise invalid output.
Impact
Summary:
A remote server can deliberately fail or truncate a transfer while leaving partial output, yet OpenVMS automation may treat curl’s resulting condition as success. Security-sensitive scripts could consequently accept corrupted downloads, incomplete configuration, or attacker-controlled partial data and continue with deployment, installation, or processing that should have been aborted.