Skip to contentSkip to conversation

ntlm: avoid breaking anti-aliasing rules

PATCH Superseded Antonio Quartulli 4 participants 8 messages Aug 12, 2017 – Aug 16, 2017

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 ntlm: avoid breaking anti-aliasing rules

From: Antonio Quartulli <[email protected]...>

The problem is visible when compiling with -O2:

ntlm.c: In function ‘ntlm_phase_3’:
ntlm.c:305:9: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

         if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)

The spec suggests to interpret those 4 bytes as a long, but this needs to be done carefully.

---

I have tried to make the statement more clear and "aliasing safe" at the same time. However, this is only compiled tested as I have no way to test the whole NTLM thing. Sorry :(

Cheers,

 src/openvpn/ntlm.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
src/openvpn/ntlm.c +2 -1
index 65c1cbf5..a251c11d 100644
@@ -302,7 +302,8 @@ ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2,
         /* Add target information block to the blob */
 
         /* Check for Target Information block */
-        if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)
+        unsigned long *flags = (unsigned long *)(buf2 + 0x14);
+        if ((*flags & htonl(0x00800000)) == htonl(0x00800000))
         {
             tib_len = buf2[0x28];            /* Get Target Information block size */
             if (tib_len > 96)
Signature · 1 line
2.14.0
#1 lore raw

Hi,

Quoted 29 lines · Antonio Quartulli

From: Antonio Quartulli <[email protected]...>

The problem is visible when compiling with -O2:

ntlm.c: In function ‘ntlm_phase_3’:
ntlm.c:305:9: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

         if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)

The spec suggests to interpret those 4 bytes as a long, but this needs to be done carefully.

---

I have tried to make the statement more clear and "aliasing safe" at the same time. However, this is only compiled tested as I have no way to test the whole NTLM thing. Sorry :(

Cheers,

 src/openvpn/ntlm.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
src/openvpn/ntlm.c +0 -0
index 65c1cbf5..a251c11d 100644

src/openvpn/ntlm.c ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2, · line 309

Quoted 9 lines
src/openvpn/ntlm.c +2 -1
@@ -302,7 +302,8 @@ ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2,
         /* Add target information block to the blob */
 
         /* Check for Target Information block */
-        if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)
+        unsigned long *flags = (unsigned long *)(buf2 + 0x14);
+        if ((*flags & htonl(0x00800000)) == htonl(0x00800000))
         {
             tib_len = buf2[0x28];            /* Get Target Information block size */
             if (tib_len > 96)

I *think* this will break the functionality. Consider the following test code:

#include <arpa/inet.h>
#include <stdio.h>

int main(char *argv[], int argc)

{
    unsigned char buf[4] = {0, 0, 0x80, 0};

    if ((*((long *)&buf[0]) & 0x00800000) == 0x00800000)
        printf("old: true\n");
    else
        printf("old: false\n");

    unsigned long *flags = (unsigned long *)(buf);
    if ((*flags & htonl(0x00800000)) == htonl(0x00800000))
        printf("new: true\n");
    else
        printf("new: false\n");

    return 0;
}

On amd64, this prints:

old: true
new: false

I'd expect some 'ntohl' call in this code somewhere, but that assumes that the NTLM spec was designed somewhat sane and uses network byte order...

For now, I'd suggest to stick to just fixing the type-punned dereference and leave out any htonl/ntohl calls.

-Steffan

#2 lore raw
Quoted 67 lines · Steffan Karger

Hi,

Quoted 29 lines · Antonio Quartulli

From: Antonio Quartulli <[email protected]...>

The problem is visible when compiling with -O2:

ntlm.c: In function ‘ntlm_phase_3’:
ntlm.c:305:9: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

         if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)

The spec suggests to interpret those 4 bytes as a long, but this needs to be done carefully.

---

I have tried to make the statement more clear and "aliasing safe" at the same time. However, this is only compiled tested as I have no way to test the whole NTLM thing. Sorry :(

Cheers,

 src/openvpn/ntlm.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
src/openvpn/ntlm.c +0 -0
index 65c1cbf5..a251c11d 100644

src/openvpn/ntlm.c ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2, · line 309

Quoted 9 lines
src/openvpn/ntlm.c +2 -1
@@ -302,7 +302,8 @@ ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2,
         /* Add target information block to the blob */
 
         /* Check for Target Information block */
-        if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)
+        unsigned long *flags = (unsigned long *)(buf2 + 0x14);
+        if ((*flags & htonl(0x00800000)) == htonl(0x00800000))
         {
             tib_len = buf2[0x28];            /* Get Target Information block size */
             if (tib_len > 96)

I *think* this will break the functionality. Consider the following test code:

#include <arpa/inet.h>
#include <stdio.h>

int main(char *argv[], int argc)

{
    unsigned char buf[4] = {0, 0, 0x80, 0};

    if ((*((long *)&buf[0]) & 0x00800000) == 0x00800000)
        printf("old: true\n");
    else
        printf("old: false\n");

    unsigned long *flags = (unsigned long *)(buf);
    if ((*flags & htonl(0x00800000)) == htonl(0x00800000))
        printf("new: true\n");
    else
        printf("new: false\n");

    return 0;
}
Quoted 8 lines

On amd64, this prints:

old: true
new: false

I'd expect some 'ntohl' call in this code somewhere, but that assumes that the NTLM spec was designed somewhat sane and uses network byte order...

Right, that's also what I imagined, otherwise the code as it is wouldn't work on every platform (not sure it does on LE archs)

Quoted 3 lines

For now, I'd suggest to stick to just fixing the type-punned dereference and leave out any htonl/ntohl calls.

yeah, might be safer. Will send v2.

Cheers,

Signature · 1 line
Antonio Quartulli
#3 lore raw

PATCH v2 1/1 ntlm: avoid breaking anti-aliasing rules

From: Antonio Quartulli <[email protected]...>

The problem is visible when compiling with -O2:

ntlm.c: In function ‘ntlm_phase_3’:
ntlm.c:305:9: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

         if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)

