1 /*
2 * Copyright (c) 2024 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 *
28 */
29
30 #include <sys/proc.h>
31 #include <sys/sysctl.h>
32 #include <sys/kern_memorystatus.h>
33 #include <mach-o/dyld.h>
34 #include <mach/mach_vm.h>
35 #include <mach/vm_page_size.h>
36 #include <mach/shared_region.h>
37 #include <mach/mach.h>
38 #include <os/reason_private.h>
39 #include <TargetConditionals.h>
40 #include <darwintest.h>
41 #include <darwintest_utils.h>
42
43 T_GLOBAL_META(
44 T_META_NAMESPACE("xnu.memorystatus"),
45 T_META_RADAR_COMPONENT_NAME("xnu"),
46 T_META_RADAR_COMPONENT_VERSION("VM"),
47 T_META_ASROOT(true),
48 T_META_TAG_VM_PREFERRED
49 );
50
51 #define SIZE_ESTIMATE_EXTRA_ENTRIES 5
52 #define MAX_TRIES 3
53
54 #define _STR(x) #x
55 #define STR(x) _STR(x)
56
57 memorystatus_priority_entry_v2_t*
get_priority_list(pid_t pid,int call,size_t entry_size,ssize_t * len)58 get_priority_list(pid_t pid, int call, size_t entry_size, ssize_t* len)
59 {
60 int i;
61 ssize_t size;
62 memorystatus_priority_entry_v2_t *list = NULL;
63
64 for (i = 0; i < MAX_TRIES; i++) {
65 T_LOG("Attempt %d", i + 1);
66
67 size = memorystatus_control(call, pid, 0, NULL, 0);
68
69 T_ASSERT_GT(size, 0l, "Priority list query size > 0");
70 T_ASSERT_EQ(size % entry_size, 0ul, "Priority list size is multiple of struct size");
71
72 if (size <= 0) {
73 return NULL;
74 }
75
76 /* pad out the size just in case the list gets bigger */
77 if (pid == 0) {
78 size += SIZE_ESTIMATE_EXTRA_ENTRIES * entry_size;
79 }
80
81 list = malloc(size);
82 T_QUIET; T_ASSERT_NOTNULL(list, "malloc");
83
84 *len = memorystatus_control(call, pid, 0, list, size);
85
86 if (*len <= 0) {
87 T_LOG("Failed, maybe the list grew? Trying again...");
88 free(list);
89 continue;
90 }
91
92 T_ASSERT_GE(size, *len, "List fits in buffer");
93 T_ASSERT_GT(size, 0l, "List size nonzero");
94 return list;
95 }
96
97 T_FAIL("Tried more than MAX_TRIES=" STR(MAX_TRIES) " times");
98 return NULL;
99 }
100
101 void
validate_entry(memorystatus_priority_entry_v2_t * entry,size_t entry_size,pid_t expect_pid)102 validate_entry(memorystatus_priority_entry_v2_t *entry, size_t entry_size, pid_t expect_pid)
103 {
104 int i;
105 if (expect_pid == -1) {
106 T_QUIET; T_ASSERT_GE(entry->pid, 0, "PID valid");
107 T_QUIET; T_ASSERT_NE(entry->pid, 0, "kernel_task not in list");
108 T_QUIET; T_ASSERT_NE(entry->pid, 1, "launchd not in list");
109 } else {
110 T_QUIET; T_ASSERT_EQ(entry->pid, expect_pid, "PID correct");
111 }
112
113 if (entry->pid > 1) {
114 T_QUIET; T_ASSERT_GE(
115 entry->priority,
116 0,
117 "Entry priority >= 0");
118 T_QUIET; T_ASSERT_LE(
119 entry->priority,
120 JETSAM_PRIORITY_MAX,
121 "Entry priority <= JETSAM_PRIORITY_MAX (" STR(JETSAM_PRIORITY_MAX) ")");
122 } else {
123 T_QUIET; T_ASSERT_EQ(
124 entry->priority,
125 JETSAM_PRIORITY_INTERNAL,
126 "Entry priority == JETSAM_PRIORITY_INTERNAL (" STR(JETSAM_PRIORITY_INTERNAL) ")");
127 }
128
129 if (entry_size == sizeof(memorystatus_priority_entry_v2_t)) {
130 boolean_t found_nonzero = false;
131 for (i = 0; i < sizeof(entry->_reserved) / sizeof(entry->_reserved[0]); i++) {
132 if (entry->_reserved[i]) {
133 found_nonzero = true;
134 break;
135 }
136 }
137 T_QUIET; T_ASSERT_FALSE(found_nonzero, "Entry reserved is empty");
138 }
139 }
140
141 T_DECL(jetsam_priority_list_v2_list, "Jetsam priority list v2 - list")
142 {
143 int i;
144 ssize_t len;
145 memorystatus_priority_entry_v2_t *list = get_priority_list(
146 0,
147 MEMORYSTATUS_CMD_GET_PRIORITY_LIST_V2,
148 sizeof(*list),
149 &len);
150
151 T_ASSERT_NOTNULL(list, "Priority list not null");
152 for (i = 0; i < len / sizeof(memorystatus_priority_entry_v2_t); i++) {
153 validate_entry(&list[i], sizeof(*list), -1);
154 }
155 free(list);
156 T_PASS("Entries valid");
157 }
158
159 void
validate_single(pid_t pid)160 validate_single(pid_t pid)
161 {
162 ssize_t len;
163 memorystatus_priority_entry_v2_t *list = get_priority_list(
164 pid,
165 MEMORYSTATUS_CMD_GET_PRIORITY_LIST_V2,
166 sizeof(*list),
167 &len);
168
169 T_ASSERT_NOTNULL(list, "Getting self priority entry");
170 T_ASSERT_EQ(len, sizeof(memorystatus_priority_entry_v2_t), "Single entry returned");
171 validate_entry(list, sizeof(*list), pid);
172 free(list);
173 }
174
175 T_DECL(jetsam_priority_list_v2_single, "Jetsam priority list v2 - single")
176 {
177 T_LOG("Getting entry for self...");
178 validate_single(getpid());
179
180 T_LOG("Getting entry for launchd...");
181 validate_single(1);
182
183 T_PASS("Entries valid");
184 }
185
186 T_DECL(jetsam_priority_list_compat, "Jetsam priority list - v1 compat")
187 {
188 int i;
189 ssize_t len;
190
191 memorystatus_priority_entry_t *list = (memorystatus_priority_entry_t*) get_priority_list(
192 0,
193 MEMORYSTATUS_CMD_GET_PRIORITY_LIST,
194 sizeof(*list),
195 &len);
196
197 T_ASSERT_NOTNULL(list, "Priority list not null");
198
199 for (i = 0; i < len / sizeof(memorystatus_priority_entry_v2_t); i++) {
200 validate_entry((memorystatus_priority_entry_v2_t*) (&list[i]), sizeof(*list), -1);
201 }
202
203 free(list);
204 T_PASS("Entries valid");
205 }
206