🐛 Add recursion depth limit to Fressian reader (#11020)

Bound read depth at 128 levels to prevent StackOverflowError from
crafted deeply-nested payloads. All recursive read handlers go
through read-object!, so a single depth check covers all paths.

AI-assisted-by: mimo-v2.5-pro
This commit is contained in:
Andrey Antukh 2026-08-05 17:41:59 +02:00 committed by GitHub
parent 689d3a1be2
commit 0481408531
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 29 additions and 2 deletions

View File

@ -31,6 +31,11 @@
([^String s, ^String encoding]
(.getBytes s encoding)))
;; --- DEPTH TRACKING
(def ^:dynamic *read-depth* 0)
(def ^:const max-read-depth 128)
;; --- LOW LEVEL FRESSIAN API
(defn write-object!
@ -41,7 +46,13 @@
(defn read-object!
[^Reader r]
(.readObject r))
(when (>= *read-depth* max-read-depth)
(throw (ex-info "maximum Fressian read depth exceeded"
{:type :validation
:code :max-read-depth-reached
:hint "maximum Fressian read depth exceeded"})))
(binding [*read-depth* (inc *read-depth*)]
(.readObject r)))
(defn write-tag!
([^Writer w ^String n]

View File

@ -21,7 +21,8 @@
(:import
java.time.Instant
java.time.OffsetDateTime
java.time.ZoneOffset))
java.time.ZoneOffset
java.util.UUID))
;; ---------------------------------------------------------------------------
;; Helpers
@ -524,3 +525,18 @@
(t/is (d/ordered-map? rt))
(t/is (= om rt))
(t/is (= (keys om) (keys rt)))))
(t/deftest decode-rejects-excessive-recursion-depth
;; N2-01: deeply nested structures must be rejected before stack overflow
(let [depth (+ fres/max-read-depth 50)
data (reduce (fn [acc _i] [acc])
:leaf
(range depth))
encoded (fres/encode data)]
(try
(fres/decode encoded)
(t/is false "expected exception for excessive recursion depth")
(catch clojure.lang.ExceptionInfo e
(let [d (ex-data e)]
(t/is (= :validation (:type d)))
(t/is (= :max-read-depth-reached (:code d))))))))