1 #include <darwintest.h>
2 #include <darwintest_utils.h>
3 #include <mach/mach.h>
4 #include <mach/task_info.h>
5 #include <mach/vm_region.h>
6 #include <mach/mach_vm.h>
7 #include <sys/kern_sysctl.h>
8 #include <errno.h>
9
10 T_GLOBAL_META(
11 T_META_NAMESPACE("xnu.vm"),
12 T_META_RADAR_COMPONENT_NAME("xnu"),
13 T_META_RADAR_COMPONENT_VERSION("VM"));
14
15 static const char* g_sysctl_name = "vm.get_owned_vmobjects";
16
17 mach_port_t get_corpse(void);
18
19 mach_port_t
get_corpse()20 get_corpse()
21 {
22 kern_return_t kr;
23 mach_port_t corpse_port;
24
25 kr = task_generate_corpse(mach_task_self(), &corpse_port);
26 if (kr != KERN_SUCCESS) {
27 mach_error("task_generate_corpse failed", kr);
28 corpse_port = MACH_PORT_NULL;
29
30 switch (kr) {
31 case KERN_NOT_SUPPORTED:
32 case KERN_FAILURE:
33 case KERN_RESOURCE_SHORTAGE:
34 break;
35 default:
36 /* convert to KERN_FAILURE after logging to catch other rc codes */
37 /* and trigger test failure */
38 kr = KERN_FAILURE;
39 break;
40 }
41 }
42
43 /* anything other than KERN_FAILURE is valid */
44 T_EXPECT_NE(kr, KERN_FAILURE, "corpse creation\n");
45
46 return corpse_port;
47 }
48
49 static void
main_test(void)50 main_test(void)
51 {
52 int ret;
53 mach_port_name_t task_name;
54 vmobject_list_output_t out_buffer;
55 size_t out_size;
56 size_t output_size;
57 const vm_size_t tmp_size = 16 * 1024 * 1024; /* arbitrary size */
58 vm_address_t tmp_buf;
59 vm_address_t tmp_buf2;
60 mach_vm_size_t addr_size;
61 mach_vm_address_t addr;
62 kern_return_t kr;
63 mach_port_t __self = mach_task_self();
64 vm_region_submap_info_data_64_t regionInfo;
65 uint32_t nestingDepth;
66 mach_msg_type_number_t count;
67
68 /* allocate a temporary buffer */
69 kr = vm_allocate(__self, &tmp_buf, tmp_size, VM_FLAGS_ANYWHERE | VM_FLAGS_PURGABLE);
70 T_QUIET;
71 T_EXPECT_EQ(kr, KERN_SUCCESS, "vm_allocate(%zu) error 0x%x (%s)",
72 (size_t) tmp_size, kr, mach_error_string(kr));
73 T_QUIET;
74 T_EXPECT_NE(tmp_buf, (vm_address_t) 0, "failed to allocate temporary purgable buffer\n");
75
76 kr = vm_allocate(__self, &tmp_buf2, tmp_size, VM_FLAGS_ANYWHERE | VM_FLAGS_PURGABLE);
77 T_QUIET;
78 T_EXPECT_EQ(kr, KERN_SUCCESS, "vm_allocate(%zu) error 0x%x (%s)",
79 (size_t) tmp_size, kr, mach_error_string(kr));
80 T_QUIET;
81 T_EXPECT_NE(tmp_buf2, (vm_address_t) 0, "failed to allocate temporary purgable buffer\n");
82
83 /* expected failures */
84 out_size = tmp_size;
85 ret = sysctlbyname(g_sysctl_name, NULL, 0, NULL, 0);
86 T_EXPECT_EQ(ret, -1, "expected failure with 0 parameters\n");
87 T_EXPECT_EQ(errno, EINVAL, "expected EINVAL with 0 parameters\n");
88
89 ret = sysctlbyname(g_sysctl_name, (void*) tmp_buf, &out_size, NULL, 0);
90 T_EXPECT_EQ(ret, -1, "expected failure with no new parameters\n");
91 T_EXPECT_EQ(errno, EINVAL, "expected EINVAL with 0 new parameters\n");
92
93 out_size = tmp_size;
94 ret = sysctlbyname(g_sysctl_name, NULL, 0, (void*) tmp_buf, out_size);
95 T_EXPECT_EQ(ret, -1, "expected failure with no old parameters\n");
96 T_EXPECT_EQ(errno, EINVAL, "expected EINVAL with 0 old parameters\n");
97
98 task_name = MACH_PORT_NULL;
99 ret = sysctlbyname(g_sysctl_name, (void*) tmp_buf, &out_size, &task_name, sizeof(task_name));
100 T_EXPECT_EQ(ret, -1, "expected failure with task_name == MACH_PORT_NULL in new parameters\n");
101 T_EXPECT_EQ(errno, ESRCH, "expected ESRCH with invalid task port name\n");
102
103 out_size = 0;
104 task_name = __self;
105 ret = sysctlbyname(g_sysctl_name, (void*) tmp_buf, &out_size, &task_name, sizeof(task_name));
106 T_QUIET;
107 T_EXPECT_EQ(ret, -1, "expected failure with out_size == 0\n");
108 T_EXPECT_EQ(errno, EINVAL, "expected EINVAL with 0 output size and valid pointer\n");
109
110 /* we should get the number of entries we should allocate for */
111 out_size = 0;
112 output_size = 0;
113 ret = sysctlbyname(g_sysctl_name, NULL, &out_size, &task_name, sizeof(task_name));
114 T_QUIET;
115 T_EXPECT_EQ(ret, 0, "failed getting the number of entries\n");
116 T_EXPECT_EQ(out_size, 2 * sizeof(vm_object_query_data_t) + sizeof(int64_t), "expected one entry\n");
117
118 /* calculcate and allocate the proper sized output buffer */
119 output_size = out_size;
120 out_buffer = (vmobject_list_output_t)calloc(output_size, 1);
121 T_QUIET;
122 T_EXPECT_NE(out_buffer, NULL, "failed to allocate the output buffer for sysctlbyname\n");
123
124 /* get the truncated list for the current process */
125 memset(out_buffer, 0, output_size);
126 out_size = 1 * sizeof(vm_object_query_data_t) + sizeof(int64_t);
127 ret = sysctlbyname(g_sysctl_name, out_buffer, &out_size, &task_name, sizeof(task_name));
128
129 T_QUIET;
130 T_EXPECT_EQ(ret, 0, "sysctlbyname failed\n");
131 T_EXPECT_EQ(out_size, 1 * sizeof(vm_object_query_data_t) + sizeof(int64_t), "sysctl return size is incorrect\n");
132 T_EXPECT_EQ(out_buffer->entries, 1ULL, "should have 1 vm object\n");
133 T_EXPECT_NE(out_buffer->data[0].object_id, 0ULL, "vm_object_id should not be 0\n");
134
135 /* get the list for the current process with an overly large size */
136 out_size = SIZE_MAX;
137 memset(out_buffer, 0, output_size);
138 ret = sysctlbyname(g_sysctl_name, out_buffer, &out_size, &task_name, sizeof(task_name));
139
140 T_QUIET;
141 T_EXPECT_EQ(ret, 0, "sysctlbyname failed\n");
142 T_EXPECT_EQ(out_size, 2 * sizeof(vm_object_query_data_t) + sizeof(int64_t), "sysctl return size is incorrect\n");
143 T_EXPECT_EQ(out_buffer->entries, 2ULL, "should have 2 vm objects\n");
144 T_EXPECT_NE(out_buffer->data[0].object_id, 0ULL, "vm_object_id should not be 0\n");
145
146 /* get the list for the current process with the correct output size */
147 out_size = output_size;
148 memset(out_buffer, 0, output_size);
149 ret = sysctlbyname(g_sysctl_name, out_buffer, &out_size, &task_name, sizeof(task_name));
150
151 T_QUIET;
152 T_EXPECT_EQ(ret, 0, "sysctlbyname failed\n");
153 T_EXPECT_EQ(out_size, 2 * sizeof(vm_object_query_data_t) + sizeof(int64_t), "sysctl return size is incorrect\n");
154 T_EXPECT_EQ(out_buffer->entries, 2ULL, "should have 2 vm objects\n");
155 T_EXPECT_NE(out_buffer->data[0].object_id, 0ULL, "vm_object_id should not be 0\n");
156
157 addr = tmp_buf;
158 addr_size = tmp_size;
159 nestingDepth = UINT_MAX;
160 count = VM_REGION_SUBMAP_INFO_V2_COUNT_64;
161 kr = mach_vm_region_recurse(__self, &addr, &addr_size, &nestingDepth, (vm_region_info_t)®ionInfo, &count);
162 T_QUIET;
163 T_EXPECT_EQ(kr, KERN_SUCCESS, "mach_vm_region_recurse(%zu) error 0x%x (%s)\n",
164 tmp_size, kr, mach_error_string(kr));
165 T_EXPECT_EQ(regionInfo.object_id_full, out_buffer->data[0].object_id, "object_id_full does not match out_buffer->object[0]\n");
166
167 addr = tmp_buf2;
168 addr_size = tmp_size;
169 nestingDepth = UINT_MAX;
170 count = VM_REGION_SUBMAP_INFO_V2_COUNT_64;
171 kr = mach_vm_region_recurse(__self, &addr, &addr_size, &nestingDepth, (vm_region_info_t)®ionInfo, &count);
172 T_QUIET;
173 T_EXPECT_EQ(kr, KERN_SUCCESS, "mach_vm_region_recurse(%zu) error 0x%x (%s)\n",
174 tmp_size, kr, mach_error_string(kr));
175 T_EXPECT_EQ(regionInfo.object_id_full, out_buffer->data[1].object_id, "object_id_full does not match out_buffer->object[1]\n");
176
177 /* corpse */
178 {
179 mach_port_t corpse_port = get_corpse();
180
181 /* corpse_port can be a valid NULL if out of resources, corpse limit, or corpses disabled */
182 if (corpse_port != MACH_PORT_NULL) {
183 vmobject_list_output_t corpse_out_buffer;
184 size_t corpse_out_size;
185 size_t corpse_output_size;
186
187 /* we should get the number of entries we should allocate for */
188 corpse_out_size = 0;
189 ret = sysctlbyname(g_sysctl_name, NULL, &corpse_out_size, &corpse_port, sizeof(corpse_port));
190
191 T_QUIET;
192 T_EXPECT_EQ(ret, 0, "failed getting the number of entries for corpse\n");
193 T_EXPECT_EQ(corpse_out_size, out_size, "corpse output size matchrd the parent process\n");
194 T_EXPECT_EQ(corpse_out_size, 2 * sizeof(vm_object_query_data_t) + sizeof(int64_t), "corpse expected one entry\n");
195
196 /* calculcate and allocate the proper sized output buffer */
197 corpse_output_size = corpse_out_size;
198 corpse_out_buffer = (vmobject_list_output_t)calloc(corpse_output_size, 1);
199 T_QUIET;
200 T_EXPECT_NE(corpse_out_buffer, NULL, "failed to allocate the output buffer for sysctlbyname for corpse\n");
201
202 /* get the list for the current process */
203 corpse_out_size = corpse_output_size;
204 memset(corpse_out_buffer, 0, corpse_output_size);
205 ret = sysctlbyname(g_sysctl_name, corpse_out_buffer, &corpse_out_size, &corpse_port, sizeof(corpse_port));
206
207 int rc = memcmp(corpse_out_buffer, out_buffer, corpse_out_size);
208
209 T_QUIET;
210 T_EXPECT_EQ(ret, 0, "corpse sysctlbyname failed\n");
211 T_EXPECT_EQ(rc, 0, "corpse vmobjects should match parent vmobjects\n");
212 T_EXPECT_EQ(corpse_out_size, 2 * sizeof(vm_object_query_data_t) + sizeof(int64_t), "corpse sysctl return size is incorrect\n");
213 T_EXPECT_EQ(corpse_out_buffer->entries, 2ULL, "corpse should have 2 vm objects\n");
214 T_EXPECT_NE(corpse_out_buffer->data[0].object_id, 0ULL, "corpse vm_object_id should not be 0\n");
215
216 free(corpse_out_buffer);
217 mach_port_deallocate(mach_task_self(), corpse_port);
218 }
219 }
220
221 kr = vm_deallocate(__self, tmp_buf, tmp_size);
222 T_QUIET;
223 T_EXPECT_EQ(kr, KERN_SUCCESS, "vm_deallocate(%zu) error 0x%x (%s)\n",
224 tmp_size, kr, mach_error_string(kr));
225
226 kr = vm_deallocate(__self, tmp_buf2, tmp_size);
227 T_QUIET;
228 T_EXPECT_EQ(kr, KERN_SUCCESS, "vm_deallocate(%zu) error 0x%x (%s)\n",
229 tmp_size, kr, mach_error_string(kr));
230
231 free(out_buffer);
232 out_buffer = NULL;
233 }
234
235 T_DECL(test_get_vmobject_list, "Get owned vm_objects for process", T_META_TAG_VM_PREFERRED)
236 {
237 main_test();
238 }
239