Add ParameterLimitValve to enforce request parameter limits for specific URLs
This is an effort of introducing Parameter Limit Valve to allow limiting the number of parameters in HTTP requests, but explicitly allowing more parameters for specific URLs. (The idea raised by this email)
It's worth to be noted that if the Parameter Limit Valve is configured, it operates independently of the Connector's maxParameterCount attribute. The Connector's maxParameterCount sets a global limit, while the Parameter Limit Valve offers additional flexibility by allowing different limits for specific URLs. However, if the maxParameterCount defined in the Connector is lower, it effectively overrides the valve by preventing large requests from ever reaching it.
For manual testing one can add something like the following in context.xml
Valve className="org.apache.catalina.valves.ParameterLimitValve"
maxGlobalParams="4"
urlPatternLimits="/api/.*=2,/admin/.*=1,/my/special/url1=3" />
and run some relevant test cases:
curl -X POST http://localhost:8080/api/resource -d "param1=val1¶m2=val2" PASS curl -X POST http://localhost:8080/api/resource -d "param1=val1¶m2=val2¶m3=val3" FAIL curl -X POST http://localhost:8080/admin/settings -d "param1=val1" PASS curl -X POST http://localhost:8080/admin/settings -d "param1=val1¶m2=val2" FAIL curl -X POST http://localhost:8080/my/special/url1 -d "param1=val1¶m2=val2¶m3=val3" PASS curl -X POST http://localhost:8080/my/special/url1 -d "param1=val1¶m2=val2¶m3=val3¶m4=val4" FAIL curl -X POST http://localhost:8080/random -d "param1=val1¶m2=val2¶m3=val3¶m4=val4" PASS curl -X POST http://localhost:8080/random -d "param1=val1¶m2=val2¶m3=val3¶m4=val4¶m5=val5" FAIL
This approach could be implemented as a Filter. If we were going to do this, I think something that enforces the limit at the point the parameters are parsed - rather than after - is the way to go. It would mean some refactoring of Request.doParseParameters() and I haven't thought it all the way through but it looks doable. Maybe add a maxParameterCount field to the request that is reset from the current connector value on recycle but the Valve could then change it. The added bonus is that if the app doesn't trigger parameter parsing, the parameters won't get parsed.
While I haven't actually looked at the code (for either request.parseParameters or this Valve), I thought the point of maxParameterCount was that the request would actually fail to be fully-parsed and recovery was possible neither via Valve nor Filter. The parameter count limit is there to protect Tomcat from a DoS caused by hash collisions (right?).
Allowing a Valve/Filter to recover from this means that that DoS protection is effectively gone, even if the request is ultimately rejected with a 4xx response. The damage has already been done.
I will refactor the implementation as follows:
- The maxParameterCount limit will be enforced directly within Request.doParseParameters(). If the Valve is set and the number of parameters exceeds the configured limit, the request will fail immediately, preventing parameter parsing operations from taking place.
- The ParameterLimitValve will only adjust the maxParameterCount on the Request object before parameter parsing occurs. The actual enforcement of the limit will remain in the parsing stage, ensuring that no recovery happens post-parsing, and thus preserving the intended DoS protection.
Please let me know if this sounds like a reasonable solution, or if there’s anything else to consider that I am missing.
The parameter count limit is there to protect Tomcat from a DoS caused by hash collisions (right?).
Hash collisions was why the 10k limit was put in place - CVE-2012-0022. That was chosen as the largest round number that was low enough to avoid the hash collision issue.
The further reduction to 1k was on the basis that very few apps need the limit that high and there is a memory cost to handling parameters. The aim was to reduce the minimum amount of RAM Tomcat needed to have and still be able to handle default maximum concurrent requests with default maximum parameters each.
Like all the Tomcat defaults, it is a trade-off.
I have refactored the valve and the request as discussed here. There were 2 failing tests after the change to Request's constructor which introduced method accessing of Connector. The change at TestPersistentManager.java is just a reordering of when the mock Connector object is activated. Regarding TestSSLValve.java, I introduced a singleton implementation of Mockrequest giving more flexibility for Connector values.