[Spice-devel] [PATCH 07/17] Get rid of C-style memset initializations, use C++ style aggregates
Frediano Ziglio
fziglio at redhat.com
Thu Feb 22 18:03:18 UTC 2018
>
> [Indeed, I had not sent this reply, took time to check standard and
> compilers]
>
> > On 20 Feb 2018, at 12:31, Frediano Ziglio <fziglio at redhat.com> wrote:
> >
> >>> On 20 Feb 2018, at 10:39, Lukáš Hrázký <lhrazky at redhat.com> wrote:
> >>>
> >>> On Tue, 2018-02-20 at 10:18 +0100, Christophe de Dinechin wrote:
> >>>>> On Feb 19, 2018, at 7:19 PM, Lukáš Hrázký <lhrazky at redhat.com> wrote:
> >>>>>
> >>>>> On Fri, 2018-02-16 at 17:15 +0100, Christophe de Dinechin wrote:
> >>>>>> From: Christophe de Dinechin <dinechin at redhat.com>
> >>>>>>
> >>>>>> Signed-off-by: Christophe de Dinechin <dinechin at redhat.com>
> >>>>>> ---
> >>>>>> src/spice-streaming-agent.cpp | 47
> >>>>>> ++++++++++++++++++++++++++-----------------
> >>>>>> 1 file changed, 28 insertions(+), 19 deletions(-)
> >>>>>>
> >>>>>> diff --git a/src/spice-streaming-agent.cpp
> >>>>>> b/src/spice-streaming-agent.cpp
> >>>>>> index 69c27a3..1e19e43 100644
> >>>>>> --- a/src/spice-streaming-agent.cpp
> >>>>>> +++ b/src/spice-streaming-agent.cpp
> >>>>>> @@ -181,19 +181,24 @@ write_all(int fd, const void *buf, const size_t
> >>>>>> len)
> >>>>>> return written;
> >>>>>> }
> >>>>>>
> >>>>>> -static int spice_stream_send_format(int streamfd, unsigned w,
> >>>>>> unsigned
> >>>>>> h, unsigned c)
> >>>>>> +static int spice_stream_send_format(int streamfd, unsigned w,
> >>>>>> unsigned
> >>>>>> h, uint8_t c)
> >>>>>> {
> >>>>>> -
> >>>>>> - SpiceStreamFormatMessage msg;
> >>>>>> - const size_t msgsize = sizeof(msg);
> >>>>>> - const size_t hdrsize = sizeof(msg.hdr);
> >>>>>> - memset(&msg, 0, msgsize);
> >>>>>> - msg.hdr.protocol_version = STREAM_DEVICE_PROTOCOL;
> >>>>>> - msg.hdr.type = STREAM_TYPE_FORMAT;
> >>>>>> - msg.hdr.size = msgsize - hdrsize; /* includes only the body? */
> >>>>>> - msg.msg.width = w;
> >>>>>> - msg.msg.height = h;
> >>>>>> - msg.msg.codec = c;
> >>>>>> + const size_t msgsize = sizeof(SpiceStreamFormatMessage);
> >>>>>> + const size_t hdrsize = sizeof(StreamDevHeader);
> >>>>>> + SpiceStreamFormatMessage msg = {
> >>>>>> + .hdr = {
> >>>>>> + .protocol_version = STREAM_DEVICE_PROTOCOL,
> >>>>>> + .padding = 0, // Workaround GCC "not implemented"
> >>>>>> bug
> >>>>>> + .type = STREAM_TYPE_FORMAT,
> >>>>>> + .size = msgsize - hdrsize
> >>>>>> + },
> >>>>>> + .msg = {
> >>>>>> + .width = w,
> >>>>>> + .height = h,
> >>>>>> + .codec = c,
> >>>>>> + .padding1 = { }
> >>>>>> + }
> >>>>>> + };
> >>>>>> syslog(LOG_DEBUG, "writing format\n");
> >>>>>> std::lock_guard<std::mutex> stream_guard(stream_mtx);
> >>>>>> if (write_all(streamfd, &msg, msgsize) != msgsize) {
> >>>>>> @@ -204,14 +209,18 @@ static int spice_stream_send_format(int
> >>>>>> streamfd,
> >>>>>> unsigned w, unsigned h, unsign
> >>>>>>
> >>>>>> static int spice_stream_send_frame(int streamfd, const void *buf,
> >>>>>> const
> >>>>>> unsigned size)
> >>>>>> {
> >>>>>> - SpiceStreamDataMessage msg;
> >>>>>> - const size_t msgsize = sizeof(msg);
> >>>>>> ssize_t n;
> >>>>>> + const size_t msgsize = sizeof(SpiceStreamFormatMessage);
> >>>>>> + SpiceStreamDataMessage msg = {
> >>>>>> + .hdr = {
> >>>>>> + .protocol_version = STREAM_DEVICE_PROTOCOL,
> >>>>>> + .padding = 0, // Workaround GCC "not implemented"
> >>>>>> bug
> >>>>>> + .type = STREAM_TYPE_DATA,
> >>>>>> + .size = size /* includes only the body? */
> >>>>>> + },
> >>>>>> + .msg = {}
> >>>>>> + };
> >>>>>
> >>>>> So, someone should find out if we can use the designated initializers,
> >>>>> I suppose it depends on the compilers on all platforms we care about
> >>>>> supporting them?
> >>>>>
> >>>>> I wasn't able to find much useful information so far. Anyone knows in
> >>>>> which version of gcc it was introduced?
> >>>>
> >>>> My experience is that clang has no issue with it in any version.
> >>>>
> >>>> GCC supports it in C++ since at least 4.8.5, as tested on RHEL 7.4 with
> >>>> the following short example:
> >>>>
> >>>> struct Thing { int x; float y; };
> >>>>
> >>>> int foo()
> >>>> {
> >>>> Thing x = { x: 10, y:20 };
> >>>> Thing y = { .x = 10, .y = 20 };
> >>>> Thing z { 10, 20 };
> >>>> Thing t { .x = 10, .y = 20 };
> >>>> }
> >>>>
> >>>> It has, however, trouble with out-of-order initializations, with the
> >>>> same
> >>>> message in 4.8.5 as on Fedora 25 (6.4.1):
> >>>>
> >>>> glop.cpp:9:30: sorry, unimplemented: non-trivial designated initializers
> >>>> not supported
> >>>> Thing a { .y = 10, .x = 20 };
> >>>>
> >>>> The “not implemented” message is a bit scary, but the fact that the code
> >>>> as written has been supported as far back as 4.8.5 makes me confident
> >>>> that we are not in some experimental section of the compiler.
> >>>
> >>> I've found this on the matter:
> >>>
> >>> https://gcc.gnu.org/ml/gcc/2014-09/msg00207.html
> >>>
> >>> That doesn't give much confidence... Is this documented anywhere? I
> >>> mean I've only used Google and haven't found anything…
> >>
> >> I think it’s a low priority thing because the “fix” in source code is so
> >> easy
> >> (reorder the fields), and the fix complicated. According to the message
> >> you
> >> referenced, presently, GCC just checks that the annotations match the
> >> names,
> >> but otherwise ignores them and proceeds like for a POD init. If it were to
> >> be implemented, it would have to support a lot of corner cases mentioned
> >> in
> >> the message, so this makes the fix non-trivial.
> >>
> >
> > This is not a bug but a feature. In C++20 fields have to be in the right
> > order so when clang will start implementing correctly the standard will
> > have to fix it. In C++ fields are initialized in order.
> > About the title aggregates are also a C style actually as we are supposed
> > to be C++11 is more a C style than a C++ style.
> > But is not this that worry me. memset(0) and aggregate initializers are
> > quite different, is not only a question of style. Consider this:
> >
> > struct xxx {
> > float f;
> > int i;
> > };
> > xxx v { };
> >
> > this initialize f and i to 0.0 and 0 respectively. On some machines
> > the memset does not set f to 0.0 (depends on floating point
> > representation).
>
> > But not a big deal, we don't use floating point.
> > But padding worry me a bit more, consider this:
> >
> > struct xxx {
> > uint8_t c;
> > uint16_t n;
> > };
> > xxx v { 1, 2 }; // or v { .c = 1, .n = 2 };
>
> All padding is initialized to zero in the case of zero-initialization, see
> http://en.cppreference.com/w/cpp/language/zero_initialization. So if you are
> really concerned, you can write:
>
> xxx v = { };
> x.c = 1; x.n = 2;
>
Source:
void aaa()
{
struct {
char c;
int i;
} x = { 1, 2 };
write(0, &x, sizeof(x));
}
Code:
0000000000000610 <_Z3aaav>:
610: 48 83 ec 18 sub $0x18,%rsp
614: 31 ff xor %edi,%edi
616: ba 08 00 00 00 mov $0x8,%edx
61b: 48 89 e6 mov %rsp,%rsi
61e: c6 04 24 01 movb $0x1,(%rsp)
622: c7 44 24 04 02 00 00 movl $0x2,0x4(%rsp)
629: 00
62a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
631: 00 00
633: 48 89 44 24 08 mov %rax,0x8(%rsp)
638: 31 c0 xor %eax,%eax
63a: e8 00 00 00 00 callq 63f <_Z3aaav+0x2f>
63f: 48 8b 44 24 08 mov 0x8(%rsp),%rax
644: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
64b: 00 00
64d: 75 05 jne 654 <_Z3aaav+0x44>
64f: 48 83 c4 18 add $0x18,%rsp
653: c3 retq
654: e8 00 00 00 00 callq 659 <_Z3aaav+0x49>
659: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
so I assume I have a compiler bug here or that the code you wrote
is potentially not zero filling the paddings.
> Now, while your concerns are correct by the word of the standard, they apply
> equally well to default copy and assignment. So unless we disable default
> copies on the C structs, we have the same risk there, including when we
> return values…
>
> It’s not a real risk, though, for multiple different reasons, the primary one
> being that we should *never ever* depend on the value padding!!! If we do,
> we have a more serious issue elsewhere (see below), and whoever did that
> deserves to spend time debugging his own mess ;-)
> >
> > now, we know there's a byte between c and n. What's the value of this byte?
> > In the case of memset is 0, in the case of aggregate initializers... we
> > don't
> > know! That is only the bytes occupied by the fields are initialized, not
> > the
> > padding. Currently is not a big issue, there are no implicit holes and all
> > bytes are initialized but is this became false we would potentially have a
> > security issue as we are sending the full raw structure to an external
> > entity.
>
> While I understand what you are saying, the problem would only arise if the
> following chain of events happened:
>
> 1) We insert a new field in an (presently nonexistent) implicit padding
> 2) We rely on that insertion not relocating the fields behind, i.e. we do it
> “on purpose” because we are so “smart” (asking for trouble)
> 3) We do so without also adding capabilities or version check and assume the
> field exists on the other end (really asking for trouble)
> 4) We access the field in new software that talks to old software which does
> not have the field (REALLY asking for trouble)
> 5) We rely on that new field being initialized at 0 by the “legacy” other
> end, when we know it’s not true (REALLY REALLY asking for trouble)
> 6) We depend on the struct never being copied on either side, which would
> erase that zero guarantee (Uh oh!)
> 7) We never thought of using the “packed” attribute for that struct… (at that
> point, why bother mentioning this?)
> 8) We compiled with gcc at -O0 to ensure it would not zero-init the padding
> 9) There is a security issue due to the 8 steps above, but we claim the
> problem is to use C++-style init
>
Only 7 apply here for good reasons. Try to see why ip protocol take into
account alignment.
> That seems far-fetched enough that I’d argue we have more pressing concerns.
>
> > At the moment the entity should be considered more secure but for
> > instance moving the same code on the server size would cause a security
> > issue. To avoid that any future structure should have no implicit padding
> > but this imposes an implementation detail of the protocol definition in a
> > project written in C (spice-protocol) just to satisfy some much higher
> > level style detail. It does seem to me quite easy to break this is the
> > future. In this respect also there are some condition which are even harder
> > to avoid. Consider something like
> >
> > struct xxx {
> > uint8_t type_of_union;
> > uint8_t padding;
> > union {
> > uint8_t type1;
> > uint16_t type2;
> > }
> > };
> > xxx v { };
> >
> > now all the bytes are covered by fields however the last byte is not
> > potentially initialized as C++ mandate that only first no static field
> > of an union is initialized.
> >
> > Yes, aggregate initialization is a bit easier to read but seems that
> > the cost is potentially high.
>
> None of the cases you evoke happens in our current codebase. We have explicit
> padding, no unions. So not a problem today and in foreseeable future.
>
I said that. Current code is not affected but what I'm complaining is
that is not robust and requires attentions.
> >
> >>>
> >>> OTOH, if the extension is robust against random mistakes and typos, and
> >>> GCC and clang are the only compilers we care about, I think it should
> >>> be fine?
> >>
> >> If we agree that a message that contains “not supported” if we mess up is
> >> OK,
> >> I think that’s fine.
> >>
> >> However, I believe I need to beef up the comment and explain what the
> >> message
> >> is and what the fix is.
> >>
> >>>
> >>> Lukas
> >>>
> >>>> The alternatives are:
> >>>> - Not tagging fields at all
> >>>> - Tagging them “the old way”, i.e. field:value, but that has been
> >>>> deprecated since 2.5 and actually has the same bug as .field = value, so
> >>>> no benefit.
> >>>>
> >>>>
> >>>>>
> >>>>> Lukas
> >>>>>
> >>>>>> - memset(&msg, 0, msgsize);
> >>>>>> - msg.hdr.protocol_version = STREAM_DEVICE_PROTOCOL;
> >>>>>> - msg.hdr.type = STREAM_TYPE_DATA;
> >>>>>> - msg.hdr.size = size; /* includes only the body? */
> >>>>>> std::lock_guard<std::mutex> stream_guard(stream_mtx);
> >>>>>> n = write_all(streamfd, &msg, msgsize);
> >>>>>> syslog(LOG_DEBUG,
> >>>>>> @@ -379,7 +388,7 @@ do_capture(int streamfd, const char *streamport,
> >>>>>> FILE *f_log)
> >>>>>>
> >>>>>> if (frame.stream_start) {
> >>>>>> unsigned width, height;
> >>>>>> - unsigned char codec;
> >>>>>> + uint8_t codec;
> >>>>>>
> >>>>>> width = frame.size.width;
> >>>>>> height = frame.size.height;
> >
> > Maybe even better if the type is SpiceVideoCodecType here or use auto
> > to avoid any possible truncation.
>
> Makes sense to change for clarity, but StreamMsgFormat codec is uint8_t.
>
I know, C has no specification for enumeration size so we can't use enums
in a binary structure.
Frediano
More information about the Spice-devel
mailing list