This is the mail archive of the glibc-bugs@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[Bug libc/23682] New: Inline assembly bugs wrt to arrays being passed through register constraints in io.h


https://sourceware.org/bugzilla/show_bug.cgi?id=23682

            Bug ID: 23682
           Summary: Inline assembly bugs wrt to arrays being passed
                    through register constraints in io.h
           Product: glibc
           Version: unspecified
            Status: UNCONFIRMED
          Severity: normal
          Priority: P2
         Component: libc
          Assignee: unassigned at sourceware dot org
          Reporter: mpetch@capp-sysware.com
                CC: drepper.fsp at gmail dot com
  Target Milestone: ---

Created attachment 11259
  --> https://sourceware.org/bugzilla/attachment.cgi?id=11259&action=edit
Revised io.h

This is a subtle big that could cause grief for those people doing direct
hardware access with <sys/io.h> on the x86/x86-64 Linux targets. This bug may
exist in other inline assembly elsewhere and an audit would probably not be a
bad idea. At the moment the bug can manifest itself in the functions insb,
insw, insl, outsb, outsw, outsl all of which pass a void *_addr to inline
assembly with a "=D", "D", "=S", "S" constraint.

This bug wasn't found because of an existing project failing. Recently someone
suggested on Stackoverflow using <sys/io.h> from GLIBC for port IO. I happened
to audit the code quickly and noticed the bug. I had seen this problem manifest
in other ways and just happen to know a way to reproduce the undesirable effect
in a minimal example.

The bug itself comes about because passing an address to a given object or
array as an input/output/both constraint VIA a register doesn't actually
guarantee that what the pointer points at has been realized into memory before
the assembly template is emitted. The same is true when an extended inline
assembly template writes to memory pointed at by a register constraint. This is
discussed in the GCC documentation @
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html in the section 6.45.2.6
Clobbers and Scratch Registers.  

The compiler may assume with optimizations on that memory wasn't actually
modified and make code generation assumptions around that. One can observe the
problematic behaviour with this code:

    #include <sys/io.h>

    int main()
    {
        char data[]="Hello There How are you?";
    /*  ioperm(0x80, 1, 1); */
        outsb (0x80, data, sizeof(data));
        insb (0x80, data, sizeof(data));
        return data[0];
    }

One would expect that the code generated would place a copy of the string on
the stack call outsb to send it out port 0x80, fill that same array with bytes
read from port 0x80 and then return the first byte of data returns by insb.
That's not what happens with optimizations on. GCC Code generated with a 32-bit
target (same thing applies to 64-bit) will look something like:

    main:
        push    edi
        push    esi
        mov     eax, 25
        mov     edx, 128
        mov     ecx, eax
        sub     esp, 32
        lea     esi, [esp+7]
        cld
        rep     outsb
        lea     edi, [esp+7]
        mov     ecx, eax
        cld
        rep     insb
        add     esp, 32
        mov     eax, 72
        pop     esi
        pop     edi
        ret

It should be observed that in this code GCC never emitted the actual string
into the buffer but it did allocate space for it. After reading in bytes from
port 0x80 is moved the value 72 (ASCII 'H') into EAX as a return value. The
compiler assumed the input buffer `data` wasn't modified so assumed that 'H'
was still the first item in the character array. 

The GCC documentation suggests a couple of ways around this. On all of the
functions with an __addr parameter that is being passed into inline assembly
use "memory" in the clobber to ensure data is written to memory before the
inline assembly is emitted and read it back out of memory if need be.

See the attached io.h for a version with the memory clobbers. The generated
code would look something like this with a correct io.h:

    main:
        push    edi
        push    esi
        mov     eax, 25
        mov     edx, 128
        mov     ecx, eax
        sub     esp, 32
        mov     DWORD PTR [esp+7], 1819043144
        mov     DWORD PTR [esp+11], 1750343791
        lea     esi, [esp+7]
        mov     DWORD PTR [esp+15], 543519333
        mov     DWORD PTR [esp+19], 544698184
        mov     DWORD PTR [esp+23], 543519329
        mov     DWORD PTR [esp+27], 1064660857
        mov     BYTE PTR [esp+31], 0
        cld
        rep     outsb
        lea     edi, [esp+7]
        mov     ecx, eax
        cld
        rep     insb
        movsx   eax, BYTE PTR [esp+7]
        add     esp, 32
        pop     esi
        pop     edi
        ret

-- 
You are receiving this mail because:
You are on the CC list for the bug.

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]