This is an automated email from the ASF dual-hosted git repository.

morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new ed0a95d7005 [fix](show command) Clamp 64-bit LIMIT and OFFSET before 
narrowing in SHOW paging (#67008)
ed0a95d7005 is described below

commit ed0a95d70057f5ef516163457d175c2a432aee71
Author: Ambuj Upadhyay <[email protected]>
AuthorDate: Wed Aug 26 16:49:42 2026 +0530

    [fix](show command) Clamp 64-bit LIMIT and OFFSET before narrowing in SHOW 
paging (#67008)
    
    ### What problem does this PR solve?
    
    Problem Summary: LIMIT and OFFSET reach the SHOW paging code as unbounded 
64-bit
    values. `limitClause` accepts a bare digit sequence, `LogicalPlanBuilder` 
parses
    it with `Long.parseLong` and checks only that it is non-negative, and
    `LimitElement` stores both as `long`. Every paging site then narrowed them 
to
    `int` before clamping, so a value above `Integer.MAX_VALUE` wrapped.
    
    Two failure modes on a 5-row result:
    
    - `LIMIT 4294967296` -> `(int) (0 + 4294967296L)` is 0, the `endIndex > 
size`
    clamp does not fire, and `subList(0, 0)` returns an empty result. The user 
gets
      zero rows back with no error.
    - `LIMIT 3000000000` -> `(int) 3000000000L` is -1294967296, which is not 
greater
    than size so it is not clamped; the `beginIndex > endIndex` branch then sets
      `beginIndex` to the same negative value and `subList` throws
    `IndexOutOfBoundsException`. `OFFSET 3000000000` reaches the same throw.
    
    The same block was duplicated at eight sites across four proc dirs, backing
    `SHOW PARTITIONS`, `SHOW ALTER TABLE COLUMN`, `SHOW ALTER TABLE ROLLUP` and
    `SHOW BUILD INDEX`. `RollupProcDir` additionally lacked the `beginIndex
    > endIndex` guard that its three siblings have, so it could call `subList` 
with
    `beginIndex > endIndex` and throw `IllegalArgumentException`.
    
    `ShowCommand.applyLimit`, which pages `SHOW LOAD`, `SHOW RESOURCES` and
    `SHOW PARTITIONS`, has the same defect in a different shape: `(limit + 
offsetValue)`
    is computed in long but can overflow to a negative value, which then
    passes the `< showResult.size()` test and is narrowed for `subList`.
    
    ### What is changed and how does it work?
    
    Added `LimitElement.applyTo(List)`, which owns both values already, and 
computes
    the window in long, saturating at `rows.size()` before narrowing:
    
    - the offset is clamped into `[0, size]` first, so an out-of-range offset 
yields
      an empty window instead of a negative index;
    - `begin + limit` is checked for a negative result, which is the signature 
of the
      long addition overflowing;
    - with no limit set the window runs to the end of the list.
    
    The eight duplicated blocks now delegate to it, which also gives 
`RollupProcDir`
    the guard it was missing. `ShowCommand.applyLimit` delegates too, keeping 
its
    existing `limit == -1` contract (no limit means return the list unchanged) 
so
    behaviour is identical apart from the overflow.
    
    Net effect is 36 fewer lines and one tested implementation instead of eight 
copies.
    
    ### Release note
    
    Fix `SHOW PARTITIONS`, `SHOW ALTER TABLE`, `SHOW BUILD INDEX`, `SHOW LOAD` 
and
    `SHOW RESOURCES` returning an empty result or failing with
    `IndexOutOfBoundsException` when LIMIT or OFFSET exceeds the 32-bit range.
---
 .../org/apache/doris/analysis/LimitElement.java    | 27 +++++++
 .../doris/common/proc/BuildIndexProcDir.java       | 17 +----
 .../doris/common/proc/PartitionsProcDir.java       | 24 +-----
 .../apache/doris/common/proc/RollupProcDir.java    | 17 +----
 .../doris/common/proc/SchemaChangeProcDir.java     | 17 +----
 .../nereids/trees/plans/commands/ShowCommand.java  | 16 ++--
 .../apache/doris/analysis/LimitElementTest.java    | 85 ++++++++++++++++++++++
 7 files changed, 126 insertions(+), 77 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/LimitElement.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/LimitElement.java
index fd11ec028ce..10c70010c79 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/LimitElement.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/LimitElement.java
@@ -20,6 +20,8 @@
 
 package org.apache.doris.analysis;
 
+import java.util.List;
+
 /**
  * Combination of limit and offset expressions.
  */
@@ -62,6 +64,31 @@ public class LimitElement {
         return offset;
     }
 
+    /**
+     * Returns the window of {@code rows} selected by this offset and limit.
+     *
+     * <p>Both values reach here as user supplied 64-bit integers, so the 
range is computed in
+     * long and saturated at {@code rows.size()} before it is narrowed to int. 
Narrowing first
+     * wraps: an offset or limit above {@link Integer#MAX_VALUE} can truncate 
to zero and
+     * silently return an empty window, or truncate to a negative index and 
make
+     * {@link List#subList} throw {@link IndexOutOfBoundsException}.
+     *
+     * <p>When no limit is set, the window runs from the offset to the end of 
{@code rows}.
+     */
+    public <T> List<T> applyTo(List<T> rows) {
+        int size = rows.size();
+        long begin = Math.min(Math.max(offset, 0L), size);
+        long end = size;
+        if (hasLimit()) {
+            end = begin + limit;
+            // A negative sum means the long addition itself overflowed.
+            if (end < 0 || end > size) {
+                end = size;
+            }
+        }
+        return rows.subList((int) begin, (int) end);
+    }
+
 
     public String toSql() {
         if (limit == -1) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/BuildIndexProcDir.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/BuildIndexProcDir.java
index a0552af614d..46e0040eca5 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/BuildIndexProcDir.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/BuildIndexProcDir.java
@@ -250,15 +250,7 @@ public class BuildIndexProcDir implements ProcDirInterface 
{
 
         //limit
         if (limitElement != null && limitElement.hasLimit()) {
-            int beginIndex = (int) limitElement.getOffset();
-            int endIndex = (int) (beginIndex + limitElement.getLimit());
-            if (endIndex > jobInfos.size()) {
-                endIndex = jobInfos.size();
-            }
-            if (beginIndex > endIndex) {
-                beginIndex = endIndex;
-            }
-            jobInfos = jobInfos.subList(beginIndex, endIndex);
+            jobInfos = limitElement.applyTo(jobInfos);
         }
 
         BaseProcResult result = new BaseProcResult();
@@ -315,12 +307,7 @@ public class BuildIndexProcDir implements ProcDirInterface 
{
 
         //limit
         if (limitElement != null && limitElement.hasLimit()) {
-            int beginIndex = (int) limitElement.getOffset();
-            int endIndex = (int) (beginIndex + limitElement.getLimit());
-            if (endIndex > jobInfos.size()) {
-                endIndex = jobInfos.size();
-            }
-            jobInfos = jobInfos.subList(beginIndex, endIndex);
+            jobInfos = limitElement.applyTo(jobInfos);
         }
 
         BaseProcResult result = new BaseProcResult();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/PartitionsProcDir.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/PartitionsProcDir.java
index 049172f8b3a..1354e71bbfa 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/PartitionsProcDir.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/PartitionsProcDir.java
@@ -314,17 +314,7 @@ public class PartitionsProcDir implements ProcDirInterface 
{
 
         //limit
         if (limitElement != null && limitElement.hasLimit()) {
-            int beginIndex = (int) limitElement.getOffset();
-            int endIndex = (int) (beginIndex + limitElement.getLimit());
-            if (endIndex > filterPartitionInfos.size()) {
-                endIndex = filterPartitionInfos.size();
-            }
-
-            // means that beginIndex is bigger than 
filterPartitionInfos.size(), just return empty
-            if (beginIndex > endIndex) {
-                beginIndex = endIndex;
-            }
-            filterPartitionInfos = filterPartitionInfos.subList(beginIndex, 
endIndex);
+            filterPartitionInfos = limitElement.applyTo(filterPartitionInfos);
         }
 
         return getBasicProcResult(filterPartitionInfos);
@@ -369,17 +359,7 @@ public class PartitionsProcDir implements ProcDirInterface 
{
 
         //limit
         if (limitElement != null && limitElement.hasLimit()) {
-            int beginIndex = (int) limitElement.getOffset();
-            int endIndex = (int) (beginIndex + limitElement.getLimit());
-            if (endIndex > filterPartitionInfos.size()) {
-                endIndex = filterPartitionInfos.size();
-            }
-
-            // means that beginIndex is bigger than 
filterPartitionInfos.size(), just return empty
-            if (beginIndex > endIndex) {
-                beginIndex = endIndex;
-            }
-            filterPartitionInfos = filterPartitionInfos.subList(beginIndex, 
endIndex);
+            filterPartitionInfos = limitElement.applyTo(filterPartitionInfos);
         }
 
         return getBasicProcResult(filterPartitionInfos);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/RollupProcDir.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/RollupProcDir.java
index 92639040b3c..e3112b252f0 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/proc/RollupProcDir.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/proc/RollupProcDir.java
@@ -145,12 +145,7 @@ public class RollupProcDir implements ProcDirInterface {
 
         //limit
         if (limitElement != null && limitElement.hasLimit()) {
-            int beginIndex = (int) limitElement.getOffset();
-            int endIndex = (int) (beginIndex + limitElement.getLimit());
-            if (endIndex > jobInfos.size()) {
-                endIndex = jobInfos.size();
-            }
-            jobInfos = jobInfos.subList(beginIndex, endIndex);
+            jobInfos = limitElement.applyTo(jobInfos);
         }
 
         BaseProcResult result = new BaseProcResult();
@@ -218,15 +213,7 @@ public class RollupProcDir implements ProcDirInterface {
 
         //limit
         if (limitElement != null && limitElement.hasLimit()) {
-            int beginIndex = (int) limitElement.getOffset();
-            int endIndex = (int) (beginIndex + limitElement.getLimit());
-            if (endIndex > jobInfos.size()) {
-                endIndex = jobInfos.size();
-            }
-            if (beginIndex > endIndex) {
-                beginIndex = endIndex;
-            }
-            jobInfos = jobInfos.subList(beginIndex, endIndex);
+            jobInfos = limitElement.applyTo(jobInfos);
         }
 
         BaseProcResult result = new BaseProcResult();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/SchemaChangeProcDir.java
 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/SchemaChangeProcDir.java
index 5ecd772acef..e981ddbd091 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/common/proc/SchemaChangeProcDir.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/common/proc/SchemaChangeProcDir.java
@@ -264,15 +264,7 @@ public class SchemaChangeProcDir implements 
ProcDirInterface {
 
         //limit
         if (limitElement != null && limitElement.hasLimit()) {
-            int beginIndex = (int) limitElement.getOffset();
-            int endIndex = (int) (beginIndex + limitElement.getLimit());
-            if (endIndex > jobInfos.size()) {
-                endIndex = jobInfos.size();
-            }
-            if (beginIndex > endIndex) {
-                beginIndex = endIndex;
-            }
-            jobInfos = jobInfos.subList(beginIndex, endIndex);
+            jobInfos = limitElement.applyTo(jobInfos);
         }
 
         BaseProcResult result = new BaseProcResult();
@@ -329,12 +321,7 @@ public class SchemaChangeProcDir implements 
ProcDirInterface {
 
         //limit
         if (limitElement != null && limitElement.hasLimit()) {
-            int beginIndex = (int) limitElement.getOffset();
-            int endIndex = (int) (beginIndex + limitElement.getLimit());
-            if (endIndex > jobInfos.size()) {
-                endIndex = jobInfos.size();
-            }
-            jobInfos = jobInfos.subList(beginIndex, endIndex);
+            jobInfos = limitElement.applyTo(jobInfos);
         }
 
         BaseProcResult result = new BaseProcResult();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCommand.java
index 62e9632b509..39f91cfa544 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowCommand.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.nereids.trees.plans.commands;
 
+import org.apache.doris.analysis.LimitElement;
 import org.apache.doris.analysis.RedirectStatus;
 import org.apache.doris.analysis.StmtType;
 import org.apache.doris.common.AnalysisException;
@@ -107,17 +108,12 @@ public abstract class ShowCommand extends Command 
implements Redirect {
             return Lists.newArrayList();
         }
 
-        long offsetValue = offset == -1L ? 0 : offset;
-        if (offsetValue >= showResult.size()) {
-            showResult = Lists.newArrayList();
-        } else if (limit != -1L) {
-            if ((limit + offsetValue) < showResult.size()) {
-                showResult = showResult.subList((int) offsetValue, (int) 
(limit + offsetValue));
-            } else {
-                showResult = showResult.subList((int) offsetValue, 
showResult.size());
-            }
+        if (limit == -1L) {
+            return showResult;
         }
-        return showResult;
+        // offset and limit are 64-bit; LimitElement.applyTo saturates the 
window in long before
+        // narrowing, so a value above Integer.MAX_VALUE cannot wrap into a 
bogus sub-list range.
+        return new LimitElement(offset == -1L ? 0 : offset, 
limit).applyTo(showResult);
     }
 
     @Override
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/analysis/LimitElementTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/analysis/LimitElementTest.java
new file mode 100644
index 00000000000..a238d13bb49
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/LimitElementTest.java
@@ -0,0 +1,85 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.analysis;
+
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class LimitElementTest {
+
+    private static List<Integer> rows(int n) {
+        List<Integer> rows = Lists.newArrayList();
+        for (int i = 0; i < n; i++) {
+            rows.add(i);
+        }
+        return rows;
+    }
+
+    @Test
+    public void testWindowWithinIntRange() {
+        Assertions.assertEquals(Lists.newArrayList(0, 1, 2), new 
LimitElement(0, 3).applyTo(rows(5)));
+        Assertions.assertEquals(Lists.newArrayList(2, 3), new LimitElement(2, 
2).applyTo(rows(5)));
+        Assertions.assertEquals(Lists.newArrayList(3, 4), new LimitElement(3, 
100).applyTo(rows(5)));
+    }
+
+    // A limit larger than Integer.MAX_VALUE must select every remaining row. 
Narrowing the
+    // offset+limit sum to int first truncates 4294967296 to 0, which silently 
returned nothing.
+    @Test
+    public void testLimitAboveIntMaxReturnsAllRows() {
+        Assertions.assertEquals(rows(5), new LimitElement(0, 
4294967296L).applyTo(rows(5)));
+        Assertions.assertEquals(rows(5), new LimitElement(0, 
Long.MAX_VALUE).applyTo(rows(5)));
+    }
+
+    // 3000000000 narrows to a negative int, which made subList throw 
IndexOutOfBoundsException.
+    @Test
+    public void testLimitTruncatingToNegativeIntReturnsAllRows() {
+        Assertions.assertEquals(rows(5), new LimitElement(0, 
3000000000L).applyTo(rows(5)));
+    }
+
+    // The offset+limit addition itself can overflow long; the window must 
still be clamped.
+    @Test
+    public void testOffsetPlusLimitOverflowIsClamped() {
+        Assertions.assertEquals(Lists.newArrayList(1, 2, 3, 4),
+                new LimitElement(1, Long.MAX_VALUE).applyTo(rows(5)));
+    }
+
+    // An offset past the end selects nothing, whatever its magnitude, and 
never throws.
+    @Test
+    public void testOffsetBeyondEndReturnsEmpty() {
+        Assertions.assertTrue(new LimitElement(5, 
10).applyTo(rows(5)).isEmpty());
+        Assertions.assertTrue(new LimitElement(3000000000L, 
10).applyTo(rows(5)).isEmpty());
+        Assertions.assertTrue(new LimitElement(Long.MAX_VALUE, 
10).applyTo(rows(5)).isEmpty());
+    }
+
+    // Without a limit the window runs from the offset to the end.
+    @Test
+    public void testNoLimitRunsToEnd() {
+        LimitElement noLimit = new LimitElement(2, -1);
+        Assertions.assertFalse(noLimit.hasLimit());
+        Assertions.assertEquals(Lists.newArrayList(2, 3, 4), 
noLimit.applyTo(rows(5)));
+    }
+
+    @Test
+    public void testEmptyInput() {
+        Assertions.assertTrue(new LimitElement(0, 
10).applyTo(rows(0)).isEmpty());
+        Assertions.assertTrue(new LimitElement(3000000000L, 
10).applyTo(rows(0)).isEmpty());
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to