Subversion FSFS repositories can be corrupted by editing packed revision properties Summary: ======== When one or more revision properties of a packed revision are set to new, larger values, a "pack file" in the repository may get split. In the process the wrong pack file may be deleted. This can lead to data loss of revision property data. Known vulnerable: ================= Subversion servers through 1.8.0 and 1.8.1. Only FSFS repositories created with Subversion 1.8 or upgraded to 1.8 format (using 'svnadmin upgrade') are affected. Known fixed: ============ Subversion 1.8.2 and newer. BDB repositories (any version) are not vulnerable. Subversion 1.7.x and earlier is not vulnerable. Details: ======== The FSFS repository stores additional information such as the log message for each revision in a revision property (revprop) file. In the latest FSFS format introduced with Apache Subversion 1.8.0, 'svnadmin pack' will aggregate those revprop files into fewer pack files plus a manifest acting as an index. Subversion limits the size of those pack files and will create multiple packs as necessary. The limit is configurable in the repository's fsfs.conf. Because revprops may be modified for existing revisions, the contents of the respective pack files must change in the process. If the new data is larger, Subversion may need to split the pack file to keep within the specified limit. Due to a simple programming oversight, the revision-to-file mapping information in the manifest file is used wrongly, if there is more than one pack file and we modify one that pertains to higher revisions: 1) Apache Subversion will most likely remove the first pack file instead of the one pertaining to the touched revision. This results in a loss of information for earlier revisions. 2) The updated manifest file will link the wrong revision(s) to the updated pack file. This causes the wrong data to be returned to the user in later queries. Part of the revprops are the commit time stamps used by Apache Subversion to assign time stamps to each node during export or checkout operations. Thus, those operations are likely to fail when the versioned data they would work on includes data from the the corrupted revision range. (Checkout of a later revision may fail too if one of the files in the later revision was last changed in a revision of the corrupted revision range.) 'svn log' might succeed but report the log messages and other revprops in the wrong revisions. Confidential information contained in those revprops may thus bypass authz filtering. Severity: ========= CVSSv2 Base Score: 6.5 CVSSv2 Base Vector: AV:N/AC:L/Au:S/C:P/I:P/A:P We consider this to be a medium risk vulnerability. Write access to revision properties is opt-in and must be activated by the administrator on a per-repository basis. Packing is opt-in too and is never run automatically by Subversion. A remote authenticated attacker with commit access may be able to corrupt repositories on a Subversion server, gain access to confidential information in the same repository and cause disruption for other users. Recommendations: ================ We recommend all 1.8.x users to upgrade to Subversion 1.8.3. Users who are unable to upgrade may apply the included patch. New Subversion packages can be found at: http://subversion.apache.org/packages.html Until the fix is in effect, 1) refrain from packing 1.8-format FSFS repositories that have not been packed in the past, and 2) disable the pre-revprop-change hook (to disable revprop changes) for 1.8-format repositories that have already been packed in the past Already corrupted repositories can be brought back into working state by the following procedure: 1) use 'svnadmin verify' to identify corrupted revision ranges 2) replace shards ($repos/db/revprops) containing corrupted revisions with their latest backups 3) repeat until corrupted revprops have been restored References: =========== CVE-2013-4246 (Subversion) Reported by: ============ Ivan Zhakov, VisualSVN Patches: ======== Patch for Subversion 1.8: [[[ Index: subversion/libsvn_fs_fs/fs_fs.c =================================================================== --- subversion/libsvn_fs_fs/fs_fs.c (revision 1513879) +++ subversion/libsvn_fs_fs/fs_fs.c (revision 1513880) @@ -3596,7 +3596,7 @@ /* sum of values in SIZES */ apr_size_t total_size; - /* first revision in the pack */ + /* first revision in the pack (>= MANIFEST_START) */ svn_revnum_t start_revision; /* size of the revprops in PACKED_REVPROPS */ @@ -3610,8 +3610,12 @@ * in the pack, i.e. the pack content without header and compression */ svn_stringbuf_t *packed_revprops; + /* First revision covered by MANIFEST. + * Will equal the shard start revision or 1, for the 1st shard. */ + svn_revnum_t manifest_start; + /* content of the manifest. - * Maps long(rev - START_REVISION) to const char* pack file name */ + * Maps long(rev - MANIFEST_START) to const char* pack file name */ apr_array_header_t *manifest; } packed_revprops_t; @@ -3730,9 +3734,11 @@ } /* Index for our revision. Rev 0 is excluded from the first shard. */ - idx = (int)(revprops->revision % ffd->max_files_per_dir); - if (revprops->revision < ffd->max_files_per_dir) - --idx; + revprops->manifest_start = revprops->revision + - (revprops->revision % ffd->max_files_per_dir); + if (revprops->manifest_start == 0) + ++revprops->manifest_start; + idx = (int)(revprops->revision - revprops->manifest_start); if (revprops->manifest->nelts <= idx) return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, @@ -4198,10 +4204,13 @@ svn_string_t *new_filename; int i; apr_file_t *file; + int manifest_offset + = (int)(revprops->start_revision - revprops->manifest_start); /* get the old (= current) file name and enlist it for later deletion */ - const char *old_filename - = APR_ARRAY_IDX(revprops->manifest, start, const char*); + const char *old_filename = APR_ARRAY_IDX(revprops->manifest, + start + manifest_offset, + const char*); if (*files_to_delete == NULL) *files_to_delete = apr_array_make(pool, 3, sizeof(const char*)); @@ -4223,7 +4232,8 @@ /* update the manifest to point to the new file */ for (i = start; i < end; ++i) - APR_ARRAY_IDX(revprops->manifest, i, const char*) = new_filename->data; + APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*) + = new_filename->data; /* create a file stream for the new file */ SVN_ERR(svn_io_file_open(&file, svn_dirent_join(revprops->folder, ]]]