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 DCEF8C5B549 for ; Wed, 4 Jun 2025 23:12:05 +0000 (UTC) Received: by kanga.kvack.org (Postfix) id 741ED6B00A3; Wed, 4 Jun 2025 19:12:05 -0400 (EDT) Received: by kanga.kvack.org (Postfix, from userid 40) id 6EB966B00AA; Wed, 4 Jun 2025 19:12:05 -0400 (EDT) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id 58CA46B0176; Wed, 4 Jun 2025 19:12:05 -0400 (EDT) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0013.hostedemail.com [216.40.44.13]) by kanga.kvack.org (Postfix) with ESMTP id 247306B00A3 for ; Wed, 4 Jun 2025 19:12:05 -0400 (EDT) Received: from smtpin16.hostedemail.com (a10.router.float.18 [10.200.18.1]) by unirelay09.hostedemail.com (Postfix) with ESMTP id 28E3D80BDF for ; Wed, 4 Jun 2025 23:12:04 +0000 (UTC) X-FDA: 83519268168.16.ADC8DB8 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) by imf14.hostedemail.com (Postfix) with ESMTP id 55F93100011 for ; Wed, 4 Jun 2025 23:12:02 +0000 (UTC) Authentication-Results: imf14.hostedemail.com; dkim=pass header.d=google.com header.s=20230601 header.b=m0Bd1D0U; spf=pass (imf14.hostedemail.com: domain of 3wdJAaAYKCLIkmjWfTYggYdW.Ugedafmp-eecnSUc.gjY@flex--surenb.bounces.google.com designates 209.85.215.201 as permitted sender) smtp.mailfrom=3wdJAaAYKCLIkmjWfTYggYdW.Ugedafmp-eecnSUc.gjY@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=1749078722; 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=JUhX3Ns/5XBc40xno+kJmwKf1OM3H2+/3zcbV1pSCHo=; b=DENRXZRM1MX9UN0VEnHT8+R0skGFwgq6n6ptBDHUmDUVpodKH1KlzxKoMB2aYNHWxJNON0 WXpmC056YRMbHeLkn1YdxsZGIFOfve7bMarSGmun2sWyXWwU21U+3zAZFeUsIBL6csrwBQ jr5b7ntDMRHMWgBNd3n++NOUXt0iC6E= ARC-Authentication-Results: i=1; imf14.hostedemail.com; dkim=pass header.d=google.com header.s=20230601 header.b=m0Bd1D0U; spf=pass (imf14.hostedemail.com: domain of 3wdJAaAYKCLIkmjWfTYggYdW.Ugedafmp-eecnSUc.gjY@flex--surenb.bounces.google.com designates 209.85.215.201 as permitted sender) smtp.mailfrom=3wdJAaAYKCLIkmjWfTYggYdW.Ugedafmp-eecnSUc.gjY@flex--surenb.bounces.google.com; dmarc=pass (policy=reject) header.from=google.com ARC-Seal: i=1; s=arc-20220608; d=hostedemail.com; t=1749078722; a=rsa-sha256; cv=none; b=75CSXnRFGZdzWldmpH78rXmElhUeh+KMaWfz+b/t2d1VJ2s0D0vAw3VADfPVbjn2SqHSMk w8v6cINrIT7XV1XRFhzibKhdRyCG9rL0BwN6vCdsnSnd2k6glCsXRy2tuhRJ0m9kaP0XAi T/8+3rC9NOWEnPXRWi8D+nsHkmQLjPs= Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-b0e5f28841dso169050a12.2 for ; Wed, 04 Jun 2025 16:12:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1749078721; x=1749683521; 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=JUhX3Ns/5XBc40xno+kJmwKf1OM3H2+/3zcbV1pSCHo=; b=m0Bd1D0U7MLHwEXLuTtLQiEFZfXccuEHvHzxZPD9uoEgVQVxglNzErhIdva4qmnOzL /6jGS4ywdaoBMCs3yE585g2QkuC3yyI/EsbVsY2RPkcJSAhJTYaaH1CWAObtrtFstCsG kzybGcHkDYTPdMRklvbLo29+dJYGdL55d6vBXnvag/eLCci4fOOyapPXYDn/TDL82ePc 0b8ybX+he7q0F6X0rZwg01fcnob5Xo55DEQcZVWDjuiGhJAT81i+gV5Kt34Jz8VgZVQV vmjjq+/X6gXE94dK9GwWmteQRJIpkfswZylwjIcCtlkVw7I9LB7tC1ZZwgP9dza+zPh4 BaNA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1749078721; x=1749683521; 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=JUhX3Ns/5XBc40xno+kJmwKf1OM3H2+/3zcbV1pSCHo=; b=Z26LIfHAuZ8Cv6mqwcZzslGeu1JMQvyrKRigoGDduAkZwj3XYysB3psg0Mye9S6V6i d1pyyp9nzuSjTeXQr8BgHkrDrPQlvs+iXe6e14ZEL/ZZnFZxuQks2d+tG64wu6Rpobpi ptzK5w6CNa41398V1Has52QPu84N0ct5+/CdehZiJSPSJekewpR2uwuJfMnC61wL3zCj 6uTOiaukvmlkoUF91PjA5AhR3tXLdbTLDQSvMu59wDt5EEu7rP7QpFceuqFPoDTb6ZKc MxyMjfos3SuN9uMbKkWIi9rwZWPjHqE+EsZ0sHP9VD8G1QH3HAyRb5N3t4wZVZHhPU3y r2ug== X-Forwarded-Encrypted: i=1; AJvYcCVJ01vCe8lrEICrDREdjF1pwx34ym61NXzRgEy2H2XJ0MBN05tzrwF0K3j/UXgGfgzNYNWPvCeZKg==@kvack.org X-Gm-Message-State: AOJu0YwwlhmGmJvFNShp2Lkd8FQmJ+nevmAP6xzsWFKs59cFwj9LuaLg uj7OxODLJuZohyAPNopfefb9HIbp4WrXyEae6q1Y8pw+94TmvLmEsCc+YV98IyNCS1R0gqrrFZW tj38K+w== X-Google-Smtp-Source: AGHT+IHlAlWPhYi49GEO/hAEiP1bg5W9jmU+hP5/sDmo0K6qFC3JqxdpNjH/sVAhRn21WKI+htX2k/ydZrc= X-Received: from pjbpw2.prod.google.com ([2002:a17:90b:2782:b0:312:187d:382d]) (user=surenb job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90a:d2cf:b0:312:26d9:d5b4 with SMTP id 98e67ed59e1d1-3130cd97ac5mr7753526a91.17.1749078721150; Wed, 04 Jun 2025 16:12:01 -0700 (PDT) Date: Wed, 4 Jun 2025 16:11:45 -0700 In-Reply-To: <20250604231151.799834-1-surenb@google.com> Mime-Version: 1.0 References: <20250604231151.799834-1-surenb@google.com> X-Mailer: git-send-email 2.49.0.1266.g31b7d2e469-goog Message-ID: <20250604231151.799834-2-surenb@google.com> Subject: [PATCH v4 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-Rspamd-Queue-Id: 55F93100011 X-Stat-Signature: kg3igadzm7bfjwhkq7u7rzan7mhyso5e X-Rspam-User: X-Rspamd-Server: rspam07 X-HE-Tag: 1749078722-455088 X-HE-Meta: U2FsdGVkX19mMa/2kcBpq1ZWBfYCeT9LvX7XctNpHGy6u3GBUmxLt7N0hC+Xpwrp/Lz7ojeS3o7lv6UWOOf+86ICSBRWz94kr+RTW4/9wPB8mvfTH4UnoE5k4zNHc3zMYM0Flygs1gEpeG83j1sdpz+9rMcsX2SpGWP75BJP/WSDUL5Qu2eIpibqPn1/B063Aq9/ad/uVQEYkpLaBtzV7GXzgQEYxLyJ6iqhMmC+Fp2Y/nHv4hya1QBzOFeAaNLdq9bvZMffr+wbv7lWiRMRAfAeT/nXOZ6f4YzIE25ejyIIAk6AMsG84e3/BRL9K67juWPwNecrYEDl5hVKhTRXgIn6pnVG3mhh7EiCrBfOvnqBc0dsvKahdF2RovwBAKZSOff7w1J6+MiVeCK8wCNXnJbZFRbs8uYCEEgGt962Ug+vKczlR9gaK2RqsIVfMxORloPvzYtmUBaQXfXVYdqQdjTjNMKhGYDCvu8SUvf6i99NwchdOwzZDSkLt7bIK4AOTrGu9eFzQpTfFZfA7LvZJR84+WIZpgU/+gF/42rHSJHri5SlY+sJe0SvjpFlw7z1Gb7lugs2dApnwmzmuVEhWoc8n6QYrxwEGGrzhUaoI2fPIiQvmki3lk+5HqeeT4eGJ1tz59LlJnnWh7mX4F7Ey/qJcg5Vgnij2/gImybXIshx00tsI2KoVzWPNU3gxjmBrUJmz+6viBhPmjSWN8IMSbnouW2KbGoTmT2+APlPnt7O0eYJdPjWAYESPHWyM4CZBHWDnUYjfoRavOcVyF0bP/idteQMCO/2qp/5xnTuHwNJx+L3Ai7v9YP1nml1MRosst1jYnUs4DBqr2eCvKKdinPz3nFYHZlCmxwT0XMEqYiVS9UgNG4VK8dEztOZGIXkQRUTFPYm/oT2B04yG+DFWa+R7Fg9yqKh2wczn3Tfak9KB1Hvdojq73G9uPqA5DGGLstr59+d9xZ9Jsf9I9t kDzVBum1 CgbtyxOSdsQGycm4kU0PcvUuUJQk/wvk7ATZGGx7SZxQW1068xlSmPQoKQIrV81WP7cSuGucfgK8AaRPWD95ZYZS/tfZ4KWvGuUQg+BIW0rXwjN/tKwE+cOEZc12v2TyGSKANFfYsG2JDfGd4AMCKmO0eLl6ef5xMbFAxm6JGMIeViEg+YvOEDZii4CY/cxER0SEMpwAsA3XMvlAf2fcY4ORaEOVgDBi8yFGwlFpOcB37jFCxBYa24wo0ELD6ZiqzGRWw17gGfhkDS7zweG7nYrddMsk0LCzD4Ged2gVgOcMtcQ1Qj4XnVR7/Va3h957ln+OxFX20d7iMvRYnCUnIxFq5/bqPQpa1eW+5aYIjXF8NJXl4I0A1k2Iq+aoEvN+9kYniFEQ9G+ErrKx1KlPq0C2dN9TJI5JkSouYcIe7novyrq7c4zMLopinhC0J5GvDrUMBahFKjMRdrC0TPxCiD1ZnTLgjEnB6rVCPPEtiA4XOTNZjFLeskAY2lY7PepmfP/zehulirD5dQ+ix3EUliMeasHMhpgP0wBZWs2dp2c9yaOg= 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.49.0.1266.g31b7d2e469-goog