为什么Bash的源不需要执行位?


57

使用Bash source,可以在不设置执行位的情况下执行脚本。这是有据可查的并且是预期的行为,但这不是反对使用执行位吗?

我知道,这source不会创建子外壳。


2
chmod可以让您使用八进制数字设置权限(包括`x)的事实为您提供了它来自哪个时代的线索。从它被发明之前的日子开始,如果它以快速而肮脏的“这是一个可以执行的二进制文件”指示开始,我不会感到惊讶,但是我没有任何证据
固定为

9
再有,当SUID发挥作用时,针对不同类别用户的不同级别的保护变得更加重要。如果需要,请继续阅读我的SUID程序。但是,如果您仅通过阅读它来执行它,那么SUID的功能就不会随之
而来

@infixed 除非设置了执行位,否则Linux程序加载器甚至不会查看 shebang。(稍微吹嘘我自己的喇叭:请看这里。)
Kyle Strand

您也可以使用+ xr,但这有点奇怪,因为通常可以通过将代码注入正在运行的进程中来读取二进制文件
Niklas B.

按我设想沿着线的东西:“如果你通过简单的读它执行它的SUID权力不与它一起到来” @KyleStrandcp /sbin/suidexecutable /tmp/mycopy; /tmp/mycopy
infixed

Answers:


36

Bash是一名口译员;它接受输入并做任何想做的事情。不需要注意可执行位。实际上,Bash是可移植的,并且可以在没有可执行位概念的操作系统和文件系统上运行。

关心可执行位的是操作系统内核。exec例如,当Linux内核执行时,它将检查是否未通过noexec选项安装文件系统,并检查程序文件的可执行位,并强制执行由安全模块(例如SELinux或AppArmor)施加的任何要求。

请注意,可执行位是一种相当随意的控制类型。例如,在Linux x86-64系统上,可以通过显式调用/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2解释器来绕过内核对可执行位的验证:

cp /bin/ls /tmp/
chmod -x /tmp/ls
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /tmp/ls

这有点类似于在Bash中采购Bash源代码,除了它ld.so是解释器,并且它执行的代码是ELF格式的机器代码。


3
使用加载程序作为“字节码”的“解释器”,它恰好是真正的可执行二进制文件.....很棒。谢谢你
凯尔·斯特兰德

@KyleStrand可能有多个间接级别(存储的程序和加载的进程之间的差异以及内存的映射方式,各种管理程序,VM等),但是原则上机器代码可以直接由CPU执行(没有微代码,等等),因此不会解释机器代码:硬件按原样理解它(它是本地语言),不需要解释器。
jfs

2
@JFSebastian是的,我们知道它是本机代码,因此“解释器”用引号引起来。的工作ld.so是进行动态链接。
200_success,2016年

@JFSebastian 200_success表示什么。同样,这就是我所说的“真正的可执行二进制文件”的意思,并且我还在引号中加上了“字节码”(因为AFAIK“字节码”通常不用于指代实际编译的本机可执行二进制代码)。另外,好的用户名。
凯尔·斯特兰德

@JFSebastian我确实相信,现代的x86型CPU实际上是内部RISC,老式的CISC指令集基本上可以驱动相应RISC指令的微代码执行,其中一个CISC指令可以导致执行许多RISC指令。因此,从某种意义上讲,从技术的角度来看,如果不完全正确地调用CPU对RISC机器代码的解释,那么至少是有效的思维模型。AES-NI可能是更极端的例子之一:每个AES回合一个CISC指令!
CVn

57

source或等效但标准的.不执行脚本,而是从脚本文件中读取命令,然后在当前Shell环境中逐行执行它们。

没有什么反对执行位的使用,因为外壳只需要具有读取权限才能读取文件的内容。

只有在运行脚本时才需要执行位。在这里,shell将fork()新建一个进程,然后使用execve()函数从脚本创建新的进程映像,该脚本必须是常规的可执行文件。


5
@alexis:我不确定我是否真的会跟随。如果这样做bash < script,您得到的结果基本上与相同source script。执行位检查提供什么保护?
出色的编译器

27
@alexis,检查其解释的脚本的权限不是解释器的工作。什么没有做-不是Python,不是Ruby,不是Java虚拟机,不是其他随机shell。execute位控制OS execv*系列的syscall 是否可与可执行文件一起使用,而不控制解释器是否将其运行。为什么通过破坏约定使人们感到困惑(并破坏评估非文件源流代码的能力)?
查尔斯·达菲,2016年

14
@alexis,...此外,还有一个不可执行的shell库,这是一个有价值的含义:它说:“我是一个库,而不是命令-来源我,不执行我”。否则,您将需要编写代码来检测和纠正仅旨在获取源代码的滥用,但是由于没有+ x权限,您可以确保用户不会(不会)以这种方式滥用图书馆。
查尔斯·达菲

6
@alexis“这是作者的决定”仅在某种意义上,未包含的所有其他可能的代码才是决定。Shell打开文件进行读取,以从中读取命令。我不希望它检查读取权限,它只是尝试打开文件,如果不能打开则失败。同样,它不检查写入或执行权限,因为这些检查与读取文件无关。
兰迪·奥里森

2
@alexis这实际上是在实践中使用的,例如,与python的virtualenv一起使用时,用于激活该脚本的脚本是要获取而不是执行,因此bin/activate没有可执行位。脚本可以完全是库或类似的东西。我想拥有.sh也可能是一个信号,但是拥有最少的步枪还是不错的:很好的是,不可能./bin/activate偶然发生而不是. bin/activate
daboross

20

nonsetuid和nonsetguid文件上的可执行位(与其余部分不同)不是一种安全机制。您可以读取的任何内容都可以间接运行,Linux可以让您间接读取您可以运行但不能直接读取的任何内容(这足以在非set(g)uid x位概念上打一个洞。安全措施)。

更为方便的是:如果该位置1,则让系统直接为我运行它,否则,我需要间接进行此操作(bash the_script;或进行某些破解以获取未读取权限的可执行文件的内存映像)。

如果您打算同时在源代码中和执行可采购的产品,则可以将其设置为方便。

但是,显然,许多共享库的实现者会分享您的想法,因此,许多系统的确要求共享库(本质上等同于外壳不可采购产品)必须标记为可执行才能使用。请参阅为什么共享库可执行?


3
@ Motte001如果确实需要,他们可以执行那些附件。这很方便。
PSkocik

2
可执行文件不是出于安全考虑:如果我拥有一个文件,chmod则可以自由使用它并使其可执行。它用于对程序中的数据进行排序,因此OP的问题是一个合理的问题。
亚历克西斯

1
@ Motte001这更是GUI文件浏览器/邮件应用程序的一项安全功能-它可以轻松地决定双击任何以“ .sh”结尾的文件都是在终端中执行(Windows风格),或者相反,双击单击“下载的附件”目录中的任何文件,将调用内置的沙盒预览应用程序。该x位只是一个额外的地方,它可以读取/写入操作提示。
IMSoP

3
我们需要可执行位的原因之一是运行setuid程序,尤其是root拥有的程序。尽管您当然可以自己执行它们,但是您需要操作系统来运行它们,以便它们具有必要的权限。在其他情况下,这确实只是一种方便。
Waleed Khan

2
“ Linux将让您阅读可以通过/ proc运行的任何内容” -好吧,我的库存Debian内核没有:/tmp$ cp /bin/cat ./cat ; chmod a-rw ./cat ; ./cat & cp /proc/$!/exe /tmp/cat2->cp: cannot stat ‘/proc/16260/exe’: Permission denied
ilkkachu

9

这是个好问题!Unix使用可执行位来区分程序和数据。OS不需要执行位,因为源脚本不会作为新进程传递给OS来执行。但是外壳程序将源脚本视为程序,并会查找$PATH您要源文件。因此,shell本身可能需要对源文件的执行权限。但事实并非如此。

这个问题一定是很久以前提出的。Bourne外壳的设计是Bell Labs居民之间“经过长时间的修改,对话,讨论”的结果,多年来,SR Bourne等人讨论了许多设计决策。不幸的是,我的快速浏览没有找到任何有关源功能的讨论(以我的辩护,很难用它来搜索它)。我确实发现的是“。” Bourne自己对Shell的早期介绍中未出现命令,但在更成熟的Version 7版本中提供了该命令。

没有权限,这是我自己的解释:

  1. .命令,又名source,实际上是文本列入(如#include在C预处理)到执行脚本或交互式会话的源代码。这样,可以说所包含的文件没有被“执行”。

  2. Unix的哲学一直是给予程序员足够的能力来吊死自己。太多的手动操作和随意的限制只会妨碍您的操作。只是在最近,一些发行版本rm -r /拒绝执行您的要求。(此命令告诉rm删除计算机上的所有内容。 请勿以root用户身份尝试它!或者更好的是,根本不要尝试。)因此,也许是Bourne。刚决定,当您尝试获取文件源时,假定您知道自己在做什么。这也避免了不必要的工作,从那时起周期就变得很重要。


但是,绝对 应该执行@cat所说的。
TOOGAM

我很惊讶它没有被标记并删除@TOOGAM,但是我放了/ s!

在那里,我不知道@cat写了什么,但是我对孩子的答案进行了更多的验证。(但是来吧,从上下文中已经很清楚了。)
Alexis

4

就操作系统而言,包含外壳脚本的文件只是数据。如果将此类数据文件的名称传递给source命令或在命令行上将其传递给bash shell的调用,则操作系统所看到的只是一个字符串,该字符串恰巧与包含数据的文件名一致。

在这种情况下,执行位将如何与之相关?


3

区别很重要,因为您可能有一个shell命令文件,该文件作为可执行文件没有用,而仅在源文件中才有用。对于此文件,您可以关闭执行位,然后除非在源命令中明确指定,否则它将永远不会被访问。发生这种情况的原因是对运行它的外壳产生副作用。对于一个特定的示例,我有一个名为fix_path的脚本,它可以查看并修改路径。


1

以防万一有人对进一步的研究和/或澄清感兴趣:在不久前实现的与POSIX兼容的外壳中,'exec_program()'和'builtin_source()'函数的内部工作非常具有示范性。在这些函数中,您确切地看到它们之间的区别是什么:

https://github.com/rsenn/shish/blob/master/src/builtin/builtin_source.c

https://github.com/rsenn/shish/blob/master/src/exec/exec_program.c

基本上,源代码可以看作是Shell临时重定向其内部文件描述符,在此它从中解析shell脚本(交互模式下的终端)。因此它与之类的其他重定向非常相似<input_file.txt>>append_to_something.list而这些重定向仅需要打开和关闭文件。

因此执行是由execve()强制执行位的系统调用来处理的。

我记得曾经看到一些系统允许执行ELF / a.out二进制文件,但是通过执行“ /lib/ld-dynamic-linker.so”并使用二进制程序(无exec位)作为第一个参数来执行。我相信这是在某些DEC Alpha或VAX机器上的(可能是SCO Unix吗?)


-2

另一个角度来看:

源脚本基本上由shell内置程序和程序调用组成。Shell内置程序(source包括它们)是Shell的一部分,并且该Shell必须首先是可执行的。每个调用的程序(即ELF,带有shebang的另一个脚本,无论如何)都必须设置执行位,否则它将无法运行。

因此,这并不反对使用执行位,因为没有执行位的任何内容都不会运行。整个源脚本都不会进行验证。它是针对每个部分分别执行的,但是确实如此。


Shell内建不是从Shell脚本获得的。通常,它们是Shell可执行文件的一部分。在ksh93,zsh和其他几个shell中,它们可以是shell扩展。
fpmurphy

@ fpmurphy1这不是我的句子“ shell Builtins(...)是shell的一部分”吗?
卡米尔Maciorowski
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.