ITPub博客

首页 > 应用开发 > XML/XSL > Sandbox a command(中等难度)

Sandbox a command(中等难度)

投稿 XML/XSL 作者:ecclw459 时间:2026-01-26 15:58:43 0 删除 编辑

这一部分涉及gdb的调试,所以我们暂时跳过,更多GDB的调试技巧可以去网上搜索一下,这里就不再阐述了。

2.Sandbox a command(中等难度)

在这一小节当中,我们需要给xv6操作系统引入一种 进程级系统调用限制机制(sandbox)。具体而言,允许用户进程通过一个新的系统调用  interpose(mask, path),为当前进程及其子进程设置一组 “被禁止的系统调用”,使得后续执行中一旦触发这些系统调用,就会被内核拒绝。官网当中告诉我们 interpose接收两个参数,一个是是屏蔽掩码 mask,另一个是路径 path (当前用不到)。

  来自官网的提示(个人解析版):

  1. 在 Makefile 中向 UPROGS 添加  $U/_sandbox,以保证编译器会编译该源文件。
  2. 由于 interpose没有任何声明和实现,所以要在 user/user.h 中添加一个  interpose 原型(不要遗漏参数) 。
  3. 在 user/usys.pl当中增加一个新的项,该文件是用户态系统调用接口的生成脚本,它会帮助生成一个汇编文件 user/usys.S,该文件中指定了每一个系统调用的参数,陷入指令和返回指令。
  4. 因为 interpose是一个新的系统调用,所以我们要在 kernel/syscall.h当中添加一个新的系统调用码,用于之后syscall函数的使用。
  5. 因为要添加一个新的系统调用,所以我们要严格按照xv6关于系统调用函数声明的规范进行命名,我们可以参考xv6当中已有的函数声明,所以我们在 kernel/sysproc.c 当中实现一个名为: sys_interpose(void)的函数,它就是最终的调用实现。
  6. 按照官网的要求,我们的屏蔽掩码需要父进程传递给子进程(或者说是子进程继承了父进程的屏蔽掩码),所以这就代表了这个屏蔽掩码需要被持久存储于进程中,于是我们需要在进程的结构体当中添加一个字段,用于记录屏蔽掩码,同时因为子进程是父进程通过调用fork创造出来的,所以一定存在一个函数用于将父进程当中某些状态/属性纹丝不动地赋值给子进程当中对应的字段,因此根据官网的提示,我们可以在 kernel/proc.c当中找到一个名为: kfork的函数,这里就是父子进行状态/属性继承的地方,我们需要在这里修改一下,使得其可以将父进程新添加的“屏蔽掩码”字段同样赋值给子进程。
  7. 因为每个系统调用都是一个函数指针,所以在 kernel/syscall.c当中,有一个数组:  syscalls,里面存放的是每一个系统调用的入口地址,我们需要在该数组当中添加一项新的数据,同时需要在此文件中添加 sys_interpose的声明(可以参考已有的xv6代码,照葫芦画瓢)。
  8. 因为我们要实现的是系统调用的屏蔽机制,所以在xv6当中,任何系统调用最终都会通过内核态函数 syscall进行调用号的识别和分发调用,所以我们可以在此函数当中添加某些判断逻辑,通过将当前请求系统调用的进程当中的屏蔽掩码与当前进程请求的系统调用的调用码向比对来得到是否要屏蔽该系统调用。


