深入理解系统调用

作业要求:

  • 找一个系统调用,系统调用号为学号最后2位相同的系统调用
  • 通过汇编指令触发该系统调用
  • 通过gdb跟踪该系统调用的内核处理过程
  • 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化

一、学号对应系统调用

查找系统调用表,发现70 对应系统调用,为setreuid

 setruid作用于ruid 与euid,与其作用相近的系统调用还有setuid 和 seteuid。分别解释其作用:

int setuid(uid_t uid)

1)       若进程具有超级用户权限,则setuid将实际用户ID、有效用户ID及保存的设置用户ID设置为uid

2)       若进程没有超级用户权限,但是uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户ID设置为uid

int seteuid(uid_t uid)

1)       若进程具有超级用户权限,则setuid只将有效用户ID设置为uid

2)       若进程没有超级用户权限,则setuid只将有效用户ID设置为uid, 但是uid必须等于实际用户ID或保存的设置用户ID,

int setreuid(uid_t ruid, uid_t euid)

1)       针对设置用户ID位的程序: 交换有效用户ID和保存的设置用户ID

2)       针对没有设置用户ID位的程序: 交换有效用户ID和实际用户ID

更直观地,用图来表示三者的关系。

setreuid(0,0)系统调用主要用来交换Linux进程的实际用户ID和有效用户ID。每个Linux进程都有有两个相关的用户ID:实际用户ID(即ruid)和有效用户ID(即euid),其中ruid表示了该进程由谁运行,即当前系统环境用户是谁,主要回答who am I?的问题;而euid则用来规范进程的实际权限控制。比如passwd文件存放了用户名和密码,当一个普通用户运行passwd时,其ruid是自己,而euid则临时变为了文件的所有者root。这主要是设置SUID来实现的,而setreuid的作用在于交换ruid和euid;

2.编写以下两个文件setreuid.c和setreuidAsm.c,比较系统API和汇编调执行结果

/*setreuid.c*/

#include<unistd.h>
#include<stdio.h>

int main(void){
    int i = 0, j=0,k = 0,m=0;
    i = geteuid();
    j=getuid();
    printf("curent euid is:%d,curent uid is:%d
", i,j);

    setreuid(j,i);
    k = geteuid();
    m=getuid();
    printf("after change euid:%d,after change uid:%d
", k,m);

    return 0;
}
/* setreuidAsm.c */

#include<unistd.h>
#include<unistd.h>
#include<stdio.h>

int main(void){
    int i = 0, j = 0, k = 0,m=0,f=0;
    asm volatile(
        "mov $0,%%ebx
	"
        "mov $0x18,%%eax
	"  /* 24号系统调用getuid */
        "int $0x80
	"
        "mov %%eax,%0
	"
        :"=m"(i)
    );
    asm volatile(
        "mov $0,%%ebx
	"
        "mov $0x6B,%%eax
	"  /* 107号系统调用geteuid */
        "int $0x80
	"
        "mov %%eax,%0
	"
        :"=m"(j)
    );
    printf("current ruid is:%d,current euid is :%d
", i,j);

    asm volatile(
        "mov $0,%%ebx
	"
     "mov $1,%%ecx
	"
        "mov $0x46,%%eax
	"  /* setreuid */
        "int $0x80
	"
        "mov %%eax,%4
	"
        :"=m"(f)
    );

    asm volatile(
        "mov $2,%%ebx
	"
        "mov $0x18,%%eax
	"  /* 24号系统调用getuid */
        "int $0x80
	"
        "mov %%eax,%2
	"
        :"=m"(k)
    );
    asm volatile(
        "mov $3,%%ebx
	"
        "mov $0x6B,%%eax
	"  /* 107号系统调用geteuid */
        "int $0x80
	"
        "mov %%eax,%3
	"
        :"=m"(m)
    );

    printf("aftr change uid is:%d,after change euid is :%d
",k,m);
    return 0;
}

编译这块就是查到对应的系统调用号装入eax里然后进入系统调用,返回值也在eax中。进入系统调用的参数可以按次序传入ebx、ecx中。

最后使用 gcc -o setreuid setreuid.c 命令编译后,运行;使用 gcc -o setreuidasm setreuidasm.c 命令编译后,运行。两程序运行结果相同。

 (不知道怎么修改这两种id,因为两者一样,交换看不出区别)

gdb的环境出了问题,总会无视断点,还在找问题中。参照其他的博客观察gdb设置断点然后单步调试过程,可以归纳出整个调用过程,寄存器eax保存系统调用号70、ebx和ecx保存栈顶参数;调用中断指令int 0x80进入内核态;linux-5.4.34/arch/x86/entry/entry_64.S 目录下的ENTRY(entry_SYSCALL_64)入口,然后开始通过swapgs 和压栈动作保存现场。取到系统调用号之后执行system_call(),此时根据eax查询系统调用表,找到服务地址,执行服务;服务结束后,恢复保存的现场,回到用户态,ret返回结果

对于system_call()的过程,其源码如下:

ENTRY(system_call)
    RING0_INT_FRAME            # can't unwind into user space anyway
    ASM_CLAC
    pushl_cfi %eax            # save orig_eax
    SAVE_ALL
    GET_THREAD_INFO(%ebp)
                    # system call tracing in operation / emulation
    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
    jnz syscall_trace_entry
    cmpl $(NR_syscalls), %eax
    jae syscall_badsys
    # 查找系统调用表
syscall_call:
    call *sys_call_table(,%eax,4)
syscall_after_call:
    movl %eax,PT_EAX(%esp)        # store the return value
syscall_exit:
    LOCKDEP_SYS_EXIT
    DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt
                    # setting need_resched or sigpending
                    # between sampling and the iret
    TRACE_IRQS_OFF
    movl TI_flags(%ebp), %ecx
    testl $_TIF_ALLWORK_MASK, %ecx    # current->work
    # 系统调用执行完后,进入。若没有进入,进行恢复现场工作
    jne syscall_exit_work

restore_all:
    TRACE_IRQS_IRET
restore_all_notrace:
#ifdef CONFIG_X86_ESPFIX32
    movl PT_EFLAGS(%esp), %eax    # mix EFLAGS, SS and CS
    # Warning: PT_OLDSS(%esp) contains the wrong/random values if we
    # are returning to the kernel.
    # See comments in process.c:copy_thread() for details.
    movb PT_OLDSS(%esp), %ah
    movb PT_CS(%esp), %al
    andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
    cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
    CFI_REMEMBER_STATE
    je ldt_ss            # returning to user-space with LDT SS
#endif
restore_nocheck:
    RESTORE_REGS 4            # skip orig_eax/error_code
    # 效果等同与iret, 返回到用户态程序继续执行
irq_return:
    INTERRUPT_RETURN

具体分析可见《庖丁解牛Linux内核分析》 第五章第三节。

原文地址:https://www.cnblogs.com/cun-yu/p/12936455.html