From nobody Wed Nov 03 11:30:26 2021 X-Original-To: freebsd-hackers@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 1028D1821AC1 for ; Wed, 3 Nov 2021 11:30:34 +0000 (UTC) (envelope-from sir@cmpwn.com) Received: from out1.migadu.com (out1.migadu.com [91.121.223.63]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 4Hkl2128VPz3KVJ for ; Wed, 3 Nov 2021 11:30:33 +0000 (UTC) (envelope-from sir@cmpwn.com) List-Id: Technical discussions relating to FreeBSD List-Archive: https://lists.freebsd.org/archives/freebsd-hackers List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-freebsd-hackers@freebsd.org MIME-Version: 1.0 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cmpwn.com; s=key1; t=1635939032; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=RPY6UXPFfnXtiuCRw8mpAdiNMyMtAnzZyfI8vJ7Ixcs=; b=Qovj+X+01WNXfeS9AJ+3nxywyMLKiIgOkJzttUR9IWzH7iy0cFr3GbzsKRAcpWxeoKjWic PIWsYFx3Hljc2m1jmwey7k7jPerQcsZnl4D7dzVl3UNugKkefMTWfZn5xNexFzgeMfrtOs ZsrHw8zAS0YNaxzaTG63w09gv8O4ekRM/1s9MDtjVfXKef7E5l1eX1aBuX0a4xsMCm/+w1 smUH0UIgt2EG+D4HyxnROP+XLmUCohIOsBcgabJ1Vi3gNwuL93634LDoi9morNV25kAS0g iwSw0pH6LdAe5mEh9b6/zBU8y9Qwp5d2kRBy9YBXAVU3+oxE22gtqIwPvw+8QA== Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=UTF-8 Date: Wed, 03 Nov 2021 12:30:26 +0100 Message-Id: Subject: Complicated interactions between O_EXEC, fdescfs, fexecve, and shebangs X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: "Drew DeVault" To: X-Migadu-Flow: FLOW_OUT X-Migadu-Auth-User: sir@cmpwn.com X-Rspamd-Queue-Id: 4Hkl2128VPz3KVJ X-Spamd-Bar: --- Authentication-Results: mx1.freebsd.org; dkim=pass header.d=cmpwn.com header.s=key1 header.b=Qovj+X+0; dmarc=pass (policy=none) header.from=cmpwn.com; spf=pass (mx1.freebsd.org: domain of sir@cmpwn.com designates 91.121.223.63 as permitted sender) smtp.mailfrom=sir@cmpwn.com X-Spamd-Result: default: False [-3.60 / 15.00]; ARC_NA(0.00)[]; NEURAL_HAM_MEDIUM(-1.00)[-1.000]; R_DKIM_ALLOW(-0.20)[cmpwn.com:s=key1]; RCVD_IN_DNSWL_LOW(-0.10)[91.121.223.63:from]; FROM_HAS_DN(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; R_SPF_ALLOW(-0.20)[+ip4:91.121.223.63]; MIME_GOOD(-0.10)[text/plain]; TO_DN_NONE(0.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; RCPT_COUNT_ONE(0.00)[1]; DKIM_TRACE(0.00)[cmpwn.com:+]; DMARC_POLICY_ALLOW(-0.50)[cmpwn.com,none]; NEURAL_HAM_SHORT(-1.00)[-1.000]; RCVD_COUNT_ZERO(0.00)[0]; FROM_EQ_ENVFROM(0.00)[]; MID_RHS_NOT_FQDN(0.50)[]; MIME_TRACE(0.00)[0:+]; ASN(0.00)[asn:16276, ipnet:91.121.0.0/16, country:FR]; RWL_MAILSPIKE_POSSIBLE(0.00)[91.121.223.63:from] X-ThisMailContainsUnwantedMimeParts: N Note: I am not subscribed to this list, please use reply-all to keep me on the thread. Thanks! $ uname -a FreeBSD megumin 13.0-RELEASE FreeBSD 13.0-RELEASE #0 releng/13.0-n244733-ea= 31abc261f: Fri Apr 9 04:24:09 UTC 2021 root@releng1.nyi.freebsd.org:/usr/o= bj/usr/src/amd64.amd64/sys/GENERIC amd64 This problem starts with the following program: #include #include extern char **environ; int main(void) { int fd =3D open("./test.sh", O_EXEC); char *argv[] =3D { "./test.sh", NULL }; fexecve(fd, argv, environ); } Given this test.sh, which is executable: #!/bin/sh echo hello world This program produces the following error: /bin/sh: cannot open /dev/fd/3: Permission denied The program works fine with O_RDONLY instead, which makes some sense. The way this works is that the kernel rewrites argv to {"/bin/sh", "/dev/fd/%d"}, where %d is the file descriptor passed to fexecat. The interpreter then has to open this file for reading, so it needs the read bit set. fdescfs preserves the permissions of the file descriptor which was originally opened, so the read bit is missing with O_EXEC. Q.E.D. The fix is to set O_RDONLY and mount fdescfs. If nothing else comes of this, I would like to request that FreeBSD consider mounting fdescfs by default, so that fexecve can be reliably expected to work correctly with interpreters. Otherwise, the value proposition of fexecve is severely limited. However, a few other problems came up while looking into this. The investigation was made more difficult by the fact that open(2) is documented in the man page as producing EINVAL when O_EXEC is combined with O_RDONLY, but this is not so: no error occurs. This is because O_RDONLY is, in fact, not a bit: it is zero. You cannot NOT provide O_RDONLY to an open call. RhodiumToad on #freebsd IRC gave a possible improvement for the man page: > Only one of O_EXEC, O_RDWR and O_WRONLY may be specified. The other issue is that this essentially makes O_EXEC useless outside of some specific cases, where the user knows for certain that the file being executed is not a script. The combination of O_EXEC and fexecve cannot generalize to support all use-cases of execve, which is frustrating because my code either (A) cannot be TOCTOU or (B) needs some awful special cases. Even in case (B), it would not generalize to the case where I have execute, but not read, permission for a script, but the interpreter has both. I'm not sure what the answer for any of this is. By way of contrast, Linux solves this problem a bit differently. It does not have O_EXEC, but it does have O_PATH, which opens a file descriptor without read, write, OR execute, but simply to keep track of an inode reference. fexecve on Linux then uses a similar /dev/fd trick, but the file in /dev/fd has no mode bits set and I'm not sure why it works.