blob: 600693c7420790b4a46fd484a71c823d12fd8973 [file] [log] [blame]
Mark Salyzynb38347a2016-04-05 09:09:46 -07001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in
12 * the documentation and/or other materials provided with the
13 * distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
Tom Cherry6b116d12019-04-25 10:34:07 -070029#include "private/grp_pwd.h"
30
Tom Cherryb368a0b2019-04-24 11:12:59 -070031#include <android/api-level.h>
Mark Salyzynb38347a2016-04-05 09:09:46 -070032#include <ctype.h>
33#include <errno.h>
34#include <grp.h>
35#include <mntent.h>
36#include <pthread.h>
37#include <pwd.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
Tom Cherryb368a0b2019-04-24 11:12:59 -070041#include <sys/system_properties.h>
Tom Cherry6b116d12019-04-25 10:34:07 -070042#include <sys/types.h>
Mark Salyzynb38347a2016-04-05 09:09:46 -070043#include <unistd.h>
44
Tom Cherry6b116d12019-04-25 10:34:07 -070045#include "private/ErrnoRestorer.h"
Mark Salyzynb38347a2016-04-05 09:09:46 -070046#include "private/android_filesystem_config.h"
Josh Gao4956c372019-12-19 16:35:51 -080047#include "platform/bionic/macros.h"
Mark Salyzynb38347a2016-04-05 09:09:46 -070048
Jiyong Parkbf383282020-10-27 03:23:02 +090049#if defined(__ANDROID__)
Elliott Hughes3f6eee92016-12-13 23:47:25 +000050// Generated android_ids array
51#include "generated_android_ids.h"
Jiyong Parkbf383282020-10-27 03:23:02 +090052#else
53// Empty array for host; everything is from the database files
54#include "empty_android_ids.h"
55#endif
56
Tom Cherry6034ef82018-02-02 16:10:07 -080057#include "grp_pwd_file.h"
58
Tom Cherry777b34d2019-02-17 09:38:23 -080059static PasswdFile passwd_files[] = {
Jiyong Parkbf383282020-10-27 03:23:02 +090060 {"/etc/passwd", "system_"}, // symlinks to /system/etc/passwd in Android
61 {"/vendor/etc/passwd", "vendor_"},
62 {"/odm/etc/passwd", "odm_"},
63 {"/product/etc/passwd", "product_"},
64 {"/system_ext/etc/passwd", "system_ext_"},
Tom Cherry777b34d2019-02-17 09:38:23 -080065};
66
67static GroupFile group_files[] = {
Jiyong Parkbf383282020-10-27 03:23:02 +090068 {"/etc/group", "system_"}, // symlinks to /system/etc/group in Android
69 {"/vendor/etc/group", "vendor_"},
70 {"/odm/etc/group", "odm_"},
71 {"/product/etc/group", "product_"},
72 {"/system_ext/etc/group", "system_ext_"},
Tom Cherry777b34d2019-02-17 09:38:23 -080073};
Elliott Hughes3f6eee92016-12-13 23:47:25 +000074
Mark Salyzynb38347a2016-04-05 09:09:46 -070075// POSIX seems to envisage an implementation where the <pwd.h> functions are
76// implemented by brute-force searching with getpwent(3), and the <grp.h>
77// functions are implemented similarly with getgrent(3). This means that it's
78// okay for all the <grp.h> functions to share state, and all the <passwd.h>
79// functions to share state, but <grp.h> functions can't clobber <passwd.h>
80// functions' state and vice versa.
Josh Gao5e2285d2017-02-22 12:19:05 -080081#include "bionic/pthread_internal.h"
Mark Salyzynb38347a2016-04-05 09:09:46 -070082
83static void init_group_state(group_state_t* state) {
Mark Salyzyn722ab052016-04-06 10:35:48 -070084 memset(state, 0, sizeof(group_state_t) - sizeof(state->getgrent_idx));
Tom Cherryc57c5bd2019-05-14 17:02:28 -070085 state->group_.gr_name = state->group_name_buffer_;
Mark Salyzynb38347a2016-04-05 09:09:46 -070086 state->group_.gr_mem = state->group_members_;
Tom Cherryc57c5bd2019-05-14 17:02:28 -070087 state->group_.gr_mem[0] = state->group_.gr_name;
Mark Salyzynb38347a2016-04-05 09:09:46 -070088}
89
Tom Cherryc57c5bd2019-05-14 17:02:28 -070090static group_state_t* get_group_tls_buffer() {
91 auto result = &__get_bionic_tls().group;
92 init_group_state(result);
Mark Salyzynb38347a2016-04-05 09:09:46 -070093 return result;
94}
95
Tom Cherryc57c5bd2019-05-14 17:02:28 -070096static void init_passwd_state(passwd_state_t* state) {
97 memset(state, 0, sizeof(passwd_state_t) - sizeof(state->getpwent_idx));
98 state->passwd_.pw_name = state->name_buffer_;
99 state->passwd_.pw_dir = state->dir_buffer_;
100 state->passwd_.pw_shell = state->sh_buffer_;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700101}
102
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700103static passwd_state_t* get_passwd_tls_buffer() {
104 auto result = &__get_bionic_tls().passwd;
105 init_passwd_state(result);
106 return result;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700107}
108
109static passwd* android_iinfo_to_passwd(passwd_state_t* state,
110 const android_id_info* iinfo) {
111 snprintf(state->name_buffer_, sizeof(state->name_buffer_), "%s", iinfo->name);
112 snprintf(state->dir_buffer_, sizeof(state->dir_buffer_), "/");
Tom Cherry777b34d2019-02-17 09:38:23 -0800113 snprintf(state->sh_buffer_, sizeof(state->sh_buffer_), "/bin/sh");
Mark Salyzynb38347a2016-04-05 09:09:46 -0700114
115 passwd* pw = &state->passwd_;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700116 pw->pw_uid = iinfo->aid;
117 pw->pw_gid = iinfo->aid;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700118 return pw;
119}
120
121static group* android_iinfo_to_group(group_state_t* state,
122 const android_id_info* iinfo) {
123 snprintf(state->group_name_buffer_, sizeof(state->group_name_buffer_), "%s", iinfo->name);
124
125 group* gr = &state->group_;
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700126 gr->gr_gid = iinfo->aid;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700127 return gr;
128}
129
Tom Cherry5fb07632019-04-24 13:35:39 -0700130static const android_id_info* find_android_id_info(unsigned id) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700131 for (size_t n = 0; n < android_id_count; ++n) {
132 if (android_ids[n].aid == id) {
Tom Cherry5fb07632019-04-24 13:35:39 -0700133 return &android_ids[n];
Mark Salyzynb38347a2016-04-05 09:09:46 -0700134 }
135 }
Yi Kong32bc0fc2018-08-02 17:31:13 -0700136 return nullptr;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700137}
138
Tom Cherry5fb07632019-04-24 13:35:39 -0700139static const android_id_info* find_android_id_info(const char* name) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700140 for (size_t n = 0; n < android_id_count; ++n) {
141 if (!strcmp(android_ids[n].name, name)) {
Tom Cherry5fb07632019-04-24 13:35:39 -0700142 return &android_ids[n];
Mark Salyzynb38347a2016-04-05 09:09:46 -0700143 }
144 }
Yi Kong32bc0fc2018-08-02 17:31:13 -0700145 return nullptr;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700146}
147
Tom Cherry4362f892017-11-14 08:50:43 -0800148// These are a list of the reserved app ranges, and should never contain anything below
149// AID_APP_START. They exist per user, so a given uid/gid modulo AID_USER_OFFSET will map
150// to these ranges.
Tom Cherry6b116d12019-04-25 10:34:07 -0700151struct IdRange {
152 id_t start;
153 id_t end;
154};
155
156static constexpr IdRange user_ranges[] = {
157 { AID_APP_START, AID_APP_END },
158 { AID_ISOLATED_START, AID_ISOLATED_END },
159};
160
161static constexpr IdRange group_ranges[] = {
Tom Cherry4362f892017-11-14 08:50:43 -0800162 { AID_APP_START, AID_APP_END },
163 { AID_CACHE_GID_START, AID_CACHE_GID_END },
164 { AID_EXT_GID_START, AID_EXT_GID_END },
165 { AID_EXT_CACHE_GID_START, AID_EXT_CACHE_GID_END },
166 { AID_SHARED_GID_START, AID_SHARED_GID_END },
167 { AID_ISOLATED_START, AID_ISOLATED_END },
168};
169
Tom Cherry6b116d12019-04-25 10:34:07 -0700170template <class T, size_t N>
171static constexpr bool verify_user_ranges_ascending(T (&ranges)[N]) {
172 auto array_size = N;
Tom Cherry4362f892017-11-14 08:50:43 -0800173 if (array_size < 2) return false;
174
Tom Cherry6b116d12019-04-25 10:34:07 -0700175 if (ranges[0].start > ranges[0].end) return false;
Tom Cherry4362f892017-11-14 08:50:43 -0800176
177 for (size_t i = 1; i < array_size; ++i) {
Tom Cherry6b116d12019-04-25 10:34:07 -0700178 if (ranges[i].start > ranges[i].end) return false;
179 if (ranges[i - 1].end > ranges[i].start) return false;
Tom Cherry4362f892017-11-14 08:50:43 -0800180 }
181 return true;
182}
183
Tom Cherry6b116d12019-04-25 10:34:07 -0700184static_assert(verify_user_ranges_ascending(user_ranges), "user_ranges must have ascending ranges");
185static_assert(verify_user_ranges_ascending(group_ranges), "user_ranges must have ascending ranges");
Tom Cherry4362f892017-11-14 08:50:43 -0800186
Tom Cherry6b116d12019-04-25 10:34:07 -0700187// This list comes from PackageManagerService.java, where platform AIDs are added to list of valid
188// AIDs for packages via addSharedUserLPw().
189static constexpr const id_t secondary_user_platform_ids[] = {
190 AID_SYSTEM, AID_RADIO, AID_LOG, AID_NFC, AID_BLUETOOTH,
191 AID_SHELL, AID_SECURE_ELEMENT, AID_NETWORK_STACK,
192};
193
194static bool platform_id_secondary_user_allowed(id_t id) {
195 for (const auto& allowed_id : secondary_user_platform_ids) {
196 if (allowed_id == id) {
197 return true;
198 }
199 }
200 return false;
201}
202
Jiyong Parkbf383282020-10-27 03:23:02 +0900203#if defined(__ANDROID__)
Tom Cherry6b116d12019-04-25 10:34:07 -0700204static bool is_valid_app_id(id_t id, bool is_group) {
Tom Cherry4362f892017-11-14 08:50:43 -0800205 id_t appid = id % AID_USER_OFFSET;
206
207 // AID_OVERFLOWUID is never a valid app id, so we explicitly return false to ensure this.
208 // This is true across all users, as there is no reason to ever map this id into any user range.
209 if (appid == AID_OVERFLOWUID) {
210 return false;
211 }
212
Tom Cherry6b116d12019-04-25 10:34:07 -0700213 auto ranges_size = is_group ? arraysize(group_ranges) : arraysize(user_ranges);
214 auto ranges = is_group ? group_ranges : user_ranges;
215
216 // If we're checking an appid that resolves below the user range, then it's a platform AID for a
217 // seconary user. We only allow a reduced set of these, so we must check that it is allowed.
218 if (appid < ranges[0].start && platform_id_secondary_user_allowed(appid)) {
Tom Cherry4362f892017-11-14 08:50:43 -0800219 return true;
220 }
221
Tom Cherry6b116d12019-04-25 10:34:07 -0700222 // The shared GID range is only valid for the first user.
223 if (appid >= AID_SHARED_GID_START && appid <= AID_SHARED_GID_END && appid != id) {
224 return false;
225 }
226
Tom Cherry4362f892017-11-14 08:50:43 -0800227 // Otherwise check that the appid is in one of the reserved ranges.
Tom Cherry6b116d12019-04-25 10:34:07 -0700228 for (size_t i = 0; i < ranges_size; ++i) {
229 if (appid >= ranges[i].start && appid <= ranges[i].end) {
Tom Cherry4362f892017-11-14 08:50:43 -0800230 return true;
231 }
232 }
233
234 return false;
235}
Jiyong Parkbf383282020-10-27 03:23:02 +0900236#else
237static bool is_valid_app_id(id_t, bool) {
238 // Host doesn't have the concept of app_id
239 return false;
240}
241#endif // if defined(__ANDROID__)
Tom Cherry4362f892017-11-14 08:50:43 -0800242
243// This provides an iterater for app_ids within the first user's app id's.
Tom Cherry6b116d12019-04-25 10:34:07 -0700244static id_t get_next_app_id(id_t current_id, bool is_group) {
245 auto ranges_size = is_group ? arraysize(group_ranges) : arraysize(user_ranges);
246 auto ranges = is_group ? group_ranges : user_ranges;
247
248 // If current_id is below the first of the ranges, then we're uninitialized, and return the first
249 // valid id.
250 if (current_id < ranges[0].start) {
251 return ranges[0].start;
Tom Cherry4362f892017-11-14 08:50:43 -0800252 }
253
Tom Cherry6b116d12019-04-25 10:34:07 -0700254 id_t incremented_id = current_id + 1;
Tom Cherry4362f892017-11-14 08:50:43 -0800255
256 // Check to see if our incremented_id is between two ranges, and if so, return the beginning of
257 // the next valid range.
Tom Cherry6b116d12019-04-25 10:34:07 -0700258 for (size_t i = 1; i < ranges_size; ++i) {
259 if (incremented_id > ranges[i - 1].end && incremented_id < ranges[i].start) {
260 return ranges[i].start;
Tom Cherry4362f892017-11-14 08:50:43 -0800261 }
262 }
263
264 // Check to see if our incremented_id is above final range, and return -1 to indicate that we've
265 // completed if so.
Tom Cherry6b116d12019-04-25 10:34:07 -0700266 if (incremented_id > ranges[ranges_size - 1].end) {
Tom Cherry4362f892017-11-14 08:50:43 -0800267 return -1;
268 }
269
270 // Otherwise the incremented_id is valid, so return it.
271 return incremented_id;
272}
273
Mark Salyzynb38347a2016-04-05 09:09:46 -0700274// Translate a user/group name to the corresponding user/group id.
Jeff Sharkey934bc862016-12-13 14:03:19 -0700275// all_a1234 -> 0 * AID_USER_OFFSET + AID_SHARED_GID_START + 1234 (group name only)
Tom Cherry6b116d12019-04-25 10:34:07 -0700276// u0_a1234_ext_cache -> 0 * AID_USER_OFFSET + AID_EXT_CACHE_GID_START + 1234 (group name only)
277// u0_a1234_ext -> 0 * AID_USER_OFFSET + AID_EXT_GID_START + 1234 (group name only)
Jeff Sharkey934bc862016-12-13 14:03:19 -0700278// u0_a1234_cache -> 0 * AID_USER_OFFSET + AID_CACHE_GID_START + 1234 (group name only)
279// u0_a1234 -> 0 * AID_USER_OFFSET + AID_APP_START + 1234
280// u2_i1000 -> 2 * AID_USER_OFFSET + AID_ISOLATED_START + 1000
281// u1_system -> 1 * AID_USER_OFFSET + android_ids['system']
Mark Salyzynb38347a2016-04-05 09:09:46 -0700282// returns 0 and sets errno to ENOENT in case of error.
283static id_t app_id_from_name(const char* name, bool is_group) {
284 char* end;
285 unsigned long userid;
286 bool is_shared_gid = false;
287
288 if (is_group && name[0] == 'a' && name[1] == 'l' && name[2] == 'l') {
289 end = const_cast<char*>(name+3);
290 userid = 0;
291 is_shared_gid = true;
292 } else if (name[0] == 'u' && isdigit(name[1])) {
293 userid = strtoul(name+1, &end, 10);
294 } else {
295 errno = ENOENT;
296 return 0;
297 }
298
299 if (end[0] != '_' || end[1] == 0) {
300 errno = ENOENT;
301 return 0;
302 }
303
304 unsigned long appid = 0;
305 if (end[1] == 'a' && isdigit(end[2])) {
306 if (is_shared_gid) {
307 // end will point to \0 if the strtoul below succeeds.
308 appid = strtoul(end+2, &end, 10) + AID_SHARED_GID_START;
309 if (appid > AID_SHARED_GID_END) {
310 errno = ENOENT;
311 return 0;
312 }
313 } else {
314 // end will point to \0 if the strtoul below succeeds.
Jeff Sharkey934bc862016-12-13 14:03:19 -0700315 appid = strtoul(end+2, &end, 10);
Tom Cherry6b116d12019-04-25 10:34:07 -0700316 if (is_group) {
317 if (!strcmp(end, "_ext_cache")) {
318 end += 10;
319 appid += AID_EXT_CACHE_GID_START;
320 } else if (!strcmp(end, "_ext")) {
321 end += 4;
322 appid += AID_EXT_GID_START;
323 } else if (!strcmp(end, "_cache")) {
324 end += 6;
325 appid += AID_CACHE_GID_START;
326 } else {
327 appid += AID_APP_START;
328 }
Jeff Sharkey934bc862016-12-13 14:03:19 -0700329 } else {
330 appid += AID_APP_START;
331 }
Mark Salyzynb38347a2016-04-05 09:09:46 -0700332 }
333 } else if (end[1] == 'i' && isdigit(end[2])) {
334 // end will point to \0 if the strtoul below succeeds.
335 appid = strtoul(end+2, &end, 10) + AID_ISOLATED_START;
Tom Cherry5fb07632019-04-24 13:35:39 -0700336 } else if (auto* android_id_info = find_android_id_info(end + 1); android_id_info != nullptr) {
337 appid = android_id_info->aid;
338 end += strlen(android_id_info->name) + 1;
Tom Cherry6b116d12019-04-25 10:34:07 -0700339 if (!platform_id_secondary_user_allowed(appid)) {
340 errno = ENOENT;
341 return 0;
342 }
Mark Salyzynb38347a2016-04-05 09:09:46 -0700343 }
344
345 // Check that the entire string was consumed by one of the 3 cases above.
346 if (end[0] != 0) {
347 errno = ENOENT;
348 return 0;
349 }
350
351 // Check that user id won't overflow.
352 if (userid > 1000) {
353 errno = ENOENT;
354 return 0;
355 }
356
357 // Check that app id is within range.
Jeff Sharkey934bc862016-12-13 14:03:19 -0700358 if (appid >= AID_USER_OFFSET) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700359 errno = ENOENT;
360 return 0;
361 }
362
Jeff Sharkey934bc862016-12-13 14:03:19 -0700363 return (appid + userid*AID_USER_OFFSET);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700364}
365
366static void print_app_name_from_uid(const uid_t uid, char* buffer, const int bufferlen) {
Jeff Sharkey934bc862016-12-13 14:03:19 -0700367 const uid_t appid = uid % AID_USER_OFFSET;
368 const uid_t userid = uid / AID_USER_OFFSET;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700369 if (appid >= AID_ISOLATED_START) {
370 snprintf(buffer, bufferlen, "u%u_i%u", userid, appid - AID_ISOLATED_START);
Jeff Sharkey934bc862016-12-13 14:03:19 -0700371 } else if (appid < AID_APP_START) {
Tom Cherry5fb07632019-04-24 13:35:39 -0700372 if (auto* android_id_info = find_android_id_info(appid); android_id_info != nullptr) {
373 snprintf(buffer, bufferlen, "u%u_%s", userid, android_id_info->name);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700374 }
375 } else {
Jeff Sharkey934bc862016-12-13 14:03:19 -0700376 snprintf(buffer, bufferlen, "u%u_a%u", userid, appid - AID_APP_START);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700377 }
378}
379
380static void print_app_name_from_gid(const gid_t gid, char* buffer, const int bufferlen) {
Jeff Sharkey934bc862016-12-13 14:03:19 -0700381 const uid_t appid = gid % AID_USER_OFFSET;
382 const uid_t userid = gid / AID_USER_OFFSET;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700383 if (appid >= AID_ISOLATED_START) {
384 snprintf(buffer, bufferlen, "u%u_i%u", userid, appid - AID_ISOLATED_START);
385 } else if (userid == 0 && appid >= AID_SHARED_GID_START && appid <= AID_SHARED_GID_END) {
386 snprintf(buffer, bufferlen, "all_a%u", appid - AID_SHARED_GID_START);
Tom Cherry6b116d12019-04-25 10:34:07 -0700387 } else if (appid >= AID_EXT_CACHE_GID_START && appid <= AID_EXT_CACHE_GID_END) {
388 snprintf(buffer, bufferlen, "u%u_a%u_ext_cache", userid, appid - AID_EXT_CACHE_GID_START);
389 } else if (appid >= AID_EXT_GID_START && appid <= AID_EXT_GID_END) {
390 snprintf(buffer, bufferlen, "u%u_a%u_ext", userid, appid - AID_EXT_GID_START);
Jeff Sharkey934bc862016-12-13 14:03:19 -0700391 } else if (appid >= AID_CACHE_GID_START && appid <= AID_CACHE_GID_END) {
392 snprintf(buffer, bufferlen, "u%u_a%u_cache", userid, appid - AID_CACHE_GID_START);
393 } else if (appid < AID_APP_START) {
Tom Cherry5fb07632019-04-24 13:35:39 -0700394 if (auto* android_id_info = find_android_id_info(appid); android_id_info != nullptr) {
395 snprintf(buffer, bufferlen, "u%u_%s", userid, android_id_info->name);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700396 }
397 } else {
Jeff Sharkey934bc862016-12-13 14:03:19 -0700398 snprintf(buffer, bufferlen, "u%u_a%u", userid, appid - AID_APP_START);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700399 }
400}
401
Jiyong Parkbf383282020-10-27 03:23:02 +0900402#if defined(__ANDROID__)
Tom Cherryb368a0b2019-04-24 11:12:59 -0700403static bool device_launched_before_api_29() {
404 // Check if ro.product.first_api_level is set to a value > 0 and < 29, if so, this device was
405 // launched before API 29 (Q). Any other value is considered to be either in development or
406 // launched after.
407 // Cache the value as __system_property_get() is expensive and this may be called often.
408 static bool result = [] {
409 char value[PROP_VALUE_MAX] = { 0 };
410 if (__system_property_get("ro.product.first_api_level", value) == 0) {
411 return false;
412 }
413 int value_int = atoi(value);
414 return value_int != 0 && value_int < 29;
415 }();
416 return result;
417}
418
Mark Salyzyn8d387ee2016-04-05 09:24:59 -0700419// oem_XXXX -> uid
420// Supported ranges:
421// AID_OEM_RESERVED_START to AID_OEM_RESERVED_END (2900-2999)
422// AID_OEM_RESERVED_2_START to AID_OEM_RESERVED_2_END (5000-5999)
423// Check OEM id is within range.
424static bool is_oem_id(id_t id) {
Tom Cherryb368a0b2019-04-24 11:12:59 -0700425 // Upgrading devices launched before API level 29 may not comply with the below check.
426 // Due to the difficulty in changing uids after launch, it is waived for these devices.
427 // The legacy range:
428 // AID_OEM_RESERVED_START to AID_EVERYBODY (2900-9996), excluding builtin AIDs.
429 if (device_launched_before_api_29() && id >= AID_OEM_RESERVED_START && id < AID_EVERYBODY &&
430 find_android_id_info(id) == nullptr) {
431 return true;
432 }
433
434 return (id >= AID_OEM_RESERVED_START && id <= AID_OEM_RESERVED_END) ||
435 (id >= AID_OEM_RESERVED_2_START && id <= AID_OEM_RESERVED_2_END);
Mark Salyzyn8d387ee2016-04-05 09:24:59 -0700436}
Jiyong Parkbf383282020-10-27 03:23:02 +0900437#else
438static bool is_oem_id(id_t) {
439 // no OEM ids in host
440 return false;
441}
442#endif // if defined(__ANDROID__)
Mark Salyzyn8d387ee2016-04-05 09:24:59 -0700443
Mark Salyzynb38347a2016-04-05 09:09:46 -0700444// Translate an OEM name to the corresponding user/group id.
Mark Salyzynb38347a2016-04-05 09:09:46 -0700445static id_t oem_id_from_name(const char* name) {
446 unsigned int id;
447 if (sscanf(name, "oem_%u", &id) != 1) {
448 return 0;
449 }
Mark Salyzyn8d387ee2016-04-05 09:24:59 -0700450 if (!is_oem_id(id)) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700451 return 0;
452 }
Mark Salyzyn8d387ee2016-04-05 09:24:59 -0700453 return static_cast<id_t>(id);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700454}
455
456static passwd* oem_id_to_passwd(uid_t uid, passwd_state_t* state) {
Tom Cherry777b34d2019-02-17 09:38:23 -0800457 for (auto& passwd_file : passwd_files) {
458 if (passwd_file.FindById(uid, state)) {
459 return &state->passwd_;
460 }
461 }
462
Mark Salyzyn8d387ee2016-04-05 09:24:59 -0700463 if (!is_oem_id(uid)) {
Tom Cherry6034ef82018-02-02 16:10:07 -0800464 return nullptr;
465 }
466
Mark Salyzyn8d387ee2016-04-05 09:24:59 -0700467 snprintf(state->name_buffer_, sizeof(state->name_buffer_), "oem_%u", uid);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700468 snprintf(state->dir_buffer_, sizeof(state->dir_buffer_), "/");
Tom Cherry777b34d2019-02-17 09:38:23 -0800469 snprintf(state->sh_buffer_, sizeof(state->sh_buffer_), "/bin/sh");
Mark Salyzynb38347a2016-04-05 09:09:46 -0700470
471 passwd* pw = &state->passwd_;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700472 pw->pw_uid = uid;
473 pw->pw_gid = uid;
474 return pw;
475}
476
477static group* oem_id_to_group(gid_t gid, group_state_t* state) {
Tom Cherry777b34d2019-02-17 09:38:23 -0800478 for (auto& group_file : group_files) {
479 if (group_file.FindById(gid, state)) {
480 return &state->group_;
481 }
Tom Cherry6034ef82018-02-02 16:10:07 -0800482 }
483
Tom Cherry777b34d2019-02-17 09:38:23 -0800484 if (!is_oem_id(gid)) {
485 return nullptr;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700486 }
487
488 snprintf(state->group_name_buffer_, sizeof(state->group_name_buffer_),
Mark Salyzyn8d387ee2016-04-05 09:24:59 -0700489 "oem_%u", gid);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700490
491 group* gr = &state->group_;
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700492 gr->gr_gid = gid;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700493 return gr;
494}
495
496// Translate a uid into the corresponding name.
Jeff Sharkey934bc862016-12-13 14:03:19 -0700497// 0 to AID_APP_START-1 -> "system", "radio", etc.
498// AID_APP_START to AID_ISOLATED_START-1 -> u0_a1234
499// AID_ISOLATED_START to AID_USER_OFFSET-1 -> u0_i1234
500// AID_USER_OFFSET+ -> u1_radio, u1_a1234, u2_i1234, etc.
Mark Salyzynb38347a2016-04-05 09:09:46 -0700501// returns a passwd structure (sets errno to ENOENT on failure).
502static passwd* app_id_to_passwd(uid_t uid, passwd_state_t* state) {
Tom Cherry6b116d12019-04-25 10:34:07 -0700503 if (uid < AID_APP_START || !is_valid_app_id(uid, false)) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700504 errno = ENOENT;
Yi Kong32bc0fc2018-08-02 17:31:13 -0700505 return nullptr;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700506 }
507
508 print_app_name_from_uid(uid, state->name_buffer_, sizeof(state->name_buffer_));
509
Jeff Sharkey934bc862016-12-13 14:03:19 -0700510 const uid_t appid = uid % AID_USER_OFFSET;
511 if (appid < AID_APP_START) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700512 snprintf(state->dir_buffer_, sizeof(state->dir_buffer_), "/");
513 } else {
514 snprintf(state->dir_buffer_, sizeof(state->dir_buffer_), "/data");
515 }
516
Tom Cherry777b34d2019-02-17 09:38:23 -0800517 snprintf(state->sh_buffer_, sizeof(state->sh_buffer_), "/bin/sh");
Mark Salyzynb38347a2016-04-05 09:09:46 -0700518
519 passwd* pw = &state->passwd_;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700520 pw->pw_uid = uid;
521 pw->pw_gid = uid;
522 return pw;
523}
524
525// Translate a gid into the corresponding app_<gid>
526// group structure (sets errno to ENOENT on failure).
527static group* app_id_to_group(gid_t gid, group_state_t* state) {
Tom Cherry6b116d12019-04-25 10:34:07 -0700528 if (gid < AID_APP_START || !is_valid_app_id(gid, true)) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700529 errno = ENOENT;
Yi Kong32bc0fc2018-08-02 17:31:13 -0700530 return nullptr;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700531 }
532
533 print_app_name_from_gid(gid, state->group_name_buffer_, sizeof(state->group_name_buffer_));
534
535 group* gr = &state->group_;
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700536 gr->gr_gid = gid;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700537 return gr;
538}
539
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700540passwd* getpwuid_internal(uid_t uid, passwd_state_t* state) {
Tom Cherry5fb07632019-04-24 13:35:39 -0700541 if (auto* android_id_info = find_android_id_info(uid); android_id_info != nullptr) {
542 return android_iinfo_to_passwd(state, android_id_info);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700543 }
Tom Cherry5fb07632019-04-24 13:35:39 -0700544
Jiyong Parkbf383282020-10-27 03:23:02 +0900545 // Find an entry from the database file
Tom Cherry5fb07632019-04-24 13:35:39 -0700546 passwd* pw = oem_id_to_passwd(uid, state);
Yi Kong32bc0fc2018-08-02 17:31:13 -0700547 if (pw != nullptr) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700548 return pw;
549 }
550 return app_id_to_passwd(uid, state);
551}
552
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700553passwd* getpwuid(uid_t uid) { // NOLINT: implementing bad function.
Josh Gao5e2285d2017-02-22 12:19:05 -0800554 passwd_state_t* state = get_passwd_tls_buffer();
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700555 return getpwuid_internal(uid, state);
556}
Mark Salyzynb38347a2016-04-05 09:09:46 -0700557
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700558passwd* getpwnam_internal(const char* login, passwd_state_t* state) {
Tom Cherry5fb07632019-04-24 13:35:39 -0700559 if (auto* android_id_info = find_android_id_info(login); android_id_info != nullptr) {
560 return android_iinfo_to_passwd(state, android_id_info);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700561 }
Tom Cherry6034ef82018-02-02 16:10:07 -0800562
Jiyong Parkbf383282020-10-27 03:23:02 +0900563 // Find an entry from the database file
Tom Cherry777b34d2019-02-17 09:38:23 -0800564 for (auto& passwd_file : passwd_files) {
565 if (passwd_file.FindByName(login, state)) {
Tom Cherry6034ef82018-02-02 16:10:07 -0800566 return &state->passwd_;
567 }
568 }
569
Mark Salyzynb38347a2016-04-05 09:09:46 -0700570 // Handle OEM range.
Tom Cherry5fb07632019-04-24 13:35:39 -0700571 passwd* pw = oem_id_to_passwd(oem_id_from_name(login), state);
Yi Kong32bc0fc2018-08-02 17:31:13 -0700572 if (pw != nullptr) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700573 return pw;
574 }
575 return app_id_to_passwd(app_id_from_name(login, false), state);
576}
577
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700578passwd* getpwnam(const char* login) { // NOLINT: implementing bad function.
579 passwd_state_t* state = get_passwd_tls_buffer();
580 return getpwnam_internal(login, state);
581}
582
583static int getpasswd_r(bool by_name, const char* name, uid_t uid, struct passwd* pwd, char* buf,
584 size_t buflen, struct passwd** result) {
585 ErrnoRestorer errno_restorer;
586 *result = nullptr;
587 char* p =
588 reinterpret_cast<char*>(__BIONIC_ALIGN(reinterpret_cast<uintptr_t>(buf), sizeof(uintptr_t)));
589 if (p + sizeof(passwd_state_t) > buf + buflen) {
590 return ERANGE;
591 }
592 passwd_state_t* state = reinterpret_cast<passwd_state_t*>(p);
593 init_passwd_state(state);
594 passwd* retval = (by_name ? getpwnam_internal(name, state) : getpwuid_internal(uid, state));
595 if (retval != nullptr) {
596 *pwd = *retval;
597 *result = pwd;
598 return 0;
599 }
600 return errno;
601}
602
603int getpwnam_r(const char* name, passwd* pwd, char* buf, size_t byte_count, passwd** result) {
604 return getpasswd_r(true, name, -1, pwd, buf, byte_count, result);
605}
606
607int getpwuid_r(uid_t uid, passwd* pwd, char* buf, size_t byte_count, passwd** result) {
608 return getpasswd_r(false, nullptr, uid, pwd, buf, byte_count, result);
609}
610
Mark Salyzynb38347a2016-04-05 09:09:46 -0700611// All users are in just one group, the one passed in.
612int getgrouplist(const char* /*user*/, gid_t group, gid_t* groups, int* ngroups) {
613 if (*ngroups < 1) {
614 *ngroups = 1;
615 return -1;
616 }
617 groups[0] = group;
618 return (*ngroups = 1);
619}
620
621char* getlogin() { // NOLINT: implementing bad function.
622 passwd *pw = getpwuid(getuid()); // NOLINT: implementing bad function in terms of bad function.
Elliott Hughes06bd5862017-07-28 16:27:49 -0700623 return pw ? pw->pw_name : nullptr;
624}
625
626int getlogin_r(char* buf, size_t size) {
627 char* login = getlogin();
628 if (login == nullptr) return errno;
629 size_t login_length = strlen(login) + 1;
630 if (login_length > size) return ERANGE;
631 memcpy(buf, login, login_length);
632 return 0;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700633}
634
Mark Salyzyn722ab052016-04-06 10:35:48 -0700635void setpwent() {
Josh Gao5e2285d2017-02-22 12:19:05 -0800636 passwd_state_t* state = get_passwd_tls_buffer();
Mark Salyzyn722ab052016-04-06 10:35:48 -0700637 if (state) {
638 state->getpwent_idx = 0;
639 }
640}
641
642void endpwent() {
643 setpwent();
644}
645
646passwd* getpwent() {
Josh Gao5e2285d2017-02-22 12:19:05 -0800647 passwd_state_t* state = get_passwd_tls_buffer();
Mark Salyzyn722ab052016-04-06 10:35:48 -0700648 if (state->getpwent_idx < 0) {
Yi Kong32bc0fc2018-08-02 17:31:13 -0700649 return nullptr;
Mark Salyzyn722ab052016-04-06 10:35:48 -0700650 }
651
652 size_t start = 0;
653 ssize_t end = android_id_count;
654 if (state->getpwent_idx < end) {
655 return android_iinfo_to_passwd(state, android_ids + state->getpwent_idx++);
656 }
657
658 start = end;
659 end += AID_OEM_RESERVED_END - AID_OEM_RESERVED_START + 1;
660
661 if (state->getpwent_idx < end) {
662 return oem_id_to_passwd(
663 state->getpwent_idx++ - start + AID_OEM_RESERVED_START, state);
664 }
665
666 start = end;
667 end += AID_OEM_RESERVED_2_END - AID_OEM_RESERVED_2_START + 1;
668
669 if (state->getpwent_idx < end) {
670 return oem_id_to_passwd(
671 state->getpwent_idx++ - start + AID_OEM_RESERVED_2_START, state);
672 }
673
Tom Cherry777b34d2019-02-17 09:38:23 -0800674 start = end;
675 end += AID_SYSTEM_EXT_RESERVED_END - AID_SYSTEM_RESERVED_START + 1;
676
677 if (state->getpwent_idx < end) {
678 // No one calls this enough to worry about how inefficient the below is.
679 auto* oem_passwd =
680 oem_id_to_passwd(state->getpwent_idx++ - start + AID_SYSTEM_RESERVED_START, state);
681 while (oem_passwd == nullptr && state->getpwent_idx < end) {
682 oem_passwd =
683 oem_id_to_passwd(state->getpwent_idx++ - start + AID_SYSTEM_RESERVED_START, state);
684 }
685 if (oem_passwd != nullptr) {
686 return oem_passwd;
687 }
688 }
689
Tom Cherry6b116d12019-04-25 10:34:07 -0700690 state->getpwent_idx = get_next_app_id(state->getpwent_idx, false);
Mark Salyzyn722ab052016-04-06 10:35:48 -0700691
Tom Cherry4362f892017-11-14 08:50:43 -0800692 if (state->getpwent_idx != -1) {
693 return app_id_to_passwd(state->getpwent_idx, state);
Mark Salyzyn722ab052016-04-06 10:35:48 -0700694 }
695
696 // We are not reporting u1_a* and higher or we will be here forever
Yi Kong32bc0fc2018-08-02 17:31:13 -0700697 return nullptr;
Mark Salyzyn722ab052016-04-06 10:35:48 -0700698}
699
Mark Salyzynb38347a2016-04-05 09:09:46 -0700700static group* getgrgid_internal(gid_t gid, group_state_t* state) {
Tom Cherry5fb07632019-04-24 13:35:39 -0700701 if (auto* android_id_info = find_android_id_info(gid); android_id_info != nullptr) {
702 return android_iinfo_to_group(state, android_id_info);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700703 }
Tom Cherry5fb07632019-04-24 13:35:39 -0700704
Jiyong Parkbf383282020-10-27 03:23:02 +0900705 // Find an entry from the database file
Tom Cherry5fb07632019-04-24 13:35:39 -0700706 group* grp = oem_id_to_group(gid, state);
Yi Kong32bc0fc2018-08-02 17:31:13 -0700707 if (grp != nullptr) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700708 return grp;
709 }
710 return app_id_to_group(gid, state);
711}
712
713group* getgrgid(gid_t gid) { // NOLINT: implementing bad function.
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700714 group_state_t* state = get_group_tls_buffer();
Mark Salyzynb38347a2016-04-05 09:09:46 -0700715 return getgrgid_internal(gid, state);
716}
717
718static group* getgrnam_internal(const char* name, group_state_t* state) {
Tom Cherry5fb07632019-04-24 13:35:39 -0700719 if (auto* android_id_info = find_android_id_info(name); android_id_info != nullptr) {
720 return android_iinfo_to_group(state, android_id_info);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700721 }
Tom Cherry6034ef82018-02-02 16:10:07 -0800722
Jiyong Parkbf383282020-10-27 03:23:02 +0900723 // Find an entry from the database file
Tom Cherry777b34d2019-02-17 09:38:23 -0800724 for (auto& group_file : group_files) {
725 if (group_file.FindByName(name, state)) {
Tom Cherry6034ef82018-02-02 16:10:07 -0800726 return &state->group_;
727 }
728 }
729
Mark Salyzynb38347a2016-04-05 09:09:46 -0700730 // Handle OEM range.
Tom Cherry5fb07632019-04-24 13:35:39 -0700731 group* grp = oem_id_to_group(oem_id_from_name(name), state);
Yi Kong32bc0fc2018-08-02 17:31:13 -0700732 if (grp != nullptr) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700733 return grp;
734 }
735 return app_id_to_group(app_id_from_name(name, true), state);
736}
737
738group* getgrnam(const char* name) { // NOLINT: implementing bad function.
Tom Cherryc57c5bd2019-05-14 17:02:28 -0700739 group_state_t* state = get_group_tls_buffer();
Mark Salyzynb38347a2016-04-05 09:09:46 -0700740 return getgrnam_internal(name, state);
741}
742
743static int getgroup_r(bool by_name, const char* name, gid_t gid, struct group* grp, char* buf,
744 size_t buflen, struct group** result) {
745 ErrnoRestorer errno_restorer;
Yi Kong32bc0fc2018-08-02 17:31:13 -0700746 *result = nullptr;
Mark Salyzynb38347a2016-04-05 09:09:46 -0700747 char* p = reinterpret_cast<char*>(
Dan Alberta613d0d2017-10-05 16:39:33 -0700748 __BIONIC_ALIGN(reinterpret_cast<uintptr_t>(buf), sizeof(uintptr_t)));
Mark Salyzynb38347a2016-04-05 09:09:46 -0700749 if (p + sizeof(group_state_t) > buf + buflen) {
750 return ERANGE;
751 }
752 group_state_t* state = reinterpret_cast<group_state_t*>(p);
753 init_group_state(state);
754 group* retval = (by_name ? getgrnam_internal(name, state) : getgrgid_internal(gid, state));
Yi Kong32bc0fc2018-08-02 17:31:13 -0700755 if (retval != nullptr) {
Mark Salyzynb38347a2016-04-05 09:09:46 -0700756 *grp = *retval;
757 *result = grp;
758 return 0;
759 }
760 return errno;
761}
762
763int getgrgid_r(gid_t gid, struct group* grp, char* buf, size_t buflen, struct group** result) {
Yi Kong32bc0fc2018-08-02 17:31:13 -0700764 return getgroup_r(false, nullptr, gid, grp, buf, buflen, result);
Mark Salyzynb38347a2016-04-05 09:09:46 -0700765}
766
767int getgrnam_r(const char* name, struct group* grp, char* buf, size_t buflen,
768 struct group **result) {
769 return getgroup_r(true, name, 0, grp, buf, buflen, result);
770}
Mark Salyzyn722ab052016-04-06 10:35:48 -0700771
772void setgrent() {
Josh Gao5e2285d2017-02-22 12:19:05 -0800773 group_state_t* state = get_group_tls_buffer();
Mark Salyzyn722ab052016-04-06 10:35:48 -0700774 if (state) {
775 state->getgrent_idx = 0;
776 }
777}
778
779void endgrent() {
780 setgrent();
781}
782
783group* getgrent() {
Josh Gao5e2285d2017-02-22 12:19:05 -0800784 group_state_t* state = get_group_tls_buffer();
Mark Salyzyn722ab052016-04-06 10:35:48 -0700785 if (state->getgrent_idx < 0) {
Yi Kong32bc0fc2018-08-02 17:31:13 -0700786 return nullptr;
Mark Salyzyn722ab052016-04-06 10:35:48 -0700787 }
788
789 size_t start = 0;
790 ssize_t end = android_id_count;
791 if (state->getgrent_idx < end) {
Mark Salyzyn722ab052016-04-06 10:35:48 -0700792 return android_iinfo_to_group(state, android_ids + state->getgrent_idx++);
793 }
794
795 start = end;
796 end += AID_OEM_RESERVED_END - AID_OEM_RESERVED_START + 1;
797
798 if (state->getgrent_idx < end) {
Mark Salyzyn722ab052016-04-06 10:35:48 -0700799 return oem_id_to_group(
800 state->getgrent_idx++ - start + AID_OEM_RESERVED_START, state);
801 }
802
803 start = end;
804 end += AID_OEM_RESERVED_2_END - AID_OEM_RESERVED_2_START + 1;
805
806 if (state->getgrent_idx < end) {
Mark Salyzyn722ab052016-04-06 10:35:48 -0700807 return oem_id_to_group(
808 state->getgrent_idx++ - start + AID_OEM_RESERVED_2_START, state);
809 }
810
811 start = end;
Tom Cherry777b34d2019-02-17 09:38:23 -0800812 end += AID_SYSTEM_EXT_RESERVED_END - AID_SYSTEM_RESERVED_START + 1;
813
814 if (state->getgrent_idx < end) {
815 // No one calls this enough to worry about how inefficient the below is.
816 init_group_state(state);
817 auto* oem_group =
818 oem_id_to_group(state->getgrent_idx++ - start + AID_SYSTEM_RESERVED_START, state);
819 while (oem_group == nullptr && state->getgrent_idx < end) {
820 oem_group = oem_id_to_group(state->getgrent_idx++ - start + AID_SYSTEM_RESERVED_START, state);
821 }
822 if (oem_group != nullptr) {
823 return oem_group;
824 }
825 }
826
827 start = end;
Jeff Sharkey934bc862016-12-13 14:03:19 -0700828 end += AID_USER_OFFSET - AID_APP_START; // Do not expose higher groups
Mark Salyzyn722ab052016-04-06 10:35:48 -0700829
Tom Cherry6b116d12019-04-25 10:34:07 -0700830 state->getgrent_idx = get_next_app_id(state->getgrent_idx, true);
Tom Cherry4362f892017-11-14 08:50:43 -0800831
832 if (state->getgrent_idx != -1) {
833 return app_id_to_group(state->getgrent_idx, state);
Mark Salyzyn722ab052016-04-06 10:35:48 -0700834 }
835
836 // We are not reporting u1_a* and higher or we will be here forever
Yi Kong32bc0fc2018-08-02 17:31:13 -0700837 return nullptr;
Mark Salyzyn722ab052016-04-06 10:35:48 -0700838}