Context
-
Version of iperf3:
3.21 (cJSON 1.7.15), git d39cf41526626b4e5a130f115d931cd6cbdffc19. The relevant code paths are unchanged on current master, so earlier 3.x releases are very likely affected as well.
-
Hardware:
x86_64 (immaterial — this is in the JSON-assembly logic, not platform-specific).
-
Operating system (and distribution, if any):
Linux. Platform-independent.
Bug Report
With -J, an error that is relayed from the server should produce a single top-level "error" member in the JSON document.
When the client receives a SERVER_ERROR control message, the -J document contains two "error" members in the top-level object — one with a SERVER ERROR - prefix, one without:
"intervals": [],
"end": {
},
"error": "SERVER ERROR - client's requested duration exceeds the server's maximum permitted limit",
"error": "client's requested duration exceeds the server's maximum permitted limit"
RFC 8259 §4 permits duplicate names but states that in that case "the behavior of software that receives such an object is unpredictable." In practice the result is parser-dependent: last-wins parsers (Python json, JS JSON.parse, Go, Rust serde_json) surface only the bare message and silently drop the SERVER ERROR - prefix; a first-wins reader (including cJSON's own cJSON_GetObjectItem) keeps the prefixed one. A consumer can't reliably tell which "error" string it will get.
# terminal 1 — server that refuses tests longer than 2s
iperf3 -s -1 --server-max-duration 2 -p 5301
# terminal 2 — client requests a 10s test, which exceeds the limit
iperf3 -c 127.0.0.1 -p 5301 -t 10 -J
The server relays SERVER_ERROR (IEMAXSERVERTESTDURATIONEXCEEDED) and the client's -J document ends with the two "error" members shown above.
Two independent code paths each add "error" to the same test->json_top, and cJSON_AddStringToObject() appends a node rather than replacing an existing key:
src/iperf_client_api.c:406 — the SERVER_ERROR case in iperf_handle_message_client() calls iperf_err(test, "SERVER ERROR - %s", iperf_strerror(i_errno)). iperf_err() writes the first "error" at src/iperf_error.c:61, then the handler returns -1.
- The -1 unwinds into
cleanup_and_fail(), which at src/iperf_client_api.c:895 adds "error" a second time (the bare iperf_strerror(i_errno)) before calling iperf_json_finish().
Neither path is aware the other has already annotated the object.
Make the second write idempotent — e.g. in cleanup_and_fail() use cJSON_ReplaceItemInObjectCaseSensitive() (or check cJSON_GetObjectItem(test->json_top, "error") before adding), or have the SERVER_ERROR handler report via a non-JSON-emitting path so only cleanup_and_fail() writes the key. Either yields a single, unambiguous "error" member.
Context
Version of iperf3:
3.21 (cJSON 1.7.15), git
d39cf41526626b4e5a130f115d931cd6cbdffc19. The relevant code paths are unchanged on currentmaster, so earlier 3.x releases are very likely affected as well.Hardware:
x86_64 (immaterial — this is in the JSON-assembly logic, not platform-specific).
Operating system (and distribution, if any):
Linux. Platform-independent.
Bug Report
With
-J, an error that is relayed from the server should produce a single top-level"error"member in the JSON document.When the client receives a
SERVER_ERRORcontrol message, the-Jdocument contains two"error"members in the top-level object — one with aSERVER ERROR -prefix, one without:RFC 8259 §4 permits duplicate names but states that in that case "the behavior of software that receives such an object is unpredictable." In practice the result is parser-dependent: last-wins parsers (Python
json, JSJSON.parse, Go, Rustserde_json) surface only the bare message and silently drop theSERVER ERROR -prefix; a first-wins reader (including cJSON's owncJSON_GetObjectItem) keeps the prefixed one. A consumer can't reliably tell which"error"string it will get.The server relays
SERVER_ERROR(IEMAXSERVERTESTDURATIONEXCEEDED) and the client's-Jdocument ends with the two"error"members shown above.Two independent code paths each add
"error"to the sametest->json_top, andcJSON_AddStringToObject()appends a node rather than replacing an existing key:src/iperf_client_api.c:406— theSERVER_ERRORcase iniperf_handle_message_client()callsiperf_err(test, "SERVER ERROR - %s", iperf_strerror(i_errno)).iperf_err()writes the first"error"atsrc/iperf_error.c:61, then the handler returns -1.cleanup_and_fail(), which atsrc/iperf_client_api.c:895adds"error"a second time (the bareiperf_strerror(i_errno)) before callingiperf_json_finish().Neither path is aware the other has already annotated the object.
Make the second write idempotent — e.g. in
cleanup_and_fail()usecJSON_ReplaceItemInObjectCaseSensitive()(or checkcJSON_GetObjectItem(test->json_top, "error")before adding), or have theSERVER_ERRORhandler report via a non-JSON-emitting path so onlycleanup_and_fail()writes the key. Either yields a single, unambiguous"error"member.