1
0
mirror of https://github.com/apache/httpd.git synced 2026-10-23 22:49:01 +03:00

mod_sed: Fix handling of malformed scripts:

* modules/filters/sed0.c (fcomp): Reject { nested more than SED_DEPTH
  deep, and an s or y command with no delimiter.
  (ycomp): Index the translation table by the unsigned value of each
  byte.
  (comple): Return NULL from a failed compile without storing circf.
  (address): Reject a line number too large for apr_int64_t.

* test/modules/filters/test_003_sed.py (test_filters_003_37,
  test_filters_003_38, test_filters_003_42, test_filters_003_43,
  test_filters_003_44, test_filters_003_46, test_filters_003_47): New
  tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub: PR #791


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1938965 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Joe Orton
2026-10-09 19:37:21 +00:00
parent 968022ebc5
commit 59ca067cb2
2 changed files with 70 additions and 3 deletions

View File

@@ -295,6 +295,10 @@ swit:
case '{':
commands->rep->command = BCOM;
commands->rep->negfl = !(commands->rep->negfl);
if (commands->depth >= SED_DEPTH) {
command_errf(commands, SEDERR_TMOMES);
return -1;
}
commands->cmpend[commands->depth++] = &commands->rep->lb1;
commands->rep = alloc_reptr(commands);
commands->rep->ad1 = p;
@@ -528,6 +532,10 @@ jtcommon:
case 's':
commands->rep->command = SCOM;
if (*commands->cp == '\0') {
command_errf(commands, SEDERR_CGMES, commands->linebuf);
return -1;
}
commands->sseof = *commands->cp++;
commands->rep->re1 = p;
p = comple(commands, &compargs, (char *) 0, commands->rep->re1,
@@ -645,6 +653,10 @@ jtcommon:
case 'y':
commands->rep->command = YCOM;
if (*commands->cp == '\0') {
command_errf(commands, SEDERR_CGMES, commands->linebuf);
return -1;
}
commands->sseof = *commands->cp++;
commands->rep->re1 = p;
p = ycomp(commands, commands->rep->re1);
@@ -839,8 +851,13 @@ static char *address(sed_commands_t *commands, char *expbuf,
rcp = commands->cp;
lno = 0;
while(*rcp >= '0' && *rcp <= '9')
lno = lno*10 + *rcp++ - '0';
while(*rcp >= '0' && *rcp <= '9') {
if (lno > (APR_INT64_MAX - (*rcp - '0')) / 10) {
*status = APR_EGENERAL;
return NULL;
}
lno = lno*10 + (*rcp++ - '0');
}
if(rcp > commands->cp) {
if (expbuf > &commands->respace[RESIZE-3]) {
@@ -954,7 +971,7 @@ static char *ycomp(sed_commands_t *commands, char *expbuf)
sp++;
c = '\n';
}
cint = (int) c;
cint = (unsigned char) c;
if((ep[cint] = *tsp++) == '\\' && *tsp == 'n') {
ep[cint] = '\n';
tsp++;
@@ -996,6 +1013,8 @@ static char *comple(sed_commands_t *commands, sed_comp_args *compargs,
char *p;
p = sed_compile(commands, compargs, ep + 1, x3, x4);
if(p == NULL)
return NULL;
if(p == ep + 1)
return(ep);
*ep = compargs->circf;

View File

@@ -452,6 +452,19 @@ class TestSed:
assert self.body(env, r"s/\(\(mo\)n\2*\)day/[\2]/").decode() == \
"one [mo] two\nthree sunday four\n[mo] monday monday\n"
# --- blocks -----------------------------------------------------------
# Blocks nest up to 20 deep. Each } goes in its own directive: the
# rest of a line after } is not read.
def test_filters_003_37(self, env):
assert self.body(env, ["{" * 20 + "s/monday/MON/"] + ["}"] * 20) \
.decode() == "one MON two\nthree sunday four\nMON monday monday\n"
# Any deeper is rejected.
def test_filters_003_38(self, env):
self.configure(env, ["{" * 21 + "p"] + ["}"] * 21, fails=True)
assert "too many {'s" in env.apachectl_stderr, env.apachectl_stderr
# --- bracket expressions ----------------------------------------------
# A bracket expression which ends after a range's "-" is rejected, on
@@ -486,6 +499,30 @@ class TestSed:
b"\\\xe9sunday\xe9s\xe9u\xe9U\xe9"]).decode() \
== "one MON two\nthree sUnday four\nMON monday monday\n"
# An s or y command which ends before its delimiter is rejected.
@pytest.mark.parametrize("expr", ["s", "/monday/s", "y", "/monday/y"])
def test_filters_003_42(self, env, expr):
self.configure(env, expr, fails=True)
assert "command garbled" in env.apachectl_stderr, \
env.apachectl_stderr
# y maps bytes with the high bit set like any other.
def test_filters_003_43(self, env):
self.write_bytes(env, "latin1.html", b"caf\xe9 na\xefve\n")
assert self.body(env, [b"y/\xe9\xef/ei/"], path="/latin1.html") == \
b"cafe naive\n"
# A regular expression which no longer fits once the compiled script
# has filled its buffer exactly, here with the names of r commands, is
# rejected without writing past the buffer. Only a memory checker sees
# the difference.
def test_filters_003_44(self, env):
self.configure(env, [f"r {'A' * 998}"] * 10 + ["r 123456789",
"/x/p"],
fails=True)
assert "too much command text" in env.apachectl_stderr, \
env.apachectl_stderr
# An interval count is rejected as too large however many digits it
# has, rather than wrapping around.
@pytest.mark.parametrize("expr", [
@@ -497,3 +534,14 @@ class TestSed:
self.configure(env, expr, fails=True)
assert "Range endpoint too large" in env.apachectl_stderr, \
env.apachectl_stderr
# A line number too large for a 64-bit count is rejected rather than
# wrapping around; one which fits, but which no document reaches, is
# accepted.
def test_filters_003_46(self, env):
self.configure(env, "99999999999999999999p", fails=True)
assert "command garbled" in env.apachectl_stderr, \
env.apachectl_stderr
def test_filters_003_47(self, env):
assert self.body(env, "9223372036854775807d").decode() == DOC