# #user/user.h中新增的内容(用户态函数声明): int interpose ( int, char *path) ; # #user/usys.pl中新增的内容: entry( "interpose" ); # #kernel/syscall.h中新增的内容(系统调用号): # define SYS_interpose 22   //interpose的系统调用码 # #kernel/proc.c/kfork函数体内,中新增的内容(子进程继承父进程的mask): ... ... /*修改点,父进程的状态mask传递给子进程  * 父进程的mask已经被修改,此时若创建新  * 的子进程则mask也要一并传递。  */  np->mask = p->mask; ... ... # #kernel/syscall.c中新增/修改的内容(系统调用声明,添加新的的项到函数指针数组,修改syscall函数): extern uint64 sys_interpose ( void) ; //新添加的系统调用声明 // An array mapping syscall numbers from syscall.h // to the function that handles the system call. static uint64 (*syscalls[]) ( void) = { [SYS_fork]    sys_fork, [SYS_exit]    sys_exit, [SYS_wait]  starlinkzhibo.cn  sys_wait, [SYS_pipe]    sys_pipe, [SYS_read]    sys_read, [SYS_kill]    sys_kill, [SYS_exec]    sys_exec, [SYS_fstat]   sys_fstat, [SYS_chdir]   sys_chdir, [SYS_dup]     sys_dup, [SYS_getpid]  sys_getpid, [SYS_sbrk]    sys_sbrk, [SYS_pause]   sys_pause, [SYS_uptime]  sys_uptime, [SYS_open]    sys_open, [SYS_write]   sys_write, [SYS_mknod]   sys_mknod, [SYS_unlink]  sys_unlink, [SYS_link]    sys_link, [SYS_mkdir]   sys_mkdir, [SYS_close]   sys_close, [SYS_interpose] xzhibo.com.cn  sys_interpose, //新添加的系统调用 }; //这里是syscall函数修改后的样子: void syscall ( void) {   int num;   struct proc * p = myproc();   // 取出在a7中存放的调用号  num = p->trapframe->a7;   if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {     //将调用屏蔽掩码和系统调用码进行相与的操作,判断当前调用是否被屏蔽/禁止     if (p->mask & ( 1 << num) ){      p->trapframe->a0 = -1 ;       return ;    }     // Use num to lookup the system call function for num, call it,     // and store its return value in p->trapframe->a0    p->trapframe->a0 = syscalls[num]();  } else {     printf ( "%d %s: unknown sys call %d\n" ,            p->pid, p->name, num);    p->trapframe->a0 = -1 ;  } } # #kernel/proc.h当中修改和新增的内容(屏蔽掩码): / Per-process state struct proc {   struct spinlock lock;   // p->lock must be held when using these:   enum procstate state;         // Process state   void *chan;                   // If non-zero, sleeping on chan   int killed;                   // If non-zero, have been killed   int xstate;                   // Exit status to be returned to parent's wait   int pid;                     // Process ID   int mask;                     // (新)进程的系统调用屏蔽掩码  ... ... # #kernel/syscall.c当中新增的内容(sys_interpose的实现): uint64 sys_interpose ( void) {   //获取参数   int n;  argint( 0 , &n);   //修改状态   struct proc * p = myproc();  p->mask = n;


这一小节是对上一阶段  系统调用屏蔽 的扩展,上一小节只是简单粗暴地屏蔽了某个系统调用(一棒子打死的那种),在本小节,我们用到了 interpose的第二个参数 Path,这个参数的具体意思是: “允许访问的路径”。

假设我们的屏蔽掩码屏蔽了  open 和  exec 这个两个系统调用,但是这两个系统调用在调用时都需要向其传入一个路径(我们假设该路径的名字为:pathA),当我们调用  open 和  exec时,如果向其传入的路径 pathA和之前的Path一致,则代表  open 和  exec正常进行,不会被屏蔽,反之则直接返回,不再执行  open 和  exec。

  个人的一些解析:

  1. 由于用到了 interpose的第二次参数,因此我们需要在进程结构体当中添加新的字段用于存放允许访问的路径。
  2. 由于在进程结构体当中添加了新成员,因此父子进程继承状态/属性时需要传递刚才添加的新成员。
  3. 官网说了,如果屏蔽码屏蔽的是  open 和  exec,则会继续判断PathA和进程结构体当中的特点字段是否一致,一致则代表  open 和  exec可以正常执行,所以结合前面的例子,屏蔽掩码具体屏蔽了谁,应该在syscall当中进行判断,而进一步地判断需要在  sys_open 和  sys_exec两个调用的具体实现当中。


