[PULL,v2,01/13] linux-user: Support for NETLINK socket options
diff mbox series

Message ID 20191106130456.6176-2-laurent@vivier.eu
State New
Headers show
Series
  • [PULL,v2,01/13] linux-user: Support for NETLINK socket options
Related show

Commit Message

Laurent Vivier Nov. 6, 2019, 1:04 p.m. UTC
From: Josh Kunz <jkz@google.com>

This change includes support for all AF_NETLINK socket options up to about
kernel version 5.4 (5.4 is not formally released at the time of writing).
Socket options that were introduced in kernel versions before the oldest
currently stable kernel version are guarded by kernel version macros.

This change has been built under gcc 8.3, and clang 9.0, and it passes
`make check`. The netlink options have been tested by emulating some
non-trival software that uses NETLINK socket options, but they have
not been exaustively verified.

Signed-off-by: Josh Kunz <jkz@google.com>
Message-Id: <20191029224310.164025-1-jkz@google.com>
[lv: updated patch according to CODING_STYLE]
Signed-off-by: Laurent Vivier <laurent@vivier.eu>
---
 linux-user/syscall.c | 101 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 101 insertions(+)

Comments

Peter Maydell Nov. 12, 2019, 10:11 a.m. UTC | #1
On Wed, 6 Nov 2019 at 13:07, Laurent Vivier <laurent@vivier.eu> wrote:
>
> From: Josh Kunz <jkz@google.com>
>
> This change includes support for all AF_NETLINK socket options up to about
> kernel version 5.4 (5.4 is not formally released at the time of writing).
> Socket options that were introduced in kernel versions before the oldest
> currently stable kernel version are guarded by kernel version macros.
>
> This change has been built under gcc 8.3, and clang 9.0, and it passes
> `make check`. The netlink options have been tested by emulating some
> non-trival software that uses NETLINK socket options, but they have
> not been exaustively verified.

Hi; Coverity reports a missing-break-in-switch error for
this commit (CID 1407221):

> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
> index f6751eecb78c..247883292ce5 100644
> --- a/linux-user/syscall.c
> +++ b/linux-user/syscall.c
> @@ -2248,6 +2248,39 @@ set_timeout:
>              return -TARGET_EFAULT;
>         ret = get_errno(setsockopt(sockfd, SOL_SOCKET, optname, &val, sizeof(val)));
>          break;
> +#ifdef SOL_NETLINK
> +    case SOL_NETLINK:
> +        switch (optname) {
> +        case NETLINK_PKTINFO:
> +        case NETLINK_ADD_MEMBERSHIP:
> +        case NETLINK_DROP_MEMBERSHIP:
> +        case NETLINK_BROADCAST_ERROR:
> +        case NETLINK_NO_ENOBUFS:
> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
> +        case NETLINK_LISTEN_ALL_NSID:
> +        case NETLINK_CAP_ACK:
> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) */
> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
> +        case NETLINK_EXT_ACK:
> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
> +        case NETLINK_GET_STRICT_CHK:
> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
> +            break;
> +        default:
> +            goto unimplemented;
> +        }
> +        val = 0;
> +        if (optlen < sizeof(uint32_t)) {
> +            return -TARGET_EINVAL;
> +        }
> +        if (get_user_u32(val, optval_addr)) {
> +            return -TARGET_EFAULT;
> +        }
> +        ret = get_errno(setsockopt(sockfd, SOL_NETLINK, optname, &val,
> +                                   sizeof(val)));
> +        break;
> +#endif /* SOL_NETLINK */
>      default:
>      unimplemented:
>          gemu_log("Unsupported setsockopt level=%d optname=%d\n", level, optname);
> @@ -2532,6 +2565,74 @@ static abi_long do_getsockopt(int sockfd, int level, int optname,
>              break;
>          }
>          break;
> +#ifdef SOL_NETLINK
> +    case SOL_NETLINK:
> +        switch (optname) {
> +        case NETLINK_PKTINFO:
> +        case NETLINK_BROADCAST_ERROR:
> +        case NETLINK_NO_ENOBUFS:
> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
> +        case NETLINK_LISTEN_ALL_NSID:
> +        case NETLINK_CAP_ACK:
> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) */
> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
> +        case NETLINK_EXT_ACK:
> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
> +        case NETLINK_GET_STRICT_CHK:
> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
> +            if (get_user_u32(len, optlen)) {
> +                return -TARGET_EFAULT;
> +            }
> +            if (len != sizeof(val)) {
> +                return -TARGET_EINVAL;
> +            }
> +            lv = len;
> +            ret = get_errno(getsockopt(sockfd, level, optname, &val, &lv));
> +            if (ret < 0) {
> +                return ret;
> +            }
> +            if (put_user_u32(lv, optlen)
> +                || put_user_u32(val, optval_addr)) {
> +                return -TARGET_EFAULT;
> +            }
> +            break;
> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
> +        case NETLINK_LIST_MEMBERSHIPS:
> +        {
> +            uint32_t *results;
> +            int i;
> +            if (get_user_u32(len, optlen)) {
> +                return -TARGET_EFAULT;
> +            }
> +            if (len < 0) {
> +                return -TARGET_EINVAL;
> +            }
> +            results = lock_user(VERIFY_WRITE, optval_addr, len, 1);
> +            if (!results) {
> +                return -TARGET_EFAULT;
> +            }
> +            lv = len;
> +            ret = get_errno(getsockopt(sockfd, level, optname, results, &lv));
> +            if (ret < 0) {
> +                unlock_user(results, optval_addr, 0);
> +                return ret;
> +            }
> +            /* swap host endianess to target endianess. */
> +            for (i = 0; i < (len / sizeof(uint32_t)); i++) {
> +                results[i] = tswap32(results[i]);
> +            }
> +            if (put_user_u32(lv, optlen)) {
> +                return -TARGET_EFAULT;
> +            }
> +            unlock_user(results, optval_addr, 0);
> +            break;
> +        }
> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) */
> +        default:
> +            goto unimplemented;
> +        }
> +#endif /* SOL_NETLINK */

Here at the end of the 'case SOL_NETLINK' we will just
fall straight through into 'default:'. Missing 'break' ?

