From: Pedro Alves <palves@redhat.com>
To: Eli Zaretskii <eliz@gnu.org>
Cc: gdb-patches@sourceware.org
Subject: Re: [pushed] Fix struct sockaddr/sockaddr_in/sockaddr_un strict aliasing violations
Date: Mon, 09 Mar 2015 10:45:00 -0000 [thread overview]
Message-ID: <54FD79B4.8070201@redhat.com> (raw)
In-Reply-To: <83oao4ljwg.fsf@gnu.org>
On 03/07/2015 06:52 PM, Eli Zaretskii wrote:
>> Date: Sat, 07 Mar 2015 18:20:18 +0000
>> From: Pedro Alves <palves@redhat.com>
>> CC: gdb-patches@sourceware.org
>>
>> Those are BSD socket types, they've been this way ever since BSD
>> invented them. The structs are not type compatible, even though
>> they have some common fields that are are put at the same offsets,
>> by design.
>>
>> See e.g.:
>>
>> http://stackoverflow.com/questions/1429645/how-to-cast-sockaddr-storage-and-avoid-breaking-strict-aliasing-rules
>
> IMO, the right way to handle this is to have our own struct (NOT
> union!) with the data, and fill the correct struct from it, field by
> field, when we need to pass it to a library function.
But that doesn't really make sense in this context.
These struct sockaddr_foo types aren't ours, they're pretty much
hard core to the sockets library. You _need_ to fill in a
struct sockaddr_foo, and pass that to bind/accept, but disguised
as a "struct sockaddr". That's just how those calls work. It's
impossible to fill in a struct sockaddr with the data, because
struct sockaddr does not have the necessary fields. E.g.:
(top-gdb) p sizeof (struct sockaddr)
$3 = 16
(top-gdb) p sizeof (struct sockaddr_in)
$2 = 16
(top-gdb) p sizeof (struct sockaddr_in6)
$1 = 28
(top-gdb) p sizeof (struct sockaddr_un)
$4 = 110
if 'struct sockaddr' HAD been defined like:
struct sockaddr {
sa_family_t sa_family;
}
and then struct sockaddr_foos had been defined as "extending"
struct sockaddr, like e.g.:
struct sockaddr_in6 {
/* Extend a sockaddr */
struct sockaddr sa;
/* ... and add more data */
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
}
That is, the first field of the structure had been the
abstract "struct sockaddr", then it'd be OK to cast struct sockaddr_foo
to "struct sockaddr". C allows that.
(Another possibility would have been for bind/accept to
take a "sa_family_t *sa_family" as argument instead of
"struct sockaddr *", same thing.)
But, they're not. That ship has sailed many years ago.
What we have is types like these (from gdb's 'ptype',
typedefs may be missing):
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
}
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[108];
}
And thus casting between "struct sockaddr *"
and "struct sockaddr_in6 *" is invalid. These types can't
alias, thus with this:
struct sockaddr_in *addr_in = ...;
struct sockaddr *addr = (struct sockaddr *) addr_in;
addr->sa_family = 1;
addr_in->sin_family = 2;
if (addr->sa_family != 1) {
abort ();
}
the compiler is free to completely eliminate the 'if'.
> A union is just a type-cast in disguise; if using it is wrong, we
> shouldn't try. (And what if one of these days GCC will acquire a
> capability to see through them?)
>
As discussed in that stackexchange page, it won't. It's
implementation-defined till C90 whether accessing a different union member
from the one last used to store a value was valid, a.k.a., type-punning.
In GCC's implementation (and others' I believe), it's valid.
Please see:
https://gcc.gnu.org/onlinedocs/gcc-4.9.0/gcc/Structures-unions-enumerations-and-bit-fields-implementation.html#Structures-unions-enumerations-and-bit-fields-implementation
and:
https://gcc.gnu.org/onlinedocs/gcc-4.9.0/gcc/Optimize-Options.html#Type-punning
And then the C99 standard changed its wording to make it valid everywhere.
See DR283 (defect report):
http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm
And C99 TC3, section 6.5, paragraph 7:
www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
Casts are invalid. Unions work in practice with old compilers,
and are required to work in "modern" (old by now) compilers. So
really unions are the proper solution for this, however unfortunate.
>> It's not a C++ restriction. The old code was invalid C code.
>
> I'm talking about the new code.
Then I don't understand what you're saying, sorry. This has nothing
to do with C++. C++ happened to catch it due to the missing cast. I
could have added the cast to that one place that missed it, but that
would just be propagating both invalid C++ _and_ invalid _C_ code.
Thanks,
Pedro Alves
next prev parent reply other threads:[~2015-03-09 10:45 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-03-07 17:44 Pedro Alves
2015-03-07 18:00 ` Eli Zaretskii
2015-03-07 18:20 ` Pedro Alves
2015-03-07 18:53 ` Eli Zaretskii
2015-03-09 10:45 ` Pedro Alves [this message]
2015-03-09 11:10 ` Pedro Alves
2015-03-09 11:38 ` Pedro Alves
2015-03-09 16:04 ` Eli Zaretskii
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=54FD79B4.8070201@redhat.com \
--to=palves@redhat.com \
--cc=eliz@gnu.org \
--cc=gdb-patches@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox