Skip to content

Add ParameterLimitValve to enforce request parameter limits for specific URLs#753

Merged
dsoumis merged 7 commits intoapache:mainfrom
dsoumis:implement_parameterlimitvalve
Jan 29, 2025
Merged

Add ParameterLimitValve to enforce request parameter limits for specific URLs#753
dsoumis merged 7 commits intoapache:mainfrom
dsoumis:implement_parameterlimitvalve

Conversation

@dsoumis
Copy link
Copy Markdown
Member

@dsoumis dsoumis commented Sep 12, 2024

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&param2=val2" PASS
curl -X POST http://localhost:8080/api/resource -d "param1=val1&param2=val2&param3=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&param2=val2" FAIL
curl -X POST http://localhost:8080/my/special/url1 -d "param1=val1&param2=val2&param3=val3" PASS
curl -X POST http://localhost:8080/my/special/url1 -d "param1=val1&param2=val2&param3=val3&param4=val4" FAIL
curl -X POST http://localhost:8080/random -d "param1=val1&param2=val2&param3=val3&param4=val4" PASS
curl -X POST http://localhost:8080/random -d "param1=val1&param2=val2&param3=val3&param4=val4&param5=val5" FAIL

- Introduced ParameterLimitValve to allow limiting the number of parameters in HTTP requests.
- Added configuration options for global parameter limits and specific limits for URL patterns.
- Requests exceeding the configured limits are rejected with an HTTP 400 Bad Request error.
- Includes unit tests (TestParameterLimitValve) to ensure full test coverage for the valve's functionality.
- Added documentation for the ParameterLimitValve, detailing its attributes, usage, and configuration.
@markt-asf
Copy link
Copy Markdown
Contributor

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.

@ChristopherSchultz
Copy link
Copy Markdown
Contributor

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.

@dsoumis
Copy link
Copy Markdown
Member Author

dsoumis commented Sep 16, 2024

I will refactor the implementation as follows:

  1. 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.
  2. 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.

@markt-asf
Copy link
Copy Markdown
Contributor

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.

…t the point when parameters are parsed - rather than after.

- Introduced a new maxParameterCount field in Request.java class
- Removed configuration option for global parameter limit, as it is replaced by the default Connector's limit.
- Replaced unit tests with integration tests to ensure better coverage for the valve's functionality.
- Fixed documentation
@dsoumis
Copy link
Copy Markdown
Member Author

dsoumis commented Sep 27, 2024

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.

valve.invoke(mockRequest, null);
} catch (NumberFormatException e) {
Assert.assertNull(mockRequest.getAttribute(Globals.KEY_SIZE_ATTR));
mockRequest.setHeader(valve.getSslCipherUserKeySizeHeader(), null);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the random ordering of the execution of the tests, I had to reset the header at this point because other tests were throwing NumberFormatException.

@dsoumis dsoumis requested a review from markt-asf September 27, 2024 13:34
Comment thread java/org/apache/catalina/valves/ParameterLimitValve.java Outdated
- Modified `setUrlPatternLimits` to accept `BufferedReader` input, allowing for flexible configuration from files.
- Updated `ParameterLimitValve` to handle configuration files:
  - Supports `parameter_limit.config` in `/WEB-INF/` for contexts and configurable host-level paths.
  - Properly parses line-by-line configuration, skipping comments and empty lines.
  - Handles multiple `=` in patterns by splitting at the last `=`.
- Enhanced `invoke` method to apply parameter limits based on decoded URIs and URL patterns.
- Added integration tests in `TestParameterLimitValve`
- Introduced logging for debugging configuration parsing and application.
@dsoumis dsoumis force-pushed the implement_parameterlimitvalve branch from 22584b7 to eeb2468 Compare January 28, 2025 11:56
@dsoumis dsoumis requested a review from rmaucher January 29, 2025 09:38
@dsoumis dsoumis merged commit ff49f19 into apache:main Jan 29, 2025
dsoumis added a commit that referenced this pull request Jan 30, 2025
…fic URLs (#753)

Introduce ParameterLimitValve to enforce request parameter limits

- Added `ParameterLimitValve`, a new valve that allows enforcing limits on the number of parameters in HTTP requests.
- Supports defining per-URL pattern parameter limits using regular expressions.
- Requests exceeding the configured limits are rejected with an HTTP 400 Bad Request error.
- Configuration is possible through `context.xml` and `server.xml`, or via dynamic management.
- Includes integration tests (`TestParameterLimitValve`) to validate enforcement behavior across different contexts.
- Added documentation detailing the valve’s attributes, configuration options, and usage examples.

(cherry picked from commit ff49f19)
markt-asf pushed a commit that referenced this pull request May 28, 2025
Introduce ParameterLimitValve to enforce request parameter limits

- Added `ParameterLimitValve`, a new valve that allows enforcing limits
on the number of parameters in HTTP requests.
- Supports defining per-URL pattern parameter limits using regular
expressions.
- Requests exceeding the configured limits are rejected with an HTTP 400
Bad Request error.
- Configuration is possible through `context.xml` and `server.xml`, or
via dynamic management.
- Includes integration tests (`TestParameterLimitValve`) to validate
enforcement behavior across different contexts.
- Added documentation detailing the valve’s attributes, configuration
options, and usage examples.
markt-asf pushed a commit that referenced this pull request May 28, 2025
Introduce ParameterLimitValve to enforce request parameter limits

- Added `ParameterLimitValve`, a new valve that allows enforcing limits
on the number of parameters in HTTP requests.
- Supports defining per-URL pattern parameter limits using regular
expressions.
- Requests exceeding the configured limits are rejected with an HTTP 400
Bad Request error.
- Configuration is possible through `context.xml` and `server.xml`, or
via dynamic management.
- Includes integration tests (`TestParameterLimitValve`) to validate
enforcement behavior across different contexts.
- Added documentation detailing the valve’s attributes, configuration
options, and usage examples.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants