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

RongtongJin pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/rocketmq.git


The following commit(s) were added to refs/heads/develop by this push:
     new 669a2e180e fix(broker): reject timer delays that overflow the absolute 
delivery time (#10965)
669a2e180e is described below

commit 669a2e180e953af5484e77738a83ba7ed954f619
Author: Gerard <[email protected]>
AuthorDate: Wed Sep 2 16:28:27 2026 +0800

    fix(broker): reject timer delays that overflow the absolute delivery time 
(#10965)
    
    * fix(broker): reject timer delays that overflow the absolute delivery time
    
    transformTimerMessage converted relative timer delays with unchecked long 
multiplication and addition, so a very large delay overflowed into a past 
timestamp, bypassed the future-delay validation, and took the immediate-message 
path. Use Math.multiplyExact/Math.addExact so overflow returns 
WHEEL_TIMER_MSG_ILLEGAL.
    
    Fixes #10872
    
    * test(broker): cover timer delay overflow and success paths
    
    Cover the remaining transformTimerMessage branches so the exact-math
    overflow checks and the millisecond success path are exercised:
    
    - Long.MAX_VALUE / 1000 overflows addExact after multiplyExact succeeds.
    - A valid PROPERTY_TIMER_DELAY_MS rewrites the message to the wheel-timer
      topic with PROPERTY_TIMER_OUT_MS set.
    
    Split the nested exact-math expression in HookUtils so each overflow
    source is independently testable and reported.
    
    ---------
    
    Co-authored-by: Gerard <[email protected]>
    Co-authored-by: gerardgao <[email protected]>
---
 .../org/apache/rocketmq/broker/util/HookUtils.java |  12 ++-
 .../broker/util/HookUtilsTimerOverflowTest.java    | 105 +++++++++++++++++++++
 2 files changed, 113 insertions(+), 4 deletions(-)

diff --git 
a/broker/src/main/java/org/apache/rocketmq/broker/util/HookUtils.java 
b/broker/src/main/java/org/apache/rocketmq/broker/util/HookUtils.java
index 94be46ea40..06eb90b6ea 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/util/HookUtils.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/util/HookUtils.java
@@ -206,19 +206,23 @@ public class HookUtils {
         //do transform
         int delayLevel = msg.getDelayTimeLevel();
         long deliverMs;
+        long now = System.currentTimeMillis();
         try {
             if (msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != 
null) {
-                deliverMs = System.currentTimeMillis() + 
Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC)) * 1000;
+                long delaySec = 
Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC));
+                deliverMs = Math.multiplyExact(delaySec, 1000L);
+                deliverMs = Math.addExact(now, deliverMs);
             } else if (msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS) 
!= null) {
-                deliverMs = System.currentTimeMillis() + 
Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS));
+                long delayMs = 
Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS));
+                deliverMs = Math.addExact(now, delayMs);
             } else {
                 deliverMs = 
Long.parseLong(msg.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS));
             }
         } catch (Exception e) {
             return new 
PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null);
         }
