HexBrain

curl · Report #3973169

42: `VMS_STS` macro typo (`< 3` vs `<< 3`) turns curl failures into successful OpenVMS conditions

Informativestatus
11upvotes
29 Aug 2026disclosed

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:

  1. post_check_result() in src/tool_operate.c:608-613 assigns VMSSTS_HIDE to vms_show when --silent is set without --show-error.
  2. main() in src/tool_main.c:200-201 passes vms_show to vms_special_exit().
  3. vms_special_exit() in src/tool_vms.c:103 ORs the mask into vms_cond[code] and calls decc$exit().

Concrete example with CURLE_PARTIAL_FILE (exit code 18):

StepValueBit 0VMS meaning
vms_cond[18] = CURL_PARTIAL_FILE0x0F01809A0Error (severity 2)
OR with buggy VMSSTS_HIDE 0x100000010x1F01809B1Success (severity 3)
OR with fixed mask 0x100000000x1F01809A0Error 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:

  1. curl built for and run under OpenVMS DCL
  2. --silent without --show-error
  3. A server or network condition causes curl to fail
  4. 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

  1. Build an affected curl command-line tool for OpenVMS, starting with curl 7.8.1.
  2. 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.
  3. Write a DCL procedure that invokes curl with --silent, without --show-error, and saves the response to a file.
  4. Immediately after curl exits, record the DCL $STATUS and $SEVERITY values and branch on the normal OpenVMS success test.
  5. Confirm that curl created an incomplete output file containing only the bytes sent before the server closed.
  6. Confirm through curl diagnostics or a control run that the underlying transfer result is CURLE_PARTIAL_FILE, not a successful transfer.
  7. 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.
  8. Confirm that automation following the ordinary DCL success path would process the incomplete output.
  9. 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.
  10. Inspect the mapped OpenVMS condition for CURLE_PARTIAL_FILE and confirm that it is an even failure value before the hide mask is applied.
  11. Apply the VMSSTS_HIDE mask generated by the affected VMS_STS macro and confirm that the < 3 comparison contributes bit zero, converting the condition into an odd success value.
  12. Replace the comparison with the intended left shift, changing ((e) & 0x1FFF) < 3 to ((e) & 0x1FFF) << 3.
  13. Rebuild curl and repeat the same silent truncated transfer.
  14. Observe the corrected result: the message remains suppressed, but $STATUS stays even, $SEVERITY reports failure, and the DCL procedure enters its failure branch.
  15. If native OpenVMS testing is unavailable, build a portable validation harness against the affected tool_vms.h and 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.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original