GreatSQL社区

搜索

DB架构师:曾凡坤

魔改PG客户端遭遇DOUBLE FREE

DB架构师:曾凡坤 已有 54 次阅读2025-2-15 17:30 |个人分类:SharkDB|系统分类:其他| 测试

最新魔改PGSQL--- 鲨鱼DB的客户端程序,为其增加SHOW 命令

在主程序MAINLOOP.C里面有个CHAR* LINE 字符串指针,该指针读取标准输出里面获得用户输入的命令


   /* Now we can fetch a line */  
    line = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);  
    line = process_show(line); //魔改处
      第2行把字符指针传入处理函数,进行替换,也可以把SQL语句用简单的SHOW COMMAND VALUE格式 简单格式替代
      
    if (strlen(pValues) > 1) // 命令后面没有其它字符   
     {     
     int NewLine_size = 90 + value_size;     
     char *NewLine = (char *)malloc(NewLine_size * sizeof(char));     
     memset(NewLine, '\0', NewLine_size);    
      strcpy(NewLine, "SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%");     
      strcat(NewLine, pValues);   
      strcat(NewLine, "%';");     
      free(pValues);     pValues = NULL;    
      free(pShow);     pShow = NULL;    
       return NewLine;   
        }
     这段代码重点是 NEWLINE动态分配,并返回给外面函数. 字C符指针接收!
     原本是这样改的,起先简单的MALLOC分配不多的时候,能正常执行下去不报错.如果是这样长SQL语句替换的话,
     psql_scan_setup和psql_scan两个函数里面的内存分配和重新分配就报错.
     
/*   * Parse line, looking for command separators.   */ 
 psql_scan_setup(scan_state, line, strlen(line), pset.encoding, standard_strings() ); 
  success = true;  line_saved_in_history = false;
  while (success || !die_on_error) 
   {   PsqlScanResult scan_result;  
    promptStatus_t prompt_tmp = prompt_status;  
     size_t  pos_in_query;  
      char    *tmp_line;   
      pos_in_query = query_buf->len;  
       scan_result = psql_scan(scan_state, query_buf, &prompt_tmp);  
        prompt_status = prompt_tmp;
        }
 报错是scan_state里面的某个成员的地址会跟LINE新地址发生了冲突.
 意思是成员的地址覆盖LINE的新地址. 折腾一休也没有找到为什么会覆盖.
 所以只好修改外面调用函数,先自定义个新字符指针来接受用户输入的命令
 然后传给处理函数,处理函数照旧把新内存地址给LINE. 然后FREE临时字符指针内存.这样上面两个函数分配和重新分配内存就不报错了
 

   /* Now we can fetch a line */   
   char *pTempLine = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);   
   line = process_show(pTempLine);  
    free(pTempLine);  
     pTempLine = NULL; 
    结果如下面能执行SQL  不过还是报错
    
SELECT n.nspname AS " Schema ",c.relname AS " Table ",t.tgname AS " Trigger Name ",pg_catalog.pg_get_triggerdef(t.oid) AS " Trigger Definition " 
FROM  pg_catalog.pg_trigger t  JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid
 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace  
 WHERE NOT t.tgisinternal AND n.nspname <> 'pg_catalog'   
 AND n.nspname <> 'information_schema' 
  ORDER BY  n.nspname, c.relname, t.tgname; 
  psql_scan_finish(scan_state); 
   if(line != NULL)  
   {     
   printf("%s\n",line);   
   free(line);  
   } 
   居然释放前地址没有变化,还能打印出内容,就是释放就报错.这就令人痛苦.
   错误和理论矛盾啊
   AI 这样说
   
常见的导致“double free or corruption”错误的原因:双重释放:你可能在某个地方多次调用了 free(line),
即两次或更多次释放了同一块内存。这会导致内存管理器试图释放已经释放的内存,从而触发错误。内存越界:如果你通过某种方式修改了指向已释放内存的指针,可能会导致重复释放,或者出现内存损坏的问题。悬挂指针:如果在释放内存后,你没有将指针设为 NULL,在之后的代码中使用该指针会导致不确定的行为(例如,再次尝试释放同一块内存)。推荐在 free() 之后将指针置为 NULL,以防止悬挂指针问题。
好像没有一条符合预期
<strong>使用内存检测工具 valgrind
</strong>
[shark@sharkdb=>sharkdb17]$valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --log-file=Valgrind.log pgsql -d testdbpgsql (17.1)Type "help" for help.SHARKSQL> show trigs+----------+---------+----------------+----------------------+|  Schema  |  Table  |  Trigger Name  |  Trigger Definition  |+----------+---------+----------------+----------------------++----------+---------+----------------+----------------------+(0 rows)Time: 138.350 msSHARKSQL> quit

