From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from kanga.kvack.org (kanga.kvack.org [205.233.56.17]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1ED53C7EE30 for ; Tue, 24 Jun 2025 19:34:09 +0000 (UTC) Received: by kanga.kvack.org (Postfix) id 4FC8A6B00AC; Tue, 24 Jun 2025 15:34:08 -0400 (EDT) Received: by kanga.kvack.org (Postfix, from userid 40) id 4AC4D6B00AD; Tue, 24 Jun 2025 15:34:08 -0400 (EDT) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id 375DB6B00AE; Tue, 24 Jun 2025 15:34:08 -0400 (EDT) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0015.hostedemail.com [216.40.44.15]) by kanga.kvack.org (Postfix) with ESMTP id 1B5906B00AC for ; Tue, 24 Jun 2025 15:34:08 -0400 (EDT) Received: from smtpin13.hostedemail.com (a10.router.float.18 [10.200.18.1]) by unirelay04.hostedemail.com (Postfix) with ESMTP id E02461A0FA8 for ; Tue, 24 Jun 2025 19:34:07 +0000 (UTC) X-FDA: 83591294934.13.289C82E Received: from mail-pf1-f201.google.com (mail-pf1-f201.google.com [209.85.210.201]) by imf01.hostedemail.com (Postfix) with ESMTP id 12AD74000A for ; Tue, 24 Jun 2025 19:34:05 +0000 (UTC) Authentication-Results: imf01.hostedemail.com; dkim=pass header.d=google.com header.s=20230601 header.b=n6fSsQuD; spf=pass (imf01.hostedemail.com: domain of 3rP1aaAYKCF0NPM9I6BJJBG9.7JHGDIPS-HHFQ57F.JMB@flex--surenb.bounces.google.com designates 209.85.210.201 as permitted sender) smtp.mailfrom=3rP1aaAYKCF0NPM9I6BJJBG9.7JHGDIPS-HHFQ57F.JMB@flex--surenb.bounces.google.com; dmarc=pass (policy=reject) header.from=google.com ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=hostedemail.com; s=arc-20220608; t=1750793646; h=from:from:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:content-transfer-encoding: in-reply-to:in-reply-to:references:references:dkim-signature; bh=BeEKEjYjVxEUNIjcbbAJOoiOTSUHH2co8aPuVjJv1wU=; b=5B/tuJ2Mtow92AqeUZXbW9jqlOGRZt2XUI99ekBoGICo7ICh8gvLGEocCmJRn8Em3wjymC oZ5XbWzgQHraW055pxJ6yYaQ+13Jm1kAucPOiA3cIrkxgdRkIyWh6XqtZGJdcgCt/cQQUj Ed3vQRScv2FhB6A4ZJiGu9sN1kGJhew= ARC-Authentication-Results: i=1; imf01.hostedemail.com; dkim=pass header.d=google.com header.s=20230601 header.b=n6fSsQuD; spf=pass (imf01.hostedemail.com: domain of 3rP1aaAYKCF0NPM9I6BJJBG9.7JHGDIPS-HHFQ57F.JMB@flex--surenb.bounces.google.com designates 209.85.210.201 as permitted sender) smtp.mailfrom=3rP1aaAYKCF0NPM9I6BJJBG9.7JHGDIPS-HHFQ57F.JMB@flex--surenb.bounces.google.com; dmarc=pass (policy=reject) header.from=google.com ARC-Seal: i=1; s=arc-20220608; d=hostedemail.com; t=1750793646; a=rsa-sha256; cv=none; b=L4yPmZmqErrvk5nf1VyF8WzYfnXCf+oTINaEj/J+fxTcXNC+sS0f3jnRCQGSMdEnlsEy+y OPkNnX6j49V0/YzYlFu/q4PM271yOHzz025yH8yMSjC5bYbst36C3LQjofEaqUn2cQOlxj sLda8NfxAoXIdWxKrH4xLGoAAkOlx+A= Received: by mail-pf1-f201.google.com with SMTP id d2e1a72fcca58-748a4f5e735so4567220b3a.3 for ; Tue, 24 Jun 2025 12:34:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1750793645; x=1751398445; darn=kvack.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=BeEKEjYjVxEUNIjcbbAJOoiOTSUHH2co8aPuVjJv1wU=; b=n6fSsQuD437gv4NbSz4XSHr4bbJK/ygstt+kJ8D97ThCGw6bXl1FaggmoFCIpR69mK jEey7YB2/m6B+M4XABsT8IPUPCCqiRijYHabvm/WrIa3f5nHG+R7rYWPgs2avszAkcOV cNJmUaunxXSoKJT0UvQ4Cy2HRUOVJxO8HLzIiBIlo/bb+cYAHkIef/h5bS86ocRSfhTw TOYPSdTZUoQ8fHGa66HDTn4FJDbKqAd6leiBYVmtEO2nHaDqhOwtdwqdlN+Fjf1THFyh 03ci9zj763I/d8sFIM5FTNI9JBVbLYAsEZM8Pp0D4R5nkE0GP2+xO5/yz+DlBrcfz4eH 7iwQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1750793645; x=1751398445; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=BeEKEjYjVxEUNIjcbbAJOoiOTSUHH2co8aPuVjJv1wU=; b=hedVmmoO6ZvJZhRFJivHT2DfFLgJTEfnwDiWamurmuVI48jhTdjMbnYy6g/MPMbVtC 6K4R9C1hEcIlsZz6HWhE/k0TaplloiVmhVNv75dSjaB3MpJpHxVSi49daZTkWsT2BIiD Htw1TNxKlnB38L5K5PKiaMhChXmDMA++yCj++5jITUjDvgnazus2z36stdQyGgq4B5tP 8Zwyhw0uJLzitpseJEbtOsw1HZ/pT/x21ziImAZeoUZZtWKVt+SgjZmgytclONo7+xHJ oHDIM2xlseRwIeWY4XaosoUDHjQJuiwsWHdmZg/pZYP1SH6kRFQiGv0gSR3mSoLbzLy5 Vqvg== X-Forwarded-Encrypted: i=1; AJvYcCVGKpcWSgGCaTy7+WWK/Vf3ay85rRQ/5dB7ml8pli/DTLQPp8Ful8kCKgdwmODlxMrxjEOkrHsVlg==@kvack.org X-Gm-Message-State: AOJu0YyWhaDWt7D8ZlE8nDLbMZT3TRxobq46NytgsqOOrhrXHbnkWXQX LzJmw2Lz1Qbk4woAi9MBmu1Np/BymLy/HWE/5V9iKBzhYOJFVfxgmqNGfwQHinMjx7yiYHkTLC8 2n4IfXg== X-Google-Smtp-Source: AGHT+IEgCjAq9juT5ySc6JmV7oHzO4880HbobM8qMc/PLoiGP0YoWDbXfdbACCK9q6rB9NqgbQp1XxC8b50= X-Received: from pfkh17.prod.google.com ([2002:a05:6a00:11:b0:746:2897:67e3]) (user=surenb job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a21:9993:b0:217:4f95:6a51 with SMTP id adf61e73a8af0-2207f28f862mr303921637.29.1750793644735; Tue, 24 Jun 2025 12:34:04 -0700 (PDT) Date: Tue, 24 Jun 2025 12:33:53 -0700 In-Reply-To: <20250624193359.3865351-1-surenb@google.com> Mime-Version: 1.0 References: <20250624193359.3865351-1-surenb@google.com> X-Mailer: git-send-email 2.50.0.714.g196bf9f422-goog Message-ID: <20250624193359.3865351-2-surenb@google.com> Subject: [PATCH v5 1/7] selftests/proc: add /proc/pid/maps tearing from vma split test From: Suren Baghdasaryan To: akpm@linux-foundation.org Cc: Liam.Howlett@oracle.com, lorenzo.stoakes@oracle.com, david@redhat.com, vbabka@suse.cz, peterx@redhat.com, jannh@google.com, hannes@cmpxchg.org, mhocko@kernel.org, paulmck@kernel.org, shuah@kernel.org, adobriyan@gmail.com, brauner@kernel.org, josef@toxicpanda.com, yebin10@huawei.com, linux@weissschuh.net, willy@infradead.org, osalvador@suse.de, andrii@kernel.org, ryan.roberts@arm.com, christophe.leroy@csgroup.eu, tjmercier@google.com, kaleshsingh@google.com, linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-mm@kvack.org, linux-kselftest@vger.kernel.org, surenb@google.com Content-Type: text/plain; charset="UTF-8" X-Rspam-User: X-Rspamd-Server: rspam09 X-Rspamd-Queue-Id: 12AD74000A X-Stat-Signature: chpbtk8iim9aga3opkr1uyjqo3sp6oj8 X-HE-Tag: 1750793645-998503 X-HE-Meta: U2FsdGVkX18jF50dMyhnlRSdI/ei4FpQbZQGIioxMiNrTgTI7bxd2ZnLNFjs9pVcVBknCZkKbCHBMTHW6aL+HVKH0NL3p56AN+Gk0P1XzF5eunD4OOW7qQ+6yj3gRNF5kX41rqeFge6kYZhOUc9bYU/UPaJpownGoQRQ4LRR/DOR+jrSMR6/Ey2rrZ+zEmRnQHG/iS9tMP6iXFFqCUDe6y5F60PvVbgGEI5GUjLIDApvgytFC9KHXVYHhQYNyptXEPa/GdA3lqJyvN+s2HHRu7Ku5kGnYm5hBHUQXYMbwT+Oj6+zhu2N9oEFpyE1AV8v0niNRp25DyrC4LeeLY8fva/ZDtfbeujW2cYlXY60pyI+pN6AZDWYAQx8nfcUrTI9Opx53cWIbrqq5CaW5Ub4JJv3mSZL8Sb0cjV6ITsvXf+cBdWeCANgFKokVWu/4JRnV11LZaK3vWUN7mJeaulCOskC6WONpMfK6Mrlu+hcV1vhZB+O+VmTfkTX6Kxk8+7A2QyUc2IOdZbCjfrWa0zalkqnhGG2KypGmtGrRRUmrSXY/Ad42UdTTbcVWBfIhgPBklr8s9x7q6B5B4HpCp3E5u+kApVlx6etZZ5Bh+SJwtR0/bjKsM//dtLaUVKaMeKXQwBiKHWTCNUcSvZVlu9/4uSFEE5s1Cc/0xLjl7nrtzZh7wYXA1GsFZ0h/87DNNrq7WEpUYWRZ6fGxFSB5ONLPTU4SV6Dn0imlzYU9w/nwH+3ZQYV9uiwYCQMTdYoi/LjeREteJwFJZTBzyJ0m73jZNBSq0evcfDON17XukKHkGyXRFpJqfVMdhDyMEmQ2kaxdJRcRDpM7362tVVIChguRKKvFavIuFHs4pOXw5lrsro4Bf78gb63O2wIO7i5I5O9Htl69L2ItoVnj7Gjut9L12CjWNgnWS7FzVLi9afzwqxvFEerQmH/8CM1xVu6u/l5kAqMQT/DpVR/Xwm2W6z T/6Kq/R1 wmZGGKfNFhPyydJam+IGIiqoEIoO5t6MZunZwm7WSov/Qrqik8f3+YPtDEkJl9PGs02h5G70No7XCpju3Mj59xPaCFEheWV9kgDGLFXF11FgKcx2zepmzoW/mXf5xykAjP7ktHX4/VkryyS+9tx8BDO1DuPLQiwEpwdUH2tBN+FWGm7hQCMSasC2iJW02GmnR5Wi7joXTKdRZ6r8+lWT0EzrM92kRjdu5AovCv+lLmopqhsvaK1xZ5+3QewAlJQgNRAaJYerLEbrsNDyP0HkyN1E07VHOLMzBchYyV16f9XusFshNemrFksw3asHmcPY7v7hn4Ah4chiGuUkU/OZH8WGYnv/Mv1DVO0fexxCg+R1Iih+66gMz4Suz/VOHLklW7klC2V5QU/MXz9kF37JqKIu4fBS/31qSiVTC+4eiDHCLey1tVzOPx82QLDepdO2hHZC2VaDgCq95f4zvEHqZkWm796sNJ5QFkwQ5tqGeHA5QgWiscBOg5It6/wvI5VbGSWZu54Q+9SOTsfjregblL6tuV3v1sh5zpxhG9vXoMdK0PsY= X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.4 Sender: owner-linux-mm@kvack.org Precedence: bulk X-Loop: owner-majordomo@kvack.org List-ID: List-Subscribe: List-Unsubscribe: The /proc/pid/maps file is generated page by page, with the mmap_lock released between pages. This can lead to inconsistent reads if the underlying vmas are concurrently modified. For instance, if a vma split or merge occurs at a page boundary while /proc/pid/maps is being read, the same vma might be seen twice: once before and once after the change. This duplication is considered acceptable for userspace handling. However, observing a "hole" where a vma should be (e.g., due to a vma being replaced and the space temporarily being empty) is unacceptable. Implement a test that: 1. Forks a child process which continuously modifies its address space, specifically targeting a vma at the boundary between two pages. 2. The parent process repeatedly reads the child's /proc/pid/maps. 3. The parent process checks the last vma of the first page and the first vma of the second page for consistency, looking for the effects of vma splits or merges. The test duration is configurable via the -d command-line parameter in seconds to increase the likelihood of catching the race condition. The default test duration is 5 seconds. Example Command: proc-pid-vm -d 10 Signed-off-by: Suren Baghdasaryan --- tools/testing/selftests/proc/proc-pid-vm.c | 430 ++++++++++++++++++++- 1 file changed, 429 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/proc/proc-pid-vm.c b/tools/testing/selftests/proc/proc-pid-vm.c index d04685771952..6e3f06376a1f 100644 --- a/tools/testing/selftests/proc/proc-pid-vm.c +++ b/tools/testing/selftests/proc/proc-pid-vm.c @@ -27,6 +27,7 @@ #undef NDEBUG #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +72,8 @@ static void make_private_tmp(void) } } +static unsigned long test_duration_sec = 5UL; +static int page_size; static pid_t pid = -1; static void ate(void) { @@ -281,11 +285,431 @@ static void vsyscall(void) } } -int main(void) +/* /proc/pid/maps parsing routines */ +struct page_content { + char *data; + ssize_t size; +}; + +#define LINE_MAX_SIZE 256 + +struct line_content { + char text[LINE_MAX_SIZE]; + unsigned long start_addr; + unsigned long end_addr; +}; + +static void read_two_pages(int maps_fd, struct page_content *page1, + struct page_content *page2) +{ + ssize_t bytes_read; + + assert(lseek(maps_fd, 0, SEEK_SET) >= 0); + bytes_read = read(maps_fd, page1->data, page_size); + assert(bytes_read > 0 && bytes_read < page_size); + page1->size = bytes_read; + + bytes_read = read(maps_fd, page2->data, page_size); + assert(bytes_read > 0 && bytes_read < page_size); + page2->size = bytes_read; +} + +static void copy_first_line(struct page_content *page, char *first_line) +{ + char *pos = strchr(page->data, '\n'); + + strncpy(first_line, page->data, pos - page->data); + first_line[pos - page->data] = '\0'; +} + +static void copy_last_line(struct page_content *page, char *last_line) +{ + /* Get the last line in the first page */ + const char *end = page->data + page->size - 1; + /* skip last newline */ + const char *pos = end - 1; + + /* search previous newline */ + while (pos[-1] != '\n') + pos--; + strncpy(last_line, pos, end - pos); + last_line[end - pos] = '\0'; +} + +/* Read the last line of the first page and the first line of the second page */ +static void read_boundary_lines(int maps_fd, struct page_content *page1, + struct page_content *page2, + struct line_content *last_line, + struct line_content *first_line) +{ + read_two_pages(maps_fd, page1, page2); + + copy_last_line(page1, last_line->text); + copy_first_line(page2, first_line->text); + + assert(sscanf(last_line->text, "%lx-%lx", &last_line->start_addr, + &last_line->end_addr) == 2); + assert(sscanf(first_line->text, "%lx-%lx", &first_line->start_addr, + &first_line->end_addr) == 2); +} + +/* Thread synchronization routines */ +enum test_state { + INIT, + CHILD_READY, + PARENT_READY, + SETUP_READY, + SETUP_MODIFY_MAPS, + SETUP_MAPS_MODIFIED, + SETUP_RESTORE_MAPS, + SETUP_MAPS_RESTORED, + TEST_READY, + TEST_DONE, +}; + +struct vma_modifier_info; + +typedef void (*vma_modifier_op)(const struct vma_modifier_info *mod_info); +typedef void (*vma_mod_result_check_op)(struct line_content *mod_last_line, + struct line_content *mod_first_line, + struct line_content *restored_last_line, + struct line_content *restored_first_line); + +struct vma_modifier_info { + int vma_count; + void *addr; + int prot; + void *next_addr; + vma_modifier_op vma_modify; + vma_modifier_op vma_restore; + vma_mod_result_check_op vma_mod_check; + pthread_mutex_t sync_lock; + pthread_cond_t sync_cond; + enum test_state curr_state; + bool exit; + void *child_mapped_addr[]; +}; + +static void wait_for_state(struct vma_modifier_info *mod_info, enum test_state state) +{ + pthread_mutex_lock(&mod_info->sync_lock); + while (mod_info->curr_state != state) + pthread_cond_wait(&mod_info->sync_cond, &mod_info->sync_lock); + pthread_mutex_unlock(&mod_info->sync_lock); +} + +static void signal_state(struct vma_modifier_info *mod_info, enum test_state state) +{ + pthread_mutex_lock(&mod_info->sync_lock); + mod_info->curr_state = state; + pthread_cond_signal(&mod_info->sync_cond); + pthread_mutex_unlock(&mod_info->sync_lock); +} + +/* VMA modification routines */ +static void *child_vma_modifier(struct vma_modifier_info *mod_info) +{ + int prot = PROT_READ | PROT_WRITE; + int i; + + for (i = 0; i < mod_info->vma_count; i++) { + mod_info->child_mapped_addr[i] = mmap(NULL, page_size * 3, prot, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(mod_info->child_mapped_addr[i] != MAP_FAILED); + /* change protection in adjacent maps to prevent merging */ + prot ^= PROT_WRITE; + } + signal_state(mod_info, CHILD_READY); + wait_for_state(mod_info, PARENT_READY); + while (true) { + signal_state(mod_info, SETUP_READY); + wait_for_state(mod_info, SETUP_MODIFY_MAPS); + if (mod_info->exit) + break; + + mod_info->vma_modify(mod_info); + signal_state(mod_info, SETUP_MAPS_MODIFIED); + wait_for_state(mod_info, SETUP_RESTORE_MAPS); + mod_info->vma_restore(mod_info); + signal_state(mod_info, SETUP_MAPS_RESTORED); + + wait_for_state(mod_info, TEST_READY); + while (mod_info->curr_state != TEST_DONE) { + mod_info->vma_modify(mod_info); + mod_info->vma_restore(mod_info); + } + } + for (i = 0; i < mod_info->vma_count; i++) + munmap(mod_info->child_mapped_addr[i], page_size * 3); + + return NULL; +} + +static void stop_vma_modifier(struct vma_modifier_info *mod_info) +{ + wait_for_state(mod_info, SETUP_READY); + mod_info->exit = true; + signal_state(mod_info, SETUP_MODIFY_MAPS); +} + +static void capture_mod_pattern(int maps_fd, + struct vma_modifier_info *mod_info, + struct page_content *page1, + struct page_content *page2, + struct line_content *last_line, + struct line_content *first_line, + struct line_content *mod_last_line, + struct line_content *mod_first_line, + struct line_content *restored_last_line, + struct line_content *restored_first_line) +{ + signal_state(mod_info, SETUP_MODIFY_MAPS); + wait_for_state(mod_info, SETUP_MAPS_MODIFIED); + + /* Copy last line of the first page and first line of the last page */ + read_boundary_lines(maps_fd, page1, page2, mod_last_line, mod_first_line); + + signal_state(mod_info, SETUP_RESTORE_MAPS); + wait_for_state(mod_info, SETUP_MAPS_RESTORED); + + /* Copy last line of the first page and first line of the last page */ + read_boundary_lines(maps_fd, page1, page2, restored_last_line, restored_first_line); + + mod_info->vma_mod_check(mod_last_line, mod_first_line, + restored_last_line, restored_first_line); + + /* + * The content of these lines after modify+resore should be the same + * as the original. + */ + assert(strcmp(restored_last_line->text, last_line->text) == 0); + assert(strcmp(restored_first_line->text, first_line->text) == 0); +} + +static inline void split_vma(const struct vma_modifier_info *mod_info) +{ + assert(mmap(mod_info->addr, page_size, mod_info->prot | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, + -1, 0) != MAP_FAILED); +} + +static inline void merge_vma(const struct vma_modifier_info *mod_info) +{ + assert(mmap(mod_info->addr, page_size, mod_info->prot, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, + -1, 0) != MAP_FAILED); +} + +static inline void check_split_result(struct line_content *mod_last_line, + struct line_content *mod_first_line, + struct line_content *restored_last_line, + struct line_content *restored_first_line) +{ + /* Make sure vmas at the boundaries are changing */ + assert(strcmp(mod_last_line->text, restored_last_line->text) != 0); + assert(strcmp(mod_first_line->text, restored_first_line->text) != 0); +} + +static void test_maps_tearing_from_split(int maps_fd, + struct vma_modifier_info *mod_info, + struct page_content *page1, + struct page_content *page2, + struct line_content *last_line, + struct line_content *first_line) +{ + struct line_content split_last_line; + struct line_content split_first_line; + struct line_content restored_last_line; + struct line_content restored_first_line; + + wait_for_state(mod_info, SETUP_READY); + + /* re-read the file to avoid using stale data from previous test */ + read_boundary_lines(maps_fd, page1, page2, last_line, first_line); + + mod_info->vma_modify = split_vma; + mod_info->vma_restore = merge_vma; + mod_info->vma_mod_check = check_split_result; + + capture_mod_pattern(maps_fd, mod_info, page1, page2, last_line, first_line, + &split_last_line, &split_first_line, + &restored_last_line, &restored_first_line); + + /* Now start concurrent modifications for test_duration_sec */ + signal_state(mod_info, TEST_READY); + + struct line_content new_last_line; + struct line_content new_first_line; + struct timespec start_ts, end_ts; + + clock_gettime(CLOCK_MONOTONIC_COARSE, &start_ts); + do { + bool last_line_changed; + bool first_line_changed; + + read_boundary_lines(maps_fd, page1, page2, &new_last_line, &new_first_line); + + /* Check if we read vmas after split */ + if (!strcmp(new_last_line.text, split_last_line.text)) { + /* + * The vmas should be consistent with split results, + * however if vma was concurrently restored after a + * split, it can be reported twice (first the original + * split one, then the same vma but extended after the + * merge) because we found it as the next vma again. + * In that case new first line will be the same as the + * last restored line. + */ + assert(!strcmp(new_first_line.text, split_first_line.text) || + !strcmp(new_first_line.text, restored_last_line.text)); + } else { + /* The vmas should be consistent with merge results */ + assert(!strcmp(new_last_line.text, restored_last_line.text) && + !strcmp(new_first_line.text, restored_first_line.text)); + } + /* + * First and last lines should change in unison. If the last + * line changed then the first line should change as well and + * vice versa. + */ + last_line_changed = strcmp(new_last_line.text, last_line->text) != 0; + first_line_changed = strcmp(new_first_line.text, first_line->text) != 0; + assert(last_line_changed == first_line_changed); + + clock_gettime(CLOCK_MONOTONIC_COARSE, &end_ts); + } while (end_ts.tv_sec - start_ts.tv_sec < test_duration_sec); + + /* Signal the modifyer thread to stop and wait until it exits */ + signal_state(mod_info, TEST_DONE); +} + +static int test_maps_tearing(void) +{ + struct vma_modifier_info *mod_info; + pthread_mutexattr_t mutex_attr; + pthread_condattr_t cond_attr; + int shared_mem_size; + char fname[32]; + int vma_count; + int maps_fd; + int status; + pid_t pid; + + /* + * Have to map enough vmas for /proc/pid/maps to containt more than one + * page worth of vmas. Assume at least 32 bytes per line in maps output + */ + vma_count = page_size / 32 + 1; + shared_mem_size = sizeof(struct vma_modifier_info) + vma_count * sizeof(void *); + + /* map shared memory for communication with the child process */ + mod_info = (struct vma_modifier_info *)mmap(NULL, shared_mem_size, + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + + assert(mod_info != MAP_FAILED); + + /* Initialize shared members */ + pthread_mutexattr_init(&mutex_attr); + pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED); + assert(!pthread_mutex_init(&mod_info->sync_lock, &mutex_attr)); + pthread_condattr_init(&cond_attr); + pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED); + assert(!pthread_cond_init(&mod_info->sync_cond, &cond_attr)); + mod_info->vma_count = vma_count; + mod_info->curr_state = INIT; + mod_info->exit = false; + + pid = fork(); + if (!pid) { + /* Child process */ + child_vma_modifier(mod_info); + return 0; + } + + sprintf(fname, "/proc/%d/maps", pid); + maps_fd = open(fname, O_RDONLY); + assert(maps_fd != -1); + + /* Wait for the child to map the VMAs */ + wait_for_state(mod_info, CHILD_READY); + + /* Read first two pages */ + struct page_content page1; + struct page_content page2; + + page1.data = malloc(page_size); + assert(page1.data); + page2.data = malloc(page_size); + assert(page2.data); + + struct line_content last_line; + struct line_content first_line; + + read_boundary_lines(maps_fd, &page1, &page2, &last_line, &first_line); + + /* + * Find the addresses corresponding to the last line in the first page + * and the first line in the last page. + */ + mod_info->addr = NULL; + mod_info->next_addr = NULL; + for (int i = 0; i < mod_info->vma_count; i++) { + if (mod_info->child_mapped_addr[i] == (void *)last_line.start_addr) { + mod_info->addr = mod_info->child_mapped_addr[i]; + mod_info->prot = PROT_READ; + /* Even VMAs have write permission */ + if ((i % 2) == 0) + mod_info->prot |= PROT_WRITE; + } else if (mod_info->child_mapped_addr[i] == (void *)first_line.start_addr) { + mod_info->next_addr = mod_info->child_mapped_addr[i]; + } + + if (mod_info->addr && mod_info->next_addr) + break; + } + assert(mod_info->addr && mod_info->next_addr); + + signal_state(mod_info, PARENT_READY); + + test_maps_tearing_from_split(maps_fd, mod_info, &page1, &page2, + &last_line, &first_line); + + stop_vma_modifier(mod_info); + + free(page2.data); + free(page1.data); + + for (int i = 0; i < vma_count; i++) + munmap(mod_info->child_mapped_addr[i], page_size); + close(maps_fd); + waitpid(pid, &status, 0); + munmap(mod_info, shared_mem_size); + + return 0; +} + +int usage(void) +{ + fprintf(stderr, "Userland /proc/pid/{s}maps test cases\n"); + fprintf(stderr, " -d: Duration for time-consuming tests\n"); + fprintf(stderr, " -h: Help screen\n"); + exit(-1); +} + +int main(int argc, char **argv) { int pipefd[2]; int exec_fd; + int opt; + + while ((opt = getopt(argc, argv, "d:h")) != -1) { + if (opt == 'd') + test_duration_sec = strtoul(optarg, NULL, 0); + else if (opt == 'h') + usage(); + } + page_size = sysconf(_SC_PAGESIZE); vsyscall(); switch (g_vsyscall) { case 0: @@ -578,6 +1002,10 @@ int main(void) assert(err == -ENOENT); } + /* Test tearing in /proc/$PID/maps */ + if (test_maps_tearing()) + return 1; + return 0; } #else -- 2.50.0.714.g196bf9f422-goog