// Per-process state struct proc {   struct spinlock lock;   // p->lock must be held when using these:   enum procstate state;         // Process state   void *chan;                   // If non-zero, sleeping on 5gzbw.com chan   int killed;                   // If non-zero, have been killed   int xstate;                   // Exit status to be returned to parent's wait   int pid;                     // Process ID   int mask;                     // 进程的系统调用屏蔽字   char allowPathName[MAXPATH];         // 被允许的路径名  ... ... # #kernel/proc.c/kfork函数体内,中新增的内容(子进程继承父进程的mask): ... ... /*修改点,父进程的状态mask传递给子进程  * 父进程的mask已经被修改,此时若创建新  * 的子进程则mask也要一并传递。  * 子进程也要继承父进程的allowPathName。  */  np->mask = p->mask;   strncpy (np->allowPathName,p->allowPathName,MAXPATH); ... ... # #kernel/syscall.c当中修改的内容(遇到open和exec则“放行”,在open和exec中再次判断): void syscall ( void) {   int num;   struct proc * p = myproc();   // 取出在a7中存放的调用号  num = p->trapframe->a7;   if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {     //修改前:将调用屏蔽掩码和系统调用码进行相与的操作,判断当前调用是否被屏蔽/禁止     //修改后:如果屏蔽的是open和exec则在open或者exec当中再次判断     if (p->mask & ( 1 << num) ){       if (num == SYS_open || num == SYS_exec){        p->trapframe->a0 = syscalls[num]();         return ;      }      p->trapframe->a0 = -1 ;       return ;    }     // Use num to lookup the system call function for num, call it,     // and store its return value in p->trapframe->a0    p->trapframe->a0 = syscalls[num]();  } else { tiyuzhibo.xj.cn    printf ( "%d %s: unknown sys call %d\n" ,            p->pid, p->name, num);    p->trapframe->a0 = -1 ;  } } # #kernel/sysfile.c/sys_open函数体内,新增的内容(添加判断逻辑):  ... ...   // 只有当 open 被 mask 掉时,才检查路径   if (p->mask & ( 1 << SYS_open)){     if ( strncmp (path,p->allowPathName,MAXPATH) != 0 ){       return -1 ;    }  }  ... ...       # #kernel/sysfile.c/sys_exec函数体内,新增的内容(添加判断逻辑):   ... ... // 只有当 exec 被 mask 掉时,才检查路径   if (p->mask & ( 1 << SYS_exec)){     if ( strncmp (path, p->allowPathName,MAXPATH) != 0 ){       return -1 ;    }


$ make qemu-gdb sandbox_mask: OK (3.0s) == Test sandbox_fork == $ make qemu-gdb sandbox_fork: OK (1.1s) == Test sandbox_path == $ make qemu-gdb sandbox_path: OK (1.2s) == Test sandbox_most == $ make qemu-gdb sandbox_most: OK (0.7s) == Test sandbox_minus == $ make qemu-gdb sandbox_minus: OK (1.0s) =tiyuzhibo.fj.cn= Test attack == $ make qemu-gdb

# include "kernel/types.h" # include "kernel/fcntl.h" # include "user/user.h" # include "kernel/riscv.h" # define DATASIZE (8*4096)   //heap的大小为8页,共32k,一页4kB int main ( int argc, char *argv[]) {   // Your code here.   //分配heap   char * buf = sbrk(DATASIZE);   //堆的大小为8页   char ch[DATASIZE/ 4 ];   //字符串大小为8k   int j = 0 ; //   for ( int i = 0 ; i < DATASIZE; i++){     char c = buf[i];     if (c == '\0' && (j >= 2 && j < DATASIZE/ 4 )){       //遇到/0,并且j大于2且在合法范围内则代表可能找到了想要的东西,截断字符串      ch[j++] = '\0' ;       //打印  ttzb.fj.cn     printf ( "%s" ,ch);       printf ( "\n" );       //j置为0继续找剩余符合条件的字符串(假设还没有扫码到heap的尽头的情况下)      j = 0 ;    }     //符合字符条件并且j的范围合理     if ( ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) || (c >= '0' && c <= '9' )) && j < DATASIZE/ 4 - 1 ){       //符合条件则赋值给字符串数组      ch[j++] = c;    }     else {       //不连续或者不是字符则将j值为0      j= 0 ;    }



来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048957/viewspace-3108979/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2026-01-07

  • 博文量
    108
  • 访问量
    102104