>      default:
>      unimplemented:
>          gemu_log("getsockopt level=%d optname=%d not yet supported\n",
> --
> 2.21.0

thanks
-- PMM
Laurent Vivier Nov. 12, 2019, 10:30 a.m. UTC | #2
Le 12/11/2019 à 11:11, Peter Maydell a écrit :
> On Wed, 6 Nov 2019 at 13:07, Laurent Vivier <laurent@vivier.eu> wrote:
>>
>> From: Josh Kunz <jkz@google.com>
>>
>> This change includes support for all AF_NETLINK socket options up to about
>> kernel version 5.4 (5.4 is not formally released at the time of writing).
>> Socket options that were introduced in kernel versions before the oldest
>> currently stable kernel version are guarded by kernel version macros.
>>
>> This change has been built under gcc 8.3, and clang 9.0, and it passes
>> `make check`. The netlink options have been tested by emulating some
>> non-trival software that uses NETLINK socket options, but they have
>> not been exaustively verified.
> 
> Hi; Coverity reports a missing-break-in-switch error for
> this commit (CID 1407221):
> 
>> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
>> index f6751eecb78c..247883292ce5 100644
>> --- a/linux-user/syscall.c
>> +++ b/linux-user/syscall.c
>> @@ -2248,6 +2248,39 @@ set_timeout:
>>              return -TARGET_EFAULT;
>>         ret = get_errno(setsockopt(sockfd, SOL_SOCKET, optname, &val, sizeof(val)));
>>          break;
>> +#ifdef SOL_NETLINK
>> +    case SOL_NETLINK:
>> +        switch (optname) {
>> +        case NETLINK_PKTINFO:
>> +        case NETLINK_ADD_MEMBERSHIP:
>> +        case NETLINK_DROP_MEMBERSHIP:
>> +        case NETLINK_BROADCAST_ERROR:
>> +        case NETLINK_NO_ENOBUFS:
>> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
>> +        case NETLINK_LISTEN_ALL_NSID:
>> +        case NETLINK_CAP_ACK:
>> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) */
>> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
>> +        case NETLINK_EXT_ACK:
>> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
>> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
>> +        case NETLINK_GET_STRICT_CHK:
>> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
>> +            break;
>> +        default:
>> +            goto unimplemented;
>> +        }
>> +        val = 0;
>> +        if (optlen < sizeof(uint32_t)) {
>> +            return -TARGET_EINVAL;
>> +        }
>> +        if (get_user_u32(val, optval_addr)) {
>> +            return -TARGET_EFAULT;
>> +        }
>> +        ret = get_errno(setsockopt(sockfd, SOL_NETLINK, optname, &val,
>> +                                   sizeof(val)));
>> +        break;
>> +#endif /* SOL_NETLINK */
>>      default:
>>      unimplemented:
>>          gemu_log("Unsupported setsockopt level=%d optname=%d\n", level, optname);
>> @@ -2532,6 +2565,74 @@ static abi_long do_getsockopt(int sockfd, int level, int optname,
>>              break;
>>          }
>>          break;
>> +#ifdef SOL_NETLINK
>> +    case SOL_NETLINK:
>> +        switch (optname) {
>> +        case NETLINK_PKTINFO:
>> +        case NETLINK_BROADCAST_ERROR:
>> +        case NETLINK_NO_ENOBUFS:
>> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
>> +        case NETLINK_LISTEN_ALL_NSID:
>> +        case NETLINK_CAP_ACK:
>> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) */
>> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
>> +        case NETLINK_EXT_ACK:
>> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
>> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
>> +        case NETLINK_GET_STRICT_CHK:
>> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
>> +            if (get_user_u32(len, optlen)) {
>> +                return -TARGET_EFAULT;
>> +            }
>> +            if (len != sizeof(val)) {
>> +                return -TARGET_EINVAL;
>> +            }
>> +            lv = len;
>> +            ret = get_errno(getsockopt(sockfd, level, optname, &val, &lv));
>> +            if (ret < 0) {
>> +                return ret;
>> +            }
>> +            if (put_user_u32(lv, optlen)
>> +                || put_user_u32(val, optval_addr)) {
>> +                return -TARGET_EFAULT;
>> +            }
>> +            break;
>> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
>> +        case NETLINK_LIST_MEMBERSHIPS:
>> +        {
>> +            uint32_t *results;
>> +            int i;
>> +            if (get_user_u32(len, optlen)) {
>> +                return -TARGET_EFAULT;
>> +            }
>> +            if (len < 0) {
>> +                return -TARGET_EINVAL;
>> +            }
>> +            results = lock_user(VERIFY_WRITE, optval_addr, len, 1);
>> +            if (!results) {
>> +                return -TARGET_EFAULT;
>> +            }
>> +            lv = len;
>> +            ret = get_errno(getsockopt(sockfd, level, optname, results, &lv));
>> +            if (ret < 0) {
>> +                unlock_user(results, optval_addr, 0);
>> +                return ret;
>> +            }
>> +            /* swap host endianess to target endianess. */
>> +            for (i = 0; i < (len / sizeof(uint32_t)); i++) {
>> +                results[i] = tswap32(results[i]);
>> +            }
>> +            if (put_user_u32(lv, optlen)) {
>> +                return -TARGET_EFAULT;
>> +            }
>> +            unlock_user(results, optval_addr, 0);
>> +            break;
>> +        }
>> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) */
>> +        default:
>> +            goto unimplemented;
>> +        }
>> +#endif /* SOL_NETLINK */
> 
> Here at the end of the 'case SOL_NETLINK' we will just
> fall straight through into 'default:'. Missing 'break' ?

