Stale memory during post fork cow pmap update
Elliott.Rabe at dell.com
Elliott.Rabe at dell.com
Sat Feb 10 21:57:44 UTC 2018
On 02/10/2018 05:18 AM, Konstantin Belousov wrote:
> On Sat, Feb 10, 2018 at 05:12:11AM +0000, Elliott.Rabe at dell.com wrote:
>> Greetings-
>>
>> I've been hunting for the root cause of elusive, slight memory
>> corruptions in a large, complex process that manages many threads. All
>> failures and experimentation thus far has been on x86_64 architecture
>> machines, and pmap_pcid is not in use.
>>
>> I believe I have stumbled into a very unlikely race condition in the way
>> the vm code updates the pmap during write fault processing following a
>> fork of the process. In this situation, when the process is forked,
>> appropriate vm entries are marked copy-on-write. One such entry
>> allocated by static process initialization is frequently used by many
>> threads in the process. This makes it a prime candidate to write-fault
>> shortly after a fork system call is made. In this scenario, such a
>> fault normally burdens the faulting thread with the task of allocating a
>> new page, entering the page as part of managed memory, and updating the
>> pmap with the new physical address and the change to writeable status.
>> This action is followed with an invalidation of the TLB on the current
>> CPU, and in this case is also followed by IPI_INVLPG IPIs to do the same
>> on other CPUs (there are often many active threads in this process).
>> Before this remote TLB invalidation has completed, other CPUs are free
>> to act on either the old OR new page characteristics. If other threads
>> are alive and using contents of the faulting page on other CPUs, bad
>> things can occur.
>>
>> In one simplified and somewhat contrived example, one thread attempts to
>> write to a location on the faulting page under the protection of a lock
>> while another thread attempts to read from the same location twice in
>> succession under the protection of the same lock. If both the writing
>> thread and reading thread are running on different CPUs, and if the
>> write is directed to the new physical address, the reads may come from
>> different physical addresses if a TLB invalidation occurs between them.
>> This seemingly violates the guarantees provided by the locking
>> primitives and can result in subtle memory corruption symptoms.
>>
>> It took me quite a while to chase these symptoms from user-space down
>> into the operating system, and even longer to end up with a stand-alone
>> test fixture able to reproduce the situation described above on demand.
>> If I alter the kernel code to perform a two-stage update of the pmap
>> entry, the observed corruption symptoms disappear. This two-stage
>> mechanism updates and invalidates the new physical address in a
>> read-only state first, and then does a second pmap update and
>> invalidation to change the status to writeable. The intended effect was
>> to cause any other threads writing to the faulting page to become
>> obstructed until the earlier fault is complete, thus eliminating the
>> possibility of the physical pages having different contents until the
>> new physical address was fully visible. This is goofy, and from an
>> efficiency standpoint it is obviously undesirable, but it was the first
>> thing that came to mind, and it seems to be working fine.
>>
>> I am not terribly familliar with the higher level design here, so it is
>> unclear to me if this problem is simply a very unlikely race condition
>> that hasn't yet been diagnosed or if this is instead the breakdown of
>> some other mechanism of which I am not aware. I would appreciate the
>> insights of those of you who have more history and experience with this
>> area of the code.
> You are right describing the operation of the memory copy on fork. But I
> cannot understand what parts of it, exactly, are problematic, from your
> description. It is necessary for you to provide the test and provide
> some kind of the test trace or the output which illustrates the issue
> you found.
>
> Do you mean something like that:
> - after fork
> - thread A writes into the page, causing page fault and COW because the
> entry has write permissions removed
> - thread B reads from the page, and since invalidation IPI was not yet
> delivered, it reads from the need-copy page, effectively seeing the
> old content, before thread A write
>
> And you claim is that you can create a situation where both threads A
> and B owns the same lock around the write and read ? I do not understand
> this, since if thread A owns a (usermode) lock, it prevents thread B from
> taking the same lock until fault is fully handled, in particular, the IPI
> is delivered.
Here is the sequence of actions I am referring to. There is only one
lock, and all the writes/reads are on one logical page.
+The process is forked transitioning a map entry to COW
+Thread A writes to a page on the map entry, faults, updates the pmap to
writable at a new phys addr, and starts TLB invalidations...
+Thread B acquires a lock, writes to a location on the new phys addr,
and releases the lock
+Thread C acquires the lock, reads from the location on the old phys addr...
+Thread A ...continues the TLB invalidations which are completed
+Thread C ...reads from the location on the new phys addr, and releases
the lock
In this example Thread B and C [lock, use and unlock] properly and
neither own the lock at the same time. Thread A was writing somewhere
else on the page and so never had/needed the lock. Thread B sees a
location that is only ever read|modified under a lock change beneath it
while it is the lock owner.
I will get a test patch together and make it available as soon as I can.
More information about the freebsd-hackers
mailing list