スクリプトファイルの起動
Rev.1を表示中。最新版はこちら。
シェルスクリプトは、そのファイルを引数にして、#!以降のコマンドで、シェルが改めて起動するものと思っていました。実はスクリプトの実行は、カーネルとして実装されています。上記処理はカーネルが行っていると言う事です。それ故スクリプトファイルは実行許可属性が必要なわけですが。また、先の理解故、スクリプトは、シェルに限った物だとの誤解があったりもいたします。以下のようにすれば、phpファイルもスクリプトとして、起動することが可能です。[root@localhost kitamura]# cat script.php #!/usr/bin/php <?php print "php by script\n"; ?> [root@localhost kitamura]# ./script.php php by scriptシェルスクリプトに限らず、全てのプログラムの起動は、do_execve_common()をコールする事で行っています。概要は、struct linux_binprm *bprmに適切な設定を行い、そこから起動するプログラムのローダを探し出し、それにてプログラムを起動します。
まず、適切なチェックをした後、open_exec()で起動プログラムのファイル構造体を取得し、bprmに設定し、prepare_binprm()をコールしてinodeにかかるbmpのメンバーを設定し、search_binary_handler()で、対応するローダを取得して、そのローダによりプログラムを起動する事になります。
static int do_execve_common(const char *filename, struct user_arg_ptr argv, struct user_arg_ptr envp, struct pt_regs *regs) { struct linux_binprm *bprm; struct file *file; struct files_struct *displaced; bool clear_in_exec; int retval; const struct cred *cred = current_cred(); if ((current->flags & PF_NPROC_EXCEEDED) && atomic_read(&cred->user->processes) > rlimit(RLIMIT_NPROC)) { retval = -EAGAIN; goto out_ret; } retval = unshare_files(&displaced); if (retval) goto out_ret; retval = -ENOMEM; bprm = kzalloc(sizeof(*bprm), GFP_KERNEL); if (!bprm) goto out_files; retval = prepare_bprm_creds(bprm); if (retval) goto out_free; retval = check_unsafe_exec(bprm); if (retval < 0) goto out_free; clear_in_exec = retval; current->in_execve = 1; file = open_exec(filename); retval = PTR_ERR(file); if (IS_ERR(file)) goto out_unmark; sched_exec(); bprm->file = file; bprm->filename = filename; bprm->interp = filename; : : retval = prepare_binprm(bprm); if (retval < 0) goto out; retval = copy_strings_kernel(1, &bprm->filename, bprm); if (retval < 0) goto out; bprm->exec = bprm->p; retval = copy_strings(bprm->envc, envp, bprm); if (retval < 0) goto out; retval = copy_strings(bprm->argc, argv, bprm); if (retval < 0) goto out; retval = search_binary_handler(bprm,regs); if (retval < 0) goto out; current->fs->in_exec = 0; current->in_execve = 0; acct_update_integrals(current); free_bprm(bprm); if (displaced) put_files_struct(displaced); return retval; : : }prepare_binprm()は、struct linux_binprm *bprmにinodeにかかる設定を行い、最後にkernel_read()で実行するファイルの先頭からBINPRM_BUF_SIZE=128バイト、bprm->bufに読み込んでいます。ここには、起動ローダの情報が入っています。シェルスクリプトで言うなら、#!/bin/sh と言うことです。
int prepare_binprm(struct linux_binprm *bprm) { umode_t mode; struct inode * inode = bprm->file->f_path.dentry->d_inode; int retval; mode = inode->i_mode; if (bprm->file->f_op == NULL) return -EACCES; bprm->cred->euid = current_euid(); bprm->cred->egid = current_egid(); if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) { if (mode & S_ISUID) { bprm->per_clear |= PER_CLEAR_ON_SETID; bprm->cred->euid = inode->i_uid; } if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { bprm->per_clear |= PER_CLEAR_ON_SETID; bprm->cred->egid = inode->i_gid; } } retval = security_bprm_set_creds(bprm); if (retval) return retval; bprm->cred_prepared = 1; memset(bprm->buf, 0, BINPRM_BUF_SIZE); return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE); }search_binary_handler()で、struct linux_binprm *bprmに対応するローダを探し出し、ファイルを起動します。カーネルとしてサポートするローダは、struct linux_binfmtとして、formatsをヘッドとしてリストされています。ローダの実装は、モジュール単位で提供され、カーネルにinsされる毎に、formatsのリストに登録されいきます。
struct linux_binfmt *fmtのload_binaryがローダで、retval = fn(bprm, regs);と引数に、bprmでコールします。ローダサイドでは、bprm->bufの内容をチェックする事で、該当するファイルかどうかチェックを行っています。
なお、for (try=0; try<2; try++)と2回ループしているのは、最初のループで該当するローダが無かったら、request_module()で対応するモジュールをinsすることで、再度検索するようになっています。
int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs) { unsigned int depth = bprm->recursion_depth; int try,retval; struct linux_binfmt *fmt; pid_t old_pid; retval = security_bprm_check(bprm); if (retval) return retval; retval = audit_bprm(bprm); if (retval) return retval; rcu_read_lock(); old_pid = task_pid_nr_ns(current, task_active_pid_ns(current->parent)); rcu_read_unlock(); retval = -ENOENT; for (try=0; try<2; try++) { read_lock(&binfmt_lock); list_for_each_entry(fmt, &formats, lh) { int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary; if (!fn) continue; if (!try_module_get(fmt->module)) continue; read_unlock(&binfmt_lock); retval = fn(bprm, regs); bprm->recursion_depth = depth; if (retval >= 0) { if (depth == 0) { UTRACE_HOOK(current, EXEC, report_exec(fmt, bprm, regs)); ptrace_event(PTRACE_EVENT_EXEC, old_pid); } put_binfmt(fmt); allow_write_access(bprm->file); if (bprm->file) fput(bprm->file); bprm->file = NULL; current->did_exec = 1; proc_exec_connector(current); return retval; } read_lock(&binfmt_lock); put_binfmt(fmt); if (retval != -ENOEXEC || bprm->mm == NULL) break; if (!bprm->file) { read_unlock(&binfmt_lock); return retval; } } read_unlock(&binfmt_lock); #ifdef CONFIG_MODULES if (retval != -ENOEXEC || bprm->mm == NULL) { break; } else { #define printable(c) (((c)=='\t') || ((c)=='\n') || (0x20<=(c) && (c)<=0x7e)) if (printable(bprm->buf[0]) && printable(bprm->buf[1]) && printable(bprm->buf[2]) && printable(bprm->buf[3])) break; /* -ENOEXEC */ if (try) break; /* -ENOEXEC */ request_module("binfmt-%04x", *(unsigned short *)(&bprm->buf[2])); } #else break; #endif } return retval; }以下は、スクリプトにかかるローダの処理(モジュール)です。このモジュールがinsmodされると、init_script_binfmt()のregister_binfmt(&script_format)で、formatsとヘッドとするリストに登録されます。
load_script()はsearch_binary_handler()からコールされるわけですが、if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!')で、ファイルの先頭が、#!となっているかどうかチェックしています。なっていないなら、スクリプトファイルでありません。
#!なら、それ以降を、ローダとして取得すればいいだけなのですが、なんでごちゃごちゃした処理してんの?って。まあとにかく取得し、このローダファイルのFILEをopen_exec()で取得し、改めてbprmをprepare_binprm()をコールする事で、このローダで再設定しなおし、search_binary_handler()をコールすることで、結果的にこのスクリプトファイル(bprm->filename)が起動されるわけです。
なお、最初のif分のbprm->recursion_depth > BINPRM_MAX_RECURSION(=4)は、load_script()がコールされる毎に、bprm->recursion_depth++としており、#!以降がシェルスクリプトの場合、4回までしか記述できないと言う事です。
static int load_script(struct linux_binprm *bprm,struct pt_regs *regs) { const char *i_arg, *i_name; char *cp; struct file *file; char interp[BINPRM_BUF_SIZE]; int retval; if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!') || (bprm->recursion_depth > BINPRM_MAX_RECURSION)) return -ENOEXEC; bprm->recursion_depth++; allow_write_access(bprm->file); fput(bprm->file); bprm->file = NULL; bprm->buf[BINPRM_BUF_SIZE - 1] = '\0'; if ((cp = strchr(bprm->buf, '\n')) == NULL) cp = bprm->buf+BINPRM_BUF_SIZE-1; *cp = '\0'; while (cp > bprm->buf) { cp--; if ((*cp == ' ') || (*cp == '\t')) *cp = '\0'; else break; } for (cp = bprm->buf+2; (*cp == ' ') || (*cp == '\t'); cp++); if (*cp == '\0') return -ENOEXEC; /* No interpreter name found */ i_name = cp; i_arg = NULL; for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) /* nothing */ ; while ((*cp == ' ') || (*cp == '\t')) *cp++ = '\0'; if (*cp) i_arg = cp; strcpy (interp, i_name); retval = remove_arg_zero(bprm); if (retval) return retval; retval = copy_strings_kernel(1, &bprm->interp, bprm); if (retval < 0) return retval; bprm->argc++; if (i_arg) { retval = copy_strings_kernel(1, &i_arg, bprm); if (retval < 0) return retval; bprm->argc++; } retval = copy_strings_kernel(1, &i_name, bprm); if (retval) return retval; bprm->argc++; bprm->interp = interp; file = open_exec(interp); if (IS_ERR(file)) return PTR_ERR(file); bprm->file = file; retval = prepare_binprm(bprm); if (retval < 0) return retval; return search_binary_handler(bprm,regs); } static struct linux_binfmt script_format = { .module = THIS_MODULE, .load_binary = load_script, }; static int __init init_script_binfmt(void) { return register_binfmt(&script_format); } static void __exit exit_script_binfmt(void) { unregister_binfmt(&script_format); } core_initcall(init_script_binfmt); module_exit(exit_script_binfmt); MODULE_LICENSE("GPL");