Skip to content

Commit 7cff341

Browse files
committed
Fix set-status crash inside if/endif at remap time
Operators inside an if/endif block never had set_hook() called on them, so _hook retained its default value of TS_HTTP_READ_RESPONSE_HDR_HOOK. When set-status executed at REMAP_PSEUDO_HOOK time, exec() took the response-hook branch and called TSHttpHdrStatusSet on a request buffer, corrupting the header union and crashing. Fix the operator to check TSHttpHdrTypeGet() at runtime instead of trusting get_hook(). Removed _hook and get_hook() from operators. set_hook() becomes is_hook_valid() in Statement and RuleSet to only validate hook compatibility without mutating _hook. Upgrade the HTTPHdr::status_set/reason_set asserts to release_assert so that writing response fields into a request buffer is caught immediately rather than silently corrupting memory. Includes an autest that reproduces the crash scenario: set-status 403 inside an if/endif block under REMAP_PSEUDO_HOOK.
1 parent efeeffa commit 7cff341

8 files changed

Lines changed: 74 additions & 34 deletions

File tree

include/proxy/hdrs/HTTP.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ inline void
10881088
HTTPHdr::status_set(HTTPStatus status)
10891089
{
10901090
ink_assert(valid());
1091-
ink_assert(m_http->m_polarity == HTTPType::RESPONSE);
1091+
ink_release_assert(m_http->m_polarity == HTTPType::RESPONSE);
10921092

10931093
http_hdr_status_set(m_http, status);
10941094
}
@@ -1112,7 +1112,7 @@ inline void
11121112
HTTPHdr::reason_set(std::string_view value)
11131113
{
11141114
ink_assert(valid());
1115-
ink_assert(m_http->m_polarity == HTTPType::RESPONSE);
1115+
ink_release_assert(m_http->m_polarity == HTTPType::RESPONSE);
11161116

11171117
http_hdr_reason_set(m_heap, m_http, value, true);
11181118
}