Yes, missing 'break'.

I'm going to send a patch to fix that.

Thanks,
Laurent

Patch
diff mbox series

diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index f6751eecb78c..247883292ce5 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -2248,6 +2248,39 @@  set_timeout:
             return -TARGET_EFAULT;
 	ret = get_errno(setsockopt(sockfd, SOL_SOCKET, optname, &val, sizeof(val)));
         break;
+#ifdef SOL_NETLINK
+    case SOL_NETLINK:
+        switch (optname) {
+        case NETLINK_PKTINFO:
+        case NETLINK_ADD_MEMBERSHIP:
+        case NETLINK_DROP_MEMBERSHIP:
+        case NETLINK_BROADCAST_ERROR:
+        case NETLINK_NO_ENOBUFS:
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
+        case NETLINK_LISTEN_ALL_NSID:
+        case NETLINK_CAP_ACK:
+#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+        case NETLINK_EXT_ACK:
+#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
+        case NETLINK_GET_STRICT_CHK:
+#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
+            break;
+        default:
+            goto unimplemented;
+        }
+        val = 0;
+        if (optlen < sizeof(uint32_t)) {
+            return -TARGET_EINVAL;
+        }
+        if (get_user_u32(val, optval_addr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = get_errno(setsockopt(sockfd, SOL_NETLINK, optname, &val,
+                                   sizeof(val)));
+        break;
+#endif /* SOL_NETLINK */
     default:
     unimplemented:
         gemu_log("Unsupported setsockopt level=%d optname=%d\n", level, optname);
@@ -2532,6 +2565,74 @@  static abi_long do_getsockopt(int sockfd, int level, int optname,
             break;
         }
         break;
+#ifdef SOL_NETLINK
+    case SOL_NETLINK:
+        switch (optname) {
+        case NETLINK_PKTINFO:
+        case NETLINK_BROADCAST_ERROR:
+        case NETLINK_NO_ENOBUFS:
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
+        case NETLINK_LISTEN_ALL_NSID:
+        case NETLINK_CAP_ACK:
+#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)
+        case NETLINK_EXT_ACK:
+#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
+        case NETLINK_GET_STRICT_CHK:
+#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) */
+            if (get_user_u32(len, optlen)) {
+                return -TARGET_EFAULT;
+            }
+            if (len != sizeof(val)) {
+                return -TARGET_EINVAL;
+            }
+            lv = len;
+            ret = get_errno(getsockopt(sockfd, level, optname, &val, &lv));
+            if (ret < 0) {
+                return ret;
+            }
+            if (put_user_u32(lv, optlen)
+                || put_user_u32(val, optval_addr)) {
+                return -TARGET_EFAULT;
+            }
+            break;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
+        case NETLINK_LIST_MEMBERSHIPS:
+        {
+            uint32_t *results;
+            int i;
+            if (get_user_u32(len, optlen)) {
+                return -TARGET_EFAULT;
+            }
+            if (len < 0) {
+                return -TARGET_EINVAL;
+            }
+            results = lock_user(VERIFY_WRITE, optval_addr, len, 1);
+            if (!results) {
+                return -TARGET_EFAULT;
+            }
+            lv = len;
+            ret = get_errno(getsockopt(sockfd, level, optname, results, &lv));
+            if (ret < 0) {
+                unlock_user(results, optval_addr, 0);
+                return ret;
+            }
+            /* swap host endianess to target endianess. */
+            for (i = 0; i < (len / sizeof(uint32_t)); i++) {
+                results[i] = tswap32(results[i]);
+            }
+            if (put_user_u32(lv, optlen)) {
+                return -TARGET_EFAULT;
+            }
+            unlock_user(results, optval_addr, 0);
+            break;
+        }
+#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) */
+        default:
+            goto unimplemented;
+        }
+#endif /* SOL_NETLINK */
     default:
     unimplemented:
         gemu_log("getsockopt level=%d optname=%d not yet supported\n",