Linux fanotify 解析【轉】

轉自:https://blog.csdn.net/pwl999/article/details/106782339

Linux fanotify 解析
1. 基本介紹
1.1 基本原理
1.2 fanotify基本功能
2. 用戶態實現
2.1 實例代碼
2.2 API介紹
3. 內核實現
3.1 配置fanotify
3.1.1 fanotify_init()
3.1.2 fanotify_mark()
3.2 觸發fanotify
3.3 響應fanotify
3.3.1 fanotify_read()
3.3.2 fanotify_write()
參考文檔:
1. 基本介紹
Fanotify (Filesystem wide access notification) 是一個 notifier,即一種對文件系統變化產生通知的機制。fanotify是inotify的一個新進版本,主要是用於文件系統掃描的檢測和分層存儲的管理。最近幾年對fanotify的完善也是很快的,查看了一下源碼可以看出來fanotify支持的文件系統事件已經比inotify多了。

fanotify與inotify最大區別是fanotify加入了打開關閉等事件的許可判斷:

即在打開或者關閉文件之前,需要程序員註冊一個函數,根據程序所需要去判斷是否允許打開文件或者關閉文件,然後將判斷的結果再寫入內核中,此時內核會執行該結果(相當於Ring0級別的Hook)。很明顯,這幾個文件系統事件可以用來實現一個文件監測控制系統,除了文件系統的掃描以外還可以控制文件的打開關閉等操作。很符合殺毒軟件的開發。
1
1.1 基本原理
fanotify功能的使用分爲以下幾步:

1.用戶調用fanotify_init()函數創建一個fd供監控者使用。在內核中這個fd對應一個fsnotify_group結構體。
2.用戶調用fanotify_mark()函數來確定需要監控哪些文件對象以及哪些類型的事件消息。在內核中會把fsnotify_group和需要監控的文件對象鏈接起來。
3.用戶調用read()函數來讀取fd接收到的監控事件消息並處理。在內核中文件對象發生相應類型的事件時,會把事件消息發送到fsnotify_group的接收消息隊列中。
4.如果註冊了FAN_OPEN_PERM/FAN_ACCESS_PERM類型的監控,在接收到事件消息後,需要對當前的操作做許可判斷(Allow/Deny),並調用write()函數把許可結果回寫給內核。在內核中文件對象發生FAN_OPEN_PERM/FAN_ACCESS_PERM類型的事件後會把當前進程掛起並等待用戶監控程序的判斷,用戶程序通過write()函數下發允許結果後,阻塞的進程恢復執行(Continue/Return)。
fanotify的內部數據關係如下圖所示,用戶創建了一個監控group,一個group可以監控多個文件對象(inode/mount)。同時一個文件對象(inode/mount)可以被多個group所監控。group和監控對象是多對多的關係:

 

 


在監控對象的事件發生時,通過fsnotify_mark建立起來的關係把事件發送到group的消息隊列中。用戶通過read()操作來讀取消息隊列,獲得監控事件:

 

 


以下是訪問控制功能的基本流程圖:

Step 1. 被監控的inode對象被進程進行(FAN_OPEN_PERM|FAN_ACCESS_PERM)類型的操作時,會通過fsnotify_mark建立起來的關係,發送event消息到group的消息隊列中。
Step 2. 判斷有人在監控(FAN_OPEN_PERM|FAN_ACCESS_PERM)類型的消息,訪問inode的進程會調用waie_event()把自己掛起,等待監控進程對自己的操作權限進行判斷。
Step 3. 監控進程通過read()操作讀到group消息隊列中的所有監控消息並處理。
Step 4. 監控進程讀取到(FAN_OPEN_PERM|FAN_ACCESS_PERM)類型的消息,判斷目標進程是否有權限進行操作,通過write()操作將判斷結果進行下發,並且喚醒目標進程來接收判斷結果,進行繼續(Allow)或者返回(Deny)操作。

 

 


1.2 fanotify基本功能
1.文件系統事件的通知
這個功能和inotify的功能是一樣的,即監聽一些普遍的文件系統事件,例如讀寫打開關閉等操作的發生。當這些事件發生後,fanotify就會通知程序員發生了什麼事件。

2.實現文件系統的監測管控
該功能是實現全文件的系統的監測。相對於inotify來講,fanotify在該功能上更具備擴展性。inotify在進行監控的時候,是需要一個wd對象進行監控的,所以當文件系統一複雜,wd的維護就變得麻煩。而fanotify所支持的三種模式可以使得該管理變得簡潔。

Fanotify 有三個個基本的模式:directed,per-mount 和 global。