-        if (deliverMs > System.currentTimeMillis()) {
-            if (delayLevel <= 0 && deliverMs - System.currentTimeMillis() > 
brokerController.getMessageStoreConfig().getTimerMaxDelaySec() * 1000L) {
+        if (deliverMs > now) {
+            if (delayLevel <= 0 && deliverMs - now > 
brokerController.getMessageStoreConfig().getTimerMaxDelaySec() * 1000L) {
                 return new 
PutMessageResult(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, null);
             }
 
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/util/HookUtilsTimerOverflowTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/util/HookUtilsTimerOverflowTest.java
new file mode 100644
index 0000000000..9c107b9b7a
--- /dev/null
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/util/HookUtilsTimerOverflowTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.rocketmq.broker.util;
+
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.common.message.MessageAccessor;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.message.MessageExtBrokerInner;
+import org.apache.rocketmq.store.PutMessageResult;
+import org.apache.rocketmq.store.PutMessageStatus;
+import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.apache.rocketmq.store.timer.TimerMessageStore;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class HookUtilsTimerOverflowTest {
+
+    private BrokerController newTimerEnabledController() {
+        BrokerController brokerController = 
Mockito.mock(BrokerController.class);
+        MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
+        messageStoreConfig.setTimerWheelEnable(true);
+        
Mockito.when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig);
+        return brokerController;
+    }
+
+    private MessageExtBrokerInner newTimerMessage(String key, String value) {
+        MessageExtBrokerInner msg = new MessageExtBrokerInner();
+        msg.setTopic("OverflowTestTopic");
+        MessageAccessor.putProperty(msg, key, value);
+        return msg;
+    }
+
+    // Regression for https://github.com/apache/rocketmq/issues/10872: an
+    // overflowing relative delay used to wrap around into a past timestamp,
+    // bypass the future-delay validation, and take the immediate-message path.
+    @Test
+    public void testTimerDelaySecOverflowRejected() {
+        PutMessageResult result = 
HookUtils.handleScheduleMessage(newTimerEnabledController(),
+            newTimerMessage(MessageConst.PROPERTY_TIMER_DELAY_SEC, 
String.valueOf(Long.MAX_VALUE)));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, 
result.getPutMessageStatus());
+    }
+
+    @Test
+    public void testTimerDelayMsOverflowRejected() {
+        PutMessageResult result = 
HookUtils.handleScheduleMessage(newTimerEnabledController(),
+            newTimerMessage(MessageConst.PROPERTY_TIMER_DELAY_MS, 
String.valueOf(Long.MAX_VALUE)));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, 
result.getPutMessageStatus());
+    }
+
+    // Long.MAX_VALUE / 1000 keeps multiplyExact within range but overflows the
+    // subsequent addExact against the current time, exercising its throw path.
+    @Test
+    public void testTimerDelaySecAddExactOverflowRejected() {
+        PutMessageResult result = 
HookUtils.handleScheduleMessage(newTimerEnabledController(),
+            newTimerMessage(MessageConst.PROPERTY_TIMER_DELAY_SEC, 
String.valueOf(Long.MAX_VALUE / 1000)));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, 
result.getPutMessageStatus());
+    }
+
+    // Exercises the successful timer transformation for a millisecond delay:
+    // the message is rewritten to the wheel-timer topic with the normalized
+    // delivery time stored in PROPERTY_TIMER_OUT_MS.
+    @Test
+    public void testTimerDelayMsSuccessPath() {
+        BrokerController brokerController = newTimerEnabledController();
+        TimerMessageStore timerMessageStore = 
Mockito.mock(TimerMessageStore.class);
+        
Mockito.when(brokerController.getTimerMessageStore()).thenReturn(timerMessageStore);
+        
Mockito.when(timerMessageStore.isReject(Mockito.anyLong())).thenReturn(false);
+
+        MessageExtBrokerInner msg = 
newTimerMessage(MessageConst.PROPERTY_TIMER_DELAY_MS, "1000");
+        PutMessageResult result = 
HookUtils.handleScheduleMessage(brokerController, msg);
+        Assert.assertNull(result);
+        
Assert.assertNotNull(msg.getProperty(MessageConst.PROPERTY_TIMER_OUT_MS));
+        Assert.assertEquals(TimerMessageStore.TIMER_TOPIC, msg.getTopic());
+    }
+
+    // Covers the future-delivery validation branch with a delay that does not
+    // overflow but exceeds the configured maximum.
+    @Test
+    public void testTimerDelaySecExceedsMaxRejected() {
+        BrokerController brokerController = newTimerEnabledController();
+        brokerController.getMessageStoreConfig().setTimerMaxDelaySec(1);
+        PutMessageResult result = 
HookUtils.handleScheduleMessage(brokerController,
+            newTimerMessage(MessageConst.PROPERTY_TIMER_DELAY_SEC, "10"));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(PutMessageStatus.WHEEL_TIMER_MSG_ILLEGAL, 
result.getPutMessageStatus());
+    }
+}

Reply via email to