日志非常多 PG17.1咋这样呢! 我们找到 free()相关的日志

==65525== Invalid free() / delete / delete[] / realloc()==65525==    at 0x4C2E338: free (vg_replace_malloc.c:989)==65525==    by 0x424853: MainLoop (mainloop.c:343)==65525==    by 0x42EEB5: main (startup.c:462)==65525==  Address 0x7f7e770 is 0 bytes inside a block of size 5 free'd==65525==    at 0x4C2E338: free (vg_replace_malloc.c:989)==65525==    by 0x424472: MainLoop (mainloop.c:173)==65525==    by 0x42EEB5: main (startup.c:462)==65525==  Block was alloc'd at==65525==    at 0x4C2B37A: malloc (vg_replace_malloc.c:446)==65525==    by 0x50CCB28: xmalloc (in /usr/lib64/libreadline.so.6.2)==65525==    by 0x50B32FC: readline_internal_teardown (in /usr/lib64/libreadline.so.6.2)==65525==    by 0x50B40E1: readline (in /usr/lib64/libreadline.so.6.2)==65525==    by 0x423194: gets_interactive (input.c:91)==65525==    by 0x424452: MainLoop (mainloop.c:170)==65525==    by 0x42EEB5: main (startup.c:462)

MAINLOOP 343行和173.从上往下看INVALID FREE()343行FREE和下面的Address 0x7f7e770 173行的FREE冲突了,

分配内存是MALLOC 170行的函数,以及函数里的调用INPUT.C:91行. 其实跟MYSQL死锁差不多,只是简单些.


#ifndef WIN32     
 puts(_("Use control-D to quit."));
 #else      puts(_("Use control-C to quit."));
#endif 
   }  
  else    {     /* exit app */     free(line);     fflush(stdout);     successResult = EXIT_SUCCESS;     break;    }   


这是退出时候FREE的,而原始分配内存代码,目前改成主函数释放掉,下面是173行释放

   /* Now we can fetch a line */ 
     char *pTempLine = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);  
      //line = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);  
       line = process_show(pTempLine);   
       free(pTempLine);     
        pTempLine = NULL;
        }   


用临时字符指针获得gets_interactive函数返回的内存.传给处理函数后就释放掉.而line是处理函数返回的内存.下面是585行的,使用VSCODE调试就这里报错


  psql_scan_finish(scan_state); 
   if(line != NULL)  {    // printf("%s\n",line);   free(line);   line=NULL;  } 

其它的FREE(LINE)有6处,且这6处原本是PG17自带的,并非魔改的.因此这6处位置都是正常的.

这就让人奇怪,难道某个地方记录了第一次分配内存的地址.释放的时候就释放第一次分配的内存地址? 第二次分配的内存地址不释放? 通俗的说我给渣男他介绍了两个女朋友. 第一个女朋友姓张,第二个女朋友姓熊.我跟渣男说 "你还是分手吧!" 结果渣男跑去见了第一个女朋友张仙女,跟她说"我们还是分手吧!" 应该跑去见第2个女朋友熊小姐说分手.

PG原本没有错,假如我不加如PROCESS_SHOW函数.而且PROCESS_SHOW函数其它SHOW命令,尤其那种比较短的,就不会报错.


  if (strcmp(arry_command, "databases") == 0) // 如果命令是databases 
   {  
    if (command_size == LineLen) // 命令后面没有其它字符
       {   
        char *NewLine = (char *)malloc(6);    
       memset(NewLine, '\0', 6);  
         strcpy(NewLine, "\\list");   
          free(pShow);    
          pShow = NULL;   
           return NewLine;  
            } 
             }
  像这样把原本的LINE内存不管,重新分配6个字节内存返回给LINE. 后续就不会出现DOUBLE FREE.  如果分配字节太大了的话,会被后面的内存分配所覆盖.
现在使用长的SQL要分配420个字节,就报错重复释放内存的怪兽!


恢复原样的话
      line = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);      line = process_show(line);
      就报此错误