directed 模式和 inotify 類似,直接工作在被監控的對象的 inode 上,一次只可以監控一個對象。因此需要監控大量目標時也很麻煩。
Global 模式則監控整個文件系統,任何變化都會通知 Listener。殺毒軟件便工作在這種模式下。
Per-mount 模式工作在 mount 點上,比如磁盤 /dev/sda2 的 mount 點在 /home,則 /home 目錄下的所有文件系統變化都可以被監控,這其實可以被看作另外一種 Global 模式。
1
2
3
長期以來,人們都希望 Linux 的 notifier 可以支持 sub-tree 通知,比如圖 2 的衆多監控對象都在 /home 目錄下面,假如 notifier 可以指定監控整個 /home 目錄,其下任意文件或者子目錄的變化都可以引起通知,監控程序便無需爲每一個 /home 下面的子目錄和文件一一添加 watch descriptor 了。
在很久以前,Fanotify 就暗示說實現 sub-tree notification 不是不可能的,但直到今天 fanotify 依然無法支持 sub-tree 監控。但比 inotify 進了一步的是,fanotify 可以監控某個目錄下的直接子節點。比如可以監控 /home 和他的直接子節點,文件 /home/foo1,/home/foo2 等都可以被監控,但 /home/pics/foo1 就不可以了,因爲 /home/pics/foo1 不是 /home 的直接子節點。希望在後續的 fanotify 版本中可以彌補這個不足。
面對 sub-tree 監控的需要,目前 fanotify 的折中方案是採用 Global 模式,然後在監控程序內部進行判斷,剔除那些不感興趣的文件系統對象。這雖然不完美,但也算一個可行的方案吧。相比 inotify,有一點兒總比完全沒有好一些吧。

3.訪問控制
訪問控制這個功能是之前inotify沒有的,這是fanotify和inotify之間最大的不同之處。fanotify增加了訪問控制的事件,例如:FAN_OPEN_PERM、FAN_CLOSE_PERM等,這些事件需要程序員通過程序需要判斷該文件是否被允許打開或者關閉操作,並把該決策向內核進行寫入和註冊,最後讓系統調用返回該結果。

4.監聽者級別的劃分
該功能便是允許多個Listener監聽同一個文件對象,並且可以設置Listener的級別。fanotify將所有的Listener設置了三個group,其優先從高到低分別爲:FAN_CLASS_PRE_CONTENT、FAN_CLASS_CONTENT、FAN_CLASS_NOTIF

從上述宏的命名也大致可知:

FAN_CLASS_PRE_CONTENT 用於 HSM 等需要在應用程序使用文件的 CONTENT 之前就得到文件操作權的應用程序;
FAN_CLASS_CONTENT 適用於殺毒軟件等需要檢查文件 CONTENT 的軟件;
FAN_CLASS_NOTIF 則用於純粹的 notification 軟件,不需要訪問文件內容的應用程序。
1
2
3
5.監聽者程序的PID過濾
監聽者程序也可能會觸發fanotify中的事件,在fanotify中,如果觸發者是Listener的PID的話,fanotify就會忽略該事件,避免兩者進入死循環。能夠實現該過濾的原因是fanotify在事件中包含了Listener的PID,而inotify並不具備該功能。

6.緩存機制
例如一個文件沒有進行任何修改,但是每次都需要掃描監測的話會很麻煩,這時候fanotify的緩存機制就有很好的體現,假設它對某個文件對象設置了ignore mask標誌位的話,只會對該對象進行一次掃描,之後如果該文件對象沒有進行任何修改的話,它的打開關閉操作就會正常執行,fanotify會忽略訪問控制事件,始終允許訪問。如果進行了修改的話,fanotify會自動清除它的ignore mask標誌位,然後對它進行正常的訪問控制等事件。

Fanotify 支持這種 cache,也叫做 ignore marks。它的工作原理很簡單,假如對一個文件系統對象設置了 ignore marks,那麼下次該文件被訪問時,相應的事件便不會觸發訪問控制的代碼,從而始終允許該文件的訪問。
殺毒軟件可以這樣使用此特性,當應用程序第一次打開文件 file A 時,Fanotify 將通知殺毒軟件 AV 進行文件內容掃描,如果 AV 軟件發現該文件沒有病毒,在允許本次訪問的同時,對該文件設置一個 ignore mark。

2. 用戶態實現
2.1 實例代碼
這裏有一份fanotify的用戶態使用的代碼:fanotify example userspace tools。