plugins/header_rewrite/operators.cc

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -198,19 +198,13 @@ OperatorSetStatus::initialize_hooks()
198198
bool
199199
OperatorSetStatus::exec(const Resources &res) const
200200
{
201-
switch (get_hook()) {
202-
case TS_HTTP_READ_RESPONSE_HDR_HOOK:
203-
case TS_HTTP_SEND_RESPONSE_HDR_HOOK:
204-
if (res.bufp && res.hdr_loc) {
205-
TSHttpHdrStatusSet(res.bufp, res.hdr_loc, static_cast<TSHttpStatus>(_status.get_int_value()), res.state.txnp, PLUGIN_NAME);
206-
if (_reason && _reason_len > 0) {
207-
TSHttpHdrReasonSet(res.bufp, res.hdr_loc, _reason, _reason_len);
208-
}
201+
if (res.bufp && res.hdr_loc && TSHttpHdrTypeGet(res.bufp, res.hdr_loc) == TS_HTTP_TYPE_RESPONSE) {
202+
TSHttpHdrStatusSet(res.bufp, res.hdr_loc, static_cast<TSHttpStatus>(_status.get_int_value()), res.state.txnp, PLUGIN_NAME);
203+
if (_reason && _reason_len > 0) {
204+
TSHttpHdrReasonSet(res.bufp, res.hdr_loc, _reason, _reason_len);
209205
}
210-
break;
211-
default:
206+
} else {
212207
TSHttpTxnStatusSet(res.state.txnp, static_cast<TSHttpStatus>(_status.get_int_value()), PLUGIN_NAME);
213-
break;
214208
}
215209

216210
Dbg(pi_dbg_ctl, "OperatorSetStatus::exec() invoked with status=%d", _status.get_int_value());
@@ -601,7 +595,7 @@ OperatorSetRedirect::exec(const Resources &res) const
601595
const_cast<Resources &>(res).changed_url = true;
602596
res._rri->redirect = 1;
603597
} else {
604-
Dbg(pi_dbg_ctl, "OperatorSetRedirect::exec() hook=%d", int(get_hook()));
598+
Dbg(pi_dbg_ctl, "OperatorSetRedirect::exec() redirect to %s", value.c_str());
605599
// Set the new status code and reason.
606600
TSHttpStatus status = static_cast<TSHttpStatus>(_status.get_int_value());
607601
TSHttpHdrStatusSet(res.bufp, res.hdr_loc, status, res.state.txnp, PLUGIN_NAME);

plugins/header_rewrite/ruleset.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ RuleSet::make_condition(Parser &p, const char *filename, int lineno)
6666

6767
Dbg(pi_dbg_ctl, " Creating condition: %%{%s} with arg: %s", p.get_op().c_str(), p.get_arg().c_str());
6868
c->initialize(p);
69-
if (!c->set_hook(_hook)) {
69+
if (!c->is_hook_valid(_hook)) {
7070
delete c;
7171
TSError("[%s] in %s:%d: can't use this condition in hook=%s: %%{%s} with arg: %s", PLUGIN_NAME, filename, lineno,
7272
TSHttpHookNameLookup(_hook), p.get_op().c_str(), p.get_arg().c_str());
@@ -93,7 +93,7 @@ RuleSet::add_operator(Parser &p, const char *filename, int lineno)
9393
if (nullptr != op) {
9494
Dbg(pi_dbg_ctl, " Adding operator: %s(%s)=\"%s\"", p.get_op().c_str(), p.get_arg().c_str(), p.get_value().c_str());
9595
op->initialize(p);
96-
if (!op->set_hook(_hook)) {
96+
if (!op->is_hook_valid(_hook)) {
9797
delete op;
9898
Dbg(pi_dbg_ctl, "in %s:%d: can't use this operator in hook=%s: %s(%s)", filename, lineno, TSHttpHookNameLookup(_hook),
9999
p.get_op().c_str(), p.get_arg().c_str());

plugins/header_rewrite/statement.cc

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,9 @@ Statement::get_resource_ids() const
4848
}
4949

5050
bool
51-
Statement::set_hook(TSHttpHookID hook)
51+
Statement::is_hook_valid(TSHttpHookID hook) const
5252
{
53-
bool ret = std::find(_allowed_hooks.begin(), _allowed_hooks.end(), hook) != _allowed_hooks.end();
54-
55-
if (ret) {
56-
_hook = hook;
57-
}
58-
59-
return ret;
53+
return std::find(_allowed_hooks.begin(), _allowed_hooks.end(), hook) != _allowed_hooks.end();
6054
}
6155

6256
// This should be overridden for any Statement which only supports some hooks

plugins/header_rewrite/statement.h

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,8 @@ class Statement
144144
Statement(const Statement &) = delete;
145145
void operator=(const Statement &) = delete;
146146

147-
// Which hook are we adding this statement to?
148-
bool set_hook(TSHttpHookID hook);
149-
TSHttpHookID
150-
get_hook() const
151-
{
152-
return _hook;
153-
}
147+
// Validate that this statement is allowed on the given hook. Used during parsing only.
148+
bool is_hook_valid(TSHttpHookID hook) const;
154149

155150
// Which hooks are this "statement" applicable for? Used during parsing only.
156151
void
@@ -267,7 +262,6 @@ class Statement
267262

268263
private:
269264
ResourceIDs _rsrc = RSRC_NONE;
270-
TSHttpHookID _hook = TS_HTTP_READ_RESPONSE_HDR_HOOK;
271265
std::vector<TSHttpHookID> _allowed_hooks;
272266
bool _initialized = false;
273267
};

src/proxy/hdrs/HTTP.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ http_hdr_url_set(HdrHeap *heap, HTTPHdrImpl *hh, URLImpl *url)
693693
void
694694
http_hdr_status_set(HTTPHdrImpl *hh, HTTPStatus status)
695695
{
696-
ink_assert(hh->m_polarity == HTTPType::RESPONSE);
696+
ink_release_assert(hh->m_polarity == HTTPType::RESPONSE);
697697
hh->u.resp.m_status = static_cast<int16_t>(status);
698698
}
699699

@@ -713,7 +713,7 @@ http_hdr_reason_get(HTTPHdrImpl *hh)
713713
void
714714
http_hdr_reason_set(HdrHeap *heap, HTTPHdrImpl *hh, std::string_view value, bool must_copy)
715715
{
716-
ink_assert(hh->m_polarity == HTTPType::RESPONSE);
716+
ink_release_assert(hh->m_polarity == HTTPType::RESPONSE);
717717
mime_str_u16_set(heap, value, &(hh->u.resp.m_ptr_reason), &(hh->u.resp.m_len_reason), must_copy);
718718
}
719719

tests/gold_tests/pluginTest/header_rewrite/header_rewrite_bundle.replay.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ autest:
176176
args:
177177
- "rules/rule_session_vars.conf"
178178

179+
- from: "http://www.example.com/from_18/"
180+
to: "http://backend.ex:{SERVER_HTTP_PORT}/to_18/"
181+
plugins:
182+
- name: "header_rewrite.so"
183+
args:
184+
- "rules/set_status_in_if.conf"
185+
179186
log_validation:
180187
traffic_out:
181188
excludes:
@@ -1880,3 +1887,27 @@ sessions:
18801887
headers:
18811888
fields:
18821889
- [ X-Session-Seen, { value: "yes", as: equal } ]
1890+
1891+
# Test 67: set-status inside if/endif at REMAP_PSEUDO_HOOK time.
1892+
# Before the fix, the set-status operator inside an if/endif block retained
1893+
# its default _hook (TS_HTTP_READ_RESPONSE_HDR_HOOK), so exec() called
1894+
# TSHttpHdrStatusSet on a request buffer, corrupting the union and crashing.
1895+
- transactions:
1896+
- client-request:
1897+
method: "GET"
1898+
version: "1.1"
1899+
url: /from_18/test
1900+
headers:
1901+
fields:
1902+
- [ Host, www.example.com ]
1903+
- [ uuid, 67 ]
1904+
1905+
server-response:
1906+
status: 200
1907+
reason: OK
1908+
headers:
1909+
fields:
1910+
- [ Connection, close ]
1911+
1912+
proxy-response:
1913+
status: 403
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# Minimal reproduction for set-status inside if/endif at remap time.
19+
# Before the fix, the operator's _hook defaulted to
20+
# TS_HTTP_READ_RESPONSE_HDR_HOOK, so exec() called TSHttpHdrStatusSet
21+
# on a request buffer, corrupting the header union and crashing.
22+
#
23+
cond %{REMAP_PSEUDO_HOOK}
24+
if
25+
cond %{CLIENT-URL:PATH} /from_18/
26+
set-status 403
27+
endif

0 commit comments

Comments
 (0)