Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
v3.0.18 - to be released
------------------------

- fix: DURATION now measures elapsed wall-clock time (monotonic
std::chrono::steady_clock) instead of process CPU time, and is reported
as an integer number of microseconds, like v2. It also works on macOS
and Windows. Together with #3687 this changes the value that rules see
[PR #3721 - @Nikhil-jaiswal007]

v3.0.17 - 2026-Sep-29
---------------------

Expand Down
7 changes: 4 additions & 3 deletions headers/modsecurity/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -433,9 +433,10 @@ class Transaction : public TransactionAnchoredVariables, public TransactionSecMa
* need to be filled if there is no rule using the variable
* `duration'.
*
* @note Must be a floating point type: utils::cpu_seconds() returns a
* double and storing it in an integer type (as the former clock_t
* did) truncated the fractional seconds, making `duration' wrong.
* @note Holds a utils::steady_micros() timestamp: a monotonic
* (std::chrono::steady_clock) time in whole microseconds. It is
* kept as a double so the layout of this public class does not
* change; a double represents integers exactly up to 2^53.
*/
const double m_creationTimeStamp;

Expand Down
2 changes: 1 addition & 1 deletion src/transaction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Transaction::Transaction(ModSecurity *ms, RulesSet *rules, const char *id, void

Transaction::Transaction(ModSecurity *ms, RulesSet *rules, const char *id,
void *logCbData, const time_t timestamp)
: m_creationTimeStamp(utils::cpu_seconds()),
: m_creationTimeStamp(utils::steady_micros()),
m_ARGScombinedSizeDouble(0),
m_clientPort(0),
m_highestSeverityAction(255),
Expand Down
9 changes: 9 additions & 0 deletions src/utils/system.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <stdint.h>
#include <inttypes.h>

#include <chrono>
#include <ctime>
#include <iostream>
#include <string>
Expand Down Expand Up @@ -95,6 +96,14 @@ double cpu_seconds(void) {
return 0;
}

double steady_micros(void) {
// Monotonic wall-clock time, in whole microseconds. A double holds
// integers exactly up to 2^53, so no precision is lost.
return static_cast<double>(
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count());
}


std::string find_resource(const std::string& resource,
const std::string& config, std::string *err) {
Expand Down
1 change: 1 addition & 0 deletions src/utils/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace utils {


double cpu_seconds(void);
double steady_micros(void);
std::string find_resource(const std::string& file, const std::string& config,
std::string *err);
std::string get_path(const std::string& file);
Expand Down
9 changes: 7 additions & 2 deletions src/variables/duration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "src/variables/duration.h"

#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
Expand All @@ -30,7 +31,11 @@
void Duration::evaluate(Transaction *transaction,
RuleWithActions *rule,
std::vector<const VariableValue *> *l) {
double e = utils::cpu_seconds() - transaction->m_creationTimeStamp;
// Elapsed wall-clock time since the transaction was created, in whole
// microseconds (same unit and type as v2: apr_time_now() - request_time).
const double now = utils::steady_micros();
const int64_t e = static_cast<int64_t>(

Check warning on line 37 in src/variables/duration.cc

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace the redundant type with "auto".

See more on https://sonarcloud.io/project/issues?id=owasp-modsecurity_ModSecurity&issues=AaGA8Qhs-B4kTqJ1SPY_&open=AaGA8Qhs-B4kTqJ1SPY_&pullRequest=3721
now - transaction->m_creationTimeStamp);

transaction->m_variableDuration.assign(std::to_string(e));

Expand All @@ -40,4 +45,4 @@


} // namespace variables
} // namespace modsecurity
} // namespace modsecurity
2 changes: 1 addition & 1 deletion test/test-cases/regression/issue-1725.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
]
},
"expected": {
"debug_log": "/test; 0.[0-9]+; 0.[0-9]+;",
"debug_log": "/test; [0-9]+; [0-9]+;",
"http_code": 200
},
"rules": [
Expand Down
47 changes: 45 additions & 2 deletions test/test-cases/regression/variable-DURATION.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
]
},
"expected": {
"debug_log": "Target value: \"[0-9\\.]+\"",
"debug_log": "Target value: \"[0-9]+\"",
"http_code": 200
},
"rules": [
Expand All @@ -47,5 +47,48 @@
"SecRule DURATION \"@contains test \" \"id:2,phase:3,pass,t:trim\"",
"SecRule DURATION \"@contains test \" \"id:3,phase:3,pass,t:trim\""
]
},
{
"enabled": 1,
"version_min": 300000,
"title": "Testing Variables :: DURATION is a non-zero integer",
"client": {
"ip": "200.249.12.31",
"port": 2313
},
"server": {
"ip": "200.249.12.31",
"port": 80
},
"request": {
"headers": {
"Host": "localhost",
"User-Agent": "curl/7.38.0",
"Accept": "*/*",
"Content-Length": "0"
},
"uri": "/test",
"method": "GET",
"http_version": 1.1,
"body": [
""
]
},
"response": {
"headers": {
"Content-Length": "0"
},
"body": [
""
]
},
"expected": {
"debug_log": "duration=[1-9][0-9]*;",
"http_code": 200
},
"rules": [
"SecRuleEngine On",
"SecRule DURATION \"@unconditionalMatch\" \"phase:2,id:10002,log,auditlog,pass,msg:'duration=%{DURATION};',logdata:'duration=%{DURATION};'\""
]
}
]
]