void synopsis(const char *progname, int status)
{
FILE *file = status ? stderr : stdout;

fprintf(file, "USAGE: %s [-" FANOTIFY_ARGUMENTS "] "
"[-o {open,close,access,modify,open_perm,access_perm}] "
"file ...\n"
"-c: learn about events on children of a directory (not decendants)\n"
"-d: send events which happen to directories\n"
"-f: set premptive ignores (go faster)\n"
"-h: this help screen\n"
"-m: place mark on the whole mount point, not just the inode\n"
"-n: do not ignore repeated permission checks\n"
"-p: check permissions, not just notification\n"
"-s N: sleep N seconds before replying to perm events\n",
progname);
exit(status);
}

int main(int argc, char *argv[])
{
int opt;
uint64_t fan_mask = FAN_OPEN | FAN_CLOSE | FAN_ACCESS | FAN_MODIFY;
unsigned int mark_flags = FAN_MARK_ADD, init_flags = 0;
bool opt_child, opt_on_mount, opt_add_perms, opt_fast, opt_ignore_perm;
int opt_sleep;
ssize_t len;
char buf[4096];
fd_set rfds;
struct sigaction sa;

sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = usr1_handler;
if (sigaction(SIGUSR1, &sa, NULL) == -1)
goto fail;

opt_child = opt_on_mount = opt_add_perms = opt_fast = false;
opt_ignore_perm = true;
opt_sleep = 0;

/* (0) 命令的參數解析 */
while ((opt = getopt(argc, argv, "o:s:"FANOTIFY_ARGUMENTS)) != -1) {
switch(opt) {
case 'o': {
char *str, *tok;

fan_mask = 0;
str = optarg;
while ((tok = strtok(str, ",")) != NULL) {
str = NULL;
if (strcmp(tok, "open") == 0)
fan_mask |= FAN_OPEN;
else if (strcmp(tok, "close") == 0)
fan_mask |= FAN_CLOSE;
else if (strcmp(tok, "access") == 0)
fan_mask |= FAN_ACCESS;
else if (strcmp(tok, "modify") == 0)
fan_mask |= FAN_MODIFY;
else if (strcmp(tok, "open_perm") == 0)
fan_mask |= FAN_OPEN_PERM;
else if (strcmp(tok, "access_perm") == 0)
fan_mask |= FAN_ACCESS_PERM;
else
synopsis(argv[0], 1);
}
break;
}
case 'c':
opt_child = true;
break;
case 'd':
fan_mask |= FAN_ONDIR;
break;
case 'f':
opt_fast = true;
opt_ignore_perm = true;
break;
case 'm':
opt_on_mount = true;
break;
case 'n':
opt_fast = false;
opt_ignore_perm = false;
break;
case 'p':
opt_add_perms = true;
break;
case 's':
opt_sleep = atoi(optarg);
break;
case 'h':
synopsis(argv[0], 0);
default: /* '?' */
synopsis(argv[0], 1);
}
}
if (optind == argc)
synopsis(argv[0], 1);

if (opt_child)
fan_mask |= FAN_EVENT_ON_CHILD;

if (opt_on_mount)
mark_flags |= FAN_MARK_MOUNT;

if (opt_add_perms)
fan_mask |= FAN_ALL_PERM_EVENTS;

if (fan_mask & FAN_ALL_PERM_EVENTS)
init_flags |= FAN_CLASS_CONTENT;
else
init_flags |= FAN_CLASS_NOTIF;

/* (1) 創建fanotify對應的文件句柄fd */
fan_fd = fanotify_init(init_flags, O_RDONLY | O_LARGEFILE);
if (fan_fd < 0)
goto fail;

/* (2) 配置fd上需要監控的對象和操作類型 */
for (; optind < argc; optind++)
if (mark_object(fan_fd, argv[optind], AT_FDCWD, fan_mask, mark_flags) != 0)
goto fail;

FD_ZERO(&rfds);
FD_SET(fan_fd, &rfds);

while (select(fan_fd+1, &rfds, NULL, NULL, NULL) < 0)
if (errno != EINTR)
goto fail;

/* (3) 通過fd的read()操作來接收監控消息 */
while ((len = read(fan_fd, buf, sizeof(buf))) > 0) {
struct fanotify_event_metadata *metadata;
char path[PATH_MAX];
int path_len;

/* (4) 逐個取出監控event消息並處理 */
metadata = (void *)buf;
while(FAN_EVENT_OK(metadata, len)) {
if (metadata->vers < 2) {
fprintf(stderr, "Kernel fanotify version too old\n");
goto fail;
}

/* (4.1) 忽略後續的重複消息 */
if (metadata->fd >= 0 &&
opt_fast &&
set_ignored_mask(fan_fd, metadata->fd,
FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS))
goto fail;

if (metadata->fd >= 0) {
sprintf(path, "/proc/self/fd/%d", metadata->fd);
path_len = readlink(path, path, sizeof(path)-1);
if (path_len < 0)
goto fail;
path[path_len] = '\0';
printf("%s:", path);
} else
printf("?:");

/* (4.2) 對一些特殊目錄,忽略重複消息 */
set_special_ignored(fan_fd, metadata->fd, path);

printf(" pid=%ld", (long)metadata->pid);

if (metadata->mask & FAN_ACCESS)
printf(" access");
if (metadata->mask & FAN_OPEN)
printf(" open");
if (metadata->mask & FAN_MODIFY)
printf(" modify");
if (metadata->mask & FAN_CLOSE) {
if (metadata->mask & FAN_CLOSE_WRITE)
printf(" close(writable)");
if (metadata->mask & FAN_CLOSE_NOWRITE)
printf(" close");
}
if (metadata->mask & FAN_OPEN_PERM)
printf(" open_perm");
if (metadata->mask & FAN_ACCESS_PERM)
printf(" access_perm");

/* (4.3) 權限允許消息的處理 */
if (metadata->mask & FAN_ALL_PERM_EVENTS) {
if (opt_sleep)
sleep(opt_sleep);

/* (4.3.1) fd的write()操作來發送允許的結果 */
if (handle_perm(fan_fd, metadata))
goto fail;

/* (4.3.2) 忽略後續的重複消息 */
if (metadata->fd >= 0 &&
opt_ignore_perm &&
set_ignored_mask(fan_fd, metadata->fd,
metadata->mask))
goto fail;
}

printf("\n");
fflush(stdout);

/* (4.4) 關閉消息中的fd,並且取下一個消息 */
if (metadata->fd >= 0 && close(metadata->fd) != 0)
goto fail;
metadata = FAN_EVENT_NEXT(metadata, len);
}
while (select(fan_fd+1, &rfds, NULL, NULL, NULL) < 0)
if (errno != EINTR)
goto fail;
}
if (len < 0)
goto fail;
return 0;

fail:
fprintf(stderr, "%s\n", strerror(errno));
return 1;
}

