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,

Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to