在Unix上启用优化时,strcpy()/ strncpy()在具有额外空间的结构成员上崩溃? [英] strcpy()/strncpy() crashes on structure member with extra space when optimization is turned on on Unix?

查看:151
本文介绍了在Unix上启用优化时,strcpy()/ strncpy()在具有额外空间的结构成员上崩溃?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在编写项目时,我遇到了一个奇怪的问题。


这是我设法编写的用于重新创建问题的最少代码。我故意将实际的字符串存储在其他地方,并分配了足够的空间。

  // #include< stdio.h> 
#include< stdlib.h>
#include< string.h>
#include< stdint.h>
#include< stddef.h> //对于offsetof()

typedef struct _pack {
//`c`的类型并不重要,只要它在结构内部即可。
int64_t c;
}包;

int main(){
pack * p;
char str [9] = aaaaaaaa; //输入
size_t len = offsetof(pack,c)+(strlen(str)+1);
p = malloc(len);
//版本1:崩溃
strcpy((char *)&(p-> c),str);
//版本2:崩溃
strncpy((char *)&(p-> c),str,strlen(str)+1);
//版本3:有效!
memcpy((char *)&(p-> c),str,strlen(str)+1);
// puts((char *)&(p-> c));
free(p);
返回0;
}

上面的代码使我感到困惑:



  • 使用 gcc / clang -O0 strcpy() memcpy ()在Linux / WSL上工作,下面的 puts()给出了我输入的内容。

  • 在OSX上使用 clang -O0 ,代码会因 strcpy()而崩溃。

  • 在Ubuntu /上使用 gcc / clang -O2 -O3 Fedora / WSL ,代码崩溃 (!!) strcpy(),而 memcpy()效果很好。

  • 在Windows上使用 gcc.exe ,代码无论优化级别如何,效果都很好。


我还发现了代码的其他一些特征:



  • (看起来)重现崩溃的最小输入为9个字节(包括零终止符),或 1 + sizeof(p-> ; c)。使用该长度(或更长)可以保证发生崩溃(亲爱的我...)。



  • 即使我分配了额外的空间(最多1MB)放在 malloc()中,没有帮助。上面的行为完全没有改变。



  • strncpy()的行为完全相同



  • 指针似乎无关紧要。如果结构成员 char * c 更改为 long long c (或 int64_t ),其行为保持不变。 (更新:已更改)。



  • 崩溃消息看起来不正常。





