reitit icon indicating copy to clipboard operation
reitit copied to clipboard

Can't get descendants of classes

Open onetom opened this issue 3 years ago • 1 comments

Problem

It's possible to evade the ring exception handler middleware and make it throw an exception, leading to no ring response at all:

  (#'reitit.ring.middleware.exception/call-error-handler
    {} ; handlers for exception `class` or `ex-data :type`
    (ex-info "" {:type java.lang.ClassCastException})
    {:re 'quest})

throws

java.lang.UnsupportedOperationException: Can't get descendants of classes
	at clojure.core$descendants.invokeStatic(core.clj:5611)
	at clojure.core$descendants.invokeStatic(core.clj:5603)
	at clojure.core$descendants.invoke(core.clj:5603)
	at reitit.ring.middleware.exception$call_error_handler.invokeStatic(exception.clj:9)
	at reitit.ring.middleware.exception$call_error_handler.invoke(exception.clj:22)
	at reitit.ring.middleware.exception$on_exception.invokeStatic(exception.clj:40)
	at reitit.ring.middleware.exception$on_exception.invoke(exception.clj:38)
	at reitit.ring.middleware.exception$wrap$fn__1760$fn__1761.invoke(exception.clj:51)
	at muuntaja.middleware$wrap_format_response$fn__18356.invoke(middleware.clj:132)
	at muuntaja.middleware$wrap_format_negotiate$fn__18349.invoke(middleware.clj:96)
	at ring.middleware.params$wrap_params$fn__18297.invoke(params.clj:75)
...

Solution

  (or (get handlers type)
      (get handlers ex-class)
      (when-not (class? type)
        (some
          (partial get handlers)
          (descendants type)))
      (some
        (partial get handlers)
        (super-classes ex-class))
      (get handlers ::default))

instead of the current

  (or (get handlers type)
      (get handlers ex-class)
      (some
        (partial get handlers)
        (descendants type))
      (some
        (partial get handlers)
        (super-classes ex-class))
      (get handlers ::default))

It might also make sense to just unit test the call-error-handler, to simplify the reitit.ring.middleware.exception-test/exception-test or maybe even extract a find-error-handler from the call-error-handler and just unit test that.

onetom avatar Jun 17 '22 03:06 onetom

To be clear, I'm not sure yet how such an exception happens in our code, but regardless, I think it shows a bug in call-error-handler, because it might call clojure.core/descendants with a class, which is not supported:

(defn descendants
  "Returns the immediate and indirect children of tag, through a
  relationship established via derive. h must be a hierarchy obtained
  from make-hierarchy, if not supplied defaults to the global
  hierarchy. Note: does not work on Java type inheritance
  relationships."
  {:added "1.0"}
  ([tag] (descendants global-hierarchy tag))
  ([h tag] (if (class? tag)
             (throw (java.lang.UnsupportedOperationException. "Can't get descendants of classes"))
             (not-empty (get (:descendants h) tag)))))

onetom avatar Jun 17 '22 03:06 onetom