Merge "Parse ND options in netlink message as an ByteBuffer slice."
diff --git a/common/device/com/android/net/module/util/netlink/NdOption.java b/common/device/com/android/net/module/util/netlink/NdOption.java
index 6755497..defc88a 100644
--- a/common/device/com/android/net/module/util/netlink/NdOption.java
+++ b/common/device/com/android/net/module/util/netlink/NdOption.java
@@ -16,6 +16,8 @@
 
 package com.android.net.module.util.netlink;
 
+import androidx.annotation.NonNull;
+
 import java.nio.ByteBuffer;
 
 /**
@@ -50,8 +52,8 @@
      * @param buf the buffer to parse.
      * @return a subclass of {@link NdOption}, or {@code null} for an unknown or malformed option.
      */
-    public static NdOption parse(ByteBuffer buf) {
-        if (buf == null || buf.remaining() < STRUCT_SIZE) return null;
+    public static NdOption parse(@NonNull ByteBuffer buf) {
+        if (buf.remaining() < STRUCT_SIZE) return null;
 
         // Peek the type without advancing the buffer.
         byte type = buf.get(buf.position());
diff --git a/common/device/com/android/net/module/util/netlink/NduseroptMessage.java b/common/device/com/android/net/module/util/netlink/NduseroptMessage.java
index 4e3b9f2..9d2402d 100644
--- a/common/device/com/android/net/module/util/netlink/NduseroptMessage.java
+++ b/common/device/com/android/net/module/util/netlink/NduseroptMessage.java
@@ -19,6 +19,7 @@
 import static android.system.OsConstants.AF_INET6;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -56,6 +57,7 @@
      * But if it does, we can simply update this code, since userspace is typically newer than the
      * kernel.
      */
+    @Nullable
     public final NdOption option;
 
     /** The IP address that sent the packet containing the option. */
@@ -80,22 +82,26 @@
         // Ensure we don't read past opts_len even if the option length is invalid.
         // Note that this check is not really necessary since if the option length is not valid,
         // this struct won't be very useful to the caller.
+        //
+        // It's safer to pass the slice of original ByteBuffer to just parse the ND option field,
+        // although parsing ND option might throw exception or return null, it won't break the
+        // original ByteBuffer position.
         buf.order(ByteOrder.BIG_ENDIAN);
-        int oldLimit = buf.limit();
-        buf.limit(start + STRUCT_SIZE + opts_len);
         try {
-            option = NdOption.parse(buf);
+            final ByteBuffer slice = buf.slice();
+            slice.limit(opts_len);
+            option = NdOption.parse(slice);
         } finally {
-            buf.limit(oldLimit);
+            // Advance buffer position according to opts_len in the header. ND option length might
+            // be incorrect in the malformed packet.
+            int newPosition = start + STRUCT_SIZE + opts_len;
+            if (newPosition >= buf.limit()) {
+                throw new IllegalArgumentException("ND option extends past end of buffer");
+            }
+            buf.position(newPosition);
         }
 
-        // The source address.
-        int newPosition = start + STRUCT_SIZE + opts_len;
-        if (newPosition >= buf.limit()) {
-            throw new IllegalArgumentException("ND options extend past end of buffer");
-        }
-        buf.position(newPosition);
-
+        // The source address attribute.
         StructNlAttr nla = StructNlAttr.parse(buf);
         if (nla == null || nla.nla_type != NDUSEROPT_SRCADDR || nla.nla_value == null) {
             throw new IllegalArgumentException("Invalid source address in ND useropt");
diff --git a/common/device/com/android/net/module/util/netlink/StructNdOptPref64.java b/common/device/com/android/net/module/util/netlink/StructNdOptPref64.java
index bde6983..f6b2e0e 100644
--- a/common/device/com/android/net/module/util/netlink/StructNdOptPref64.java
+++ b/common/device/com/android/net/module/util/netlink/StructNdOptPref64.java
@@ -135,7 +135,7 @@
      *         (for example, if it was truncated, or if the prefix length code was wrong).
      */
     public static StructNdOptPref64 parse(@NonNull ByteBuffer buf) {
-        if (buf == null || buf.remaining() < STRUCT_SIZE) return null;
+        if (buf.remaining() < STRUCT_SIZE) return null;
         try {
             return new StructNdOptPref64(buf);
         } catch (IllegalArgumentException e) {
diff --git a/common/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java b/common/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java
index f297108..4fc5ec2 100644
--- a/common/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java
@@ -139,6 +139,19 @@
     }
 
     @Test
+    public void testParseTruncatedRdnssOptionWithinNetlinkMessage() throws Exception {
+        final String truncatedHexBytes =
+                "38000000440000000000000000000000"
+                + "0A0018001E0000008600000000000000"
+                + "1903000000001770FD123456789000000000000000000001";  // RDNSS option
+
+        ByteBuffer buf = toBuffer(truncatedHexBytes);
+        buf.order(ByteOrder.nativeOrder());
+        NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE);
+        assertNull(nlMsg);
+    }
+
+    @Test
     public void testParseUnknownOptionWithinNetlinkMessage() throws Exception {
         final String hexBytes =
                 "4C000000440000000000000000000000"
diff --git a/common/tests/unit/src/com/android/net/module/util/netlink/StructNdOptRdnssTest.java b/common/tests/unit/src/com/android/net/module/util/netlink/StructNdOptRdnssTest.java
index 7df8fa7..1dcb9b5 100644
--- a/common/tests/unit/src/com/android/net/module/util/netlink/StructNdOptRdnssTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/netlink/StructNdOptRdnssTest.java
@@ -163,11 +163,6 @@
     }
 
     @Test
-    public void testParsing_nullByteBuffer() {
-        assertNull(StructNdOptRdnss.parse(null));
-    }
-
-    @Test
     public void testParsing_invalidByteBufferLength() throws Exception {
         final ByteBuffer buf = makeRdnssOption((byte) ICMPV6_ND_OPTION_RDNSS,
                 (byte) 5 /* length */, 3600 /* lifetime */, DNS_SERVER1, DNS_SERVER2);