• 深入理解系统调用


    作业要求:

    • 找一个系统调用,系统调用号为学号最后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内核分析》 第五章第三节。

  • 相关阅读:
    Python -- Redis List
    Python --Redis Hash操作
    Python使用redis介绍
    缓存服务器
    linux python3获取ip地址
    Rabbitmq -- rpc
    Rabbitmq--topic
    Rabbitmq -- direct
    删除rabbitmq中持久化的队列和数据
    Exchange-fanout 广播模式
  • 原文地址:https://www.cnblogs.com/cun-yu/p/12936455.html
Copyright © 2020-2023  润新知