自学内容网 自学内容网

linux手册翻译 execve

名称

execve - 执行程序

简介

#include <unistd.h>

int execve(const char *pathname, char *const argv[], char *const envp[]);

描述

execve() 执行由参数 pathname指定的程序. 这将导致调用进程当前正在运行的程序被一个新程序替换,该程序具有新初始化的堆栈、堆和(初始化和未初始化的)数据段。
参数pathname必须也是一个可执行的二进制文件, 或者是一个 #!interpreter [optional-arg] 形式的脚本. 详情见下面 “Interpreter scripts” 部分.

参数 argv 一组指向字符串的指针的数组. 这些字符串作为命令行参数传递给新程序. 按照惯例,这些字符串中的第一个(即argv[0])应该包含与正在执行的文件相关联的文件名。argv数组必须以NULL指针结束。(因此,在新程序中,argv[argc]将为NULL。)

参数envp也是一组指向字符串的指针的数组, 他们以key=value的传统形式, 作为环境变量传递给新策划功能性. envp数组必须以NULL指针作为结尾.

实参vector和environment可以被新程序的main函数访问,当它被定义为:

int main(int argc, char *argv[], char *envp[])

但是请注意,POSIX.1中没有指定使用main函数的第三个参数;根据POSIX.1,应该通过外部变量environ(7)来访问环境。
execve()执行成功后不返回. 调用程序的代码, 初始化的数据, 未初始化的数据(bss), 和栈会根据新加载的程序的内容被覆盖掉.

如果当前程序正在被跟踪(traced),则在成功执行execute()之后向它发送一个SIGTRAP信号。
如果在pathname所引用的程序文件上设置了set-user-ID位,则调用进程的有效用户ID将更改为程序文件所有者的用户ID。同样,如果在程序文件上设置了set-group-ID位,则调用进程的有效组ID将设置为程序文件的组。
如果满足以下任何一个条件,则不执行上述有效id的转换(即忽略set-user-ID和set-group-ID位):

  • 调用的线程设置了 no_new_privs 属性(见 prctl(2))
  • 潜在的文件系统被挂载为( MS_NOSUID flag for mount(2))
  • 调用的进程在被跟踪.

如果上述任何一个为真,程序文件的功能(参见capabilities(7))也会被忽略。

将进程的有效用户ID复制到保存的set-user-ID中;同样,将有效的组ID复制到保存的set-group-ID中。这种复制发生在由于set-user-ID和set-group-ID模式位而发生的任何有效ID更改之后。

进程的实际UID和实际GID,以及它的补充组id,通过调用execute()来改变。

如果可执行文件是包含共享库stubs的a.out动态链接的二进制可执行文件,则在执行开始时调用Linux动态链接器ld.so(8),将所需的共享对象带入内存并将可执行文件与它们链接起来。

如果可执行文件是动态链接的ELF可执行文件,则使用PT_INTERP段中命名的解释器来加载所需的共享对象。这个解释器通常是**/lib/ld-linux.so**。2链接到glibc的二进制文件(参见ld-linux.so(8))。

进程属性的作用

在执行execve()期间,所有进程属性都被保留,除了以下属性:

  • 被捕获的任何信号的配置被重置为默认值(signal(7))。
  • 任何可选的信号堆栈都不会被保留(sigaltstack(2))。
  • 内存映射不会被保留(mmap(2)).
  • 附加的System V共享内存段被分离(shmat(2))。
  • POSIX 共享内存区域被解除映射 (shm_open(3)).
  • 打开的POSIX消息队列描述被关闭 (mq_overview(7)).
  • 任何打开的POSIX命名信号量都是关闭的(sem_overview(7))。
  • POSIX计时器不会被保留 (timer_create(2)).
  • 任何打开的目录流回被关闭(opendir(3)).
  • 内存锁不会被保留(mlock(2), mlockall(2))
  • 退出的handle不会被保留 (atexit(3), on_exit(3)).
  • 浮点环境被重置为默认 (see fenv(3)).

