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![](./figures/tag.png)
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![](./figures/fdsan-error-2.png)
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