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. 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 15- **value**: a 56-bit string uniquely identifying a tag. 16 17 **Figure** Tag 18 19 20 21 22 23## Available APIs 24 25### fdsan_set_error_level 26 27``` 28enum fdsan_error_level fdsan_set_error_level(enum fdsan_error_level new_level); 29``` 30 31**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**. 32 33**Parameters**<br>**fdsan_error_level** 34 35| Value | Description | 36| -------------------------- | ------------------------------------------------------------ | 37| `FDSAN_ERROR_LEVEL_DISABLED` | fdsan is disabled, that is, no processing is performed. | 38| `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**).| 39| `FDSAN_ERROR_LEVEL_WARN_ALWAYS` | Give a warning in HiLog only each time the error is detected.| 40| `FDSAN_ERROR_LEVEL_FATAL` | Call **abort** to terminate the process when the error is detected.| 41 42**Return value**<br>Old **error_level**. 43 44### fdsan_get_error_level 45 46``` 47enum fdsan_error_level fdsan_get_error_level(); 48``` 49 50**Description**<br>Obtains the current error level. 51 52**Return value**<br>Current error level. 53 54### fdsan_create_owner_tag 55``` 56uint64_t fdsan_create_owner_tag(enum fdsan_owner_type type, uint64_t tag); 57``` 58**Description**<br>Creates a tag for a file descriptor. 59 60**Parameters**<br>**fdsan_owner_type** 61 62| Value | Description | 63| -------------------------- | ------------------------------------------------------------ | 64| `FDSAN_OWNER_TYPE_GENERIC_00` | Default type. | 65| `FDSAN_OWNER_TYPE_GENERIC_FF` | Default type for invalid file descriptors.| 66| `FDSAN_OWNER_TYPE_FILE` | Type for a file, which can be opened by using **fopen()** or **fdopen()**.| 67| `FDSAN_OWNER_TYPE_DIRECTORY` | Type for a directory, which can be opened by using **opendir** or **fdopendir**.| 68| `FDSAN_OWNER_TYPE_UNIQUE_FD` | Type for **unique_fd**. This value is reserved.| 69| `FDSAN_OWNER_TYPE_ZIPARCHIVE` | Type for a .zip file. This value is reserved.| 70 71**Return value**<br>Created tag, which can be used as an input of **fdsan_exchange_owner_tag**. 72 73### fdsan_exchange_owner_tag 74 75``` 76void fdsan_exchange_owner_tag(int fd, uint64_t expected_tag, uint64_t new_tag); 77``` 78**Description**<br>Modifies the tag of a file descriptor. 79 80Locate 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. 81 82If the value of **close_tag** is not the same as that of **expected_tag**, an error occurs. 83 84**Parameters** 85 86| Name | Type | Description | 87| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 88| `fd` | int | File descriptor, which serves as an index of **FdEntry**.| 89| `expected_tag` | uint64_t | Expected value of the tag. | 90| `new_tag` | uint64_t | New value of the tag. | 91 92 93 94### fdsan_close_with_tag 95 96``` 97int fdsan_close_with_tag(int fd, uint64_t tag); 98``` 99**Description**<br>Closes a file descriptor based on the tag. 100 101Locate 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. 102 103**Parameters** 104 105| Name | Type | Description | 106| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 107| `fd` | int | File descriptor to close.| 108| `tag` | uint64_t | Expected tag. | 109 110**Return value**<br>Returns **0** if the file descriptor is closed; returns **-1** otherwise. 111 112### fdsan_get_owner_tag 113``` 114uint64_t fdsan_get_owner_tag(int fd); 115``` 116**Description**<br>Obtains tag information based on the given file descriptor. 117 118Locate **FdEntry** based on the file descriptor and obtain **close_tag**. 119 120**Parameters** 121 122| Name | Type | Description | 123| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 124| `tag` | uint64_t | Owner tag. | 125 126**Return value**<br>Tag of the file descriptor. 127 128### fdsan_get_tag_type 129``` 130const char* fdsan_get_tag_type(uint64_t tag); 131``` 132**Description**<br>Obtains the file descriptor type based on the given tag. 133 134The type information can be calculated based on the tag information. 135 136**Parameters** 137 138| Name | Type | Description | 139| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 140| `tag` | uint64_t | Owner tag. | 141 142**Return value**<br>Type obtained. 143 144### fdsan_get_tag_value 145``` 146uint64_t fdsan_get_tag_value(uint64_t tag); 147``` 148**Description**<br>Obtains the owner value based on the given tag. 149 150The value contained in a tag can be obtained via offset calculation . 151 152**Parameters** 153 154| Name | Type | Description | 155| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 156| `tag` | uint64_t | Owner tag. | 157 158**Return value**<br>Owner value obtained. 159 160## Example 161 162Use fdsan to detect a double-close problem. 163 164``` 165void good_write() 166{ 167 sleep(1); 168 int fd = open(DEV_NULL_FILE, O_RDONLY); 169 sleep(3); 170 ssize_t ret = write(fd, "fdsan test\n", 11); 171 if (ret == -1) { 172 OH_LOG_ERROR(LOG_APP, "good write but failed?!"); 173 } 174 close(fd); 175} 176 177void bad_close() 178{ 179 int fd = open(DEV_NULL_FILE, O_RDONLY); 180 close(fd); 181 sleep(2); 182 // This close is expected to be detected by fdsan. 183 close(fd); 184} 185 186void functional_test() 187{ 188 std::vector<std::thread> threads; 189 for (auto function : { good_write, bad_close }) { 190 threads.emplace_back(function); 191 } 192 for (auto& thread : threads) { 193 thread.join(); 194 } 195} 196 197int main() 198{ 199 functional_test(); 200 return 0; 201} 202``` 203In 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: 204 205 206 207The **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. 208 209The fdsan tool can detect such problems in two ways: using standard library APIs or implementing APIs with fdsan. 210 211### Using Standard Library APIs 212 213The **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: 214 215```cpp 216void good_write() 217{ 218 sleep(1); 219 // fopen is protected by fdsan. Use fopen to replace open. 220 // int fd = open(DEV_NULL_FILE, O_RDONLY); 221 FILE *f = fopen(DEV_NULL_FILE, O_RDONLY); 222 sleep(3); 223 ssize_t ret = write(fileno(f), "fdsan test\n", 11); 224 if (ret == -1) { 225 OH_LOG_ERROR(LOG_APP, "good write but failed?!"); 226 } 227 close(fileno(f)); 228} 229``` 230 231Each 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: 232 233``` 234# hilog | grep MUSL-FDSAN 23504-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 236``` 237 238As indicated by the log, the file of **FILE** is closed by mistake. You can further locate the fault based on the address of **FILE**. 239 240In 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**: 241 242``` 243Reason:Signal:SIGABRT(SI_TKILL)@0x0000076e from:1902:20010043 244Fault thread info: 245Tid:15312, Name:e.myapplication 246#00 pc 000e65bc /system/lib/ld-musl-arm.so.1(raise+176)(3de40c79448a2bbced06997e583ef614) 247#01 pc 0009c3bc /system/lib/ld-musl-arm.so.1(abort+16)(3de40c79448a2bbced06997e583ef614) 248#02 pc 0009de4c /system/lib/ld-musl-arm.so.1(fdsan_error+116)(3de40c79448a2bbced06997e583ef614) 249#03 pc 0009e2e8 /system/lib/ld-musl-arm.so.1(fdsan_close_with_tag+836)(3de40c79448a2bbced06997e583ef614) 250#04 pc 0009e56c /system/lib/ld-musl-arm.so.1(close+20)(3de40c79448a2bbced06997e583ef614) 251#05 pc 000055d8 /data/storage/el1/bundle/libs/arm/libentry.so(bad_close()+96)(f3339aac824c099f449153e92718e1b56f80b2ba) 252#06 pc 00006cf4 /data/storage/el1/bundle/libs/arm/libentry.so(decltype(std::declval<void (*)()>()()) std::__n1::__invoke[abi:v15004]<void (*)()>(void (*&&)())+24)(f3339aac824c099f449153e92718e1b56f80b2ba) 253#07 pc 00006c94 /data/storage/el1/bundle/libs/arm/libentry.so(f3339aac824c099f449153e92718e1b56f80b2ba) 254#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) 255#09 pc 00105a6c /system/lib/ld-musl-arm.so.1(start+248)(3de40c79448a2bbced06997e583ef614) 256#10 pc 000700b0 /system/lib/ld-musl-arm.so.1(3de40c79448a2bbced06997e583ef614) 257``` 258 259The stack information provides information about **bad_close** and all opened files, helping quickly locate faults. 260 261``` 262OpenFiles: 2630->/dev/null native object of unknown type 0 2641->/dev/null native object of unknown type 0 2652->/dev/null native object of unknown type 0 2663->socket:[28102] native object of unknown type 0 2674->socket:[28103] native object of unknown type 0 2685->anon_inode:[eventpoll] native object of unknown type 0 2696->/sys/kernel/debug/tracing/trace_marker native object of unknown type 0 2707->anon_inode:[eventpoll] native object of unknown type 0 2718->anon_inode:[eventpoll] native object of unknown type 0 2729->/dev/console native object of unknown type 0 27310->pipe:[95598] native object of unknown type 0 27411->pipe:[95598] native object of unknown type 0 27512->socket:[18542] native object of unknown type 0 27613->pipe:[96594] native object of unknown type 0 27714->socket:[18545] native object of unknown type 0 27815->pipe:[96594] native object of unknown type 0 27916->anon_inode:[eventfd] native object of unknown type 0 28017->/dev/binder native object of unknown type 0 28118->/data/storage/el1/bundle/entry.hap native object of unknown type 0 28219->anon_inode:[eventpoll] native object of unknown type 0 28320->anon_inode:[signalfd] native object of unknown type 0 28421->socket:[29603] native object of unknown type 0 28522->anon_inode:[eventfd] native object of unknown type 0 28623->anon_inode:[eventpoll] native object of unknown type 0 28724->anon_inode:[eventfd] native object of unknown type 0 28825->anon_inode:[eventpoll] native object of unknown type 0 28926->anon_inode:[eventfd] native object of unknown type 0 29027->anon_inode:[eventpoll] native object of unknown type 0 29128->anon_inode:[eventfd] native object of unknown type 0 29229->anon_inode:[eventpoll] native object of unknown type 0 29330->anon_inode:[eventfd] native object of unknown type 0 29431->anon_inode:[eventpoll] native object of unknown type 0 29532->anon_inode:[eventfd] native object of unknown type 0 29633->anon_inode:[eventpoll] native object of unknown type 0 29734->anon_inode:[eventfd] native object of unknown type 0 29835->socket:[97409] native object of unknown type 0 29936->socket:[94716] native object of unknown type 0 30038->socket:[94720] native object of unknown type 0 30140->/data/storage/el1/bundle/entry_test.hap native object of unknown type 0 30241->socket:[95617] native object of unknown type 0 30342->/sys/kernel/debug/tracing/trace_marker native object of unknown type 0 30443->/dev/null FILE* 4155724704 30544->socket:[94737] native object of unknown type 0 30645->pipe:[95634] native object of unknown type 0 30746->pipe:[95634] native object of unknown type 0 30847->pipe:[95635] native object of unknown type 0 30949->pipe:[95636] native object of unknown type 0 31050->pipe:[95636] native object of unknown type 0 311``` 312 313 314### Implementing APIs with fdsan 315 316You 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. 317 318Example: 319 320```cpp 321struct fdsan_fd { 322 fdsan_fd() = default; 323 324 explicit fdsan_fd(int fd) 325 { 326 reset(fd); 327 } 328 329 fdsan_fd(const fdsan_fd& copy) = delete; 330 fdsan_fd(fdsan_fd&& move) 331 { 332 *this = std::move(move); 333 } 334 335 ~fdsan_fd() 336 { 337 reset(); 338 } 339 340 fdsan_fd& operator=(const fdsan_fd& copy) = delete; 341 fdsan_fd& operator=(fdsan_fd&& move) 342 { 343 if (this == &move) { 344 return *this; 345 } 346 reset(); 347 if (move.fd_ != -1) { 348 fd_ = move.fd_; 349 move.fd_ = -1; 350 // Acquire ownership from the moved-from object. 351 exchange_tag(fd_, move.tag(), tag()); 352 } 353 return *this; 354 } 355 356 int get() 357 { 358 return fd_; 359 } 360 361 void reset(int new_fd = -1) 362 { 363 if (fd_ != -1) { 364 close(fd_, tag()); 365 fd_ = -1; 366 } 367 if (new_fd != -1) { 368 fd_ = new_fd; 369 // Acquire ownership of the presumably unowned fd. 370 exchange_tag(fd_, 0, tag()); 371 } 372 } 373 374 private: 375 int fd_ = -1; 376 377 // Use the address of object as the file tag 378 uint64_t tag() 379 { 380 return reinterpret_cast<uint64_t>(this); 381 } 382 383 static void exchange_tag(int fd, uint64_t old_tag, uint64_t new_tag) 384 { 385 if (&fdsan_exchange_owner_tag) { 386 fdsan_exchange_owner_tag(fd, old_tag, new_tag); 387 } 388 } 389 390 static int close(int fd, uint64_t tag) 391 { 392 if (&fdsan_close_with_tag) { 393 return fdsan_close_with_tag(fd, tag); 394 } 395 } 396}; 397``` 398 399In 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. 400 401You can use the implemented API in the following code to detect and prevent file descriptor mishandling problems: 402 403```cpp 404void good_write() 405{ 406 sleep(1); 407 // int fd = open(DEV_NULL_FILE, O_RDONLY); 408 fdsan_fd fd(open(DEV_NULL_FILE, O_RDONLY)); 409 sleep(3); 410 ssize_t ret = write(fd.get(), "fdsan test\n", 11); 411 if (ret == -1) { 412 OH_LOG_ERROR(LOG_APP, "good write but failed?!"); 413 } 414 close(fd.get()); 415} 416``` 417 418When 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. 419