corrupted size vs. prev_size: 0x00000000011dc150
AI回答:
是 C 语言程序在使用动态内存分配时,由于内存管理出现错误而引发的错误消息,通常与内存泄漏、越界访问或者双重释放等问题有关。它表明程序在释放内存时,检测到内存管理系统中的元数据(如内存块大小)被篡改或损坏。错误原因在 C 语言中,动态内存的分配是由 malloc、calloc、realloc 等函数管理的,内存块通常会存储一些元数据,包括内存块的大小信息。当你释放内存时,内存管理系统会检查这些元数据来确保内存的正确释放。如果内存块的大小信息在某种操作中被意外篡改,可能会导致这种错误。corrupted size vs. prev_size 错误通常是以下几种情况造成的:越界写入(Buffer Overflow):当你在一个动态分配的内存块中写入超出分配范围的内容时,可能会修改内存块前后的元数据,导致内存管理系统无法正确识别块的大小。双重释放(Double Free):如果你尝试释放一个已经释放过的内存块,或者内存块的释放顺序不正确,内存管理系统可能会发现内存块的元数据不一致,从而触发这个错误。未初始化或已销毁的指针:如果你使用一个未初始化或已经销毁的指针来访问内存,可能会导致内存管理系统检测到内存结构损坏。内存分配错误:内存分配时发生了某些不可预见的错误,导致内存块的元数据被损坏,触发检测机制。也就是说我窜改了LINE 字符指针的元数据, 这就有意思了!
</code></pre><pre style="text-align: left;"><code>
写个简单的C语言验证下
#include
 #include 
 #include
  char * process(char * pline)
  {
   char* new_line=malloc(100);
    strcpy(new_line, "SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%"); 
    printf("new_line size:%d\n",strlen(new_line));
     return new_line;
     }
     int main()
     {
     char *line;line= malloc(8);
     line=process(line);
     if(line !=NULL)
     {
      printf("%s\n",line);
       free(line)     ;
       }
       }
居然没有报错了
[shark@sharkdb=>sharkdb17]$./main.exenew_line size:84SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%
</code></pre><pre style="text-align: left;"><code>
这样子也不报错:
#include 
#include 
#include 
char * process(char * pline)
{ 
char* new_line=malloc(100);
 memset(new_line,'\0',100);
 strcpy(new_line, "SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%");
  printf("new_line size:%d\n",strlen(new_line)); return new_line;
  }
  int main()
  {
  char *line;line= malloc(8);
  memset(line,'\0',8);
  strcpy(line,"I LOVE DB");
  printf("old_line:%s\n",line);
  line=process(line);
  if(line !=NULL)
  {
   printf("%s\n",line);
    free(line);
  }
  }
