]> git.ipfire.org Git - thirdparty/dracut-ng.git/commitdiff
fix(network-legacy): add input validation to RFC 3442 route parser
authorAndrey Prokopenko <9478806+terem42@users.noreply.github.com>
Mon, 12 Jan 2026 12:47:20 +0000 (13:47 +0100)
committerNeal Gompa (ニール・ゴンパ) <ngompa13@gmail.com>
Sun, 15 Mar 2026 02:59:55 +0000 (22:59 -0400)
The parse_option_121() function that parses DHCP option 121 (classless
static routes) could fail with shell errors when processing malformed
or truncated data:

- "integer expression expected" when comparing non-numeric mask values
- "shift count out of range" when not enough arguments remain

Add validation to:
- Verify mask is a number between 0-32
- Pre-calculate required argument count before consuming
- Validate destination octets are numeric before multicast check
- Add bounds checking before shifts

Tested with Hetzner cloud infrastructure which sends RFC 3442 routes.

modules.d/35network-legacy/dhclient-script.sh

index 6751f2d6062aa5d4381dd62e576b5b6b78c587b3..0cb00ab45107f5847f1b52f98b7ccdfc867acd92 100755 (executable)
@@ -109,50 +109,80 @@ setup_interface6() {
 }
 
 parse_option_121() {
-    while [ $# -ne 0 ]; do
+    # RFC 3442 classless static routes format:
+    # Each route is: <mask_width> <dest_octets...> <gateway_4_octets>
+    # mask_width determines how many destination octets follow (0-4)
+    #
+    # This version validates arguments before operations to prevent
+    # "integer expression expected" and "shift count out of range" errors.
+
+    while [ $# -ge 5 ]; do
         mask="$1"
+
+        # Validate mask is a number between 0-32
+        case "$mask" in
+            '' | *[!0-9]*) return 0 ;;
+        esac
+        if [ "$mask" -lt 0 ] 2> /dev/null || [ "$mask" -gt 32 ] 2> /dev/null; then
+            return 0
+        fi
         shift
 
-        # Is the destination a multicast group?
-        if [ "$1" -ge 224 ] && [ "$1" -lt 240 ]; then
-            multicast=1
+        # Calculate how many destination address bytes we need based on mask
+        if [ "$mask" -gt 24 ]; then
+            need_dest=4
+        elif [ "$mask" -gt 16 ]; then
+            need_dest=3
+        elif [ "$mask" -gt 8 ]; then
+            need_dest=2
+        elif [ "$mask" -gt 0 ]; then
+            need_dest=1
         else
-            multicast=0
+            need_dest=0
+        fi
+
+        # We need: destination bytes + 4 gateway bytes
+        need_total=$((need_dest + 4))
+        if [ $# -lt $need_total ]; then
+            return 0
         fi
 
-        # Parse the arguments into a CIDR net/mask string
+        # Check if destination is multicast (224.0.0.0 - 239.255.255.255)
+        multicast=0
+        if [ $need_dest -ge 1 ]; then
+            case "$1" in
+                '' | *[!0-9]*) return 0 ;;
+            esac
+            if [ "$1" -ge 224 ] 2> /dev/null && [ "$1" -lt 240 ] 2> /dev/null; then
+                multicast=1
+            fi
+        fi
+
+        # Build destination address based on mask width
         if [ "$mask" -gt 24 ]; then
             destination="$1.$2.$3.$4/$mask"
-            shift
-            shift
-            shift
-            shift
+            shift 4
         elif [ "$mask" -gt 16 ]; then
             destination="$1.$2.$3.0/$mask"
-            shift
-            shift
-            shift
+            shift 3
         elif [ "$mask" -gt 8 ]; then
             destination="$1.$2.0.0/$mask"
-            shift
-            shift
+            shift 2
         elif [ "$mask" -gt 0 ]; then
             destination="$1.0.0.0/$mask"
-            shift
+            shift 1
         else
             destination="0.0.0.0/$mask"
         fi
 
-        # Read the gateway
+        # Read gateway (always 4 bytes)
+        if [ $# -lt 4 ]; then
+            return 0
+        fi
         gateway="$1.$2.$3.$4"
-        shift
-        shift
-        shift
-        shift
+        shift 4
 
-        # Multicast routing on Linux
-        #  - If you set a next-hop address for a multicast group, this breaks with Cisco switches
-        #  - If you simply leave it link-local and attach it to an interface, it works fine.
+        # Build and emit the route command
         if [ $multicast -eq 1 ] || [ "$gateway" = "0.0.0.0" ]; then
             temp_result="$destination dev $interface"
         else