/* * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include "mount.h" #define DEFINE_DLOG(_name) extern int fslog_##_name(const char *fmt, ...) DEFINE_DLOG(dlog_mm); DEFINE_DLOG(dlog_efs); DEFINE_DLOG(dlog_etc); DEFINE_DLOG(dlog_rmdir); #define EXT_SHIFT 7 #define MAX_EXT (1 << 7) #define MAX_DEPTH 2 #define MEDIA_MIN_SIZE (512 * 1024) struct dlog_keyword { struct hlist_node hlist; const char *keyword; }; struct dlog_keyword_hash_tbl { DECLARE_HASHTABLE(table, 7); }; enum { DLOG_HT_EXTENSION = 0, DLOG_HT_EXCEPTION, DLOG_HT_MAX }; enum { DLOG_SUPP_PART_DATA = 0, DLOG_SUPP_PART_EFS, DLOG_SUPP_PART_FUSE, DLOG_SUPP_PART_MAX }; enum { DLOG_MM = 0, DLOG_EFS, DLOG_ETC, DLOG_RMDIR }; static struct dlog_keyword_hash_tbl ht[DLOG_HT_MAX]; static const char *support_part[] = { "data", "efs", "emulated", NULL, }; static const char *extensions[] = { /* image */ "arw", "bmp", "cr2", "dng", "gif", "jpeg", "jpg", "nef", "nrw", "orf", "pef", "png", "raf", "rw2", "srw", "wbmp", "webp", /* audio */ "3ga", "aac", "amr", "awb", "dff", "dsf", "flac", "imy", "m4a", "mid", "midi", "mka", "mp3", "mpga", "mxmf", "oga", "ogg", "ota", "rtttl", "rtx", "smf", "wav", "wma", "xmf", "dcf", /* video */ "3g2", "3gp", "3gpp", "3gpp2", "ak3g", "asf", "avi", "divx", "flv", "m2t", "m2ts", "m4v", "mkv", "mp4", "mpeg", "mpg", "mts", "skm", "tp", "trp", "ts", "webm", "wmv", /* document */ "asc", "csv", "doc", "docm", "docx", "dot", "dotx", "htm", "html", "hwdt", "hwp", "hwpx", "hwt", "memo", "pdf", "pot", "potx", "pps", "ppsx", "ppt", "pptm", "pptx", "rtf", "sdoc", "snb", "spd", "xls", "xlsm", "xlsx", "xlt", "xltx", "xml", NULL, }; static const char *exceptions[] = { /* exception extension */ "db-shm", "shm", "bak", "mbak", "gz", "log", "swap", "dcs", "tmp", "temp", "txt", NULL, }; static const char **dlog_keyword_tbl[DLOG_HT_MAX] = { extensions, exceptions }; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) static inline unsigned int dlog_full_name_hash(const char *name, unsigned int len) { return full_name_hash((void *)0x0, name, len); } #else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) */ static inline unsigned int dlog_full_name_hash(const char *name, unsigned int len) { return full_name_hash(name, len); } #endif bool is_ext(const char *name, const char token, struct dlog_keyword_hash_tbl *hash_tbl) { char *ext = strrchr(name, token); struct dlog_keyword *hash_cur; unsigned hash; int i; bool ret = false; if (!ext || !ext[1]) return false; ext = kstrdup(&ext[1], GFP_KERNEL); if (!ext) return false; for (i = 0; ext[i]; i++) ext[i] = tolower(ext[i]); hash = dlog_full_name_hash(ext, strlen(ext)); hash_for_each_possible(hash_tbl->table, hash_cur, hlist, hash) { if (!strcmp(ext, hash_cur->keyword)) { ret = true; break; } } kfree(ext); return ret; } int __init dlog_keyword_hash_init(void) { int i; int tbl_idx = 0; do { hash_init(ht[tbl_idx].table); for (i = 0; dlog_keyword_tbl[tbl_idx][i]; i++) { struct dlog_keyword *hte; hte = kzalloc(sizeof(struct dlog_keyword), GFP_KERNEL); if (!hte) return -ENOMEM; INIT_HLIST_NODE(&hte->hlist); hte->keyword = dlog_keyword_tbl[tbl_idx][i]; hash_add(ht[tbl_idx].table, &hte->hlist, dlog_full_name_hash(hte->keyword, strlen(hte->keyword))); } tbl_idx++; } while(tbl_idx < DLOG_HT_MAX); return 0; } module_init(dlog_keyword_hash_init); void __exit dlog_keyword_hash_exit(void) { int i; int num_ht = 0; struct dlog_keyword *hash_cur; do { hash_for_each(ht[num_ht].table, i, hash_cur, hlist) kfree(hash_cur); } while(++num_ht < DLOG_HT_MAX); } module_exit(dlog_keyword_hash_exit); static int get_support_part_id(struct vfsmount *mnt) { int idx = 0; struct mount *mount = real_mount(mnt); /* check partition which need register delete log */ do { if (!strcmp(mount->mnt_mountpoint->d_name.name, support_part[idx])) return idx; } while(support_part[++idx]); return -1; } #ifndef SDFAT_SUPER_MAGIC #define SDFAT_SUPER_MAGIC 0x5EC5DFA4 #endif #ifndef FUSE_SUPER_MAGIC #define FUSE_SUPER_MAGIC 0x65735546 #endif static int is_sdcard(struct vfsmount *mnt) { /* internal storage (external storage till Andorid 8.x) */ if (mnt->mnt_sb->s_magic == SDCARDFS_SUPER_MAGIC) return true; /* internal storage (external storage till Andorid 11.x) */ else if (mnt->mnt_sb->s_magic == FUSE_SUPER_MAGIC) return true; /* external storage from Android 9.x */ else if (mnt->mnt_sb->s_magic == SDFAT_SUPER_MAGIC) return true; else if (mnt->mnt_sb->s_magic == MSDOS_SUPER_MAGIC) return true; return false; } static void make_prefix(int part_id, char *prefix) { if (part_id == DLOG_SUPP_PART_DATA) *prefix = 'd'; else *prefix = 's'; } static void store_log(struct dentry *dentry, struct inode *inode, struct path *path, int type, int part_id) { kuid_t euid = current->cred->euid; unsigned long ino = inode ? inode->i_ino : 0; loff_t isize = inode ? inode->i_size : 0; char prefix = 0; char *buf, *full_path; char unit = 'B'; buf = kzalloc(PATH_MAX, GFP_KERNEL); if (!buf) { printk(KERN_ERR "%s memory alloc failed : ENOMEM\n", __func__); return; } full_path = dentry_path_raw(dentry, buf, PATH_MAX); if (IS_ERR(full_path)) goto out; make_prefix(part_id, &prefix); if (isize >> 10) { isize >>= 10; unit = 'K'; } if (type == DLOG_MM) { fslog_dlog_mm("[%c]\"%s\" (%u, %lu, %lu, %lld%c)\n", prefix, full_path, euid, path->dentry->d_inode->i_ino, ino, isize, unit); goto out; } if (type == DLOG_ETC) { fslog_dlog_etc("[%c]\"%s\" (%u, %lu, %lu, %lld%c)\n", prefix, full_path, euid, path->dentry->d_inode->i_ino, ino, isize, unit); goto out; } if (type == DLOG_EFS) { fslog_dlog_efs("\"%s\" (%lu, %lu, %lld%c)\n", full_path, path->dentry->d_inode->i_ino, ino, isize, unit); goto out; } if (type == DLOG_RMDIR) fslog_dlog_rmdir("[%c]\"%s\" (%u, %lu)\n", prefix, full_path, euid, path->dentry->d_inode->i_ino); out: kfree(buf); } void dlog_hook(struct dentry *dentry, struct inode *inode, struct path *path) { int part_id = get_support_part_id(path->mnt); if ((part_id < 0) && !is_sdcard(path->mnt)) return; /* for efs partition */ if (part_id == DLOG_SUPP_PART_EFS) { store_log(dentry, inode, path, DLOG_EFS, part_id); goto out; } /* for data partition`s only multimedia file */ if (is_ext(dentry->d_name.name, '.', &ht[DLOG_HT_EXTENSION])) { if (i_size_read(inode) >= MEDIA_MIN_SIZE) store_log(dentry, inode, path, DLOG_MM, part_id); goto out; } /* for data partition except multimedia file */ if (!is_ext(dentry->d_name.name, '.', &ht[DLOG_HT_EXCEPTION]) && !is_ext(dentry->d_name.name, '-', &ht[DLOG_HT_EXCEPTION])) store_log(dentry, inode, path, DLOG_ETC, part_id); out: return; } static int get_dentry_depth(struct dentry *dentry) { int depth = 0; struct dentry *tmp_de = dentry; while (!IS_ROOT(tmp_de)) { struct dentry *parent = tmp_de->d_parent; depth++; if (depth > MAX_DEPTH) return -1; tmp_de = parent; } return depth; } void dlog_hook_rmdir(struct dentry *dentry, struct path *path) { int depth = 0; int part_id = get_support_part_id(path->mnt); if (part_id != DLOG_SUPP_PART_DATA) return; depth = get_dentry_depth(dentry); if (depth < 0) return; store_log(dentry, NULL, path, DLOG_RMDIR, part_id); return; } MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Logging unlink file path");