162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * linux/fs/binfmt_script.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 1996 Martin von Löwis 662306a36Sopenharmony_ci * original #!-checking implemented by tytso. 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/string.h> 1162306a36Sopenharmony_ci#include <linux/stat.h> 1262306a36Sopenharmony_ci#include <linux/binfmts.h> 1362306a36Sopenharmony_ci#include <linux/init.h> 1462306a36Sopenharmony_ci#include <linux/file.h> 1562306a36Sopenharmony_ci#include <linux/err.h> 1662306a36Sopenharmony_ci#include <linux/fs.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistatic inline bool spacetab(char c) { return c == ' ' || c == '\t'; } 1962306a36Sopenharmony_cistatic inline const char *next_non_spacetab(const char *first, const char *last) 2062306a36Sopenharmony_ci{ 2162306a36Sopenharmony_ci for (; first <= last; first++) 2262306a36Sopenharmony_ci if (!spacetab(*first)) 2362306a36Sopenharmony_ci return first; 2462306a36Sopenharmony_ci return NULL; 2562306a36Sopenharmony_ci} 2662306a36Sopenharmony_cistatic inline const char *next_terminator(const char *first, const char *last) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci for (; first <= last; first++) 2962306a36Sopenharmony_ci if (spacetab(*first) || !*first) 3062306a36Sopenharmony_ci return first; 3162306a36Sopenharmony_ci return NULL; 3262306a36Sopenharmony_ci} 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic int load_script(struct linux_binprm *bprm) 3562306a36Sopenharmony_ci{ 3662306a36Sopenharmony_ci const char *i_name, *i_sep, *i_arg, *i_end, *buf_end; 3762306a36Sopenharmony_ci struct file *file; 3862306a36Sopenharmony_ci int retval; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci /* Not ours to exec if we don't start with "#!". */ 4162306a36Sopenharmony_ci if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!')) 4262306a36Sopenharmony_ci return -ENOEXEC; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci /* 4562306a36Sopenharmony_ci * This section handles parsing the #! line into separate 4662306a36Sopenharmony_ci * interpreter path and argument strings. We must be careful 4762306a36Sopenharmony_ci * because bprm->buf is not yet guaranteed to be NUL-terminated 4862306a36Sopenharmony_ci * (though the buffer will have trailing NUL padding when the 4962306a36Sopenharmony_ci * file size was smaller than the buffer size). 5062306a36Sopenharmony_ci * 5162306a36Sopenharmony_ci * We do not want to exec a truncated interpreter path, so either 5262306a36Sopenharmony_ci * we find a newline (which indicates nothing is truncated), or 5362306a36Sopenharmony_ci * we find a space/tab/NUL after the interpreter path (which 5462306a36Sopenharmony_ci * itself may be preceded by spaces/tabs). Truncating the 5562306a36Sopenharmony_ci * arguments is fine: the interpreter can re-read the script to 5662306a36Sopenharmony_ci * parse them on its own. 5762306a36Sopenharmony_ci */ 5862306a36Sopenharmony_ci buf_end = bprm->buf + sizeof(bprm->buf) - 1; 5962306a36Sopenharmony_ci i_end = strnchr(bprm->buf, sizeof(bprm->buf), '\n'); 6062306a36Sopenharmony_ci if (!i_end) { 6162306a36Sopenharmony_ci i_end = next_non_spacetab(bprm->buf + 2, buf_end); 6262306a36Sopenharmony_ci if (!i_end) 6362306a36Sopenharmony_ci return -ENOEXEC; /* Entire buf is spaces/tabs */ 6462306a36Sopenharmony_ci /* 6562306a36Sopenharmony_ci * If there is no later space/tab/NUL we must assume the 6662306a36Sopenharmony_ci * interpreter path is truncated. 6762306a36Sopenharmony_ci */ 6862306a36Sopenharmony_ci if (!next_terminator(i_end, buf_end)) 6962306a36Sopenharmony_ci return -ENOEXEC; 7062306a36Sopenharmony_ci i_end = buf_end; 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci /* Trim any trailing spaces/tabs from i_end */ 7362306a36Sopenharmony_ci while (spacetab(i_end[-1])) 7462306a36Sopenharmony_ci i_end--; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci /* Skip over leading spaces/tabs */ 7762306a36Sopenharmony_ci i_name = next_non_spacetab(bprm->buf+2, i_end); 7862306a36Sopenharmony_ci if (!i_name || (i_name == i_end)) 7962306a36Sopenharmony_ci return -ENOEXEC; /* No interpreter name found */ 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci /* Is there an optional argument? */ 8262306a36Sopenharmony_ci i_arg = NULL; 8362306a36Sopenharmony_ci i_sep = next_terminator(i_name, i_end); 8462306a36Sopenharmony_ci if (i_sep && (*i_sep != '\0')) 8562306a36Sopenharmony_ci i_arg = next_non_spacetab(i_sep, i_end); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci /* 8862306a36Sopenharmony_ci * If the script filename will be inaccessible after exec, typically 8962306a36Sopenharmony_ci * because it is a "/dev/fd/<fd>/.." path against an O_CLOEXEC fd, give 9062306a36Sopenharmony_ci * up now (on the assumption that the interpreter will want to load 9162306a36Sopenharmony_ci * this file). 9262306a36Sopenharmony_ci */ 9362306a36Sopenharmony_ci if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE) 9462306a36Sopenharmony_ci return -ENOENT; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci /* 9762306a36Sopenharmony_ci * OK, we've parsed out the interpreter name and 9862306a36Sopenharmony_ci * (optional) argument. 9962306a36Sopenharmony_ci * Splice in (1) the interpreter's name for argv[0] 10062306a36Sopenharmony_ci * (2) (optional) argument to interpreter 10162306a36Sopenharmony_ci * (3) filename of shell script (replace argv[0]) 10262306a36Sopenharmony_ci * 10362306a36Sopenharmony_ci * This is done in reverse order, because of how the 10462306a36Sopenharmony_ci * user environment and arguments are stored. 10562306a36Sopenharmony_ci */ 10662306a36Sopenharmony_ci retval = remove_arg_zero(bprm); 10762306a36Sopenharmony_ci if (retval) 10862306a36Sopenharmony_ci return retval; 10962306a36Sopenharmony_ci retval = copy_string_kernel(bprm->interp, bprm); 11062306a36Sopenharmony_ci if (retval < 0) 11162306a36Sopenharmony_ci return retval; 11262306a36Sopenharmony_ci bprm->argc++; 11362306a36Sopenharmony_ci *((char *)i_end) = '\0'; 11462306a36Sopenharmony_ci if (i_arg) { 11562306a36Sopenharmony_ci *((char *)i_sep) = '\0'; 11662306a36Sopenharmony_ci retval = copy_string_kernel(i_arg, bprm); 11762306a36Sopenharmony_ci if (retval < 0) 11862306a36Sopenharmony_ci return retval; 11962306a36Sopenharmony_ci bprm->argc++; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci retval = copy_string_kernel(i_name, bprm); 12262306a36Sopenharmony_ci if (retval) 12362306a36Sopenharmony_ci return retval; 12462306a36Sopenharmony_ci bprm->argc++; 12562306a36Sopenharmony_ci retval = bprm_change_interp(i_name, bprm); 12662306a36Sopenharmony_ci if (retval < 0) 12762306a36Sopenharmony_ci return retval; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci /* 13062306a36Sopenharmony_ci * OK, now restart the process with the interpreter's dentry. 13162306a36Sopenharmony_ci */ 13262306a36Sopenharmony_ci file = open_exec(i_name); 13362306a36Sopenharmony_ci if (IS_ERR(file)) 13462306a36Sopenharmony_ci return PTR_ERR(file); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci bprm->interpreter = file; 13762306a36Sopenharmony_ci return 0; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic struct linux_binfmt script_format = { 14162306a36Sopenharmony_ci .module = THIS_MODULE, 14262306a36Sopenharmony_ci .load_binary = load_script, 14362306a36Sopenharmony_ci}; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic int __init init_script_binfmt(void) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci register_binfmt(&script_format); 14862306a36Sopenharmony_ci return 0; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic void __exit exit_script_binfmt(void) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci unregister_binfmt(&script_format); 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cicore_initcall(init_script_binfmt); 15762306a36Sopenharmony_cimodule_exit(exit_script_binfmt); 15862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 159