lukaszlenart commented on PR #1815:
URL: https://github.com/apache/struts/pull/1815#issuecomment-5088015503

   Thanks for this — the underlying problem is real and you diagnosed it 
correctly. I traced it independently and it holds up:
   
   `LazyParamInjector#injectParams` (`WithLazyParams.java:78-84`) writes 
resolved values straight onto the interceptor, and that interceptor is a 
config-time singleton (`InterceptorBuilder.java:73-74`, `:175-177`). It's worse 
than same-action concurrency: `InterceptorBuilder.java:79` hands out the same 
`InterceptorMapping` objects to every action that references a stack without 
overriding params, so the instance is shared across actions too. Between 
`DefaultActionInvocation.java:269` (resolve, write shared fields) and `:275` → 
`acceptFile` (read shared fields) there is no guard at all. Good catch.
   
   I don't want to merge this particular fix though, for a few reasons:
   
   1. `static ThreadLocal PARAM_INJECTION_IN_PROGRESS` makes 
`setAllowedTypes`/`setAllowedExtensions`/`setMaximumSize` behave differently 
depending on hidden thread state. A setter whose target depends on an ambient 
flag is hard to reason about and hard to test.
   2. `clearRequestScopedUploadValidationPolicy()` is only called from 
`ActionFileUploadInterceptor#intercept`'s `finally`. `WithLazyParams` is public 
API — any other implementer that forgets that call leaks the `ThreadLocal`.
   3. The unsafe write path still exists; it's conditionally bypassed rather 
than removed. The next `WithLazyParams` implementer gets the original bug back 
by default.
   4. It doesn't cover `DefaultActionInvocation.java:262-267`, which does 
`params.putAll(...)` on the live shared map returned by 
`InterceptorMapping#getParams` (`:60-62`) — an unsynchronised write to a shared 
`HashMap` on every request.
   
   So rather than patch the one implementer, I'd like to fix the 
`WithLazyParams` contract so the failure mode can't be expressed. The direction 
is to have resolved params written into a per-invocation object that the 
interceptor supplies and receives back, leaving the singleton immutable after 
`init()`:
   
   ```java
   public interface WithLazyParams<P> {
       P newLazyParams();
       String intercept(ActionInvocation invocation, P lazyParams) throws 
Exception;
   }
   ```
   
   That keeps OGNL type conversion (the holder has typed setters, so 
`maximumSize` still converts to `Long`), removes the mutation entirely, and 
makes the data flow an explicit argument instead of ambient state. It's an 
interface change, so it targets the next minor rather than 7.2.x.
   
   I've filed [WW-5659](https://issues.apache.org/jira/browse/WW-5659) to track 
it and I'm writing up the design now. Your regression test is the valuable part 
of this PR — the scenario it pins down is exactly what the new design has to 
keep passing, and I'd like to carry it over (with attribution) once the 
interface lands. If you'd like to take the implementation on the new contract, 
say so and it's yours; otherwise I'll pick it up and credit you on the ticket.
   
   One process note for next time, not a criticism of this one: when a change 
touches something that could weaken a security control — upload validation, 
parameter filtering, OGNL access — please email [email protected] 
before opening a public PR. `SECURITY.md` covers why. We looked at this one and 
concluded it's a thread-safety defect rather than a vulnerability (it needs the 
opt-in `${...}` config, and `struts-default.xml:60` ships `actionFileUpload` 
with no params, so default setups write nothing), which is why it's fine to 
keep the discussion here.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to