int handle_perm(int fan_fd, struct fanotify_event_metadata *metadata)
{
struct fanotify_response response_struct;
int ret;

/* (4.3.1.1) 對操作進行判斷,是否允許 */
response_struct.fd = metadata->fd;
response_struct.response = FAN_ALLOW;

/* (4.3.1.2) fd的write()操作來發送允許的結果 */
ret = write(fan_fd, &response_struct, sizeof(response_struct));
if (ret < 0)
return ret;

return 0;
}

2.2 API介紹
fanotify_init()
#include <fcntl.h>
#include <sys/fanotify.h>
int fanotify_init(unsigned int flags, unsigned int event_f_flags);

該函數初始化fanotify事件組,並且該fanotify組的事件隊列的int類型句柄。它的另一個優勢,在這裏可以看出,可以通過epoll、select、kqueue等監聽。

第1個flags參數包含一個多位字段,該字段定義偵聽應用程序的通知類,並進一步包含一個位字段,指定文件描述符的行爲。其中包括:

FAN_CLASS_PRE_CONTENT:
此值允許接收通知文件已被訪問的事件,以及可能訪問文件時用於權限決策的事件。它適用於需要在包含最終數據之前訪問文件的事件偵聽器。例如,分層存儲管理器可能使用這個通知類。

FAN_CLASS_CONTENT:
此值允許接收通知文件已被訪問的事件,以及可能訪問文件時用於權限決策的事件。它是爲那些需要訪問已經包含最終內容的文件的事件偵聽器而設計的。例如,惡意軟件檢測程序可能會使用這個通知類。

FAN_REPORT_FID (since Linux 5.1):
此值允許接收包含有關與事件關聯的底層文件系統對象的附加信息的事件。附加結構封裝了關於對象的信息,並與通用事件元數據結構一起包含。用來表示與事件相關的對象的文件描述符被替換爲文件句柄。它適用於可能發現使用文件句柄標識對象比使用文件描述符更合適的應用程序。此外,它還可以用於對目錄條目事件感興趣的應用程序,例如FAN_CREATE、FAN_ATTRIB、FAN_MOVE和FAN_DELETE。注意,在監視掛載點時不支持使用目錄修改事件。此標誌不允許使用FAN_CLASS_CONTENT或FAN_CLASS_PRE_CONTENT,並將導致錯誤EINVAL。更多信息請參見fanotify(7)。

FAN_CLASS_NOTIF(默認值):
這是默認值。它不需要指定。此值只允許接收通知文件已被訪問的事件。不可能在訪問文件之前做出權限決定。

第2個參數event_f_flags和open函數的第二個參數意義相同。event_f_flags參數定義將在爲fanotify事件創建的打開文件描述上設置的文件狀態標誌。有關這些標誌的詳細信息,請參見open(2)中的標誌值描述。event_f_flags包含一個用於訪問模式的多位字段。