</code></pre><pre style="text-align: left;"><code>
  • [shark@sharkdb=>sharkdb17]$./main.exeold_line:I LOVE DBnew_line size:84SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%
    </code></pre><pre style="text-align: left;"><code>模拟DOUBLE FREE场景
    
    #include #include #include
     char * process(char * pline)
     { char* new_line=malloc(100);
      memset(new_line,'\0',100); 
      strcpy(new_line, "SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%");
       printf("new_line size:%d\n",strlen(new_line));
        return new_line;}
        
        int main()
        {char *line;char *tline;
        tline= malloc(8);
        memset(tline,'\0',8);
        strcpy(tline,"I LOVE DB");
        printf("old_line:%s\n",tline);
        line=process(tline);free(tline);
        tline=NULL;
        if(line !=NULL)
        { printf("%s\n",line); free(line);})
    也不报错!
    
    [shark@sharkdb=>sharkdb17]$./main.exeold_line:I LOVE DBnew_line size:84SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%
    自然是存在内存泄漏的.


    valgrind --tool=memcheck --leak-check=yes --show-reachable=yes ./main.exe
    直接打印到屏幕上,先看最后总结部分


    old_line:I LOVE DBnew_line size:84SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%==71981== ==71981== HEAP SUMMARY:==71981==     in use at exit: 0 bytes in 0 blocks==71981==   total heap usage: 2 allocs, 2 frees, 108 bytes allocated==71981== ==71981== All heap blocks were freed -- no leaks are possible==71981== ==71981== For lists of detected and suppressed errors, rerun with: -s==71981== ERROR SUMMARY: 6 errors from 4 contexts (suppressed: 0 from 0)
    堆分配两次,释放了两次 合计分配了108个字节,内存分配符合程序预期.和逻辑
    </code></pre><pre style="text-align: left;"><code>第1个错,无效写入2个字节,分别在19行和21行
    
    ==71981== Memcheck, a memory error detector==71981== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.==71981== Using Valgrind-3.24.0 and LibVEX; rerun with -h for copyright info==71981== Command: ./main.exe==71981== ==71981== Invalid write of size 2==71981==    at 0x4007C1: main (main.c:21)==71981==  Address 0x520d048 is 0 bytes after a block of size 8 alloc'd==71981==    at 0x4C2B37A: malloc (vg_replace_malloc.c:446)==71981==    by 0x400795: main (main.c:19)
    分配8个字节写入9个字母,外加\0,
    
      19 tline= malloc(8);
    21 strcpy(tline,"I LOVE DB");
    </code></pre><pre style="text-align: left;"><code>第2个错误 无效读入1个字节
    
      ==71981== Invalid read of size 1
    ==71981==    at 0x4E8C079: vfprintf (in /usr/lib64/libc-2.17.so)==71981==    by 0x4E924E8: printf (in /usr/lib64/libc-2.17.so)==71981==    by 0x4007DC: main (main.c:22)==71981==  Address 0x520d048 is 0 bytes after a block of size 8 alloc'd==71981==    at 0x4C2B37A: malloc (vg_replace_malloc.c:446)==71981==    by 0x400795: main (main.c:19)
    </code></pre><ul><li></li></ul><pre><code >22 printf("old_line:%s\n",tline);
    </code></pre><pre style="text-align: left;"><code>运行正常 9个全打印出来了
    
    [shark@sharkdb=>sharkdb17]$./main.exeold_line:I LOVE DBnew_line size:84SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%
    后面两个错误跟第2个是一样的,只是调用了其它系统函数,并且有些系统函数地址不一样
    
    ==71981== Invalid read of size 1==71981==    at 0x4EB9ADD: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib64/libc-2.17.so)==71981==    by 0x4E8C032: vfprintf (in /usr/lib64/libc-2.17.so)==71981==    by 0x4E924E8: printf (in /usr/lib64/libc-2.17.so)==71981==    by 0x4007DC: main (main.c:22)==71981==  Address 0x520d048 is 0 bytes after a block of size 8 alloc'd==71981==    at 0x4C2B37A: malloc (vg_replace_malloc.c:446)==71981==    by 0x400795: main (main.c:19)==71981== ==71981== Invalid read of size 1==71981==    at 0x4C38E36: __GI_mempcpy (vg_replace_strmem.c:1701)==71981==    by 0x4EB9A04: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib64/libc-2.17.so)==71981==    by 0x4E8C032: vfprintf (in /usr/lib64/libc-2.17.so)==71981==    by 0x4E924E8: printf (in /usr/lib64/libc-2.17.so)==71981==    by 0x4007DC: main (main.c:22)==71981==  Address 0x520d048 is 0 bytes after a block of size 8 alloc'd==71981==    at 0x4C2B37A: malloc (vg_replace_malloc.c:446)==71981==    by 0x400795: main (main.c:19)
    最后改成10个字节,就没有这些错误了.
    
    19 tline= malloc(10);
    
    
    此时此刻,不是标准C11的错! 也不是我的错,第一次理论与实践发生了冲突,通常来讲是理论有问题,这看来是实践有问题,实践也是PGSQL源码编写有问题. 这个锅应该PG17.1背
    
    
    
    这事百思不得其解,春节假期过后,思来思去,唯有可能是PG客户端有一套内存回收机制,也就是自己整了一套,
    避免NEW个指针的内存泄漏的表.该表登记了内存分配指针指向内存的地址和地址长度.
    下个内存分配从前面已分配的地址尾巴开始.类似个链表.
    比如这样子 A ->B->C  要是在A和B之间,插入个E 必然是遭遇到我碰到的问题.就是用标准C内存分配函数,
    在程序运行过程中,这个顺序,在A之后分配内存给E,后面使用原有的代码再分配内存给B,那么B会在E的内存空间里分配,导致分配报错!
    PG本身内存分配行数有个定长分配大小,所以只要字数不超过定长,自然就不会出现任何内存异常.
    这样就完美解释了遇到的现象了,虽然没有找出证据出来,主要是其中两个C文件代码是由PERL临时生成的,
    编译时候项目文件没有把它俩加入管控中,就是没有夹带调试信息.
    
    解决方案一
    就是利用PG套壳内存分配函数去给E分配内存,也就是把E登记在链表里.
    方案二
    就是在魔改函数里完成 匹配,替换,发送,回收数据,展现数据,循环等待.
    这个工作量有点太,不太适合我这样忙里偷闲,国产数据库散户.
    



    评论 (0 个评论)

    facelist

    您需要登录后才可以评论 登录 | 立即注册

    合作电话:010-64087828

    社区邮箱:greatsql@greatdb.com

    社区公众号
    社区小助手
    QQ群
    GMT+8, 2025-2-21 23:56 , Processed in 0.016154 second(s), 10 queries , Redis On.
    返回顶部