Parse ND options in netlink message as an ByteBuffer slice.

Parsing ND options as an ByteBuffer slice provides more reliable way to
read raw data from buffer no matter of the option might be malformed,
truncated or options with correct length and value, which doesn't affect
the buffer pointer advances to deal with the remaining data such as
attribute fields even if parsing ND options return null.

Bug: 163492391
Test: atest NetworkStaticLibsTest
Change-Id: Ic348abe6b5d1ceddfde47f19dcaec324927798d1
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);