我尝试了所有这些编译器,但它们没有什么区别:



  • GCC 5.4.0(Ubuntu / Fedora / OS X / WSL,全部是64位)

  • GCC 6.3.0(仅适用于Ubuntu)

  • GCC 7.2.0(Android,norepro ???)(这是来自




    PS最初,我想编写一个结构,其最后一项是指向动态分配空间(用于字符串)的指针。将结构写入文件时,无法编写指针。我必须写出实际的字符串。所以我想出了一个解决方案:强制将字符串存储在指针位置。


    也请不要抱怨 gets()。我不在项目中使用它,而是仅上面的示例代码。

    解决方案

    我复制了此问题在我的Ubuntu 16.10上,我发现了一些有趣的东西。



    当用 gcc -O3 -o ./test ./test.c,如果输入超过8个字节,程序将崩溃。



    经过一些反转后,我发现GCC替换了 strcpy memcpy_chk ,请参见此。

      //从IDA $ b $反编译b int __cdecl main(int argc,const char ** argv,const char ** envp)
    {
    int * v3; // rbx
    int v4; // edx
    unsigned int v5; // eax
    签署了__int64 v6; // rbx
    char * v7; // rax
    void * v8; // r12
    const char * v9; // rax
    __int64 _0; // [rsp + 0h] [rbp + 0h]
    unsigned __int64 vars408; // [rsp + 408h] [rbp + 408h]

    vars408 = __readfsqword(0x28u);
    v3 =(int *)& _0;
    gets(& _0,argv,envp);
    do
    {
    v4 = * v3;
    ++ v3;
    v5 =〜v4& (v4-16843009)& 0x80808080;
    }
    而(!v5);
    if(!((unsigned __int16)〜(_WORD)v4&(unsigned __int16)(v4-257)& 0x8080))
    v5> == 16;
    if(!((unsigned __int16)〜(_WORD)v4&(unsigned __int16)(v4-257)& 0x8080))
    v3 =(int *)((char *)v3 + 2);
    v6 =(char *)v3-__CFADD __((_ BYTE)v5,(_BYTE)v5)-3-(char *)& _0; // strlen
    v7 =(char *)malloc(v6 + 9);
    v8 = v7;
    v9 =(const char *)_ memcpy_chk(v7 + 8,& _0,v6 + 1,8LL); // Forth参数为8!
    puts(v9);
    free(v8);
    返回0;
    }

    您的结构包使GCC相信元素 c 恰好是8个字节。



    如果复制长度为 memcpy_chk 将会失败。比第四个参数大!



    所以有两种解决方案:




    • 修改结构


    • 使用编译选项 -D_FORTIFY_SOURCE = 0 (例如 gcc test.c -O3 -D_FORTIFY_SOURCE = 0 -o ./test )关闭强化功能。



      警告:这将完全禁用整个程序中的缓冲区溢出检查!



    When writing a project, I ran into a strange issue.

    This is the minimal code I managed to write to recreate the issue. I am intentionally storing an actual string in the place of something else, with enough space allocated.

    // #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdint.h>
    #include <stddef.h> // For offsetof()
    
    typedef struct _pack{
        // The type of `c` doesn't matter as long as it's inside of a struct.
        int64_t c;
    } pack;
    
    int main(){
        pack *p;
        char str[9] = "aaaaaaaa"; // Input
        size_t len = offsetof(pack, c) + (strlen(str) + 1);
        p = malloc(len);
        // Version 1: crash
            strcpy((char*)&(p->c), str);
        // Version 2: crash
            strncpy((char*)&(p->c), str, strlen(str)+1);
        // Version 3: works!
            memcpy((char*)&(p->c), str, strlen(str)+1);
        // puts((char*)&(p->c));
        free(p);
      return 0;
    }
    

    The above code is confusing me:

    • With gcc/clang -O0, both strcpy() and memcpy() works on Linux/WSL, and the puts() below gives whatever I entered.
    • With clang -O0 on OSX, the code crashes with strcpy().
    • With gcc/clang -O2 or -O3 on Ubuntu/Fedora/WSL, the code crashes (!!) at strcpy(), while memcpy() works well.
    • With gcc.exe on Windows, the code works well whatever the optimization level is.

    Also I found some other traits of the code:

    • (It looks like) the minimum input to reproduce the crash is 9 bytes (including zero terminator), or 1+sizeof(p->c). With that length (or longer) a crash is guaranteed (Dear me ...).

    • Even if I allocate extra space (up to 1MB) in malloc(), it doesn't help. The above behaviors don't change at all.

    • strncpy() behaves exactly the same, even with the correct length supplied to its 3rd argument.

    • The pointer does not seem to matter. If structure member char *c is changed into long long c (or int64_t), the behavior remains the same. (Update: changed already).

    • The crash message doesn't look regular. A lot of extra info is given along.

    I tried all these compilers and they made no difference:

    • GCC 5.4.0 (Ubuntu/Fedora/OS X/WSL, all are 64-bit)
    • GCC 6.3.0 (Ubuntu only)
    • GCC 7.2.0 (Android, norepro???) (This is the GCC from C4droid)
    • Clang 5.0.0 (Ubuntu/OS X)
    • MinGW GCC 6.3.0 (Windows 7/10, both x64)

    Additionally, this custom string copy function, which looks exactly like the standard one, works well with any compiler configuration mentioned above:

    char* my_strcpy(char *d, const char* s){
        char *r = d;
        while (*s){
            *(d++) = *(s++);
        }
        *d = '\0';
        return r;
    }
    

    Questions:

    • Why does strcpy() fail? How can it?
    • Why does it fail only if optimization is on?
    • Why doesn't memcpy() fail regardless of -O level??

    *If you want to discuss about struct member access violation, pleast head over here.


    Part of objdump -d's output of a crashing executable (on WSL):


    P.S. Initially I want to write a structure, the last item of which is a pointer to a dynamically allocated space (for a string). When I write the struct to file, I can't write the pointer. I must write the actual string. So I came up with this solution: force store a string in the place of a pointer.

    Also please don't complain about gets(). I don't use it in my project, but the example code above only.

    解决方案

    I reproduced this issue on my Ubuntu 16.10 and I found something interesting.

    When compiled with gcc -O3 -o ./test ./test.c, the program will crash if the input is longer than 8 bytes.

    After some reversing I found that GCC replaced strcpy with memcpy_chk, see this.

    // decompile from IDA
    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      int *v3; // rbx
      int v4; // edx
      unsigned int v5; // eax
      signed __int64 v6; // rbx
      char *v7; // rax
      void *v8; // r12
      const char *v9; // rax
      __int64 _0; // [rsp+0h] [rbp+0h]
      unsigned __int64 vars408; // [rsp+408h] [rbp+408h]
    
      vars408 = __readfsqword(0x28u);
      v3 = (int *)&_0;
      gets(&_0, argv, envp);
      do
      {
        v4 = *v3;
        ++v3;
        v5 = ~v4 & (v4 - 16843009) & 0x80808080;
      }
      while ( !v5 );
      if ( !((unsigned __int16)~(_WORD)v4 & (unsigned __int16)(v4 - 257) & 0x8080) )
        v5 >>= 16;
      if ( !((unsigned __int16)~(_WORD)v4 & (unsigned __int16)(v4 - 257) & 0x8080) )
        v3 = (int *)((char *)v3 + 2);
      v6 = (char *)v3 - __CFADD__((_BYTE)v5, (_BYTE)v5) - 3 - (char *)&_0; // strlen
      v7 = (char *)malloc(v6 + 9);
      v8 = v7;
      v9 = (const char *)_memcpy_chk(v7 + 8, &_0, v6 + 1, 8LL); // Forth argument is 8!!
      puts(v9);
      free(v8);
      return 0;
    }
    

    Your struct pack makes GCC believe that the element c is exactly 8 bytes long.

    And memcpy_chk will fail if the copying length is larger than the forth argument!

    So there are 2 solutions:

    • Modify your structure

    • Using compile options -D_FORTIFY_SOURCE=0(likes gcc test.c -O3 -D_FORTIFY_SOURCE=0 -o ./test) to turn off fortify functions.

      Caution: This will fully disable buffer overflow checking in the whole program!!

    这篇关于在Unix上启用优化时,strcpy()/ strncpy()在具有额外空间的结构成员上崩溃?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