xref: /third_party/musl/src/fdsan/linux/fdsan.c (revision 570af302)
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#include <unistd.h>
27
28
29#include "musl_log.h"
30#include "musl_fdsan.h"
31#include "libc.h"
32#include "pthread_impl.h"
33#include "hilog_adapter.h"
34#include <info/fatal_message.h>
35
36#ifdef OHOS_ENABLE_PARAMETER
37#include "sys_param.h"
38#define MUSL_FDSAN_ERROR(fmt, ...) ((void)HiLogAdapterPrint(MUSL_LOG_TYPE, LOG_ERROR, MUSL_LOG_DOMAIN, "MUSL-FDSAN", \
39															fmt, __VA_ARGS__))
40#else
41#define MUSL_FDSAN_ERROR(fmt, ...)
42#endif
43
44#define MAX_DEBUG_MSG_LEN 1024
45
46const char *fdsan_parameter_name = "musl.debug.fdsan";
47#define ALIGN(x,y) ((x)+(y)-1 & -(y))
48extern int __close(int fd);
49
50static struct FdTable g_fd_table = {
51	.error_level = FDSAN_ERROR_LEVEL_WARN_ALWAYS,
52	.overflow = NULL,
53};
54
55struct FdTable* __get_fdtable()
56{
57	return &g_fd_table;
58}
59
60static struct FdEntry* get_fd_entry(size_t idx)
61{
62	struct FdEntry *entries = __get_fdtable()->entries;
63	if (idx < FdTableSize) {
64		return &entries[idx];
65	}
66	// Try to create the overflow table ourselves.
67	struct FdTableOverflow* local_overflow = atomic_load(&__get_fdtable()->overflow);
68	if (__predict_false(!local_overflow)) {
69		struct rlimit rlim = { .rlim_max = 32768 };
70		getrlimit(RLIMIT_NOFILE, &rlim);
71		rlim_t max = rlim.rlim_max;
72
73		if (max == RLIM_INFINITY) {
74			max = 32768; // Max fd size
75		}
76
77		if (idx > max) {
78			return NULL;
79		}
80		size_t required_count = max - FdTableSize;
81		size_t required_size = sizeof(struct FdTableOverflow) + required_count * sizeof(struct FdEntry);
82		size_t aligned_size = ALIGN(required_size, PAGE_SIZE);
83		size_t aligned_count = (aligned_size - sizeof(struct FdTableOverflow)) / sizeof(struct FdEntry);
84		void* allocation =
85				mmap(NULL, aligned_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
86		if (allocation == MAP_FAILED) {
87			MUSL_LOGE("fdsan: mmap failed");
88		}
89
90		struct FdTableOverflow* new_overflow = (struct FdTableOverflow*)(allocation);
91		new_overflow->len = aligned_count;
92
93		if (atomic_compare_exchange_strong(&__get_fdtable()->overflow, &local_overflow, new_overflow)) {
94			local_overflow = new_overflow;
95		} else {
96			// Another thread had mmaped.
97			munmap(allocation, aligned_size);
98		}
99	}
100
101	size_t offset = idx - FdTableSize;
102	if (local_overflow->len <= offset) {
103		return NULL;
104	}
105	return &local_overflow->entries[offset];
106}
107
108void __init_fdsan()
109{
110	enum fdsan_error_level default_level = FDSAN_ERROR_LEVEL_WARN_ALWAYS;
111	fdsan_set_error_level_from_param(default_level);
112}
113
114// Exposed to the platform to allow crash_dump to print out the fd table.
115void* fdsan_get_fd_table()
116{
117	return __get_fdtable();
118}
119
120static struct FdEntry* GetFdEntry(int fd)
121{
122	if (fd < 0) {
123		return NULL;
124	}
125	return get_fd_entry(fd);
126}
127
128/*
129 * @brief Trigger the signal to grab the stack and save it on site,
130 *        and the msg will be recorded in the fault log
131 *        Will wait for the signal handle to grab the stack until it is completed or exits abnormally
132 * @param msg The debug message
133 */
134static void save_debug_message(const char *msg)
135{
136	if (msg == NULL) {
137		MUSL_LOGW("debug msg is NULL");
138		return;
139	}
140
141	const int NUMBER_ONE_THOUSAND = 1000; // 1000 : second to millisecond convert ratio
142	const int NUMBER_ONE_MILLION = 1000000; // 1000000 : nanosecond to millisecond convert ratio
143	struct timespec ts;
144	(void)clock_gettime(CLOCK_REALTIME, &ts);
145
146	debug_msg_t debug_message = {0, NULL};
147	debug_message.timestamp = ((uint64_t)ts.tv_sec * NUMBER_ONE_THOUSAND) +
148		(((uint64_t)ts.tv_sec) / NUMBER_ONE_MILLION);
149	debug_message.msg = msg;
150
151	const int signo = 42; // Custom stack capture signal and leak reuse
152	const int si_code = 1; // When si_signo = 42, use si_code = 1 mark the event as fdsan
153	siginfo_t info;
154	info.si_signo = signo;
155	info.si_code = si_code;
156	info.si_value.sival_ptr = &debug_message;
157	if (syscall(__NR_rt_tgsigqueueinfo, getpid(), __syscall(SYS_gettid), signo, &info) == -1) {
158		MUSL_LOGE("send failed errno=%{public}d", errno);
159	}
160}
161
162static void fdsan_error(const char* fmt, ...)
163{
164	struct FdTable* fd_table = __get_fdtable();
165
166	enum fdsan_error_level error_level = atomic_load(&fd_table->error_level);
167	if (error_level == FDSAN_ERROR_LEVEL_DISABLED) {
168		return;
169	}
170	char msg[MAX_DEBUG_MSG_LEN] = {0};
171	va_list va;
172	va_start(va, fmt);
173	(void)vsnprintf(msg, sizeof(msg) - 1, fmt, va);
174	va_end(va);
175	switch (error_level) {
176		case FDSAN_ERROR_LEVEL_WARN_ONCE:
177			MUSL_FDSAN_ERROR("%{public}s", msg);
178			atomic_compare_exchange_strong(&fd_table->error_level, &error_level, FDSAN_ERROR_LEVEL_DISABLED);
179		case FDSAN_ERROR_LEVEL_WARN_ALWAYS: {
180			MUSL_FDSAN_ERROR("%{public}s", msg);
181			save_debug_message(msg);
182			break;
183		}
184		case FDSAN_ERROR_LEVEL_FATAL:
185			MUSL_FDSAN_ERROR("%{public}s", msg);
186			abort();
187		case FDSAN_ERROR_LEVEL_DISABLED:
188			break;
189	}
190}
191
192uint64_t fdsan_create_owner_tag(enum fdsan_owner_type type, uint64_t tag)
193{
194	if (tag == 0) {
195		return 0;
196	}
197
198	if (__predict_false((type & 0xff) != type)) {
199		MUSL_LOGE("invalid fdsan_owner_type value: %x", type);
200		abort();
201	}
202
203	uint64_t result = (uint64_t)(type) << 56;
204	uint64_t mask = ((uint64_t)(1) << 56) - 1;
205	result |= tag & mask;
206	return result;
207}
208
209const char* fdsan_get_tag_type(uint64_t tag)
210{
211	uint64_t type = tag >> 56;
212	uint64_t high_bits = tag >> 48;
213	switch (type) {
214		case FDSAN_OWNER_TYPE_FILE:
215			return "FILE*";
216		case FDSAN_OWNER_TYPE_DIRECTORY:
217			return "DIR*";
218		case FDSAN_OWNER_TYPE_UNIQUE_FD:
219			return "unique_fd";
220		case FDSAN_OWNER_TYPE_ZIP_ARCHIVE:
221			return "ZipArchive";
222		case FDSAN_OWNER_TYPE_MAX:
223			if (high_bits == (1 << 16) - 1) {
224				return "native object of unknown type";
225			}
226			return "object of unknown type";
227		case FDSAN_OWNER_TYPE_DEFAULT:
228		default:
229			return "native object of unknown type";
230	}
231}
232
233uint64_t fdsan_get_tag_value(uint64_t tag)
234{
235	// Lop off the most significant byte and sign extend.
236	return (uint64_t)((int64_t)(tag << 8) >> 8);
237}
238
239void fdsan_exchange_owner_tag(int fd, uint64_t expected_tag, uint64_t new_tag)
240{
241	if (__pthread_self()->by_vfork) {
242		return;
243	}
244	struct FdEntry* fde = GetFdEntry(fd);
245	if (!fde) {
246		return;
247	}
248
249	uint64_t tag = expected_tag;
250	if (!atomic_compare_exchange_strong(&fde->close_tag, &tag, new_tag)) {
251		if (expected_tag && tag) {
252			fdsan_error("failed to exchange ownership of file descriptor: fd %d, "\
253						"was owned by %s 0x%016lx, "\
254						"was expected to be owned by %s 0x%016lx",
255						fd, fdsan_get_tag_type(tag), fdsan_get_tag_value(tag),
256						fdsan_get_tag_type(expected_tag), fdsan_get_tag_value(expected_tag));
257		} else if (expected_tag && !tag) {
258			fdsan_error("failed to exchange ownership of file descriptor: fd %d is unowned, "\
259						"was expected to be owned by %s 0x%016lx",
260						fd, fdsan_get_tag_type(expected_tag), fdsan_get_tag_value(expected_tag));
261		} else if (!expected_tag && tag) {
262			fdsan_error("failed to exchange ownership of file descriptor: fd %d, "\
263						"was owned by %s 0x%016lx, was expected to be unowned",
264						fd, fdsan_get_tag_type(tag), fdsan_get_tag_value(tag));
265		} else if (!expected_tag && !tag) {
266			// expected == actual == 0 but cas failed?
267			MUSL_LOGE("fdsan compare and set failed unexpectedly while exchanging owner tag");
268		}
269	}
270}
271
272int fdsan_close_with_tag(int fd, uint64_t expected_tag)
273{
274	if (__pthread_self()->by_vfork) {
275		 return __close(fd);
276	}
277	struct FdEntry* fde = GetFdEntry(fd);
278	if (!fde) {
279		return __close(fd);
280	}
281
282	uint64_t tag = expected_tag;
283	if (!atomic_compare_exchange_strong(&fde->close_tag, &tag, 0)) {
284		const char* expected_type = fdsan_get_tag_type(expected_tag);
285		uint64_t expected_owner = fdsan_get_tag_value(expected_tag);
286		const char* actual_type = fdsan_get_tag_type(tag);
287		uint64_t actual_owner = fdsan_get_tag_value(tag);
288		if (expected_tag && tag) {
289			fdsan_error("attempted to close file descriptor %d, "\
290						"expected to be owned by %s 0x%016lx, "\
291						"actually owned by %s 0x%016lx",
292						fd, expected_type, expected_owner, actual_type, actual_owner);
293		} else if (expected_tag && !tag) {
294			fdsan_error("attempted to close file descriptor %d,"\
295						"expected to be owned by %s 0x%016lx, actually unowned", \
296						fd, expected_type, expected_owner);
297		} else if (!expected_tag && tag) {
298			fdsan_error("attempted to close file descriptor %d, "\
299						"expected to be unowned, actually owned by %s 0x%016lx", \
300						fd, actual_type, actual_owner);
301		} else if (!expected_tag && !tag) {
302			// expected == actual == 0 but cas failed?
303			MUSL_LOGE("fdsan compare and set failed unexpectedly while closing");
304			abort();
305		}
306	}
307
308	int rc = __close(fd);
309	// If we were expecting to close with a tag, abort on EBADF.
310	if (expected_tag && rc == -1 && errno == EBADF) {
311		fdsan_error("EBADF: close failed for fd %d with expected tag: 0x%016lx", fd, expected_tag);
312	}
313	return rc;
314}
315
316uint64_t fdsan_get_owner_tag(int fd)
317{
318  struct FdEntry* fde = GetFdEntry(fd);
319  if (!fde) {
320	return 0;
321  }
322  return fde->close_tag;
323}
324
325enum fdsan_error_level fdsan_get_error_level()
326{
327	return __get_fdtable()->error_level;
328}
329
330enum fdsan_error_level fdsan_set_error_level(enum fdsan_error_level new_level)
331{
332	 if (__pthread_self()->by_vfork) {
333		 return fdsan_get_error_level();
334	 }
335
336	return atomic_exchange(&__get_fdtable()->error_level, new_level);
337}
338
339enum fdsan_error_level fdsan_set_error_level_from_param(enum fdsan_error_level default_level)
340{
341#ifdef OHOS_ENABLE_PARAMETER
342		static CachedHandle param_handler = NULL;
343		if (param_handler == NULL) {
344				param_handler = CachedParameterCreate(fdsan_parameter_name, "0");
345		}
346		char *param_value = CachedParameterGet(param_handler);
347		if (param_value == NULL) {
348				return fdsan_set_error_level(default_level);
349		} else if (strcmp(param_value, "fatal") == 0) {
350				return fdsan_set_error_level(FDSAN_ERROR_LEVEL_FATAL);
351		} else if (strcmp(param_value, "warn") == 0) {
352				return fdsan_set_error_level(FDSAN_ERROR_LEVEL_WARN_ALWAYS);
353		} else if (strcmp(param_value, "warn_once") == 0) {
354				return fdsan_set_error_level(FDSAN_ERROR_LEVEL_WARN_ONCE);
355		} else {
356				MUSL_LOGD("[fdsan] musl.debug.fdsan set to unknown value '%{public}s'", param_value);
357		}
358#endif
359		return fdsan_set_error_level(default_level);
360}
361
362int close(int fd)
363{
364	int rc = fdsan_close_with_tag(fd, 0);
365	if (rc == -1 && errno == EINTR) {
366		return 0;
367	}
368	return rc;
369}
370