該字段可以取以下值:O_RDONLY、O_WRONLY、O_RDWR.
1
fanotify_mark()
#include <sys/fanotify.h>
int fanotify_mark(int fanotify_fd, unsigned int flags,
uint64_t mask, int dirfd, const char *pathname);

fanotify_mark()在文件系統對象上添加、刪除或修改fanotify標記。調用者必須對要標記的文件系統對象具有讀權限。

第1個參數fanotify_fd爲fanotify_init()函數的返回值。

第2個參數flags標誌位是描述要執行的操作:

#define FAN_MARK_ADD 0x00000001
#define FAN_MARK_REMOVE 0x00000002
#define FAN_MARK_DONT_FOLLOW 0x00000004
#define FAN_MARK_ONLYDIR 0x00000008
#define FAN_MARK_MOUNT 0x00000010
#define FAN_MARK_IGNORED_MASK 0x00000020
#define FAN_MARK_IGNORED_SURV_MODIFY 0x00000040
#define FAN_MARK_FLUSH 0x00000080

第3個參數mask描述的是需要監控的事件:

/* the following events that user-space can register for */
#define FAN_ACCESS 0x00000001 /* File was accessed */
#define FAN_MODIFY 0x00000002 /* File was modified */
#define FAN_CLOSE_WRITE 0x00000008 /* Writtable file closed */
#define FAN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */
#define FAN_OPEN 0x00000020 /* File was opened */
#define FAN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */

#define FAN_OPEN_PERM 0x00010000 /* File open in perm check */
#define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */

#define FAN_ONDIR 0x40000000 /* event occurred against dir */
#define FAN_EVENT_ON_CHILD 0x08000000 /* interested in child events */

第4個參數dirfd和第5個參數pathname描述的是監控點路徑。優先使用pathname來確定路徑,否則使用dirfd來確定路徑。

3. 內核實現
3.1 配置fanotify
3.1.1 fanotify_init()
相對於用戶態的fanotify_init()函數,內核有一個同名的系統調用與之對應。該函數的主要作用是分配了一個fsnotify_group,並且分配了一個anon inode和fd方便用戶態來操作。

/* fanotify syscalls */
SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
{
struct fsnotify_group *group;
int f_flags, fd;
struct user_struct *user;
struct fanotify_event_info *oevent;

pr_debug("%s: flags=%d event_f_flags=%d\n",
__func__, flags, event_f_flags);

if (!capable(CAP_SYS_ADMIN))
return -EPERM;

if (flags & ~FAN_ALL_INIT_FLAGS)
return -EINVAL;

if (event_f_flags & ~FANOTIFY_INIT_ALL_EVENT_F_BITS)
return -EINVAL;

switch (event_f_flags & O_ACCMODE) {
case O_RDONLY:
case O_RDWR:
case O_WRONLY:
break;
default:
return -EINVAL;
}

user = get_current_user();
if (atomic_read(&user->fanotify_listeners) > FANOTIFY_DEFAULT_MAX_LISTENERS) {
free_uid(user);
return -EMFILE;
}

f_flags = O_RDWR | FMODE_NONOTIFY;
if (flags & FAN_CLOEXEC)
f_flags |= O_CLOEXEC;
if (flags & FAN_NONBLOCK)
f_flags |= O_NONBLOCK;

/* fsnotify_alloc_group takes a ref. Dropped in fanotify_release */
/* (1) 分配核心數據結構:fsnotify_group
fanotify_fsnotify_ops包含了group接收到消息後的處理函數
*/
group = fsnotify_alloc_group(&fanotify_fsnotify_ops);
if (IS_ERR(group)) {
free_uid(user);
return PTR_ERR(group);
}

/* (2) */
group->fanotify_data.user = user;
atomic_inc(&user->fanotify_listeners);

/* (3) 預先分配好overflow event */
oevent = fanotify_alloc_event(NULL, FS_Q_OVERFLOW, NULL);
if (unlikely(!oevent)) {
fd = -ENOMEM;
goto out_destroy_group;
}
group->overflow_event = &oevent->fse;

if (force_o_largefile())
event_f_flags |= O_LARGEFILE;
group->fanotify_data.f_flags = event_f_flags;
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
spin_lock_init(&group->fanotify_data.access_lock);
init_waitqueue_head(&group->fanotify_data.access_waitq);
INIT_LIST_HEAD(&group->fanotify_data.access_list);
#endif

/* (4) 根據傳入flags參數中的標誌,設置group的優先級 */
switch (flags & FAN_ALL_CLASS_BITS) {
case FAN_CLASS_NOTIF:
group->priority = FS_PRIO_0;
break;
case FAN_CLASS_CONTENT:
group->priority = FS_PRIO_1;
break;
case FAN_CLASS_PRE_CONTENT:
group->priority = FS_PRIO_2;
break;
default:
fd = -EINVAL;
goto out_destroy_group;
}

/* (5) 配置group消息隊列的大小 */
if (flags & FAN_UNLIMITED_QUEUE) {
fd = -EPERM;
if (!capable(CAP_SYS_ADMIN))
goto out_destroy_group;
group->max_events = UINT_MAX;
} else {
group->max_events = FANOTIFY_DEFAULT_MAX_EVENTS;
}

if (flags & FAN_UNLIMITED_MARKS) {
fd = -EPERM;
if (!capable(CAP_SYS_ADMIN))
goto out_destroy_group;
group->fanotify_data.max_marks = UINT_MAX;
} else {
group->fanotify_data.max_marks = FANOTIFY_DEFAULT_MAX_MARKS;
}

/* (6) 分配inode/fd,和group鏈接起來
fanotify_fops包含了group的文件接口操作
*/
fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags);
if (fd < 0)
goto out_destroy_group;

