]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
copy: protect against infinite loop due to pathological race
authorPádraig Brady <P@draigBrady.com>
Mon, 5 Jan 2026 14:46:33 +0000 (14:46 +0000)
committerPádraig Brady <P@draigBrady.com>
Tue, 6 Jan 2026 20:41:15 +0000 (20:41 +0000)
Consider:

1. In infer_scantype():
    - SEEK_DATA returns 0
      - hole punched at 0
    - SEEK_HOLE returns 0 (now a hole)
    - Cache scan_inference->hole_start = 0
2. In lseek_copy():
      - data written at 0
    - ext_start = 0, use cached hole_start = 0
    - ext_len = 0
    - now loop doesn't progress

* src/copy-file-data.c (lseek_copy): Apply a more defensive check
to ensure we only use the cached offsets in SCAN_INFERENCE once.
This protects against an infinite loop where an extent (at SRC_POS)
flip flops between data and hole extent while infer_scantype()
and lseek_copy() are inspecting it.  I.e. ensure we use SEEK_HOLE
to progress the copy.

src/copy-file-data.c

index 56b669fe7267d9027714eae5b8816a3c2de2ec2b..9bc4311af421004ae2f21dfe6c73b4a704699334 100644 (file)
@@ -335,12 +335,19 @@ lseek_copy (int src_fd, int dest_fd, char **abuf, idx_t buf_size,
 
   debug->sparse_detection = COPY_DEBUG_EXTERNAL;
 
+  bool used_scan_inference = false;
+
   for (off_t ext_start = scan_inference->ext_start;
        0 <= ext_start && ext_start < max_ipos; )
     {
-      off_t ext_end = (ext_start == src_pos
-                       ? scan_inference->hole_start
-                       : lseek (src_fd, ext_start, SEEK_HOLE));
+      off_t ext_end;
+      if (ext_start == src_pos && ! used_scan_inference)
+        {
+          ext_end = scan_inference->hole_start;
+          used_scan_inference = true;
+        }
+      else
+        ext_end = lseek (src_fd, ext_start, SEEK_HOLE);
       if (0 <= ext_end)
         ext_end = MIN (ext_end, max_ipos);
       else