On 7. 7. 25 21:30, Daniel Sahlberg wrote:
Den mån 7 juli 2025 kl 17:15 skrev Branko Čibej <br...@apache.org>:

    On 7. 7. 25 16:07, Daniel Sahlberg wrote:
    > Den mån 7 juli 2025 kl 12:08 skrev Branko Čibej <br...@apache.org>:
    >
    >     On 7. 7. 25 11:08, Branko Čibej wrote:
    >>     As you've probably noticed, I've been going through compilation
    >>     warnings, especially implicit narrowing conversions, and trying
    >>     to fix them in a sane way. Many of them, by the way, I
    don't see
    >>     with gcc because it doesn't have a -Wshorten-64-to-32 flag, so
    >>     I'm using clang. This time I found a gem:
    >>
    >>
    >>        .../protocols/http2_stream.c:162:48: warning: implicit
    >>     conversion loses integer precision:
    >>              'apr_size_t' (aka 'unsigned long') to 'apr_uint32_t'
    >>     (aka 'unsigned int') [-Wshorten-64-to-32]
    >>           146 | max_payload_size,
    >>     | ^~~~~~~~~~~~~~~~
    >>
    >>
    >>     This is in a call to serf__bucket_http2_frame_create(), which
    >>     expects max_payload_size as an apr_uint32_t.
    >>
    >>
    >>     But then it goes on to allocate a serf_http2_frame_context_t
    >>     (see: http2_frame_buckets.h line 603) which has a member
    >>     max_payload_size that is an apr_size_t. And if that's not funny
    >>     enough, here's how the struct initialisation actually happens:
    >>
    >>
    >> serf__bucket_http2_frame_create(serf_bucket_t *stream,
    >>                                         unsigned char frame_type,
    >>                                         unsigned char flags,
    >>                                         apr_int32_t *stream_id,
    >>     void(*stream_id_alloc)(
    >>                                             void *baton,
    >> apr_int32_t *stream_id),
    >>                                         void *stream_id_baton,
    >> apr_uint32_t max_payload_size,
    >>     serf_bucket_alloc_t *alloc)
    >>        {
    >>             serf_http2_frame_context_t *ctx =
    >>     serf_bucket_mem_alloc(alloc,
    >>     sizeof(*ctx));
    >>
    >>             ctx->alloc = alloc;
    >>             ctx->stream = stream;
    >>             ctx->chunk = serf_bucket_aggregate_create(alloc);
    >>             ctx->max_payload_size = max_payload_size;
    >>             ctx->frametype = frame_type;
    >>             ctx->flags = flags;
    >>
    >>             if (max_payload_size > 0xFFFFFF)
    >>                 max_payload_size = 0xFFFFFF;
    >>        ...
    >>
    >>
    >>
    >>     From this point onward, max_payload_size (the function
    argument,
    >>     an apr_uint32_t) is not used. Um. The size adjustment is a
    no-op?
    >>
    >>
    >>     So to silence the warning, it would be enough to change the
    type
    >>     of the function argument. But this bit of code is actually
    >>     nonsense. Should the max size be limited to 16 MiB or not? If
    >>     yes, and it isn't, how does the HTTP/2 code even work
    correctly?
    >>     Or maybe we just haven't encountered a case where it breaks?
    >>
    >>
    >>     I don't know anything about HTTP/2 framing. RFC 9113/4.2 says
    >>     that 16 MiB is the upper limit. My gut feeling is to move the
    >>     size adjustment before 'ctx' initialisation, and to change the
    >>     type of the parameter -- the latter because Serf operates with
    >>     apr_size_t all the time.
    >
    > Not sure how much I can help out here and I'm sure you read the
    logs
    > as well as I do, but it seems there are only a few revisions of
    that
    > particular part of the code. Initially, it seems
    max_payload_size was
    > indeed apr_size_t but r1712557 changed to the current
    apr_uint32_t: "
    > (serf__bucket_http2_frame_create): Remove some arguments. Limits
    > max_payload_size to a uint32, as that is how http2 configures
    it  and
    > it has to fit in 24 bits anyway."
    >
    > In the case you quoted above, stream_send_headers(), the
    > max_payload_size is apr_size_t. Checking some places this is called
    > find http2_stream_enque_response which gets the argument from
    > serf_http2__max_payload_size which return apr_size_t BUT it get the
    > actual returned number from an serf_http2_protocol_t struct member
    > lr_max_framesize which in turn is (*TADA*) apr_uint32_t.
    >
    > It seems Bert's observation "it has to fit in 24 bits anyway" is
    > correct and from that point of view it makes sense to use
    > apr_uint32_t. But I don't see any real reason why it would be
    > unreasonable to use apr_size_t - personally I think that type is
    more
    > logical.


    The observation is correct, but changing that in only one place
    and then
    immediately converting it to apr_size_t is just ... I dunno,
    incomplete?
    I've noticed that the implementation (not just the http2 part) plays
    fast and loose with int/apr_size_t/apr_uint32_t, creating problems
    and
    requiring conversions. It doesn't help that APR itself is
    inconsistent
    about that (apr_base64, /me rolls eyes).

    This goes all the way to the public API in some places. Specifically,
    the source of that apr_size_t is in Serf's public bucket API, so
    non-negotiable.


    > Is there a platform where apr_size_t is < 24 bits?


    Theoretically, 16-bit platforms could have 16-bit apr_size_t, but I'm
    pretty sure we don't support those for other reasons. All platforms
    should have sizeof(apr_size_t) >= sizeof(int) but not necessarily >=
    sizeof(apr_uint32_t). I'm pretty sure APR would fall on its face
    on such
    platforms, so I wouldn't worry about them.


    >     Specifically, this silences the warning and all tests still
    pass,
    >     including a checkout with Subversion over https (though I've no
    >     idea how to verify if that actually uses HTTP/2; any pointers
    >     would be welcome).
    >
    >
    >     Index: buckets/http2_frame_buckets.c
    >  ===================================================================
    >     --- buckets/http2_frame_buckets.c       (revision 1927021)
    >     +++ buckets/http2_frame_buckets.c       (working copy)
    >     @@ -630,12 +630,17 @@
    >                                           void *baton,
    >                                           apr_int32_t *stream_id),
    >                                       void *stream_id_baton,
    >     -                                apr_uint32_t max_payload_size,
    >     +                                apr_size_t max_payload_size,
    >  serf_bucket_alloc_t *alloc)
    >       {
    >           serf_http2_frame_context_t *ctx =
    serf_bucket_mem_alloc(alloc,
    >            sizeof(*ctx));
    >
    >     +    /* The upper limit for HTTP/2 MAX_FRAME_SIZE is 16 M1B.
    >     +https://www.rfc-editor.org/rfc/rfc9113.html#section-4.2 */
    >     +    if (max_payload_size > 0xFFFFFF)
    >     +        max_payload_size = 0xFFFFFF;
    >     +
    >
    >
    > +1 to moving.
    >
    >           ctx->alloc = alloc;
    >           ctx->stream = stream;
    >           ctx->chunk = serf_bucket_aggregate_create(alloc);
    >     @@ -643,9 +648,6 @@
    >           ctx->frametype = frame_type;
    >           ctx->flags = flags;
    >
    >     -    if (max_payload_size > 0xFFFFFF)
    >     -        max_payload_size = 0xFFFFFF;
    >     -
    >           if (!stream_id_alloc || (stream_id && *stream_id >= 0))
    >           {
    >             /* Avoid all alloc handling; we know the final id */
    >     @@ -980,4 +982,3 @@
    >           serf_http2_frame_get_remaining,
    >           serf_http2_frame_set_config
    >       };
    >     -
    >     Index: protocols/http2_buckets.h
    >  ===================================================================
    >     --- protocols/http2_buckets.h   (revision 1927021)
    >     +++ protocols/http2_buckets.h   (working copy)
    >     @@ -202,7 +202,7 @@
    >                                             void *baton,
    >                                             apr_int32_t *stream_id),
    >                                       void *stream_id_baton,
    >     -                                apr_uint32_t max_payload_size,
    >     +                                apr_size_t max_payload_size,
    >  serf_bucket_alloc_t *alloc);
    >
    >       /*
    ====================================================================
    */
    >
    >
    > Makes sense to me, but it would probably make sense to update the
    > other instances where we work on the size - as mentioned above the
    > serf_http2_protocol_t  is riddled with apr_uint32_t:s.

    Yes ... but we're either not compiling those bits, or they're
    converted
    _to_ apr_size_t. Which tends to be safe.

    It would be lovely to make the code consistent, but that's a *lot* of
    changes for dubious benefit (and somewhat less dubious additional
    source
    of bugs). I'm not in the mood to tackle that, especially given
    that I'm
    not the author of the code and would have to understand[1] every
    change
    before making it.

    -- Brane


    [1] Right now I'm looking at some similar warnings in the use of
    hpack_int() and, for now, I can't prove to myself that the
    conversions
    are safe -- so I'll leave the warnings in. I just have to find the
    right
    warning flags to make gcc yammer about them, too; seeing the warnings
    only on macOS isn't exactly useful, and while they're present on
    Windows, most of the MSVC warnings are red herrings and just
    obscure the
    real issues.


What I'm suggesting is to change the apr_uint32_t:s to apr_size_t. That would match the public API and eliminate conversions.

I'm probably not experienced enough in C to see if there are potential bugs hiding in such a change. (Of course, if there are places in the standard where the maximum value is limited to 2^32-1 we could potentially send too large values - but these should be taken care of anyway to avoid a rollover, much like the existing max_payload_size check above).

By "the standard", I expect you mean the HTTP(/2) RFCs? I haven't read them all, but surely there are some "SHOULD" and "MUST" kinds of limits. At least the MAX_FRAME_SIZE has both a lower and an upper bound defined, but I don't know whether the actual value is controlled by the server or somehow negotiated between server and client.

About all those apr_uint32_t's ... I'm not seeing any warnings from there. But, again, I haven't investigated.

-- Brane

Reply via email to