copy: protect against overlapping extents
authorPádraig Brady <P@draigBrady.com>
Mon, 28 Mar 2011 18:22:21 +0000 (19:22 +0100)
committerPádraig Brady <P@draigBrady.com>
Fri, 1 Apr 2011 13:42:46 +0000 (14:42 +0100)
* src/extent-scan.c (extent_scan_read): Add a more stringent check
for OFF_T overflow, to ensure subsequent code is immune.
Detect overlapping extents and adjust, so as files always copied.
Detection using a single scan with fallback to a standard copy
was thought too expensive in memory or time.
* NEWS: Mention the fix

NEWS
src/extent-scan.c

diff --git a/NEWS b/NEWS
index 9af1cd9..0c1aa6b 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,10 @@ GNU coreutils NEWS                                    -*- outline -*-
   copying the symlink and then not preserving its timestamp.
   [bug introduced in coreutils-8.0]
 
+  cp now avoids FIEMAP issues with BTRFS before Linux 2.6.38,
+  which could result in corrupt copies of sparse files.
+  [bug introduced in coreutils-8.10]
+
   cut could segfault when invoked with a user-specified output
   delimiter and an unbounded range like "-f1234567890-".
   [bug introduced in coreutils-5.3.0]
index f10d8e0..c0a5de6 100644 (file)
@@ -122,7 +122,7 @@ extent_scan_read (struct extent_scan *scan)
 
   for (i = 0; i < scan->ei_count; i++)
     {
-      assert (fm_extents[i].fe_logical <= OFF_T_MAX);
+      assert (fm_extents[i].fe_logical <= OFF_T_MAX - fm_extents[i].fe_length);
 
       if (si && last_ei->ext_flags ==
           (fm_extents[i].fe_flags & ~FIEMAP_EXTENT_LAST)
@@ -134,6 +134,38 @@ extent_scan_read (struct extent_scan *scan)
           /* Copy flags in case different.  */
           last_ei->ext_flags = fm_extents[i].fe_flags;
         }
+      else if ((si == 0 && scan->scan_start > fm_extents[i].fe_logical)
+               || (si && last_ei->ext_logical + last_ei->ext_length >
+                   fm_extents[i].fe_logical))
+        {
+          /* BTRFS before 2.6.38 could return overlapping extents
+             for sparse files.  We adjust the returned extents
+             rather than failing, as otherwise it would be inefficient
+             to detect this on the initial scan.  */
+          uint64_t new_logical;
+          uint64_t length_adjust;
+          if (si == 0)
+            new_logical = scan->scan_start;
+          else
+            {
+              /* We could return here if scan->scan_start == 0
+                 but don't so as to minimize special cases.  */
+              new_logical = last_ei->ext_logical + last_ei->ext_length;
+            }
+          length_adjust = new_logical - fm_extents[i].fe_logical;
+          /* If an extent is contained within the previous one, just fail.  */
+          if (length_adjust < fm_extents[i].fe_length)
+            {
+              if (scan->scan_start == 0)
+                scan->initial_scan_failed = true;
+              return false;
+            }
+          fm_extents[i].fe_logical = new_logical;
+          fm_extents[i].fe_length -= length_adjust;
+          /* Process the adjusted extent again.  */
+          i--;
+          continue;
+        }
       else
         {
           last_ei = scan->ext_info + si;