#include #include #include #include #include #include #include #include #include #include #include "lve_internal.h" #include "lve_debug.h" #define LVE_OLD_DC_API #ifdef FEAT_DC #define DC_BLOCK_SIZE 4096 /* If the tail is less than 16 chars, * we can skip insertion attempt in case insert_fn() is heavy */ #define DC_RSV_NAME 16 struct dc_block_hdr { struct list_head list; unsigned short size; unsigned short left; }; #define DC_LIST_TO_HDR(l) container_of((l), struct dc_block_hdr, list) struct dc_event_record { unsigned long counter; unsigned long euid; unsigned long type; char name[0]; }; struct dc_namedata { #ifdef LVE_OLD_DC_API char *root; #else char root[BDEVNAME_SIZE]; /* 32 bytes */ #endif struct dentry *parent; const char *name; int namelen; }; static LIST_HEAD(dc_event_list); static DEFINE_MUTEX(dc_mutex); static unsigned long dc_event_counter = 1; static unsigned long dc_block_counter; unsigned long lve_dc = 0; unsigned long lve_dc_min_uid = 500; static unsigned long lve_dc_buffer_kb = 40; static unsigned long lve_dc_max_events; static unsigned long lve_dc_events_in_buffer; static struct dc_block_hdr *alloc_block(void) { struct dc_block_hdr *hdr; if (dc_block_counter * (DC_BLOCK_SIZE >> 10) >= lve_dc_buffer_kb) return NULL; hdr = kmalloc(DC_BLOCK_SIZE, GFP_KERNEL); if (hdr != NULL) { hdr->size = DC_BLOCK_SIZE; hdr->left = DC_BLOCK_SIZE - sizeof(struct dc_block_hdr); dc_block_counter++; } return hdr; } static void free_block(struct dc_block_hdr *hdr) { dc_block_counter--; kfree(hdr); } /* 0 if failed, >0 if succeeded */ static int insert_fn(char *ptr, int maxlen, struct dc_namedata *name) { char *buf; int len, rlen; rlen = strlen(name->root); if (rlen + 1 > maxlen) return 0; memcpy(ptr, name->root, rlen); ptr += rlen; maxlen -= rlen; #ifdef LVE_OLD_DC_API if (rlen && ptr[-1] == '/') { rlen--; ptr--; maxlen++; } #else ptr[0] = ':'; ptr++; rlen++; maxlen--; #endif /* will return -ENAMETOOLONG if the name does not fit */ buf = dentry_path_raw(name->parent, ptr, maxlen); if (IS_ERR_OR_NULL(buf)) return 0; len = strlen(buf); memmove(ptr, buf, len + 1); if (name->name) { int nlen = name->namelen; if (len + 1 + nlen + 1 > maxlen) return 0; ptr[len] = '/'; memcpy(ptr + len + 1, name->name, nlen + 1); len += 1 + nlen; } return rlen + len + 1; } static int try_insert(struct dc_block_hdr *hdr, int type, struct dc_namedata *name) { struct dc_event_record *event; int rc; /* should fit at least dc_event + DC_RSV_NAME */ if (hdr->left < sizeof(*event) + DC_RSV_NAME) return 0; event = (struct dc_event_record *)((char *)hdr + hdr->size - hdr->left); event->counter = dc_event_counter++; /* always count the event! */ event->type = type; event->euid = from_kuid(&init_user_ns, current_fsuid()); rc = insert_fn(event->name, hdr->left - sizeof(*event), name); if (rc > 0) { hdr->left -= sizeof(*event) + rc; lve_dc_events_in_buffer++; } return !!rc; } static int insert(struct list_head *list, int type, struct dc_namedata *name) { struct dc_block_hdr *hdr; int rc = 0; mutex_lock(&dc_mutex); if (lve_dc_max_events && lve_dc_events_in_buffer >= lve_dc_max_events) goto out; /* first try to put the event record into the last allocated block */ if (!list_empty(list)) { rc = try_insert(DC_LIST_TO_HDR(list->prev), type, name); if (rc) goto out; } if ((hdr = alloc_block()) == NULL) { mutex_unlock(&dc_mutex); LVE_DBG("alloc_block failed\n"); return 0; } list_add_tail(&hdr->list, list); rc = try_insert(hdr, type, name); out: mutex_unlock(&dc_mutex); return rc; } static void *events_start(struct seq_file *s, loff_t *pos) { struct list_head *list; int i = 0; mutex_lock(&dc_mutex); list = dc_event_list.next; while (list != &dc_event_list && i++ < *pos) list = list->next; return list == &dc_event_list ? NULL : DC_LIST_TO_HDR(list); } static void *events_next(struct seq_file *s, void *v, loff_t *ppos) { struct dc_block_hdr *hdr = v; if (hdr->list.next != &dc_event_list) { (*ppos)++; return DC_LIST_TO_HDR(hdr->list.next); } return NULL; } static void events_stop(struct seq_file *s, void *v) { mutex_unlock(&dc_mutex); } static int events_show(struct seq_file *s, void *v) { struct dc_block_hdr *hdr = v; struct dc_event_record *event = (struct dc_event_record *)(hdr + 1); while ((char *)hdr + hdr->size - hdr->left > (char *)event) { seq_printf(s, "%lu:%lu:%lu:%s\n", event->counter, event->type, event->euid, event->name); event = (struct dc_event_record *)&event->name[strlen(event->name) + 1]; } return 0; } const struct seq_operations lve_events_op = { .start = events_start, .next = events_next, .stop = events_stop, .show = events_show }; /* returns 1 if the block was partially flushed */ static int lve_events_flush_partial(struct dc_block_hdr *hdr, unsigned long last_removed) { struct dc_event_record *event = (struct dc_event_record *)(hdr + 1); while ((char *)hdr + hdr->size - hdr->left > (char *)event) { if (event->counter > last_removed) { /* we need to keep this event and everything behind it */ memmove(hdr + 1, event, (char *)hdr + hdr->size - hdr->left - (char *)event); hdr->left += (char *)event - (char *)(hdr + 1); return 1; } event = (struct dc_event_record *)&event->name[strlen(event->name) + 1]; lve_dc_events_in_buffer--; } return 0; } /* remove all events up to and including @last_removed */ void lve_events_flush(unsigned long last_removed) { struct list_head *list; mutex_lock(&dc_mutex); list = dc_event_list.next; while (list != &dc_event_list) { struct dc_block_hdr *hdr = DC_LIST_TO_HDR(list); list = list->next; if (lve_events_flush_partial(hdr, last_removed)) break; list_del_init(&hdr->list); free_block(hdr); } mutex_unlock(&dc_mutex); } static int lve_get_root(struct dentry *dentry, char **root_path) { struct super_block *sb = dentry->d_sb; #ifdef LVE_OLD_DC_API if (sb->s_magic != EXT4_SUPER_MAGIC) { *root_path = ""; } else { /* Sigh. Ugly, but ext4.h is not exported by the kernel. * At this point, we know that superblock bh should be pinned * in memory and s_last_mounted is located at +0x88 from the * beginning of the superblock (ext4 fixed on-disk format). */ int block = sb->s_blocksize == 1024 ? 1 : 0, offset = sb->s_blocksize == 1024 ? 0 : 1024; struct buffer_head *bh = __find_get_block(sb->s_bdev, block, sb->s_blocksize); if (bh) { *root_path = (char *)bh->b_data + offset + 0x88; /* bh is still pinned by the filesystem via s_es */ put_bh(bh); } else { *root_path = ""; } } return 0; #else if (sb && sb->s_bdev) { /* * we would like to get an init_ns path here, * but it's not always possible, e.g there can * be no mount for this sb in init_ns or it can * have a different root */ bdevname(sb->s_bdev, root_path); return 0; } return -ENODATA; #endif } static DEFINE_CTL_TABLE_POLL(dc_events_poll); /* Path can come in a variety of ways: * 1) dentry != NULL - the dentry for the path * 2) parent != NULL, name != NULL - use file @name in dir @parent * 3) parent != NULL, name == NULL - use any name of @parent */ void lve_fsnotify_process_event(enum dc_event event, struct inode *parent, const char *name, int namelen, struct dentry *dentry) { struct dentry *dparent; struct dc_namedata dc; int rc; if (dentry == NULL) { dparent = d_find_alias(parent); } else { dparent = dentry; } if (dparent == NULL) return; rc = lve_get_root(dparent, &dc.root); if (rc < 0) return; dc.parent = dparent; dc.name = name; dc.namelen = namelen; insert(&dc_event_list, event, &dc); if (dparent != dentry) dput(dparent); lve_proc_sys_poll_notify(&dc_events_poll); } #ifdef LVE_OLD_DC_API #include static int dc_event_handler(struct ctl_table *table, int write, void __user *buffer, size_t *lenp, loff_t *ppos) { size_t size = *lenp; struct list_head *list; struct dc_event_record *event; char *output_buf; char *pos; size_t sz; int ret = 0; if (write) { unsigned long user_int; static DEFINE_MUTEX(flush_mutex); mutex_lock(&flush_mutex); table->data = &user_int; ret = proc_doulongvec_minmax(table, write, buffer, lenp, ppos); table->data = NULL; mutex_unlock(&flush_mutex); if (ret) return ret; lve_events_flush(user_int); return 0; } /* If there's no size, or if no file position, exit. */ if (!size || *ppos) { *lenp = 0; return 0; } /* Allocate a buffer for our output. */ output_buf = vzalloc(size); if (output_buf == NULL) return -ENOMEM; pos = output_buf; mutex_lock(&dc_mutex); list_for_each(list, &dc_event_list) { struct dc_block_hdr *hdr = DC_LIST_TO_HDR(list); event = (struct dc_event_record *)(hdr + 1); while ((char *)hdr + hdr->size - hdr->left > (char *)event) { char *n = event->name; size_t oldsize = size; sz = snprintf(pos, size, "%lu:%lu:%lu:", event->counter, event->type, event->euid); event = (struct dc_event_record *) &event->name[strlen(n) + 1]; /* truncated ?*/ if (sz >= size) goto overflow; pos += sz; size -= sz; /* print the path, mangle \\ and \n */ while (1) { switch(*n) { case '\\': sz = snprintf(pos, size, "%s", "\\\\"); break; case '\n': sz = snprintf(pos, size, "%s", "\\n"); break; case '\0': sz = snprintf(pos, size, "%c", '\n'); break; default: sz = snprintf(pos, size, "%c", *n); break; } if (sz >= size) { /* revert the size, we only want full * size records go to userspace */ size = oldsize; goto overflow; } pos += sz; size -= sz; if (!*n) break; n++; } } } overflow: mutex_unlock(&dc_mutex); sz = *lenp - size; #if RHEL_MAJOR < 9 /* Copy to user space buffer */ if (copy_to_user(buffer, output_buf, sz)) ret = -EFAULT; #else memcpy(buffer, output_buf, sz); #endif vfree(output_buf); if (ret == 0) { *ppos = *ppos + sz; *lenp = sz; } return ret; } static int zero = 0; static int one = 1; int lve_read_only_users = -1; static struct ctl_table dc_sysctl_events[] __read_mostly = { { .procname = "events", .mode = 0400, .maxlen = -1, .proc_handler = &dc_event_handler, .poll = &dc_events_poll, }, { .procname = "flush", .mode = 0200, .maxlen = sizeof(unsigned long), .proc_handler = &dc_event_handler }, { .procname = "user_ro_mode", .data = &lve_read_only_users, .mode = 0600, .maxlen = sizeof(unsigned int), .proc_handler = proc_dointvec_minmax }, { .procname = "min_event_uid", .data = &lve_dc_min_uid, .mode = 0600, .maxlen = sizeof(unsigned long), .proc_handler = proc_doulongvec_minmax }, { .procname = "max_events", .data = &lve_dc_max_events, .mode = 0600, .maxlen = sizeof(unsigned long), .proc_handler = proc_doulongvec_minmax }, { .procname = "entries_in_buffer", .data = &lve_dc_events_in_buffer, .mode = 0400, .maxlen = sizeof(unsigned long), .proc_handler = proc_doulongvec_minmax }, { .procname = "enable", .data = &lve_dc, .mode = 0600, .maxlen = sizeof(unsigned int), .proc_handler = proc_dointvec_minmax, .extra1 = &zero, .extra2 = &one }, {} }; static struct ctl_table dc_dir[] __read_mostly = { { .procname = "datacycle", .mode = 0500, .child = dc_sysctl_events }, {} }; static struct ctl_table dc_sysctl_path[] __read_mostly = { { .procname = "fs", .mode = 0555, .child = dc_dir }, {} }; #endif static struct ctl_table_header *dc_sysctl; int lve_dc_init(void) { #ifdef LVE_OLD_DC_API dc_sysctl = register_sysctl_table(dc_sysctl_path); #endif return 0; } void lve_dc_fini(void) { #ifdef LVE_OLD_DC_API if (dc_sysctl) unregister_sysctl_table(dc_sysctl); #endif lve_events_flush(ULONG_MAX); } module_param(lve_dc, ulong, 0644); MODULE_PARM_DESC(lve_dc, "Datacycle-based file monitor"); module_param(lve_dc_min_uid, ulong, 0644); MODULE_PARM_DESC(lve_dc_min_uid, "Minimal fsuid for the DC event logger"); module_param(lve_dc_buffer_kb, ulong, 0644); MODULE_PARM_DESC(lve_dc_buffer_kb, "Size of buffer for DC events (in kilobytes)"); #endif