return fd;

out_destroy_group:
fsnotify_destroy_group(group);
return fd;
}

3.1.2 fanotify_mark()
相對於用戶態的fanotify_mark()函數,內核有一個同名的系統調用與之對應。該函數給每一個監控點創建了中間變量fsnotify_mark,一個fsnotify_group通過多個fsnotify_mark和多個監控點建立起了鏈接關係。

SYSCALL_DEFINE5(fanotify_mark, int, fanotify_fd, unsigned int, flags,
__u64, mask, int, dfd,
const char __user *, pathname)
{
struct inode *inode = NULL;
struct vfsmount *mnt = NULL;
struct fsnotify_group *group;
struct fd f;
struct path path;
int ret;

pr_debug("%s: fanotify_fd=%d flags=%x dfd=%d pathname=%p mask=%llx\n",
__func__, fanotify_fd, flags, dfd, pathname, mask);

/* we only use the lower 32 bits as of right now. */
if (mask & ((__u64)0xffffffff << 32))
return -EINVAL;

/* (0) flag和mask的一系列合法性判斷 */
if (flags & ~FAN_ALL_MARK_FLAGS)
return -EINVAL;
switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE | FAN_MARK_FLUSH)) {
case FAN_MARK_ADD: /* fallthrough */
case FAN_MARK_REMOVE:
if (!mask)
return -EINVAL;
break;
case FAN_MARK_FLUSH:
if (flags & ~(FAN_MARK_MOUNT | FAN_MARK_FLUSH))
return -EINVAL;
break;
default:
return -EINVAL;
}

if (mask & FAN_ONDIR) {
flags |= FAN_MARK_ONDIR;
mask &= ~FAN_ONDIR;
}

#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
if (mask & ~(FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS | FAN_EVENT_ON_CHILD))
#else
if (mask & ~(FAN_ALL_EVENTS | FAN_EVENT_ON_CHILD))
#endif
return -EINVAL;

f = fdget(fanotify_fd);
if (unlikely(!f.file))
return -EBADF;

/* verify that this is indeed an fanotify instance */
ret = -EINVAL;
if (unlikely(f.file->f_op != &fanotify_fops))
goto fput_and_out;

/* (1) 通過fd找到對應的group結構 */
group = f.file->private_data;

/*
* group->priority == FS_PRIO_0 == FAN_CLASS_NOTIF. These are not
* allowed to set permissions events.
*/
ret = -EINVAL;
if (mask & FAN_ALL_PERM_EVENTS &&
group->priority == FS_PRIO_0)
goto fput_and_out;

/* (2) FAN_MARK_FLUSH,清理掉group上所有的監控點 */
if (flags & FAN_MARK_FLUSH) {
ret = 0;
if (flags & FAN_MARK_MOUNT)
fsnotify_clear_vfsmount_marks_by_group(group);
else
fsnotify_clear_inode_marks_by_group(group);
goto fput_and_out;
}

/* (3) 根據pathname或者dfd,找到監控點對應的path結構
普通模式監控inode
mount模式監控mnt
*/
ret = fanotify_find_path(dfd, pathname, &path, flags);
if (ret)
goto fput_and_out;

/* inode held in place by reference to path; group by fget on fd */
if (!(flags & FAN_MARK_MOUNT))
inode = path.dentry->d_inode;
else
mnt = path.mnt;