上述列表中的进程属性均在POSIX.1中指定。以下linux特定的进程属性在执行execve() 时也不会被保留:

  • 进程的“dumpable”属性被设置为值1,除非一个set-user- id程序,一个set-group- id程序,或者一个具有能力的程序正在执行,在这种情况下,dumpable标志可以被重置为/proc/sys/fs/suid_dumpable中的值,在prctl(2)中PR_SET_DUMPABLE所描述的情况下。注意,对“dumpable”属性的更改可能会导致进程/proc/[pid]目录下文件的所有权变为root:root,如proc(5)中所述。
  • prctl(2) PR_SET_KEEPCAPS标准被清除
  • (自Linux 2.4.36 / 2.6.23起)如果一个set-user- id或set-group- id程序正在执行,那么由prctl(2) PR_SET_PDEATHSIG标志设置的父死亡信号被清除。
  • SECBIT_KEEP_CAPS securebits 标志被清除. See capabilities(7).
  • 结束信号被重置为 SIGCHLD (see clone(2)).
  • 文件描述符表是非共享的,取消了clone(2)的CLONE_FILES标志的效果。

请注意以下几点:

  • 在执行execve() 期间,除调用线程之外的所有线程都被销毁。互斥锁、条件变量和其他pthread对象不被保留。

  • 相当于setlocale(LC_ALL, “C”) 的语句在程序启动时执行。

  • POSIX.1指定忽略或设置为默认值的任何信号的配置保持不变。POSIX.1指定了一个例外:如果SIGCHLD被忽略,则实现可以保持处置不变或将其重置为默认值;Linux是前者。

  • 所有未完成的异步I/O操作都被取消(aio_read(3), aio_write(3))。

  • 有关在execve() 期间对能力的处理,请参见capabilities(7)。

  • 默认情况下,文件描述符在execve() 中保持打开状态。标记为close-on-exec的文件描述符被关闭;参见fcntl(2)中FD_CLOEXEC的描述。(如果文件描述符被关闭,这将导致释放该进程在底层文件上获得的所有记录锁。详细信息请参见fcntl(2)。POSIX.1表示,如果文件描述符0、1和2在成功执行execve()之后将被关闭,并且进程将获得特权,因为在执行的文件上设置了set-user- id或set-group- id模式位,那么系统可能会为每个这些文件描述符打开一个未指定的文件。作为一般原则,任何可移植程序,无论是否具有特权,都不能假定这三个文件描述符在执行一次execute() 时保持关闭状态。

Interpreter scripts

解释脚本是被添加了可执行权限的文件, 它第一行的形式是

#!interpreter [optional-arg]

interpreter 必须是一个可执行文件的合法路径名. 如果execve()的pathname参数指定了解释器脚本,那么解释器将使用以下参数调用:

interpreter [optional-arg] pathname arg...

其中pathname是作为execve() 的第一个参数指定的文件的绝对路径名,而 arg… 是execve() 的argv参数指向的一系列单词,从argv[1]开始。注意,没有办法获取传递给execute()调用的argv[0]。

为了便于移植,optional-arg 应该不存在,或者指定为单个字(即,它不应该包含空白);见下面的说明。

从Linux 2.6.28开始,内核允许脚本的解释器本身就是一个脚本。此权限是递归的,最多只能递归4次,因此解释器可以是由脚本解释的脚本,以此类推。

参数和环境变量大小的限制

大多数UNIX实现对可能传递给新程序的命令行参数(argv)和环境(envp)字符串的总大小施加了一些限制。POSIX.1允许使用ARG_MAX常量(可以在<limits.h>中定义,也可以在运行时使用sysconf(_SC_ARG_MAX)调用)来发布此限制。

在内核2.6.23之前的Linux中,用于存储环境和参数字符串的内存被限制为32页(由内核常量MAX_ARG_PAGES定义)。在页面大小为4 kB的体系结构上,这产生的最大大小为128 kB。

在内核2.6.23及更高版本中,大多数体系结构都支持从软RLIMIT_STACK资源限制派生的大小限制(参见getrlimit(2)),该限制在执行execve() 调用时生效。(没有内存管理单元的体系结构除外:它们保持内核2.6.23之前有效的限制。)这个改变允许程序拥有更大的参数和/或环境列表。对于这些体系结构,总大小被限制为允许堆栈大小的1/4。(施加1/4限制确保新程序总是有一些堆栈空间。)此外,总大小被限制为内核常量_STK_LIM值的3/4 (8 Mibibytes)。从Linux 2.6.25开始,内核还设置了32页的上限. 因此,即使RLIMIT_STACK设置得非常低,也可以保证应用程序至少拥有与Linux 2.6.23及更早版本提供的一样多的参数和环境空间。(在Linux 2.6.23和2.6.24中没有提供此保证。)此外,每个字符串的限制是32页(内核常量MAX_ARG_STRLEN),字符串的最大数目是0x7FFFFFFF。

返回值

成功时,execve() 不返回,错误时返回-1,并适当地设置errno。

ERRORS

  • E2BIG 环境(envp)和参数列表(argv)中的总字节数太大。

  • EACCES 在pathname的路径前缀或脚本解释器名称的组件上拒绝搜索权限。(参见path_resolution(7)。)

  • EACCES 文件或脚本解释器不是常规文件。

  • EACCES 拒绝文件、脚本或ELF解释器的执行权限。

  • EACCES 文件系统是noexec挂载的。

  • EAGAIN(从Linux 3.1开始)
    在使用set*uid() 调用之一更改了它的实际UID之后,调用者过去是(现在仍然是)高于它的RLIMIT_NPROC资源限制(参见setrlimit(2))。有关此错误的更详细解释,请参见注释。

  • EFAULT 路径名或向量argv或envp中的指针之一指向可访问的地址空间之外。

  • EINVAL 一个ELF可执行文件有多个PT_INTERP段(即,试图命名多个解释器)。

  • EIO 发生I/O错误。

  • EISDIR ELF解释器是一个目录。

  • ELIBBAD ELF解释器的格式不被识别。

  • ELOOP 在解析路径名或脚本或ELF解释器的名称时遇到了太多的符号链接。

  • ELOOP 在递归脚本解释期间达到了最大递归限制(参见上面的“解释器脚本”)。在Linux 3.8之前,这种情况产生的错误是ENOEXEC。

  • EMFILE 已达到每个进程打开的文件描述符的数量限制。

  • ENAMETOOLONG 路径名太长。

  • ENFILE 已达到打开文件总数的全系统限制。

  • ENOENT enent文件路径名或脚本或ELF解释器不存在。

  • ENOEXEC 可执行文件的格式不被识别,适用于错误的体系结构,或者有其他格式错误,这意味着它无法执行。

  • ENOMEM 内核内存不足。

  • ENOTDIR pathname或脚本或ELF解释器的路径前缀的组件不是目录。

  • EPERM 文件系统以nosuid方式挂载,用户不是超级用户,文件的位设置为set-user- id或set-group- id。

  • EPERM 正在跟踪进程,用户不是超级用户,文件设置了set-user- id或set-group- id位。

  • EPERM “capability-dumb” 应用程序将无法获得可执行文件授予的全部允许功能。看到能力(7)。

  • ETXTBSY 指定的可执行文件已打开供一个或多个进程写入。

CONFORMING TO

POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD。POSIX没有记录#!行为,但它存在于其他UNIX系统上(有一些变化)。

注意

人们有时会看到 execec()(以及exec(3)中描述的相关函数)被描述为“执行一个新进程”(或类似的)。这是一种极具误导性的描述:没有新的进程;调用进程的许多属性保持不变(特别是其PID)。execve() 所做的就是安排现有进程(调用进程)执行一个新程序。

set-user-ID和set-group-ID进程不能ptrace(2)d。

挂载文件系统 nosuid 的结果在不同的Linux内核版本中是不同的:有些会拒绝执行set-user-ID和set-group-ID可执行文件,当这会给用户提供他们没有的权限时(并返回EPERM),有些会忽略set-user-ID和set-group-ID位并成功执行exec()。

在Linux上,argv和envp可以指定为NULL。在这两种情况下,这与将实参指定为指向包含单个空指针的列表的指针具有相同的效果。不要利用这个非标准和不可移植的错误特性!在许多其他UNIX系统上,将argv指定为NULL将导致错误(EFAULT)。其他一些UNIX系统对envp==NULL的处理方式与Linux相同。

POSIX.1表示sysconf(3)返回的值在进程的生命周期内应该是不变的。但是,从Linux 2.6.23开始,如果RLIMIT_STACK资源限制发生变化,那么_SC_ARG_MAX报告的值也会发生变化,以反映保存命令行参数和环境变量的空间限制发生变化的事实。

在大多数情况下,当execve() 失败时,控制将返回到原始的可执行映像,然后execve() 的调用者可以处理错误。然而,在(罕见的)情况下(通常是由资源耗尽引起的),失败可能会发生在无法返回的点上:原始的可执行映像已被关闭,但新映像无法完全构建。在这种情况下,内核使用SIGSEGV (Linux 3.17之前为SIGKILL)信号终止进程。

Interpreter scripts

内核对脚本开头的“#!”字符后面的文本规定了最大长度;超出限制的字符将被忽略。在Linux 5.1之前,限制是127个字符。从Linux 5.1开始,限制是255个字符。

解释器脚本的optional-arg参数的语义因实现而异。在Linux上,解释器名称后面的整个字符串作为单个参数传递给解释器,该字符串可以包含空白。然而,行为在其他一些系统上有所不同。有些系统使用第一个空白来终止optional-arg。在某些系统上,解释器脚本可以有多个参数,可选参数中的空格用于分隔参数。

Linux(像大多数其他现代UNIX系统一样)忽略脚本上的set-user-ID和set-group-ID位。

execve() and EAGAIN

调用execve() 时可能出现的EAGAIN错误(从Linux 3.1开始)的更详细解释如下。

当之前调用setuid(2)、setreuid(2)或setreuid(2)导致进程的真实用户ID发生变化,并且该变化导致进程超过其RLIMIT_NPROC资源限制(即,属于新的真实UID的进程数量超过资源限制)时,就会发生EAGAIN错误。从Linux 2.6.0到3.0,这会导致set*uid() 调用失败。(在2.6之前,对更改用户id的进程没有施加资源限制。)

从Linux 3.1开始,刚才描述的场景不再导致setuid() 调用失败,因为它经常导致安全漏洞,在这些漏洞中,有bug的应用程序不检查返回状态,并假设 如果调用者拥有根权限-调用总是会成功。相反,setuid() 调用可以成功地更改真正的uid,但是内核设置了一个名为PF_NPROC_EXCEEDED的内部标志,以注意已超过RLIMIT_NPROC资源限制。如果设置了PF_NPROC_EXCEEDED标志,并且在随后的execute() 调用时仍然超过了资源限制,则该调用将失败并返回EAGAIN错误。这个内核逻辑确保RLIMIT_NPROC资源限制仍然适用于公共特权守护进程工作流,即fork(2) + set*uid() + execve()。

如果在执行execve() 调用时仍然没有超过资源限制(因为属于这个真实UID的其他进程在set*uid() 调用和执行execve() 调用之间终止),那么执行execve() 调用成功,内核清除PF_NPROC_EXCEEDED进程标志。如果这个进程对fork(2)的后续调用成功,该标志也会被清除。

Historical

在UNIX V6中,exec() 调用的参数列表以0结束,而main的参数列表以-1结束。因此,这个参数列表不能在以后的exec() 调用中直接使用。从UNIX V7开始,两者都是NULL

例子

下面的程序被设计为由下面的第二个程序执行。它只是回显其命令行参数,每行一个。

           /* myecho.c */

           #include <stdio.h>
           #include <stdlib.h>

           int
           main(int argc, char *argv[])
           {
               for (int j = 0; j < argc; j++)
                   printf("argv[%d]: %s\n", j, argv[j]);

               exit(EXIT_SUCCESS);
           }
           /* execve.c */

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

           int
           main(int argc, char *argv[])
           {
               char *newargv[] = { NULL, "hello", "world", NULL };
               char *newenviron[] = { NULL };

               if (argc != 2) {
                   fprintf(stderr, "Usage: %s <file-to-exec>\n", argv[0]);
                   exit(EXIT_FAILURE);
               }

               newargv[0] = argv[1];

               execve(argv[1], newargv, newenviron);
               perror("execve");   /* execve() returns only on error */
               exit(EXIT_FAILURE);
           }

我们可以使用第二个程序来执行第一个:

           $ cc myecho.c -o myecho
           $ cc execve.c -o execve
           $ ./execve ./myecho
           argv[0]: ./myecho
           argv[1]: hello
           argv[2]: world

SEE ALSO

chmod(2),  execveat(2), fork(2), get_robust_list(2), ptrace(2), exec(3), fexecve(3), getopt(3), system(3), capabilities(7), credentials(7), environ(7), path_resolution(7), ld.so(8)

版本记录

本页是Linux手册页项目5.10版的一部分。项目的描述、有关报告错误的信息以及本页的最新版本,可以在https://www.kernel.org/doc/man-pages/上找到。

Linux                                 2020-08-13                                 EXECVE(2)

原文地址:https://blog.csdn.net/sunqin_csdn/article/details/142904110

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!