From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 0211D2139CB for ; Fri, 10 Jan 2025 18:41:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736534476; cv=none; b=Koj+Vo4SaO17oWD1zhO0sEA+pCbsbzso8oHRSqHfGds7M0OAwCguZ6QqOpVSqN5ZhuWgY16Ug9JL9UpL8u5TjGPep3Axh7iZCGiTijZFZwPyVrTNGKjp35v04FzDJGzfIlPExdaIwKiFf3nknXfNNP12uIBMdo5iGt2g/9X5g/o= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736534476; c=relaxed/simple; bh=ZM72RhQ6qnt3wRLE5uCO1XWpDw9lUSY2Nc3eyB8yMs4=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=FzS9CcmaNKh5JJUVH31snhcpg/ajWxKtwBzVSfJ+HkoIhpth2KJ0Oj7xWNZomnAAgyzaJJyv8HWn2lx2XdRZo+V6VQAUFb/BSDybzkEQ5FN3RX3bXr7KDo3R5Q9lM5iHWYHKalg2Cz4VSLK3HKGyrC0nY5yMRaokoF94y1yN5AM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id E171D11FB; Fri, 10 Jan 2025 10:41:33 -0800 (PST) Received: from J2N7QTR9R3 (usa-sjc-imap-foss1.foss.arm.com [10.121.207.14]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 6BBC63F59E; Fri, 10 Jan 2025 10:41:03 -0800 (PST) Date: Fri, 10 Jan 2025 18:41:00 +0000 From: Mark Rutland To: Peter Zijlstra Cc: Mathieu Desnoyers , libc-alpha , Florian Weimer , "carlos@redhat.com" , linux-kernel , x86@kernel.org, paulmck , Michael Jeanson , Andy Lutomirski , Will Deacon Subject: Re: Prevent inconsistent CPU state after sequence of dlclose/dlopen Message-ID: References: <20250110165412.GC4213@noisy.programming.kicks-ass.net> <8c1ad304-61bb-4bdf-aa75-8633f3d0196c@efficios.com> <20250110171112.GF4213@noisy.programming.kicks-ass.net> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20250110171112.GF4213@noisy.programming.kicks-ass.net> Hi Peter, [adding Andy and Will, since we've discussed related cases in the past] On Fri, Jan 10, 2025 at 06:11:12PM +0100, Peter Zijlstra wrote: > On Fri, Jan 10, 2025 at 12:02:27PM -0500, Mathieu Desnoyers wrote: > > On 2025-01-10 11:54, Peter Zijlstra wrote: > > > On Fri, Jan 10, 2025 at 10:55:36AM -0500, Mathieu Desnoyers wrote: > > > > Hi, > > > > > > > > I was discussing with Mark Rutland recently, and he pointed out that a > > > > sequence of dlclose/dlopen mapping new code at the same addresses in > > > > multithreaded environments is an issue on ARM, and possibly on Intel/AMD > > > > with the newer TLB broadcast maintenance. > > > > > > What is the exact race? Should not munmap() invalidate the TLBs before > > > it allows overlapping mmap() to complete? > > > > The race Mark mentioned (on ARM) is AFAIU the following scenario: > > > > CPU 0 CPU 1 > > > > - dlopen() > > - mmap PROT_EXEC @addr > > - fetch insn @addr, CPU state expects unchanged insn. > > - execute unrelated code > > - dlclose(addr) > > - munmap @addr > > - dlopen() > > - mmap PROT_EXEC @addr > > - fetch new insn @addr. Incoherent CPU state. For the benefit of others, what I specifically said was: | There's a fun (latent, been around forever) issue whereby reusing the | same VA for different code (e.g. dlopen() ... dlclose() ... dlopen()) | could blow up in a multi-threaded environment I hadn't reported this on a list yet because there are many subtleties, this is vanishingly unlikely to occur in practice today, and I didn't want to get people excited/confused/angry over an incomplete or misleading description. > Urgh.. Mark, is this because of non-coherent i-cache or somesuch misery? Sort-of. The key detail is that while instructions are being executed (including speculative execution), the CPU pipeline/OoO-engine/whatever effectively caches a copy of an instruction while it is "in-flight" (e.g. potentially broken down into micro-ops): On the ARM architecture, those in-flight copies are only guaranteed to be discarded by a context-synchronization-event, and are not guaranteed to be discarded due to TLB maintenance, data cache maintenance, or instruction cache maintenance. Instruction cache maintenance will guarantee that *subsequent* fetches from any instruction cache observe the new value. The first time a page of executable code is mapped in at a VA, this isn't a problem because there was nothing previously at that VA which could have been fetched from (since entering userspace, as exception return from kernel to user provides a context-synchronization-event). However, if some code A is mapped at a VA, then unmapped, then some distinct code B is mapped at that VA, then some CPUs might still have code A in-flight, regardless of TLB and cache maintenance, unless a context-synchronization-event occurs. Imagine you have a CPU microarchitecture with a long speculative execution window, and you have to threads running pinned on two CPUs sharing an address space, with some shared function pointer P which is initially NULL. Then you have something like: Thread 0 Thread 1 // Speculating some long-to-resolve // sequence of instructions. - mmap() code A at VA X - enters kernel - kernel loads data into page - kernel performs D$ + I$ maintenance - kernel updates page tables - returns to userspace // Begins speculating if (P) { P() }; // Begins speculating P(), predicted as // VA X. // Fetches code A from X into pipeline // Code A now in-flight - munmap() VA X - enters kernel - updates page tables - performs TLB maintenance (broadcast) - returns to userspace // Code A still in-flight - mmap() code B at VA X - enters kernel - kernel loads data into page - kernel performs D$ + I$ maintenance // Code A no longer in I$ // Code A still in-flight - kernel updates page tables - returns to userspace // Code A still in-flight - Publishes P as pointer in code B (e.g. WRITE_ONCE(P, X)) // Completes speculation of // long-to-resolve sequence. // Resolves P is VA X, and // commits speculation of code A ... and BANG, stale instructions executed. Note that on architectures that use IPIs for TLB invalidation (e.g. x86 today), the munmap() is likely to provide the serialization to discard the in-flight copy by virtue of the IPI. Practically speaking, actually hitting this is very unlikely because you need to get very unlucky with predication, the predicted instructions need to remain in-flight for a very long time without being discarded for other reasons (e.g. a mis-prediction, IRQ, etc), and the VA needs to be reused within that time window. > But shouldn't flush_{,i}cache_range() or something along those lines not > handle this? Unfortunately not, those only affect the explicit data/instruction caches, and not the in-flight copies of the instructions. Mark.