Francis Perron created BEANUTILS-580:
----------------------------------------
Summary: PropertyUtilsBean/WrapDynaClass descriptor caches can
re-expose the 'class' property after introspectors are changed at runtime
Key: BEANUTILS-580
URL: https://issues.apache.org/jira/browse/BEANUTILS-580
Project: Commons BeanUtils
Issue Type: Improvement
Components: Bean / Property Utils
Affects Versions: 1.11.0
Environment: Apache Commons BeanUtils 1.x (verified against
1.11.1-SNAPSHOT, commit cd7afd9). JDK/OS independent, not platform-specific.
Reporter: Francis Perron
Attachments: beanutils.patch
Summary
After a bean class has been introspected, calling
PropertyUtilsBean.addBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS)
does not invalidate the already-cached property descriptors in
PropertyUtilsBean's WeakFastHashMap cache or in WrapDynaClass's derived
descriptorsMap. As a result, the class property can remain reachable even after
an application (re-)installs the suppressor, and a concurrent variant of the
same issue can expose a stale descriptor during a startup race between
introspection and introspector-list changes.
This does not affect the out-of-the-box default: SUPPRESS_CLASS has been
registered automatically since 1.9.4, so the class property is already
suppressed from first introspection. The issue only surfaces if an application
removes the suppressor, introspects, and later re-adds it (or resets it)
expecting the cache to reflect the new state — a "mutate a shared bean's
introspectors at runtime" pattern the project's own guidance already
discourages in favor of configuring introspectors once at startup.
Steps to reproduce
1. Introspect a bean class through PropertyUtilsBean (or WrapDynaClass) with
SUPPRESS_CLASS removed.
2. Call addBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS)
(or resetBeanIntrospectors()).
3. Access the class property on the already-introspected bean — the cached
descriptor is served unchanged and class is still reachable.
4. A companion timing test exercises the same stale-read window when
introspection on one thread races with an introspector-list change on another.
Proposed fix
A patch is attached that invalidates the derived descriptor caches whenever the
introspector list changes, and makes the currency check plus cache lookup
atomic in WrapDynaClass (synchronized on this). Patch size: +840/‑6 (main
changes to PropertyUtilsBean.java, WeakFastHashMap.java, WrapDynaClass.java),
plus four new JUnit test classes covering both the direct-bypass and the
concurrent-race cases. It targets the 1.X branch and applies cleanly to
origin/1.X.
Impact assessment
This is being filed as a hardening improvement rather than a security bug: it
requires an application to first opt out of the default suppressor and later
attempt to restore it, which is not the shipped default posture. We'd
appreciate maintainer input on:
- Whether resetBeanIntrospectors() / re-adding a suppressor is intended to also
restore suppression for classes already in the descriptor cache (if so, the
current behavior contradicts that intent and this may warrant a stronger fix).
- Whether the concurrent-race variant (introspection racing with
introspector-list mutation) is an accepted risk of "configure once at startup,"
or worth hardening regardless.
Happy to open this as a PR against 1.X if that's preferred to an attached patch.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)