From 8629f464db1e3ddd5c31ec7731a3ec71f94c6fdd Mon Sep 17 00:00:00 2001 From: Jose Pedro Oliveira Date: Fri, 17 Jun 2011 04:02:53 +0100 Subject: [PATCH] Patch for CVE-2011-1951: syslog-ng-3.1.4-pcre-dos.patch (#709088) Enabled the test suite --- ...g-ng-3.1.4-disable-sql-and-ssl-tests.patch | 19 ++ syslog-ng-3.1.4-pcre-dos.patch | 271 ++++++++++++++++++ syslog-ng.spec | 15 +- 3 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 syslog-ng-3.1.4-disable-sql-and-ssl-tests.patch create mode 100644 syslog-ng-3.1.4-pcre-dos.patch diff --git a/syslog-ng-3.1.4-disable-sql-and-ssl-tests.patch b/syslog-ng-3.1.4-disable-sql-and-ssl-tests.patch new file mode 100644 index 0000000..11e3a76 --- /dev/null +++ b/syslog-ng-3.1.4-disable-sql-and-ssl-tests.patch @@ -0,0 +1,19 @@ +diff -ruN syslog-ng-3.1.4/tests/functional/func_test.py syslog-ng-3.1.4-modified/tests/functional/func_test.py +--- syslog-ng-3.1.4/tests/functional/func_test.py 2009-11-21 15:48:09.000000000 +0000 ++++ syslog-ng-3.1.4-modified/tests/functional/func_test.py 2011-06-17 03:44:18.057873606 +0100 +@@ -60,11 +60,12 @@ + # import test modules + import test_file_source + import test_filters +-import test_input_drivers ++#import test_input_drivers + import test_performance +-import test_sql ++#import test_sql + +-tests = (test_input_drivers, test_sql, test_file_source, test_filters, test_performance) ++#tests = (test_input_drivers, test_sql, test_file_source, test_filters, test_performance) ++tests = (test_file_source, test_filters, test_performance) + + init_env() + seed_rnd() diff --git a/syslog-ng-3.1.4-pcre-dos.patch b/syslog-ng-3.1.4-pcre-dos.patch new file mode 100644 index 0000000..08a12f9 --- /dev/null +++ b/syslog-ng-3.1.4-pcre-dos.patch @@ -0,0 +1,271 @@ +commit 35de55e53dd653c50c8da5daf41a99ab22e7e8aa +Author: Balazs Scheidler +Date: Tue May 3 20:54:53 2011 +0200 + + pcre: fixed a potential resource hogging infinite loop when an error occurs + + Any kind of PCRE error case would cause an infinite loop, when the + "global" flag is present and pcre returns an error code. + + The reported problem is that with PCRE 8.12 we indeed get such an error + while doing a global replace. + + This patch also reworks the way PCRE based replacements are made, that code + was hairy, and I just hope this one is one bit less so. One performance + related change also made it that improves the speed pcre replacements, + which previously zeroed out a 3k array unconditionally in every invocation. + + Also added some additional testcases to be sure I didn't break anything. + + Reported-By: Micah Anderson + Signed-off-by: Balazs Scheidler + +diff --git a/src/logmatcher.c b/src/logmatcher.c +index 67b6c1b..6b70f13 100644 +--- a/src/logmatcher.c ++++ b/src/logmatcher.c +@@ -504,7 +504,6 @@ typedef struct _LogMatcherPcreRe + pcre *pattern; + pcre_extra *extra; + gint match_options; +- int start_offset; + } LogMatcherPcreRe; + + static gboolean +@@ -623,29 +622,37 @@ static gboolean + log_matcher_pcre_re_match(LogMatcher *s, LogMessage *msg, gint value_handle, const gchar *value, gssize value_len) + { + LogMatcherPcreRe *self = (LogMatcherPcreRe *) s; +- int matches[RE_MAX_MATCHES * 3]; ++ gint *matches; ++ gsize matches_size; ++ gint num_matches; + gint rc; + +- if(value_len == -1) ++ if (value_len == -1) + value_len = strlen(value); + +- self->start_offset = 0; +- rc = pcre_exec(self->pattern, self->extra, value, value_len, self->start_offset, self->match_options, matches, (RE_MAX_MATCHES * 3)); ++ if (pcre_fullinfo(self->pattern, self->extra, PCRE_INFO_CAPTURECOUNT, &num_matches) < 0) ++ g_assert_not_reached(); ++ if (num_matches > RE_MAX_MATCHES) ++ num_matches = RE_MAX_MATCHES; ++ ++ matches_size = 3 * (num_matches + 1); ++ matches = g_alloca(matches_size * sizeof(gint)); ++ ++ rc = pcre_exec(self->pattern, self->extra, ++ value, value_len, 0, self->match_options, matches, matches_size); + if (rc < 0) + { + switch (rc) + { +- case PCRE_ERROR_NOMATCH: +- /* +- msg_debug("No match", NULL); +- */ ++ case PCRE_ERROR_NOMATCH: + break; ++ ++ default: + /* Handle other special cases */ +- default: +- msg_error("Error while matching regexp", +- evt_tag_int("error_code",rc), +- NULL); +- break; ++ msg_error("Error while matching regexp", ++ evt_tag_int("error_code", rc), ++ NULL); ++ break; + } + return FALSE; + } +@@ -668,78 +675,120 @@ static gchar * + log_matcher_pcre_re_replace(LogMatcher *s, LogMessage *msg, gint value_handle, const gchar *value, gssize value_len, LogTemplate *replacement, gssize *new_length) + { + LogMatcherPcreRe *self = (LogMatcherPcreRe *) s; +- int matches[RE_MAX_MATCHES * 3]; +- gint rc; +- gboolean first_round = TRUE; + GString *new_value = NULL; +- gssize last_offset = 0; +- gint options = 0; ++ gint *matches; ++ gsize matches_size; ++ gint num_matches; ++ gint rc; ++ gint start_offset, last_offset; ++ gint options; ++ gboolean last_match_was_empty; ++ ++ if (pcre_fullinfo(self->pattern, self->extra, PCRE_INFO_CAPTURECOUNT, &num_matches) < 0) ++ g_assert_not_reached(); ++ if (num_matches > RE_MAX_MATCHES) ++ num_matches = RE_MAX_MATCHES; ++ ++ matches_size = 3 * (num_matches + 1); ++ matches = g_alloca(matches_size * sizeof(gint)); ++ ++ /* we need zero initialized offsets for the last match as the ++ * algorithm tries uses that as the base position */ + +- memset(matches, 0, sizeof(matches)); ++ matches[0] = matches[1] = matches[2] = 0; + + if (value_len == -1) + value_len = strlen(value); + ++ last_offset = start_offset = 0; ++ last_match_was_empty = FALSE; + do + { +- options = 0; +- self->start_offset = matches[1]; /* Start at end of previous match 0 on the first iteration*/ +- +- /* If the previous match was for an empty string, we are finished if we are +- at the end of the subject. Otherwise, arrange to run another match at the +- same point to see if a non-empty match can be found. ++ /* loop over the string, replacing one occurence at a time. */ ++ ++ /* NOTE: zero length matches need special care, as we could spin ++ * forever otherwise (since the current position wouldn't be ++ * advanced). ++ * ++ * A zero-length match can be as simple as "a*" which will be ++ * returned unless PCRE_NOTEMPTY is specified. ++ * ++ * By supporting zero-length matches, we basically make it ++ * possible to insert replacement between each incoming ++ * character. ++ * ++ * For example: ++ * pattern: a* ++ * replacement: # ++ * input: message ++ * result: #m#e#s#s#a#g#e# ++ * ++ * This mimics Perl behaviour. + */ + +- if (matches[0] == matches[1] && !first_round) ++ if (last_match_was_empty) + { +- if (matches[0] == value_len) +- break; ++ /* Otherwise, arrange to run another match at the same point ++ * to see if a non-empty match can be found. ++ */ ++ + options = PCRE_NOTEMPTY | PCRE_ANCHORED; + } ++ else ++ { ++ options = 0; ++ } + +- rc = pcre_exec(self->pattern, self->extra, value, value_len, self->start_offset/*start offset*/, (self->match_options | options) , matches, (RE_MAX_MATCHES * 3) ); +- if (rc < 0) ++ rc = pcre_exec(self->pattern, self->extra, ++ value, value_len, ++ start_offset, (self->match_options | options), matches, matches_size); ++ if (rc < 0 && rc != PCRE_ERROR_NOMATCH) + { +- if(rc == PCRE_ERROR_NOMATCH) +- { +- /* msg_debug("No match", NULL); */ +- if(!first_round) +- { +- if (options == 0) +- break; +- else +- matches[1] = self->start_offset + 1; +- continue; /* Go round the loop again */ +- } +- } +- else +- { +- /* Handle other special cases */ +- msg_error("Error while matching regexp", +- evt_tag_int("error_code",rc), +- NULL); +- } ++ msg_error("Error while matching regexp", ++ evt_tag_int("error_code", rc), ++ NULL); ++ break; + } +- else if (rc == 0) ++ else if (rc < 0) + { +- msg_error("Error while storing matching substrings", NULL); ++ if ((options & PCRE_NOTEMPTY) == 0) ++ { ++ /* we didn't match, even when we permitted to match the ++ * empty string. Nothing to find here, bail out */ ++ break; ++ } ++ ++ /* we didn't match, quite possibly because the empty match ++ * was not permitted. Skip one character in order to avoid ++ * infinite loop over the same zero-length match. */ ++ ++ start_offset = start_offset + 1; ++ /* FIXME: handle complex sequences like utf8 and newline characters */ ++ last_match_was_empty = FALSE; ++ continue; + } + else + { ++ /* if the output array was too small, truncate the number of ++ captures to RE_MAX_MATCHES */ ++ ++ if (rc == 0) ++ rc = matches_size / 3; ++ + log_matcher_pcre_re_feed_backrefs(s, msg, value_handle, matches, rc, value); + log_matcher_pcre_re_feed_named_substrings(s, msg, matches, value); + + if (!new_value) + new_value = g_string_sized_new(value_len); +- /* literal */ ++ /* append non-matching portion */ + g_string_append_len(new_value, &value[last_offset], matches[0] - last_offset); + /* replacement */ + log_template_append_format(replacement, msg, 0, TS_FMT_BSD, NULL, 0, 0, new_value); +- last_offset = matches[1]; ++ last_match_was_empty = (matches[0] == matches[1]); ++ start_offset = last_offset = matches[1]; + } +- first_round = FALSE; + } +- while (TRUE && (self->super.flags & LMF_GLOBAL)); ++ while (self->super.flags & LMF_GLOBAL && start_offset < value_len); + + if (new_value) + { +diff --git a/tests/unit/test_matcher.c b/tests/unit/test_matcher.c +index 3df98e5..95866b3 100644 +--- a/tests/unit/test_matcher.c ++++ b/tests/unit/test_matcher.c +@@ -144,9 +144,18 @@ main() + /* empty match with global flag*/ + testcase_replace("<155>2006-02-11T10:34:56+01:00 bzorp syslog-ng[23323]: aa bb", 0, "c*", "#", "#a#a# #b#b#", LMF_GLOBAL, log_matcher_pcre_re_new()); + testcase_replace("<155>2006-02-11T10:34:56+01:00 bzorp syslog-ng[23323]: aa bb", 0, "a*", "?", "?? ?b?b?", LMF_GLOBAL, log_matcher_pcre_re_new()); ++ testcase_replace("<155>2006-02-11T10:34:56+01:00 bzorp syslog-ng[23323]: aa", 0, "aa|b*", "@", "@@", LMF_GLOBAL, log_matcher_pcre_re_new()); ++ testcase_replace("<155>2006-02-11T10:34:56+01:00 bzorp syslog-ng[23323]: aa", 0, "aa|b*", "@", "@", 0, log_matcher_pcre_re_new()); ++ testcase_replace("<155>2006-02-11T10:34:56+01:00 bzorp syslog-ng[23323]: aa", 0, "b*|aa", "@", "@@@", LMF_GLOBAL, log_matcher_pcre_re_new()); ++ testcase_replace("<155>2006-02-11T10:34:56+01:00 bzorp syslog-ng[23323]: aa", 0, "b*|aa", "@", "@aa", 0, log_matcher_pcre_re_new()); + + testcase_replace("<155>2006-02-11T10:34:56+01:00 bzorp syslog-ng[23323]: wikiwiki", 0, "wi", "", "kiki", LMF_GLOBAL, log_matcher_pcre_re_new()); + testcase_replace("<155>2006-02-11T10:34:56+01:00 bzorp syslog-ng[23323]: wikiwiki", 0, "wi", "kuku", "kukukikukuki", LMF_GLOBAL, log_matcher_pcre_re_new()); ++ ++ /* this tests a pcre 8.12 incompatibility */ ++ ++ testcase_replace("<155>2006-02-11T10:34:56+01:00 bzorp syslog-ng[23323]: wikiwiki", 0, "([[:digit:]]{1,3}\\.){3}[[:digit:]]{1,3}", "foo", "wikiwiki", LMF_GLOBAL, log_matcher_pcre_re_new()); ++ + #endif + + return 0; diff --git a/syslog-ng.spec b/syslog-ng.spec index 29e977a..2458354 100644 --- a/syslog-ng.spec +++ b/syslog-ng.spec @@ -4,7 +4,7 @@ Name: syslog-ng Version: 3.1.4 -Release: 3%{?dist} +Release: 4%{?dist} Summary: Next-generation syslog server Group: System Environment/Daemons @@ -16,6 +16,9 @@ Source2: syslog-ng.init.d Source3: syslog-ng.sysconfig Source4: syslog-ng.logrotate +Patch0: syslog-ng-3.1.4-disable-sql-and-ssl-tests.patch +Patch1: syslog-ng-3.1.4-pcre-dos.patch + BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: pkgconfig @@ -49,6 +52,8 @@ ideal for firewalled environments. %prep %setup -q +%patch0 -p1 +%patch1 -p1 # fix perl path %{__sed} -i 's|^#!/usr/local/bin/perl|#!%{__perl}|' contrib/relogger.pl @@ -109,6 +114,10 @@ for vimver in 70 71 72 73 ; do done +%check +make check + + %clean rm -rf %{buildroot} @@ -179,6 +188,10 @@ fi %changelog +* Fri Jun 17 2011 Jose Pedro Oliveira - 3.1.4-4 +- Patch for CVE-2011-1951: syslog-ng-3.1.4-pcre-dos.patch (#709088) +- Enabled the test suite + * Mon May 9 2011 Jose Pedro Oliveira - 3.1.4-3 - Bumped the eventlog version to match the latest upstream version (0.2.12) - Overrided the default _localstatedir value (configure --localstatedir)