1# Using fdsan 2 3## Introduction 4 5File descriptor sanitizer (fdsan) is a tool used to detect mishandling of file descriptor ownership, which includes double-close and use-after-close. The file descriptor can indicate a file, directory, network socket, I/O device, and the like in an operating system. When a file or a socket is opened in an application, a file descriptor is generated. If a file descriptor is repeatedly closed after being used or is used after being closed, security risks such as memory leaks and file handle leaks will be caused. This type of problems is difficult to locate and fix. That is why fdsan comes in handy. 6 7## Working Principle 8 9fdsan provides functions to associate a file descriptor with an owner and enforces detection of file descriptor errors based on ownership. When a file is opened or created, the file descriptor returned is associated with a tag, which indicates the owner responsible for closing it. Before the file is closed, the tag associated with the file descriptor is checked to determine whether the owner is correct. If yes, the file can be closed. Otherwise, an exception will be thrown to trigger error handling. 10 11A tag is of 64 bits, consisting of the following: 12 13- **type**: an 8-bit string indicating how a file descriptor is encapsulated for management. For example, **FDSAN_OWNER_TYPE_FILE** indicates that the file descriptor is managed as a handle to a file. The value of **type** is defined in **fdsan_owner_type**. 14- **value**: a 56-bit string uniquely identifying a tag. 15 16 **Figure** Tag 17 18 19 20 21 22## Available APIs 23 24### fdsan_set_error_level 25 26``` 27enum fdsan_error_level fdsan_set_error_level(enum fdsan_error_level new_level); 28``` 29 30**Description**<br>Sets an error level, which determines the processing behavior when an exception is detected. The default value is **FDSAN_ERROR_LEVEL_WARN_ALWAYS**. 31 32**Parameters**<br>**fdsan_error_level** 33 34| Value | Description | 35| -------------------------- | ------------------------------------------------------------ | 36| `FDSAN_ERROR_LEVEL_DISABLED` | fdsan is disabled, that is, no processing is performed. | 37| `FDSAN_ERROR_LEVEL_WARN_ONCE` | Give a warning in HiLog only when the error is detected for the first time and then continue execution with fdsan disabled (**FDSAN_ERROR_LEVEL_DISABLED**).| 38| `FDSAN_ERROR_LEVEL_WARN_ALWAYS` | Give a warning in HiLog only each time the error is detected.| 39| `FDSAN_ERROR_LEVEL_FATAL` | Call **abort** to terminate the process when the error is detected.| 40 41**Return value**<br>Old **error_level**. 42 43### fdsan_get_error_level 44 45``` 46enum fdsan_error_level fdsan_get_error_level(); 47``` 48 49**Description**<br>Obtains the current error level. 50 51**Return value**<br>Current error level. 52 53### fdsan_create_owner_tag 54``` 55uint64_t fdsan_create_owner_tag(enum fdsan_owner_type type, uint64_t tag); 56``` 57**Description**<br>Creates a tag for a file descriptor. 58 59**Parameters**<br>**fdsan_owner_type** 60 61| Value | Description | 62| -------------------------- | ------------------------------------------------------------ | 63| `FDSAN_OWNER_TYPE_GENERIC_00` | Default type. | 64| `FDSAN_OWNER_TYPE_GENERIC_FF` | Default type for invalid file descriptors.| 65| `FDSAN_OWNER_TYPE_FILE` | Type for a file, which can be opened by using **fopen()** or **fdopen()**.| 66| `FDSAN_OWNER_TYPE_DIRECTORY` | Type for a directory, which can be opened by using **opendir** or **fdopendir**.| 67| `FDSAN_OWNER_TYPE_UNIQUE_FD` | Type for **unique_fd**. This value is reserved.| 68| `FDSAN_OWNER_TYPE_ZIPARCHIVE` | Type for a .zip file. This value is reserved.| 69 70**Return value**<br>Created tag, which can be used as an input of **fdsan_exchange_owner_tag**. 71 72### fdsan_exchange_owner_tag 73 74``` 75void fdsan_exchange_owner_tag(int fd, uint64_t expected_tag, uint64_t new_tag); 76``` 77**Description**<br>Modifies the tag of a file descriptor. 78 79Locate the **FdEntry** based on the file descriptor and check whether the value of **close_tag** is the same as that of **expected_tag**. If yes, you can change the value of **FdEntry** with the value of **new_tag** passed in. 80 81If the value of **close_tag** is not the same as that of **expected_tag**, an error occurs. 82 83**Parameters** 84 85| Name | Type | Description | 86| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 87| `fd` | int | File descriptor, which serves as an index of **FdEntry**.| 88| `expected_tag` | uint64_t | Expected value of the tag. | 89| `new_tag` | uint64_t | New value of the tag. | 90 91 92 93### fdsan_close_with_tag 94 95``` 96int fdsan_close_with_tag(int fd, uint64_t tag); 97``` 98**Description**<br>Closes a file descriptor based on the tag. 99 100Locate the **FdEntry** based on the file descriptor. If **close_tag** is the same as **tag**, the file descriptor can be closed. Otherwise, an exception occurs. 101 102**Parameters** 103 104| Name | Type | Description | 105| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 106| `fd` | int | File descriptor to close.| 107| `tag` | uint64_t | Expected tag. | 108 109**Return value**<br>Returns **0** if the file descriptor is closed; returns **-1** otherwise. 110 111### fdsan_get_owner_tag 112``` 113uint64_t fdsan_get_owner_tag(int fd); 114``` 115**Description**<br>Obtains tag information based on the given file descriptor. 116 117Locate **FdEntry** based on the file descriptor and obtain **close_tag**. 118 119**Parameters** 120 121| Name | Type | Description | 122| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 123| `tag` | uint64_t | Owner tag. | 124 125**Return value**<br>Tag of the file descriptor. 126 127### fdsan_get_tag_type 128``` 129const char* fdsan_get_tag_type(uint64_t tag); 130``` 131**Description**<br>Obtains the file descriptor type based on the given tag. 132 133The type information can be calculated based on the tag information. 134 135**Parameters** 136 137| Name | Type | Description | 138| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 139| `tag` | uint64_t | Owner tag. | 140 141**Return value**<br>Type obtained. 142 143### fdsan_get_tag_value 144``` 145uint64_t fdsan_get_tag_value(uint64_t tag); 146``` 147**Description**<br>Obtains the owner value based on the given tag. 148 149The value contained in a tag can be obtained via offset calculation . 150 151**Parameters** 152 153| Name | Type | Description | 154| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 155| `tag` | uint64_t | Owner tag. | 156 157**Return value**<br>Owner value obtained. 158 159## Example 160 161Use fdsan to detect a double-close problem. 162 163``` 164void good_write() 165{ 166 sleep(1); 167 int fd = open(DEV_NULL_FILE, O_RDONLY); 168 sleep(3); 169 ssize_t ret = write(fd, "fdsan test\n", 11); 170 if (ret == -1) { 171 OH_LOG_ERROR(LOG_APP, "good write but failed?!"); 172 } 173 close(fd); 174} 175 176void bad_close() 177{ 178 int fd = open(DEV_NULL_FILE, O_RDONLY); 179 close(fd); 180 sleep(2); 181 // This close is expected to be detected by fdsan. 182 close(fd); 183} 184 185void functional_test() 186{ 187 std::vector<std::thread> threads; 188 for (auto function : { good_write, bad_close }) { 189 threads.emplace_back(function); 190 } 191 for (auto& thread : threads) { 192 thread.join(); 193 } 194} 195 196int main() 197{ 198 functional_test(); 199 return 0; 200} 201``` 202In this example, **good_write** is used to open a file and write data to it; **bad_close** is used to open a file and trigger a double-close problem. If the two threads run at the same time, the application execution is as follows: 203 204 205 206The **open()** API returns file descriptors in sequence. After the main function is called, the first available file descriptor is **43**. When **bad_close** is called, the file descriptor returned by **open()** for the first time is **43**. After **close()** is called, the file descriptor **43** becomes available. When **good_write** is called, the **open()** function returns the first available file descriptor, that is, **43**. Since **bad_close()** has the double-close problem, the file opened in another thread is incorrectly closed, causing a write failure. 207 208The fdsan tool can detect such problems in two ways: using standard library APIs or implementing APIs with fdsan. 209 210### Using Standard Library APIs 211 212The **fopen**, **fdopen**, **opendir**, and **fdopendir** APIs in libc have integrated fdsan. Using these APIs instead of **open** can help detect file descriptor mishandling problems. Use **fopen** instead of **open** in the following code: 213 214```c 215#include <stdio.h> 216#include <errno.h> 217#define TEMP_FILE "/data/local/tmp/test.txt" 218 219void good_write() 220{ 221 // fopen is protected by fdsan. Use fopen to replace open. 222 // int fd = open(TEMP_FILE, O_RDONLY); 223 FILE *f = fopen(TEMP_FILE, "w+"); 224 if (f == NULL) { 225 printf("fopen failed errno=%d\n", errno); 226 return; 227 } 228 // ssize_t ret = write(fd, "fdsan test\n", 11); 229 int ret = fprintf(f, "fdsan test %d\n", 11); 230 if (ret < 0) { 231 printf("fprintf failed errno=%d\n", errno); 232 } 233 // close(fd); 234 fclose(f); 235} 236``` 237 238Each file descriptor returned by **fopen** has a tag. When the file descriptor is closed by **close**, fdsan checks whether the file descriptor matches the tag. If the file descriptor does not match the tag, related log information is displayed by default. The log information for the preceding code is as follows: 239 240``` 241# hilog | grep MUSL-FDSAN 24204-30 15:03:41.760 10933 1624 E C03f00/MUSL-FDSAN: attempted to close file descriptor 43, expected to be unowned, actually owned by FILE* 0x00000000f7b90aa2 243``` 244 245As indicated by the log, the file of **FILE** is closed by mistake. You can further locate the fault based on the address of **FILE**. 246 247In addition, you can use **fdsan_set_error_level** to set an error level. If **error_level** is set to **FDSAN_ERROR_LEVEL_FATAL**, fdsan also provides stack information for fault locating in addition to the log information. The following is an example of the stack information generated upon a crash after **error_level** is set to **FDSAN_ERROR_LEVEL_FATAL**: 248 249``` 250Reason:Signal:SIGABRT(SI_TKILL)@0x0000076e from:1902:20010043 251Fault thread info: 252Tid:15312, Name:e.myapplication 253#00 pc 000e65bc /system/lib/ld-musl-arm.so.1(raise+176)(3de40c79448a2bbced06997e583ef614) 254#01 pc 0009c3bc /system/lib/ld-musl-arm.so.1(abort+16)(3de40c79448a2bbced06997e583ef614) 255#02 pc 0009de4c /system/lib/ld-musl-arm.so.1(fdsan_error+116)(3de40c79448a2bbced06997e583ef614) 256#03 pc 0009e2e8 /system/lib/ld-musl-arm.so.1(fdsan_close_with_tag+836)(3de40c79448a2bbced06997e583ef614) 257#04 pc 0009e56c /system/lib/ld-musl-arm.so.1(close+20)(3de40c79448a2bbced06997e583ef614) 258#05 pc 000055d8 /data/storage/el1/bundle/libs/arm/libentry.so(bad_close()+96)(f3339aac824c099f449153e92718e1b56f80b2ba) 259#06 pc 00006cf4 /data/storage/el1/bundle/libs/arm/libentry.so(decltype(std::declval<void (*)()>()()) std::__n1::__invoke[abi:v15004]<void (*)()>(void (*&&)())+24)(f3339aac824c099f449153e92718e1b56f80b2ba) 260#07 pc 00006c94 /data/storage/el1/bundle/libs/arm/libentry.so(f3339aac824c099f449153e92718e1b56f80b2ba) 261#08 pc 000067b8 /data/storage/el1/bundle/libs/arm/libentry.so(void* std::__n1::__thread_proxy[abi:v15004]<std::__n1::tuple<std::__n1::unique_ptr<std::__n1::__thread_struct, std::__n1::default_delete<std::__n1::__thread_struct>>, void (*)()>>(void*)+100)(f3339aac824c099f449153e92718e1b56f80b2ba) 262#09 pc 00105a6c /system/lib/ld-musl-arm.so.1(start+248)(3de40c79448a2bbced06997e583ef614) 263#10 pc 000700b0 /system/lib/ld-musl-arm.so.1(3de40c79448a2bbced06997e583ef614) 264``` 265 266The stack information provides information about **bad_close** and all opened files, helping quickly locate faults. 267 268``` 269OpenFiles: 2700->/dev/null native object of unknown type 0 2711->/dev/null native object of unknown type 0 2722->/dev/null native object of unknown type 0 2733->socket:[28102] native object of unknown type 0 2744->socket:[28103] native object of unknown type 0 2755->anon_inode:[eventpoll] native object of unknown type 0 2766->/sys/kernel/debug/tracing/trace_marker native object of unknown type 0 2777->anon_inode:[eventpoll] native object of unknown type 0 2788->anon_inode:[eventpoll] native object of unknown type 0 2799->/dev/console native object of unknown type 0 28010->pipe:[95598] native object of unknown type 0 28111->pipe:[95598] native object of unknown type 0 28212->socket:[18542] native object of unknown type 0 28313->pipe:[96594] native object of unknown type 0 28414->socket:[18545] native object of unknown type 0 28515->pipe:[96594] native object of unknown type 0 28616->anon_inode:[eventfd] native object of unknown type 0 28717->/dev/binder native object of unknown type 0 28818->/data/storage/el1/bundle/entry.hap native object of unknown type 0 28919->anon_inode:[eventpoll] native object of unknown type 0 29020->anon_inode:[signalfd] native object of unknown type 0 29121->socket:[29603] native object of unknown type 0 29222->anon_inode:[eventfd] native object of unknown type 0 29323->anon_inode:[eventpoll] native object of unknown type 0 29424->anon_inode:[eventfd] native object of unknown type 0 29525->anon_inode:[eventpoll] native object of unknown type 0 29626->anon_inode:[eventfd] native object of unknown type 0 29727->anon_inode:[eventpoll] native object of unknown type 0 29828->anon_inode:[eventfd] native object of unknown type 0 29929->anon_inode:[eventpoll] native object of unknown type 0 30030->anon_inode:[eventfd] native object of unknown type 0 30131->anon_inode:[eventpoll] native object of unknown type 0 30232->anon_inode:[eventfd] native object of unknown type 0 30333->anon_inode:[eventpoll] native object of unknown type 0 30434->anon_inode:[eventfd] native object of unknown type 0 30535->socket:[97409] native object of unknown type 0 30636->socket:[94716] native object of unknown type 0 30738->socket:[94720] native object of unknown type 0 30840->/data/storage/el1/bundle/entry_test.hap native object of unknown type 0 30941->socket:[95617] native object of unknown type 0 31042->/sys/kernel/debug/tracing/trace_marker native object of unknown type 0 31143->/dev/null FILE* 4155724704 31244->socket:[94737] native object of unknown type 0 31345->pipe:[95634] native object of unknown type 0 31446->pipe:[95634] native object of unknown type 0 31547->pipe:[95635] native object of unknown type 0 31649->pipe:[95636] native object of unknown type 0 31750->pipe:[95636] native object of unknown type 0 318``` 319 320 321### Implementing APIs with fdsan 322 323You can also implement APIs with fdsan by using **fdsan_exchange_owner_tag** and **fdsan_close_with_tag**. The former can be used to set a tag for a file descriptor; the latter can be used to check the tag when a file is closed. 324 325Example: 326 327```cpp 328#include <errno.h> 329#include <stdio.h> 330#include <fcntl.h> 331#include <unistd.h> 332 333#include <utility> 334 335struct fdsan_fd { 336 fdsan_fd() = default; 337 338 explicit fdsan_fd(int fd) 339 { 340 reset(fd); 341 } 342 343 fdsan_fd(const fdsan_fd& copy) = delete; 344 fdsan_fd(fdsan_fd&& move) 345 { 346 *this = std::move(move); 347 } 348 349 ~fdsan_fd() 350 { 351 reset(); 352 } 353 354 fdsan_fd& operator=(const fdsan_fd& copy) = delete; 355 fdsan_fd& operator=(fdsan_fd&& move) 356 { 357 if (this == &move) { 358 return *this; 359 } 360 reset(); 361 if (move.fd_ != -1) { 362 fd_ = move.fd_; 363 move.fd_ = -1; 364 // Acquire ownership from the moved-from object. 365 exchange_tag(fd_, move.tag(), tag()); 366 } 367 return *this; 368 } 369 370 int get() 371 { 372 return fd_; 373 } 374 375 void reset(int new_fd = -1) 376 { 377 if (fd_ != -1) { 378 close(fd_, tag()); 379 fd_ = -1; 380 } 381 if (new_fd != -1) { 382 fd_ = new_fd; 383 // Acquire ownership of the presumably unowned fd. 384 exchange_tag(fd_, 0, tag()); 385 } 386 } 387 388 private: 389 int fd_ = -1; 390 391 // Use the address of object as the file tag 392 uint64_t tag() 393 { 394 return reinterpret_cast<uint64_t>(this); 395 } 396 397 static void exchange_tag(int fd, uint64_t old_tag, uint64_t new_tag) 398 { 399 if (&fdsan_exchange_owner_tag) { 400 fdsan_exchange_owner_tag(fd, old_tag, new_tag); 401 } 402 } 403 404 static int close(int fd, uint64_t tag) 405 { 406 if (&fdsan_close_with_tag) { 407 return fdsan_close_with_tag(fd, tag); 408 } 409 } 410}; 411``` 412 413In this example, **fdsan_exchange_owner_tag** is used to bind a file descriptor and the address of a struct object. Then, **fdsan_close_with_tag** is used to check whether the tag matches the file descriptor before the file is closed. The expected tag is the struct object address. 414 415You can use the implemented API in the following code to detect and prevent file descriptor mishandling problems: 416 417```cpp 418#define TEMP_FILE "/data/local/tmp/test.txt" 419 420void good_write() 421{ 422 // int fd = open(DEV_NULL_FILE, O_RDONLY); 423 fdsan_fd fd(open(TEMP_FILE, O_CREAT | O_RDWR)); 424 if (fd.get() == -1) { 425 printf("fopen failed errno=%d\n", errno); 426 return; 427 } 428 ssize_t ret = write(fd.get(), "fdsan test\n", 11); 429 if (ret == -1) { 430 printf("write failed errno=%d\n", errno); 431 } 432 fd.reset(); 433} 434``` 435 436When the application is executed, the double-close problem of another thread can be detected. You can also set **error_level** to **fatal** so that fdsan can proactively crash after detecting a crash. 437