Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting pointer to unrelated type

I'm currently trying to get hang of C strict-aliasing rules and this code by my current understanding is violating them.

We have converted buffer pointer to struct setup pointer and by C standard it should lead to undefined behavior, right?

static inline void libusb_fill_control_transfer(
    struct libusb_transfer *transfer, libusb_device_handle *dev_handle,
    unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data,
    unsigned int timeout)
{
    struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *) buffer;
    transfer->dev_handle = dev_handle;
    transfer->endpoint = 0;
    transfer->type = LIBUSB_TRANSFER_TYPE_CONTROL;
    transfer->timeout = timeout;
    transfer->buffer = buffer;
    if (setup)
        transfer->length = (int) (LIBUSB_CONTROL_SETUP_SIZE
            + libusb_le16_to_cpu(setup->wLength));
    transfer->user_data = user_data;
    transfer->callback = callback;
}

Edit.

This is part of libusb project https://github.com/libusb/libusb/blob/7ffad5c137ed4c1d8a3ac485f35770fb979ca53a/libusb/libusb.h#L1578

Edit 2.

Adding libusb_control_setup struct definition

struct libusb_control_setup {
    /** Request type. Bits 0:4 determine recipient, see
     * \ref libusb_request_recipient. Bits 5:6 determine type, see
     * \ref libusb_request_type. Bit 7 determines data transfer direction, see
     * \ref libusb_endpoint_direction.
     */
    uint8_t  bmRequestType;

    /** Request. If the type bits of bmRequestType are equal to
     * \ref libusb_request_type::LIBUSB_REQUEST_TYPE_STANDARD
     * "LIBUSB_REQUEST_TYPE_STANDARD" then this field refers to
     * \ref libusb_standard_request. For other cases, use of this field is
     * application-specific. */
    uint8_t  bRequest;

    /** Value. Varies according to request */
    uint16_t wValue;

    /** Index. Varies according to request, typically used to pass an index
     * or offset */
    uint16_t wIndex;

    /** Number of bytes to transfer */
    uint16_t wLength;
};
like image 312
antun Avatar asked Sep 06 '25 03:09

antun


2 Answers

Assuming that the parameter buffer doesn't actually point to an object of type struct libusb_control_setup (probably an unsigned char array?), then yes this is a strict aliasing violation which is undefined behavior.

The rules governing aliasing are specified in section 6.5p7 of the C standard:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:88)

  • a type compatible with the effective type of the object,
  • a qualified version of a type compatible with the effective type of the object,
  • a type that is the signed or unsigned type corresponding to the effective type of the object,
  • a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
  • a character type.

88 ) The intent of this list is to specify those circumstances in which an object may or may not be aliased.

Note that this does not include treating a char array as if it were some other type, although the reverse is allowed.

The proper way to handle this is to create a local structure of the given type, then use memcpy to copy the bytes over.

struct libusb_control_setup setup;
memcpy(&setup, buffer, sizeof setup);
like image 89
dbush Avatar answered Sep 07 '25 20:09

dbush


As an addition to @dbush answer

Regarding the provided example, that's how I would have done it, but since this is code from respectable project which is obviously used in a lot of places I'm not sure what to think about it.

Pointer punning is used by many programmers, who think that is safe because it works on their computers. Many programmers also think that using memcpy will make their code less efficient and more memory greedy.

In most circumstances (when possible of course) the compiler will optimize out the memcpy call

example:

typedef struct
{
    int a;
    double b;
    int (*callback)(int);
}mt;

int foo(char *ptr, int par)
{
    mt m;

    memcpy(&m, ptr, sizeof(m));

    printf("%f\n", m.b);
    m.callback(par);
    return m.a;
}

The x86 compiler produces code :

.LC0:
        .string "%f\n"
foo:
        push    rbp
        mov     ebp, esi
        sub     rsp, 32
        movdqu  xmm1, XMMWORD PTR [rdi]
        mov     rax, QWORD PTR [rdi+16]
        mov     edi, OFFSET FLAT:.LC0
        movaps  XMMWORD PTR [rsp], xmm1
        movsd   xmm0, QWORD PTR [rsp+8]
        mov     QWORD PTR [rsp+16], rax
        mov     eax, 1
        call    printf
        mov     edi, ebp
        call    [QWORD PTR [rsp+16]]
        mov     eax, DWORD PTR [rsp]
        add     rsp, 32
        pop     rbp
        ret

But ARM Cortex M0 will call the memcpy as an unaligned version of the pointer will cause hardware exception.

.LC0:
        .ascii  "%f\012\000"
foo:
        push    {r4, lr}
        movs    r4, r1
        sub     sp, sp, #32
        movs    r1, r0
        movs    r2, #24
        add     r0, sp, #8
        bl      memcpy
        ldr     r2, [sp, #16]
        ldr     r3, [sp, #20]
        ldr     r0, .L3
        str     r2, [sp]
        str     r3, [sp, #4]
        bl      printf
        movs    r0, r4
        ldr     r3, [sp, #24]
        blx     r3
        ldr     r0, [sp, #8]
        add     sp, sp, #32
        pop     {r4, pc}
.L3:
        .word   .LC0
like image 43
0___________ Avatar answered Sep 07 '25 21:09

0___________