• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![](./figures/tag.png)
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![](./figures/fdsan-error-2.png)
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