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
41const char *fdsan_parameter_name = "musl.debug.fdsan";
42#define ALIGN(x,y) ((x)+(y)-1 & -(y))
43extern int __close(int fd);
44
45static struct FdTable g_fd_table = {
46	.error_level = FDSAN_ERROR_LEVEL_WARN_ALWAYS,
47	.overflow = NULL,
48};
49
50struct FdTable* __get_fdtable()
51{
52	return &g_fd_table;
53}
54
55static 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
103void __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.
110void* fdsan_get_fd_table()
111{
112	return __get_fdtable();
113}
114
115static struct FdEntry* GetFdEntry(int fd)
116{
117	if (fd < 0) {
118		return NULL;
119	}
120	return get_fd_entry(fd);
121}
122
123static 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
149uint64_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
166const 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
190uint64_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
196void 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
229int 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
273uint64_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
282enum fdsan_error_level fdsan_get_error_level()
283{
284	return __get_fdtable()->error_level;
285}
286
287enum 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
296enum 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
319int 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}