1 /*
2 * Copyright (C) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <atomic.h>
17 #include <errno.h>
18 #include <string.h>
19 #include <stdint.h>
20 #include <sys/cdefs.h>
21 #include <sys/resource.h>
22 #include <sys/mman.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <stdarg.h>
26
27
28 #include "musl_log.h"
29 #include "musl_fdsan.h"
30 #include "libc.h"
31 #include "pthread_impl.h"
32 #include "hilog_adapter.h"
33
34 #ifdef OHOS_ENABLE_PARAMETER
35 #include "sys_param.h"
36 #define MUSL_FDSAN_ERROR(fmt, ap) HiLogAdapterVaList(MUSL_LOG_TYPE, LOG_ERROR, MUSL_LOG_DOMAIN, "MUSL-FDSAN", fmt, ap)
37 #else
38 #define MUSL_FDSAN_ERROR(fmt, ap)
39 #endif
40
41 const char *fdsan_parameter_name = "musl.debug.fdsan";
42 #define ALIGN(x,y) ((x)+(y)-1 & -(y))
43 extern int __close(int fd);
44
45 static struct FdTable g_fd_table = {
46 .error_level = FDSAN_ERROR_LEVEL_WARN_ALWAYS,
47 .overflow = NULL,
48 };
49
__get_fdtable()50 struct FdTable* __get_fdtable()
51 {
52 return &g_fd_table;
53 }
54
get_fd_entry(size_t idx)55 static struct FdEntry* get_fd_entry(size_t idx)
56 {
57 struct FdEntry *entries = __get_fdtable()->entries;
58 if (idx < FdTableSize) {
59 return &entries[idx];
60 }
61 // Try to create the overflow table ourselves.
62 struct FdTableOverflow* local_overflow = atomic_load(&__get_fdtable()->overflow);
63 if (__predict_false(!local_overflow)) {
64 struct rlimit rlim = { .rlim_max = 32768 };
65 getrlimit(RLIMIT_NOFILE, &rlim);
66 rlim_t max = rlim.rlim_max;
67
68 if (max == RLIM_INFINITY) {
69 max = 32768; // Max fd size
70 }
71
72 if (idx > max) {
73 return NULL;
74 }
75 size_t required_count = max - FdTableSize;
76 size_t required_size = sizeof(struct FdTableOverflow) + required_count * sizeof(struct FdEntry);
77 size_t aligned_size = ALIGN(required_size, PAGE_SIZE);
78 size_t aligned_count = (aligned_size - sizeof(struct FdTableOverflow)) / sizeof(struct FdEntry);
79 void* allocation =
80 mmap(NULL, aligned_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
81 if (allocation == MAP_FAILED) {
82 MUSL_LOGE("fdsan: mmap failed");
83 }
84
85 struct FdTableOverflow* new_overflow = (struct FdTableOverflow*)(allocation);
86 new_overflow->len = aligned_count;
87
88 if (atomic_compare_exchange_strong(&__get_fdtable()->overflow, &local_overflow, new_overflow)) {
89 local_overflow = new_overflow;
90 } else {
91 // Another thread had mmaped.
92 munmap(allocation, aligned_size);
93 }
94 }
95
96 size_t offset = idx - FdTableSize;
97 if (local_overflow->len <= offset) {
98 return NULL;
99 }
100 return &local_overflow->entries[offset];
101 }
102
__init_fdsan()103 void __init_fdsan()
104 {
105 enum fdsan_error_level default_level = FDSAN_ERROR_LEVEL_WARN_ALWAYS;
106 fdsan_set_error_level_from_param(default_level);
107 }
108
109 // Exposed to the platform to allow crash_dump to print out the fd table.
fdsan_get_fd_table()110 void* fdsan_get_fd_table()
111 {
112 return __get_fdtable();
113 }
114
GetFdEntry(int fd)115 static struct FdEntry* GetFdEntry(int fd)
116 {
117 if (fd < 0) {
118 return NULL;
119 }
120 return get_fd_entry(fd);
121 }
122
fdsan_error(const char * fmt,...)123 static void fdsan_error(const char* fmt, ...)
124 {
125 struct FdTable* fd_table = __get_fdtable();
126
127 enum fdsan_error_level error_level = atomic_load(&fd_table->error_level);
128 if (error_level == FDSAN_ERROR_LEVEL_DISABLED) {
129 return;
130 }
131 va_list va;
132 va_start(va, fmt);
133 switch (error_level) {
134 case FDSAN_ERROR_LEVEL_WARN_ONCE:
135 MUSL_FDSAN_ERROR(fmt, va);
136 atomic_compare_exchange_strong(&fd_table->error_level, &error_level, FDSAN_ERROR_LEVEL_DISABLED);
137 case FDSAN_ERROR_LEVEL_WARN_ALWAYS:
138 MUSL_FDSAN_ERROR(fmt, va);
139 break;
140 case FDSAN_ERROR_LEVEL_FATAL:
141 MUSL_FDSAN_ERROR(fmt, va);
142 abort();
143 case FDSAN_ERROR_LEVEL_DISABLED:
144 break;
145 }
146 va_end(va);
147 }
148
fdsan_create_owner_tag(enum fdsan_owner_type type,uint64_t tag)149 uint64_t fdsan_create_owner_tag(enum fdsan_owner_type type, uint64_t tag)
150 {
151 if (tag == 0) {
152 return 0;
153 }
154
155 if (__predict_false((type & 0xff) != type)) {
156 MUSL_LOGE("invalid fdsan_owner_type value: %x", type);
157 abort();
158 }
159
160 uint64_t result = (uint64_t)(type) << 56;
161 uint64_t mask = ((uint64_t)(1) << 56) - 1;
162 result |= tag & mask;
163 return result;
164 }
165
fdsan_get_tag_type(uint64_t tag)166 const char* fdsan_get_tag_type(uint64_t tag)
167 {
168 uint64_t type = tag >> 56;
169 uint64_t high_bits = tag >> 48;
170 switch (type) {
171 case FDSAN_OWNER_TYPE_FILE:
172 return "FILE*";
173 case FDSAN_OWNER_TYPE_DIRECTORY:
174 return "DIR*";
175 case FDSAN_OWNER_TYPE_UNIQUE_FD:
176 return "unique_fd";
177 case FDSAN_OWNER_TYPE_ZIP_ARCHIVE:
178 return "ZipArchive";
179 case FDSAN_OWNER_TYPE_MAX:
180 if (high_bits == (1 << 16) - 1) {
181 return "native object of unknown type";
182 }
183 return "object of unknown type";
184 case FDSAN_OWNER_TYPE_DEFAULT:
185 default:
186 return "native object of unknown type";
187 }
188 }
189
fdsan_get_tag_value(uint64_t tag)190 uint64_t fdsan_get_tag_value(uint64_t tag)
191 {
192 // Lop off the most significant byte and sign extend.
193 return (uint64_t)((int64_t)(tag << 8) >> 8);
194 }
195
fdsan_exchange_owner_tag(int fd,uint64_t expected_tag,uint64_t new_tag)196 void fdsan_exchange_owner_tag(int fd, uint64_t expected_tag, uint64_t new_tag)
197 {
198 if (__pthread_self()->by_vfork) {
199 return;
200 }
201 struct FdEntry* fde = GetFdEntry(fd);
202 if (!fde) {
203 return;
204 }
205
206 uint64_t tag = expected_tag;
207 if (!atomic_compare_exchange_strong(&fde->close_tag, &tag, new_tag)) {
208 if (expected_tag && tag) {
209 fdsan_error("failed to exchange ownership of file descriptor: fd %{public}d, \
210 was owned by %{public}s 0x%{public}016lx, \
211 was expected to be owned by %{public}s 0x%{public}016lx", \
212 fd, fdsan_get_tag_type(tag), fdsan_get_tag_value(tag),
213 fdsan_get_tag_type(expected_tag), fdsan_get_tag_value(expected_tag));
214 } else if (expected_tag && !tag) {
215 fdsan_error("failed to exchange ownership of file descriptor: fd %{public}d is unowned, \
216 was expected to be owned by %{public}s 0x%{public}016lx", \
217 fd, fdsan_get_tag_type(expected_tag), fdsan_get_tag_value(expected_tag));
218 } else if (!expected_tag && tag) {
219 fdsan_error("failed to exchange ownership of file descriptor: fd %{public}d, \
220 was owned by %{public}s 0x%{public}016lx, was expected to be unowned", \
221 fd, fdsan_get_tag_type(tag), fdsan_get_tag_value(tag));
222 } else if (!expected_tag && !tag) {
223 // expected == actual == 0 but cas failed?
224 MUSL_LOGE("fdsan compare and set failed unexpectedly while exchanging owner tag");
225 }
226 }
227 }
228
fdsan_close_with_tag(int fd,uint64_t expected_tag)229 int fdsan_close_with_tag(int fd, uint64_t expected_tag)
230 {
231 if (__pthread_self()->by_vfork) {
232 return __close(fd);
233 }
234 struct FdEntry* fde = GetFdEntry(fd);
235 if (!fde) {
236 return __close(fd);
237 }
238
239 uint64_t tag = expected_tag;
240 if (!atomic_compare_exchange_strong(&fde->close_tag, &tag, 0)) {
241 const char* expected_type = fdsan_get_tag_type(expected_tag);
242 uint64_t expected_owner = fdsan_get_tag_value(expected_tag);
243 const char* actual_type = fdsan_get_tag_type(tag);
244 uint64_t actual_owner = fdsan_get_tag_value(tag);
245 if (expected_tag && tag) {
246 fdsan_error("attempted to close file descriptor %{public}d, \
247 expected to be owned by %{public}s 0x%{public}016lx, \
248 actually owned by %{public}s 0x%{public}016lx", \
249 fd, expected_type, expected_owner, actual_type, actual_owner);
250 } else if (expected_tag && !tag) {
251 fdsan_error("attempted to close file descriptor %{public}d, \
252 expected to be owned by %{public}s 0x%{public}016lx, actually unowned", \
253 fd, expected_type, expected_owner);
254 } else if (!expected_tag && tag) {
255 fdsan_error("attempted to close file descriptor %{public}d, \
256 expected to be unowned, actually owned by %{public}s 0x%{public}016lx", \
257 fd, actual_type, actual_owner);
258 } else if (!expected_tag && !tag) {
259 // expected == actual == 0 but cas failed?
260 MUSL_LOGE("fdsan compare and set failed unexpectedly while closing");
261 abort();
262 }
263 }
264
265 int rc = __close(fd);
266 // If we were expecting to close with a tag, abort on EBADF.
267 if (expected_tag && rc == -1 && errno == EBADF) {
268 fdsan_error("EBADF: close failed for fd %{public}d with expected tag: 0x%{public}016lx", fd, expected_tag);
269 }
270 return rc;
271 }
272
fdsan_get_owner_tag(int fd)273 uint64_t fdsan_get_owner_tag(int fd)
274 {
275 struct FdEntry* fde = GetFdEntry(fd);
276 if (!fde) {
277 return 0;
278 }
279 return fde->close_tag;
280 }
281
fdsan_get_error_level()282 enum fdsan_error_level fdsan_get_error_level()
283 {
284 return __get_fdtable()->error_level;
285 }
286
fdsan_set_error_level(enum fdsan_error_level new_level)287 enum fdsan_error_level fdsan_set_error_level(enum fdsan_error_level new_level)
288 {
289 if (__pthread_self()->by_vfork) {
290 return fdsan_get_error_level();
291 }
292
293 return atomic_exchange(&__get_fdtable()->error_level, new_level);
294 }
295
fdsan_set_error_level_from_param(enum fdsan_error_level default_level)296 enum fdsan_error_level fdsan_set_error_level_from_param(enum fdsan_error_level default_level)
297 {
298 #ifdef OHOS_ENABLE_PARAMETER
299 static CachedHandle param_handler = NULL;
300 if (param_handler == NULL) {
301 param_handler = CachedParameterCreate(fdsan_parameter_name, "0");
302 }
303 char *param_value = CachedParameterGet(param_handler);
304 if (param_value == NULL) {
305 return fdsan_set_error_level(default_level);
306 } else if (strcmp(param_value, "fatal") == 0) {
307 return fdsan_set_error_level(FDSAN_ERROR_LEVEL_FATAL);
308 } else if (strcmp(param_value, "warn") == 0) {
309 return fdsan_set_error_level(FDSAN_ERROR_LEVEL_WARN_ALWAYS);
310 } else if (strcmp(param_value, "warn_once") == 0) {
311 return fdsan_set_error_level(FDSAN_ERROR_LEVEL_WARN_ONCE);
312 } else {
313 MUSL_LOGD("[fdsan] musl.debug.fdsan set to unknown value '%{public}s'", param_value);
314 }
315 #endif
316 return fdsan_set_error_level(default_level);
317 }
318
close(int fd)319 int close(int fd)
320 {
321 int rc = fdsan_close_with_tag(fd, 0);
322 if (rc == -1 && errno == EINTR) {
323 return 0;
324 }
325 return rc;
326 }