/* create/update an inode mark */
/* (4) 根據flags中的FAN_MARK_ADD/FAN_MARK_REMOVE命令,
來給group增加/移除監控點
*/
switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) {
case FAN_MARK_ADD:
if (flags & FAN_MARK_MOUNT)
ret = fanotify_add_vfsmount_mark(group, mnt, mask, flags);
else
ret = fanotify_add_inode_mark(group, inode, mask, flags);
break;
case FAN_MARK_REMOVE:
if (flags & FAN_MARK_MOUNT)
ret = fanotify_remove_vfsmount_mark(group, mnt, mask, flags);
else
ret = fanotify_remove_inode_mark(group, inode, mask, flags);
break;
default:
ret = -EINVAL;
}

path_put(&path);
fput_and_out:
fdput(f);
return ret;
}


fanotify_add_inode_mark():
普通inode模式,將group和inode監控點鏈接起來:

static int fanotify_add_inode_mark(struct fsnotify_group *group,
struct inode *inode, __u32 mask,
unsigned int flags)
{
struct fsnotify_mark *fsn_mark;
__u32 added;

pr_debug("%s: group=%p inode=%p\n", __func__, group, inode);

/*
* If some other task has this inode open for write we should not add
* an ignored mark, unless that ignored mark is supposed to survive
* modification changes anyway.
*/
if ((flags & FAN_MARK_IGNORED_MASK) &&
!(flags & FAN_MARK_IGNORED_SURV_MODIFY) &&
(atomic_read(&inode->i_writecount) > 0))
return 0;

mutex_lock(&group->mark_mutex);
fsn_mark = fsnotify_find_inode_mark(group, inode);
if (!fsn_mark) {
/* (4.1) 分配fsnotify_mark結構用來記錄group和inode監控點的對應關係
將fsnotify_mark加入group->marks_list鏈表
將fsnotify_mark加入inode->i_fsnotify_marks鏈表
*/
fsn_mark = fanotify_add_new_mark(group, inode, NULL);
if (IS_ERR(fsn_mark)) {
mutex_unlock(&group->mark_mutex);
return PTR_ERR(fsn_mark);
}
}

/* (4.2) 根據mask指定的監控類型給fsnotify_mark賦值
Normal:tmask = fsn_mark->mask | mask;
Ignore:tmask = fsn_mark->ignored_mask | mask;
*/
added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
mutex_unlock(&group->mark_mutex);

if (added & ~inode->i_fsnotify_mask)
fsnotify_recalc_inode_mask(inode);

fsnotify_put_mark(fsn_mark);
return 0;
}

fanotify_add_vfsmount_mark()
mount模式,將group和mount監控點鏈接起來:

static int fanotify_add_vfsmount_mark(struct fsnotify_group *group,
struct vfsmount *mnt, __u32 mask,
unsigned int flags)
{
struct fsnotify_mark *fsn_mark;
__u32 added;

mutex_lock(&group->mark_mutex);
fsn_mark = fsnotify_find_vfsmount_mark(group, mnt);
if (!fsn_mark) {
/* (4.1) 分配fsnotify_mark結構用來記錄group和mount監控點的對應關係
將fsnotify_mark加入group->marks_list鏈表
將fsnotify_mark加入m->mnt_fsnotify_marks鏈表
*/
fsn_mark = fanotify_add_new_mark(group, NULL, mnt);
if (IS_ERR(fsn_mark)) {
mutex_unlock(&group->mark_mutex);
return PTR_ERR(fsn_mark);
}
}

/* (4.2) 根據mask指定的監控類型給fsnotify_mark賦值
Normal:tmask = fsn_mark->mask | mask;
Ignore:tmask = fsn_mark->ignored_mask | mask;
*/
added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
mutex_unlock(&group->mark_mutex);

if (added & ~real_mount(mnt)->mnt_fsnotify_mask)
fsnotify_recalc_vfsmount_mask(mnt);

fsnotify_put_mark(fsn_mark);
return 0;
}

3.2 觸發fanotify
訪問一個文件觸發fanotify事件。

read() -> vfs_read() -> fsnotify_access() -> fsnotify() -> send_to_group() -> group->ops->handle_event() -> fanotify_handle_event() -> fanotify_get_response() :

static int fanotify_handle_event(...)
{
...

/* (1) 上報event消息,給需要偵測這個inode/mnt節點的group/fd */
event = fanotify_alloc_event(inode, mask, data);
if (unlikely(!event))
return -ENOMEM;

fsn_event = &event->fse;
/* (1.1) 將event消息加入到group->notification_list消息鏈表中 */
ret = fsnotify_add_event(group, fsn_event, fanotify_merge);
if (ret) {
/* Permission events shouldn't be merged */
BUG_ON(ret == 1 && mask & FAN_ALL_PERM_EVENTS);
/* Our event wasn't used in the end. Free it. */
fsnotify_destroy_event(group, fsn_event);

return 0;
}

/* (2) 如果是權限攔截類型的消息,需要阻塞住當前進程,等待用戶的策略處理 */
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
if (mask & FAN_ALL_PERM_EVENTS) {
ret = fanotify_get_response(group, FANOTIFY_PE(fsn_event));
fsnotify_destroy_event(group, fsn_event);
}
#endif
return ret;
}

