スクリプトファイルの起動
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");






