From 0a0448c7d5dabe0eef940108c6e85de16e45e757 Mon Sep 17 00:00:00 2001
From: "Gary E. Miller" <gem@rellim.com>
Date: Wed, 3 Dec 2025 19:04:03 -0800
Subject: [PATCH] gpsd/packet.c: Fix integer underflow is malicious Navcom
 packet

Causes DoS.  Fix issue 358

CVE: CVE-2025-67269
Upstream-Status: Backport [https://gitlab.com/gpsd/gpsd/-/commit/ffa1d6f40bca0b035fc7f5e563160ebb67199da7]
Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
---
 gpsd/packet.c | 63 ++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 47 insertions(+), 16 deletions(-)

diff --git a/gpsd/packet.c b/gpsd/packet.c
index 8e14a17ff..51c51ced9 100644
--- a/gpsd/packet.c
+++ b/gpsd/packet.c
@@ -947,18 +947,22 @@ static bool nextstate(struct gps_lexer_t *lexer, unsigned char c)
 #endif  // SIRF_ENABLE || SKYTRAQ_ENABLE
 #ifdef SIRF_ENABLE
     case SIRF_LEADER_2:
-        // first part of length
-        lexer->length = (size_t) (c << 8);
+        // first part of length, MSB
+        lexer->length = (c & 0x7f) << 8;
+        if (lexer->length > MAX_PACKET_LENGTH) {
+            lexer->length = 0;
+            return character_pushback(lexer, GROUND_STATE);
+        } // else
         lexer->state = SIRF_LENGTH_1;
         break;
     case SIRF_LENGTH_1:
         // second part of length
         lexer->length += c + 2;
-        if (lexer->length <= MAX_PACKET_LENGTH) {
-            lexer->state = SIRF_PAYLOAD;
-        } else {
+        if (lexer->length > MAX_PACKET_LENGTH) {
+            lexer->length = 0;
             return character_pushback(lexer, GROUND_STATE);
-        }
+        } // else
+        lexer->state = SIRF_PAYLOAD;
         break;
     case SIRF_PAYLOAD:
         if (0 == --lexer->length) {
@@ -1000,6 +1004,7 @@ static bool nextstate(struct gps_lexer_t *lexer, unsigned char c)
             return character_pushback(lexer, GROUND_STATE);
         }
         if (MAX_PACKET_LENGTH < lexer->length) {
+            lexer->length = 0;
             return character_pushback(lexer, GROUND_STATE);
         }
         lexer->state = SKY_PAYLOAD;
@@ -1182,14 +1187,29 @@ static bool nextstate(struct gps_lexer_t *lexer, unsigned char c)
         }
         break;
     case NAVCOM_LEADER_3:
+        // command ID
         lexer->state = NAVCOM_ID;
         break;
     case NAVCOM_ID:
-        lexer->length = (size_t)c - 4;
+        /* Length LSB
+         * Navcom length includes command ID, length bytes. and checksum.
+         * So for more than just the payload length.
+         * Minimum 4 bytes */
+        if (4 > c) {
+            return character_pushback(lexer, GROUND_STATE);
+        }
+        lexer->length = c;
         lexer->state = NAVCOM_LENGTH_1;
         break;
     case NAVCOM_LENGTH_1:
+        // Length USB.  Navcom allows payload length up to 65,531
         lexer->length += (c << 8);
+        // don't count ID, length and checksum  in payload length
+        lexer->length -= 4;
+        if (MAX_PACKET_LENGTH < lexer->length) {
+            lexer->length = 0;
+            return character_pushback(lexer, GROUND_STATE);
+        }  // else
         lexer->state = NAVCOM_LENGTH_2;
         break;
     case NAVCOM_LENGTH_2:
@@ -1316,11 +1336,11 @@ static bool nextstate(struct gps_lexer_t *lexer, unsigned char c)
         lexer->length += 2;     // checksum
         // 10 bytes is the length of the Zodiac header
         // no idea what Zodiac max length really is
-        if ((MAX_PACKET_LENGTH - 10) >= lexer->length) {
-            lexer->state = ZODIAC_PAYLOAD;
-        } else {
+        if ((MAX_PACKET_LENGTH - 10) < lexer->length) {
+            lexer->length = 0;
             return character_pushback(lexer, GROUND_STATE);
-        }
+        }  // else
+        lexer->state = ZODIAC_PAYLOAD;
         break;
     case ZODIAC_PAYLOAD:
         if (0 == --lexer->length) {
@@ -1356,6 +1376,7 @@ static bool nextstate(struct gps_lexer_t *lexer, unsigned char c)
             lexer->state = UBX_LENGTH_2;
         } else {
             // bad length
+            lexer->length = 0;
             return character_pushback(lexer, GROUND_STATE);
         }
         break;
@@ -1502,16 +1523,16 @@ static bool nextstate(struct gps_lexer_t *lexer, unsigned char c)
         lexer->state = GEOSTAR_MESSAGE_ID_2;
         break;
     case GEOSTAR_MESSAGE_ID_2:
-        lexer->length = (size_t)c * 4;
+        lexer->length = c * 4;
         lexer->state = GEOSTAR_LENGTH_1;
         break;
     case GEOSTAR_LENGTH_1:
         lexer->length += (c << 8) * 4;
-        if (MAX_PACKET_LENGTH >= lexer->length) {
-            lexer->state = GEOSTAR_LENGTH_2;
-        } else {
+        if (MAX_PACKET_LENGTH < lexer->length) {
+            lexer->length = 0;
             return character_pushback(lexer, GROUND_STATE);
-        }
+        }  // else
+        lexer->state = GEOSTAR_LENGTH_2;
         break;
     case GEOSTAR_LENGTH_2:
         lexer->state = GEOSTAR_PAYLOAD;
@@ -1823,6 +1844,16 @@ static bool nextstate(struct gps_lexer_t *lexer, unsigned char c)
 #endif  // STASH_ENABLE
     }
 
+    /* Catch length overflow.  Should not happen.
+     * length is size_t, so underflow looks like overflow too. */
+    if (MAX_PACKET_LENGTH <= lexer->length) {
+        GPSD_LOG(LOG_WARN, &lexer->errout,
+                 "Too long: %zu state %u %s c x%x\n",
+                 lexer->length, lexer->state, state_table[lexer->state], c);
+        // exit(255);
+        lexer->length = 0;
+        return character_pushback(lexer, GROUND_STATE);
+    }
     return true;        // no pushback
 }
 
