From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 12894 invoked by alias); 10 Sep 2016 08:49:51 -0000 Mailing-List: contact libffi-discuss-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libffi-discuss-owner@sourceware.org Received: (qmail 12881 invoked by uid 89); 10 Sep 2016 08:49:50 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: =?ISO-8859-1?Q?No, score=-0.2 required=5.0 tests=BAYES_00,FREEMAIL_FROM,RCVD_IN_DNSWL_LOW,RCVD_IN_SORBS_SPAM,SPF_PASS autolearn=no version=3.3.2 spammy=bientt, bient=c3=b4t, H*Ad:D*co.uk?= X-HELO: mail-wm0-f53.google.com Received: from mail-wm0-f53.google.com (HELO mail-wm0-f53.google.com) (74.125.82.53) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Sat, 10 Sep 2016 08:49:40 +0000 Received: by mail-wm0-f53.google.com with SMTP id 199so9965109wma.1 for ; Sat, 10 Sep 2016 01:49:39 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:sender:from:date:message-id:subject :to:cc:content-transfer-encoding; bh=Pd2wMNPJh7Ue772wUOJbplzJ8gfxMBTIT67HY0xrFSc=; b=l/rfqaznqGcTO0AhRgJOj1vh2NLMUI9LKf7tjUcfhlo2qzvVziYydnW5drDOjZhf/S SBugdveEvX78Q1iFr9uABFNM5nsgC2VnEs1Vj0w5Hz2ETdTdPryNpDRyjyRafynL4o8e av+Oa1QQUS5dnulnRdgOIzYb2ulStKzLGddJna3vvx03buvH7JKE13RXmOv36+aCGcW0 TMxu/F+Hm+h63OSetifDLcVf1jw8hlJHC0DUEXT37K2NbSP3AyVFwT4Dnb0lEKkNrMaW RUhDl6dX7eUoMEkQm9qiqhstqCKFS2oA5nrmNPozQN059eCBjkEFWTZdNTCn6Jm1SilS 8IOg== X-Gm-Message-State: AE9vXwPcFLEJu4e9r31ui6Zs3g5MKu5LQFzVK2kDEddpAfcIAYUM9aKROiCFpRX++3jbvzkO0JXIgAm3O0NIlA== X-Received: by 10.194.134.101 with SMTP id pj5mr6607467wjb.59.1473497377645; Sat, 10 Sep 2016 01:49:37 -0700 (PDT) MIME-Version: 1.0 Received: by 10.28.141.197 with HTTP; Sat, 10 Sep 2016 01:48:57 -0700 (PDT) From: Armin Rigo Date: Sat, 10 Sep 2016 08:49:00 -0000 Message-ID: Subject: Crashes of libffi when using W^X memory and forks To: libffi-discuss@sourceware.org Cc: Edd Barrett Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-SW-Source: 2016/txt/msg00039.txt.bz2 Hi all, Libffi supports "W^X" memory, which is the name of a security- related operating system feature where a process cannot have both writeable and executable memory. (I don't want to discuss the pros and cons of it, let's just accept that operating system tend to move in that direction nowadays.) However, that support doesn't really work in the presence of fork(), and it can't be easily fixed to. The problem is the following. When we create a callback, ffi_closure_alloc() alloctes memory in MAP_SHARED mmap pages, in such a way that the same memory is visible twice: once at some address as writeable memory, and once at some different address as executable memory. (There is a custom 5000-lines implementation of dlmalloc() in the source of libffi just to allocate small fixed-size pieces out of these pages.) The problem is that this approach fails if the main program calls fork() and both parent and child continue to use the same memory---still shared between them, so now visible in four places instead of two. Small examples as easy to come by: it is enough that both parent and child call any of the ffi_closure_alloc() or ffi_closure_free() functions. The crash occurs typically because dlmalloc gets confused by the other process' dlmalloc (unsynchronized writes to shared memory). It can also occur because one process calls ffi_closure_free() then allocates a fresh new closure at the same place; when the other process tries to use the old callback, it sees suddenly different bytes in it. This is not only a theoretical problem. Some time ago I opened http://bugs.python.org/issue25653 summarizing the situation for CPython's libffi-based ctypes module. The issue contains references to other places. If fixing this situation the easy way appears impossible, we would like to fix it the hard way. That means changing the internals of libffi, so that it stops relying on MAP_SHARED memory. Something similar is already done for the ARM platform; see FFI_EXEC_TRAMPOLINE_TABLE. The more general idea is to allocate a few pages of memory, fill the first page with precomputed executable trampolines, and turn that first page into read-only executable memory. Each trampoline references data that will be stored in the other pages, but isn't so far. Every time ffi_closure_alloc() is called, we grab one of these trampolines. Every time ffi_prep_closure{_loc}() is called, we fill the corresponding data; but the code is already there and read-only. This makes the extra argument to ffi_prep_closure_loc() unneeded again, but we can certainly support it for backward compatibility. Moreover, ffi_prep_closure() can check that the pointer was really obtained with ffi_closure_alloc(), and if not it must still fill in the code (and hope that then the OS is not running W^X). This concerns most platforms, not just one. It can probably be done incrementally, though. We could deprecate "closures.c" but keep it here for transition, and write a new "closures2.c" which would be used instead of "closures.c". This would remove the need for selinux_enabled_check() and dlmalloc.c. (The different is_emutramp_enabled() probably needs to stay.) Does this sound like a plan? A bient=C3=B4t, Armin.