The spec suggests to interpret those 4 bytes as a long, but this needs to be done carefully.

---

v2: remove htonl/ntohl conversion as there is none in the original code

 src/openvpn/ntlm.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)
src/openvpn/ntlm.c +13 -1
index 167c10b8..658c875e 100644
@@ -302,7 +302,19 @@ ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2,
         /* Add target information block to the blob */
 
         /* Check for Target Information block */
-        if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)
+        /* The NTLM spec instructs to interpret these 4 consecutive bytes as a
+         * 32bit long integer. However, no endianness is specified.
+         * The code here and that found in other NTLM implementations point
+         * towards the assumption that the byte order on the wire has to
+         * match the order on the sending and receiving hosts. Probably NTLM has
+         * been thought to be always running on x86_64/i386 machine thus
+         * implying Little-Endian everywhere.
+         *
+         * This said, in case of future changes, we should keep in mind that the
+         * byte order on the wire for the NTLM header is LE.
+         */
+        unsigned long *flags = (unsigned long *)(buf2 + 0x14);
+        if ((*flags & 0x00800000) == 0x00800000)
         {
             tib_len = buf2[0x28];            /* Get Target Information block size */
             if (tib_len > 96)
Signature · 1 line
2.14.0
#4 lore raw

PATCH v3 1/1 ntlm: avoid breaking anti-aliasing rules

From: Antonio Quartulli <[email protected]...>

The problem is visible when compiling with -O2:

ntlm.c: In function ‘ntlm_phase_3’:
ntlm.c:305:9: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

         if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)

The spec suggests to interpret those 4 bytes as a long, but this needs to be done carefully.

---

v2: remove htonl/ntohl conversion as there is none in the original code
v3: correct author's email

 src/openvpn/ntlm.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)
src/openvpn/ntlm.c +13 -1
index 167c10b8..658c875e 100644
@@ -302,7 +302,19 @@ ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2,
         /* Add target information block to the blob */
 
         /* Check for Target Information block */
-        if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)
+        /* The NTLM spec instructs to interpret these 4 consecutive bytes as a
+         * 32bit long integer. However, no endianness is specified.
+         * The code here and that found in other NTLM implementations point
+         * towards the assumption that the byte order on the wire has to
+         * match the order on the sending and receiving hosts. Probably NTLM has
+         * been thought to be always running on x86_64/i386 machine thus
+         * implying Little-Endian everywhere.
+         *
+         * This said, in case of future changes, we should keep in mind that the
+         * byte order on the wire for the NTLM header is LE.
+         */
+        unsigned long *flags = (unsigned long *)(buf2 + 0x14);
+        if ((*flags & 0x00800000) == 0x00800000)
         {
             tib_len = buf2[0x28];            /* Get Target Information block size */
             if (tib_len > 96)
Signature · 1 line
2.14.0
#5 lore raw

PATCH v4 1/1 ntlm: avoid breaking anti-aliasing rules

From: Antonio Quartulli <[email protected]...>

The problem is visible when compiling with -O2:

ntlm.c: In function ‘ntlm_phase_3’:
ntlm.c:305:9: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

         if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)

The spec suggests to interpret those 4 bytes as a long, but this needs to be done carefully.

---

v2: remove htonl/ntohl conversion as there is none in the original code
v3: correct author's email
v4: code used in v3 was not really alias-safe (thanks Steffan), fix it

 src/openvpn/ntlm.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)
src/openvpn/ntlm.c +15 -1
index 167c10b8..077fa3e2 100644
@@ -302,7 +302,21 @@ ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2,
         /* Add target information block to the blob */
 
         /* Check for Target Information block */