#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
static int fanotify_get_response(struct fsnotify_group *group,
struct fanotify_perm_event_info *event)
{
int ret;

pr_debug("%s: group=%p event=%p\n", __func__, group, event);

/* (2.1) 阻塞進消息隊列,等待用戶的策略處理 */
wait_event(group->fanotify_data.access_waitq, event->response);

/* (2.2) 根據用戶選擇的策略,決定放行還是攔截 */
/* userspace responded, convert to something usable */
switch (event->response) {
case FAN_ALLOW:
ret = 0;
break;
case FAN_DENY:
default:
ret = -EPERM;
}
event->response = 0;

pr_debug("%s: group=%p event=%p about to return ret=%d\n", __func__,
group, event, ret);

return ret;
}
#endif

3.3 響應fanotify
用戶態的處理:

int main(int argc, char *argv[])
{
...

/* (1) 創建fanotify監控的fd */
fan_fd = fanotify_init(init_flags, O_RDONLY | O_LARGEFILE);
if (fan_fd < 0)
goto fail;

/* (2) 使用fd,配置需要監控那些文件的那些事件 */
for (; optind < argc; optind++)
if (mark_object(fan_fd, argv[optind], AT_FDCWD, fan_mask, mark_flags) != 0)
goto fail;

/* (3) 讀取fd中的fanotify事件消息,並處理 */
while ((len = read(fan_fd, buf, sizeof(buf))) > 0) {
struct fanotify_event_metadata *metadata;
char path[PATH_MAX];
int path_len;

metadata = (void *)buf;
while(FAN_EVENT_OK(metadata, len)) {

/* (3.1) 權限攔截類的消息處理 */
if (metadata->mask & FAN_ALL_PERM_EVENTS) {
if (opt_sleep)
sleep(opt_sleep);

if (handle_perm(fan_fd, metadata))
goto fail;
if (metadata->fd >= 0 &&
opt_ignore_perm &&
set_ignored_mask(fan_fd, metadata->fd,
metadata->mask))
goto fail;
}

printf("\n");
fflush(stdout);

if (metadata->fd >= 0 && close(metadata->fd) != 0)
goto fail;
metadata = FAN_EVENT_NEXT(metadata, len);
}
...
}
...
}

int handle_perm(int fan_fd, struct fanotify_event_metadata *metadata)
{
struct fanotify_response response_struct;
int ret;

response_struct.fd = metadata->fd;
response_struct.response = FAN_ALLOW;

/* (3.1.1) 通過fd的寫操作來發送處理策略 */
ret = write(fan_fd, &response_struct, sizeof(response_struct));
if (ret < 0)
return ret;

return 0;
}

內核態處理:

fanotify_write() -> process_access_response():

static int process_access_response(struct fsnotify_group *group,
struct fanotify_response *response_struct)
{
struct fanotify_perm_event_info *event;
int fd = response_struct->fd;
int response = response_struct->response;

pr_debug("%s: group=%p fd=%d response=%d\n", __func__, group,
fd, response);
/*
* make sure the response is valid, if invalid we do nothing and either
* userspace can send a valid response or we will clean it up after the
* timeout
*/
switch (response) {
case FAN_ALLOW:
case FAN_DENY:
break;
default:
return -EINVAL;
}

if (fd < 0)
return -EINVAL;

/* (3.1.1.1) 設置處理結果 */
event = dequeue_event(group, fd);
if (!event)
return -ENOENT;

event->response = response;

/* (3.1.1.2) 喚醒等待處理策略的文件操作線程 */
wake_up(&group->fanotify_data.access_waitq);

return 0;
}

3.3.1 fanotify_read()
用戶態通過read()系統調用讀取event消息,最終調用到了fanotify_read()。該函數的主要作用就是在group的消息鏈表(group->notification_list)中讀取消息。

3.3.2 fanotify_write()
用戶態通過write()系統來通知fanotify的判斷結果,最終調用到了fanotify_write()。該函數的主要作用在process_access_response()中體現。

參考文檔:
1.利用fanotify進行文件系統實時監測的認識
2.linux fanotify和inotify
3.fanotify example userspace tools
————————————————
版權聲明:本文爲CSDN博主「pwl999」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/pwl999/article/details/106782339

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章