I've downgraded this because it does not affect the default configuration. It only affects installations which use the optional feature to use a MySQL database of commits.
I'm attaching a debdiff for the changes in case if anyone thinks they should be applied in isolation (perhaps as a stable-security update). The ordinary functionality still works after these changes, but the database feature is sufficiently complex to set up that I gave up on trying to test it. Ben. -- Ben Hutchings The two most common things in the universe are hydrogen and stupidity.
diff -u viewvc-1.1.5/debian/changelog viewvc-1.1.5/debian/changelog --- viewvc-1.1.5/debian/changelog +++ viewvc-1.1.5/debian/changelog @@ -1,3 +1,13 @@ +viewvc (1.1.5-1.2) UNRELEASED; urgency=low + + * Non-maintainer upload + * view_query: No longer allow an undocumented URL parameter to + override the admin-declared SQL row limit, which could result + in excessive CPU usage and memory consumption (CVE-2009-5024) + (Closes: #671482) + + -- Ben Hutchings <[email protected]> Sat, 12 May 2012 23:13:16 +0100 + viewvc (1.1.5-1.1) unstable; urgency=low * Non-maintainer upload. diff -u viewvc-1.1.5/debian/patches/series viewvc-1.1.5/debian/patches/series --- viewvc-1.1.5/debian/patches/series +++ viewvc-1.1.5/debian/patches/series @@ -5,0 +6,2 @@ +rev2547 +rev2551 only in patch2: unchanged: --- viewvc-1.1.5.orig/debian/patches/rev2547 +++ viewvc-1.1.5/debian/patches/rev2547 @@ -0,0 +1,175 @@ +------------------------------------------------------------------------ +r2547 | cmpilato | 2011-04-19 21:40:04 +0100 (Tue, 19 Apr 2011) | 30 lines + +Try to make some sense of the various CVSdb-related limitation +mechanisms, namely by removing the largely redundant "global" limit +and allowing the per-query row limit (which already exist, too) to do +its work. + +While here, remove a poorly conceived (but thankfully unhighlighted) +mechanism for overriding the administrative limit on database rows +which was accessible via URL CGI params. + +* lib/viewvc.py + (_legal_params): Remove 'limit' as a legal parameter. + (view_query): No longer allow an undocumented URL parameter to + override the admin-declared SQL row limit. That should have never + been allowed! + +* lib/cvsdb.py + (CheckinDatabase.__init__): Remove 'row_limit' parameter and + associated self._row_limit member. + (CheckinDatabase.CreateSQLQueryString): No longer fuss with + self._row_limit. Let the individual query carry the row limit. + (ConnectDatabase): Update call to CheckinDatabase(). + +* lib/query.py + (form_to_cvsdb_query): Now accept 'cfg' parameter, and set the + query's row limit from the configured defaults. + (run_query): Update call to form_to_cvsdb_query(). + +* docs/url-reference.html + Remove reference to the 'limit' parameter. + +------------------------------------------------------------------------ +--- viewvc-1.1.5.orig/docs/url-reference.html ++++ viewvc-1.1.5/docs/url-reference.html +@@ -1193,13 +1193,6 @@ + page</td> + </tr> + <tr> +- <td><code>limit=<var>LIMIT</var></code></td> +- <td>optional</td> +- <td>maximum number of file-revisions to process during a +- query. Default is value of <code>row_limit</code> configuration +- option</td> +- </tr> +- <tr> + <td><code>limit_changes=<var>LIMIT_CHANGES</var></code></td> + <td>optional</td> + <td>maximum number of files to list per commit in query +--- viewvc-1.1.5.orig/lib/cvsdb.py ++++ viewvc-1.1.5/lib/cvsdb.py +@@ -38,13 +38,12 @@ + ## complient database interface + + class CheckinDatabase: +- def __init__(self, host, port, user, passwd, database, row_limit): ++ def __init__(self, host, port, user, passwd, database): + self._host = host + self._port = port + self._user = user + self._passwd = passwd + self._database = database +- self._row_limit = row_limit + self._version = None + + ## database lookup caches +@@ -444,13 +443,11 @@ + conditions = string.join(joinConds + condList, " AND ") + conditions = conditions and "WHERE %s" % conditions + +- ## limit the number of rows requested or we could really slam +- ## a server with a large database ++ ## apply the query's row limit, if any (so we avoid really ++ ## slamming a server with a large database) + limit = "" + if query.limit: + limit = "LIMIT %s" % (str(query.limit)) +- elif self._row_limit: +- limit = "LIMIT %s" % (str(self._row_limit)) + + sql = "SELECT %s.* FROM %s %s %s %s" \ + % (commits_table, tables, conditions, order_by, limit) +@@ -769,8 +766,8 @@ + self.data = data + self.match = match + +-## CheckinDatabaseQueryData is a object which contains the search parameters +-## for a query to the CheckinDatabase ++## CheckinDatabaseQuery is an object which contains the search ++## parameters for a query to the Checkin Database + class CheckinDatabaseQuery: + def __init__(self): + ## sorting +@@ -861,7 +858,7 @@ + user = cfg.cvsdb.user + passwd = cfg.cvsdb.passwd + db = CheckinDatabase(cfg.cvsdb.host, cfg.cvsdb.port, user, passwd, +- cfg.cvsdb.database_name, cfg.cvsdb.row_limit) ++ cfg.cvsdb.database_name) + db.Connect() + return db + +--- viewvc-1.1.5.orig/lib/viewvc.py ++++ viewvc-1.1.5/lib/viewvc.py +@@ -706,7 +706,6 @@ + 'mindate' : _re_validate_datetime, + 'maxdate' : _re_validate_datetime, + 'format' : _re_validate_alpha, +- 'limit' : _re_validate_number, + + # for redirect_pathrev + 'orig_path' : None, +@@ -3959,7 +3958,6 @@ + mindate = request.query_dict.get('mindate', '') + maxdate = request.query_dict.get('maxdate', '') + format = request.query_dict.get('format') +- limit = int(request.query_dict.get('limit', 0)) + limit_changes = int(request.query_dict.get('limit_changes', + cfg.options.limit_changes)) + +@@ -4029,16 +4027,17 @@ + query.SetFromDateObject(mindate) + if maxdate is not None: + query.SetToDateObject(maxdate) +- if limit: +- query.SetLimit(limit) +- elif format == 'rss': ++ ++ # Set the admin-defined (via configuration) row limits. This is to avoid ++ # slamming the database server with a monster query. ++ if format == 'rss': + query.SetLimit(cfg.cvsdb.rss_row_limit) ++ else: ++ query.SetLimit(cfg.cvsdb.row_limit) + + # run the query + db.RunQuery(query) + +- sql = request.server.escape(db.CreateSQLQueryString(query)) +- + # gather commits + commits = [] + plus_count = 0 +@@ -4120,7 +4119,7 @@ + + data = common_template_data(request) + data.merge(ezt.TemplateData({ +- 'sql': sql, ++ 'sql': request.server.escape(db.CreateSQLQueryString(query)), + 'english_query': english_query(request), + 'queryform_href': request.get_url(view_func=view_queryform, escape=1), + 'backout_href': backout_href, +--- viewvc-1.1.5.orig/lib/query.py ++++ viewvc-1.1.5/lib/query.py +@@ -217,8 +217,9 @@ + else: + return "exact" + +-def form_to_cvsdb_query(form_data): ++def form_to_cvsdb_query(cfg, form_data): + query = cvsdb.CreateCheckinQuery() ++ query.SetLimit(cfg.cvsdb.row_limit) + + if form_data.repository: + for cmd, str in listparse_string(form_data.repository): +@@ -380,7 +381,7 @@ + return ob + + def run_query(server, cfg, form_data, viewvc_link): +- query = form_to_cvsdb_query(form_data) ++ query = form_to_cvsdb_query(cfg, form_data) + db = cvsdb.ConnectDatabaseReadOnly(cfg) + db.RunQuery(query) + only in patch2: unchanged: --- viewvc-1.1.5.orig/debian/patches/rev2551 +++ viewvc-1.1.5/debian/patches/rev2551 @@ -0,0 +1,308 @@ +------------------------------------------------------------------------ +r2551 | cmpilato | 2011-04-20 15:50:40 +0100 (Wed, 20 Apr 2011) | 41 lines + +Fix (to the degree that I believe is reasonable at this time) issue +#433 ("queries return only partial results"). When a database query +is artificially limited by the 'row_limit' setting, inform the user +that the returned data is incomplete. + +* lib/cvsdb.py + (CheckinDatabase.CreateSQLQueryString): Add 'detect_leftover' + parameter, used internally to check for a reached query limit. + (CheckinDatabase.RunQuery): Update call to CreateSQLQueryString(), + and check for leftover query response rows. If any are found, set + the appropriate flag on the query object. + (CheckinDatabaseQuery.__init__): Set initial values for new + 'executed' and 'limit_reached' members. + (CheckinDatabaseQuery.SetExecuted, + CheckinDatabaseQuery.SetLimitReached, + CheckinDatabaseQuery.GetLimitReached, + CheckinDatabaseQuery.GetCommitList): New functions. + +* lib/viewvc.py + (view_query): Use query.GetCommitList() now instead of poking into + the query object directly. Also, check query.GetLimitReached(), + reporting the findings through the data dictionary (via a new + 'row_limit_reached' item) to the templates. + +* lib/query.py + (run_query): Use query.GetCommitList() now instead of poking into + the query object directly. Now return a 2-tuple of commits and a + limit-reached flag. + (main): Update expectations of run_query() call. Populate + 'row_limit_reached' data dictionary item. + +* templates/query_results.ezt, +* templates/query.ezt + Display a warning if the query results are incomplete. + +* templates/docroot/styles.css + (.vc_warning): New style definition. + +* docs/template-authoring-guide.html + Document the new 'row_limit_reached' template item. + +------------------------------------------------------------------------ +--- viewvc-1.1.5.orig/docs/template-authoring-guide.html ++++ viewvc-1.1.5/docs/template-authoring-guide.html +@@ -1822,6 +1822,14 @@ + <tt>date</tt>, <tt>author</tt>, and <tt>file</tt>.</td> + </tr> + <tr class="varlevel1"> ++ <td class="varname">row_limit_reached</td> ++ <td>Boolean</td> ++ <td>Indicates whether the internal database row limit threshold (set ++ via the <code>cvsdb.row_limit</code> ++ and <code>cvsdb.rss_row_limit</code> configuration options) was ++ reached by the query.</td> ++</tr> ++<tr class="varlevel1"> + <td class="varname">show_branch</td> + <td>Boolean</td> + <td>Indicates whether or not to list branch names in the results. True +--- viewvc-1.1.5.orig/templates/query_results.ezt ++++ viewvc-1.1.5/templates/query_results.ezt +@@ -7,6 +7,15 @@ + + <p><strong>[english_query]</strong></p> + [# <!-- {sql} --> ] ++[if-any row_limit_reached] ++<p class="vc_warning">WARNING: These query results have been ++ artificially limited by an administrative threshold value and do ++ <em>not</em> represent the entirety of the data set which matches ++ the query. Consider <a href="[queryform_href]">modifying your ++ query to be more specific</a>, using your version control tool's ++ query capabilities, or asking your adminstrator to raise the ++ database response size threshold.</p> ++[end] + <p><a href="[queryform_href]">Modify query</a></p> + <p><a href="[backout_href]">Show commands which could be used to back out these changes</a></p> + +--- viewvc-1.1.5.orig/templates/query.ezt ++++ viewvc-1.1.5/templates/query.ezt +@@ -158,7 +158,15 @@ + [is query "skipped"] + [else] + <p><strong>[num_commits]</strong> matches found.</p> +- ++[if-any row_limit_reached] ++<p class="vc_warning">WARNING: These query results have been ++ artificially limited by an administrative threshold value and do ++ <em>not</em> represent the entirety of the data set which matches ++ the query. Consider modifying your query to be more specific</a>, ++ using your version control tool's query capabilities, or asking ++ your adminstrator to raise the database response size ++ threshold.</p> ++[end] + [if-any commits] + <table cellspacing="0" cellpadding="2"> + <thead> +--- viewvc-1.1.5.orig/templates/docroot/styles.css ++++ viewvc-1.1.5/templates/docroot/styles.css +@@ -272,3 +272,14 @@ + .vc_query_form { + background-color: #e6e6e6; + } ++ ++ ++/*** Warning! ***/ ++.vc_warning { ++ border-width: 1px 2px 2px 2px; ++ border-color: black; ++ border-style: solid; ++ background-color: red; ++ color: white; ++ padding: 0.5em; ++} +--- viewvc-1.1.5.orig/lib/cvsdb.py ++++ viewvc-1.1.5/lib/cvsdb.py +@@ -362,7 +362,7 @@ + + return "(%s)" % (string.join(sqlList, " OR ")) + +- def CreateSQLQueryString(self, query): ++ def CreateSQLQueryString(self, query, detect_leftover=0): + commits_table = self._version >= 1 and 'commits' or 'checkins' + tableList = [(commits_table, None)] + condList = [] +@@ -447,7 +447,10 @@ + ## slamming a server with a large database) + limit = "" + if query.limit: +- limit = "LIMIT %s" % (str(query.limit)) ++ if detect_leftover: ++ limit = "LIMIT %s" % (str(query.limit + 1)) ++ else: ++ limit = "LIMIT %s" % (str(query.limit)) + + sql = "SELECT %s.* FROM %s %s %s %s" \ + % (commits_table, tables, conditions, order_by, limit) +@@ -455,14 +458,20 @@ + return sql + + def RunQuery(self, query): +- sql = self.CreateSQLQueryString(query) ++ sql = self.CreateSQLQueryString(query, 1) + cursor = self.db.cursor() + cursor.execute(sql) ++ query.SetExecuted() ++ row_count = 0 + + while 1: + row = cursor.fetchone() + if not row: + break ++ row_count = row_count + 1 ++ if query.limit and (row_count > query.limit): ++ query.SetLimitReached() ++ break + + (dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID, + dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines, +@@ -767,7 +776,8 @@ + self.match = match + + ## CheckinDatabaseQuery is an object which contains the search +-## parameters for a query to the Checkin Database ++## parameters for a query to the Checkin Database and -- after the ++## query is executed -- the data returned by the query. + class CheckinDatabaseQuery: + def __init__(self): + ## sorting +@@ -787,7 +797,8 @@ + + ## limit on number of rows to return + self.limit = None +- ++ self.limit_reached = 0 ++ + ## list of commits -- filled in by CVS query + self.commit_list = [] + +@@ -795,6 +806,9 @@ + ## are added + self.commit_cb = None + ++ ## has this query been run? ++ self.executed = 0 ++ + def SetRepository(self, repository, match = "exact"): + self.repository_list.append(QueryEntry(repository, match)) + +@@ -840,6 +854,20 @@ + def AddCommit(self, commit): + self.commit_list.append(commit) + ++ def SetExecuted(self): ++ self.executed = 1 ++ ++ def SetLimitReached(self): ++ self.limit_reached = 1 ++ ++ def GetLimitReached(self): ++ assert self.executed ++ return self.limit_reached ++ ++ def GetCommitList(self): ++ assert self.executed ++ return self.commit_list ++ + + ## + ## entrypoints +--- viewvc-1.1.5.orig/lib/viewvc.py ++++ viewvc-1.1.5/lib/viewvc.py +@@ -4037,20 +4037,22 @@ + + # run the query + db.RunQuery(query) +- ++ commit_list = query.GetCommitList() ++ row_limit_reached = query.GetLimitReached() ++ + # gather commits + commits = [] + plus_count = 0 + minus_count = 0 + mod_time = -1 +- if query.commit_list: ++ if commit_list: + files = [] + limited_files = 0 +- current_desc = query.commit_list[0].GetDescriptionID() +- current_rev = query.commit_list[0].GetRevision() ++ current_desc = commit_list[0].GetDescriptionID() ++ current_rev = commit_list[0].GetRevision() + dir_strip = _path_join(repos_dir) + +- for commit in query.commit_list: ++ for commit in commit_list: + commit_desc = commit.GetDescriptionID() + commit_rev = commit.GetRevision() + +@@ -4128,6 +4130,7 @@ + 'show_branch': show_branch, + 'querysort': querysort, + 'commits': commits, ++ 'row_limit_reached' : ezt.boolean(row_limit_reached), + 'limit_changes': limit_changes, + 'limit_changes_href': limit_changes_href, + 'rss_link_href': request.get_url(view_func=view_query, +--- viewvc-1.1.5.orig/lib/query.py ++++ viewvc-1.1.5/lib/query.py +@@ -385,8 +385,11 @@ + db = cvsdb.ConnectDatabaseReadOnly(cfg) + db.RunQuery(query) + +- if not query.commit_list: +- return [ ] ++ commit_list = query.GetCommitList() ++ if not commit_list: ++ return [ ], 0 ++ ++ row_limit_reached = query.GetLimitReached() + + commits = [ ] + files = [ ] +@@ -397,8 +400,8 @@ + for key, value in rootitems: + cvsroots[cvsdb.CleanRepository(value)] = key + +- current_desc = query.commit_list[0].GetDescription() +- for commit in query.commit_list: ++ current_desc = commit_list[0].GetDescription() ++ for commit in commit_list: + desc = commit.GetDescription() + if current_desc == desc: + files.append(commit) +@@ -421,7 +424,7 @@ + return len(commit.files) > 0 + commits = filter(_only_with_files, commits) + +- return commits ++ return commits, row_limit_reached + + def main(server, cfg, viewvc_link): + try: +@@ -430,10 +433,12 @@ + form_data = FormData(form) + + if form_data.valid: +- commits = run_query(server, cfg, form_data, viewvc_link) ++ commits, row_limit_reached = run_query(server, cfg, ++ form_data, viewvc_link) + query = None + else: + commits = [ ] ++ row_limit_reached = 0 + query = 'skipped' + + data = ezt.TemplateData({ +@@ -453,6 +458,7 @@ + 'date' : form_data.date, + + 'query' : query, ++ 'row_limit_reached' : ezt.boolean(row_limit_reached), + 'commits' : commits, + 'num_commits' : len(commits), + 'rss_href' : None,
signature.asc
Description: This is a digitally signed message part