-        if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)
+        /* The NTLM spec instructs to interpret these 4 consecutive bytes as a
+         * 32bit long integer. However, no endianness is specified.
+         * The code here and that found in other NTLM implementations point
+         * towards the assumption that the byte order on the wire has to
+         * match the order on the sending and receiving hosts. Probably NTLM has
+         * been thought to be always running on x86_64/i386 machine thus
+         * implying Little-Endian everywhere.
+         *
+         * This said, in case of future changes, we should keep in mind that the
+         * byte order on the wire for the NTLM header is LE.
+         */
+        const size_t hoff = 0x14;
+        unsigned long flags = buf2[hoff] | (buf2[hoff + 1] << 8) |
+                              (buf2[hoff + 2] << 16) | (buf2[hoff + 3] << 24);
+        if ((flags & 0x00800000) == 0x00800000)
         {
             tib_len = buf2[0x28];            /* Get Target Information block size */
             if (tib_len > 96)
Signature · 1 line
2.14.0
#6 lore raw

Hi,

Quoted 40 lines · Antonio Quartulli

From: Antonio Quartulli <[email protected]...>

The problem is visible when compiling with -O2:

ntlm.c: In function ‘ntlm_phase_3’:
ntlm.c:305:9: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

         if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)

The spec suggests to interpret those 4 bytes as a long, but this needs to be done carefully.

---

v2: remove htonl/ntohl conversion as there is none in the original code
v3: correct author's email
v4: code used in v3 was not really alias-safe (thanks Steffan), fix it

 src/openvpn/ntlm.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)
src/openvpn/ntlm.c +8 -1
index 167c10b8..077fa3e2 100644
@@ -302,7 +302,21 @@ ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2,
         /* Add target information block to the blob */
 
         /* Check for Target Information block */
-        if ((*((long *)&buf2[0x14]) & 0x00800000) == 0x00800000)
+        /* The NTLM spec instructs to interpret these 4 consecutive bytes as a
+         * 32bit long integer. However, no endianness is specified.
+         * The code here and that found in other NTLM implementations point
+         * towards the assumption that the byte order on the wire has to
+         * match the order on the sending and receiving hosts. Probably NTLM has
+         * been thought to be always running on x86_64/i386 machine thus
+         * implying Little-Endian everywhere.
+         *

src/openvpn/ntlm.c ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2, · line 322

Quoted 10 lines
src/openvpn/ntlm.c +7 -0
+         * This said, in case of future changes, we should keep in mind that the
+         * byte order on the wire for the NTLM header is LE.
+         */
+        const size_t hoff = 0x14;
+        unsigned long flags = buf2[hoff] | (buf2[hoff + 1] << 8) |
+                              (buf2[hoff + 2] << 16) | (buf2[hoff + 3] << 24);
+        if ((flags & 0x00800000) == 0x00800000)
         {
             tib_len = buf2[0x28];            /* Get Target Information block size */
             if (tib_len > 96)

This fixes the strict aliasing, makes the LE assumption explicit, and doesn't change the behaviour on LE machines. Good enough for me, ACK.

-Steffan

#7 lore raw
In reply to Antonio Quartulli

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Your patch has been applied to the following branches

commit e84b6994b4d2b53bcebd5415a58de4cecd411a7b (master) commit 30e0778a57a8db3d57d144471a869647037a115b (release/2.4)

Author: Antonio Quartulli
Date:   Wed Aug 16 20:18:06 2017 +0800
     ntlm: avoid breaking anti-aliasing rules

     Signed-off-by: Antonio Quartulli <[email protected]...>
     Acked-by: Steffan Karger <[email protected]...>
     Message-Id: <[email protected]...>
     URL: https://www.mail-archive.com/[email protected]/msg15268.html
     Signed-off-by: David Sommerseth <[email protected]...>

- --
kind regards,

David Sommerseth

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.22 (GNU/Linux)

iQIcBAEBCgAGBQJZlGAXAAoJEIbPlEyWcf3yypsP/302vOTwTQ/HizJ83s9QJYPh 9xRA2/4fwV9iNyoj76SHskMnAtkyIZGUlWN/mMIw9ekZF0lydIQn6XK0ftvhuwEr XYiaLhUzB1wIDTXRhU8scSW+Xe0ZzbQ1EQMKuUW6muTcGCVlCWRIQkZ8FFfJsxFi PnXFpEL1UuNCwaFbLdPbdVSXb14vgQ9q7++PCNFIc2Z7dyocsoFzFJlHL1fmaGd/ 1G2cURTDtFXinIphFOzTFdsog6CyRciqnqTWm8k8COAE23ESqdaU/74CbqT+W9W4 YCmixcNttTImeeVMIepPoCrC/zEEyyRMVha6MPhZu6o2zYVjgkkgXek0hoJoY8ns z6cFbDXs9IyeSQmO1rCQnm5f79yA/UQ3GZNNtaWg6vkl+Jp6KDhUpW5Vxh9z+VXo PGqCWxyGqVw+ycAs2X0bddHVIAwg5pTjGXVXcthZTgg584RlqmOXKZZ5VP940CCh JZ1SZam64wQqjWlxFcVmZ9AVnOWN+FSOoKvK8I4h9xHbrQMT6DkBzwoDXRBwb1il TSTl0Rev6HKrB6HF290BgLcHzpUzaKcRcw/bIMXRUO6L1Lp/ciOeEyiBTBrGdQw7 tT4rjvbJg0/iL0lM9GkDmjI0tWNmVS0V4+HC1r0mdtOxq9j0AJTBKtkiEY6Y+XWv gsmqfSDNCqn6d/osOEKx
=8ER+
-----END PGP SIGNATURE-----