aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/trace
diff options
context:
space:
mode:
authorMasami Hiramatsu <masami.hiramatsu.pt@hitachi.com>2014-11-21 05:25:16 -0500
committerSteven Rostedt <rostedt@goodmis.org>2014-11-21 14:42:10 -0500
commitf8b8be8a310a55856fd2c369dade08088d85df3b (patch)
tree4c427a2981e4b66392b5bf9d7f8988479040150c /kernel/trace
parenta017784f1b236cbc42ce83b4345a667c21113481 (diff)
ftrace, kprobes: Support IPMODIFY flag to find IP modify conflict
Introduce FTRACE_OPS_FL_IPMODIFY to avoid conflict among ftrace users who may modify regs->ip to change the execution path. If two or more users modify the regs->ip on the same function entry, one of them will be broken. So they must add IPMODIFY flag and make sure that ftrace_set_filter_ip() succeeds. Note that ftrace doesn't allow ftrace_ops which has IPMODIFY flag to have notrace hash, and the ftrace_ops must have a filter hash (so that the ftrace_ops can hook only specific entries), because it strongly depends on the address and must be allowed for only few selected functions. Link: http://lkml.kernel.org/r/20141121102516.11844.27829.stgit@localhost.localdomain Cc: Jiri Kosina <jkosina@suse.cz> Cc: Seth Jennings <sjenning@redhat.com> Cc: Petr Mladek <pmladek@suse.cz> Cc: Vojtech Pavlik <vojtech@suse.cz> Cc: Miroslav Benes <mbenes@suse.cz> Cc: Ingo Molnar <mingo@kernel.org> Cc: Ananth N Mavinakayanahalli <ananth@in.ibm.com> Cc: Josh Poimboeuf <jpoimboe@redhat.com> Cc: Namhyung Kim <namhyung@kernel.org> Signed-off-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> [ fixed up some of the comments ] Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'kernel/trace')
-rw-r--r--kernel/trace/ftrace.c142
1 files changed, 140 insertions, 2 deletions
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 588af40d33db..929a733d302e 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -1358,6 +1358,9 @@ ftrace_hash_rec_disable_modify(struct ftrace_ops *ops, int filter_hash);
1358static void 1358static void
1359ftrace_hash_rec_enable_modify(struct ftrace_ops *ops, int filter_hash); 1359ftrace_hash_rec_enable_modify(struct ftrace_ops *ops, int filter_hash);
1360 1360
1361static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops,
1362 struct ftrace_hash *new_hash);
1363
1361static int 1364static int
1362ftrace_hash_move(struct ftrace_ops *ops, int enable, 1365ftrace_hash_move(struct ftrace_ops *ops, int enable,
1363 struct ftrace_hash **dst, struct ftrace_hash *src) 1366 struct ftrace_hash **dst, struct ftrace_hash *src)
@@ -1368,8 +1371,13 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable,
1368 struct ftrace_hash *new_hash; 1371 struct ftrace_hash *new_hash;
1369 int size = src->count; 1372 int size = src->count;
1370 int bits = 0; 1373 int bits = 0;
1374 int ret;
1371 int i; 1375 int i;
1372 1376
1377 /* Reject setting notrace hash on IPMODIFY ftrace_ops */
1378 if (ops->flags & FTRACE_OPS_FL_IPMODIFY && !enable)
1379 return -EINVAL;
1380
1373 /* 1381 /*
1374 * If the new source is empty, just free dst and assign it 1382 * If the new source is empty, just free dst and assign it
1375 * the empty_hash. 1383 * the empty_hash.
@@ -1403,6 +1411,16 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable,
1403 } 1411 }
1404 1412
1405update: 1413update:
1414 /* Make sure this can be applied if it is IPMODIFY ftrace_ops */
1415 if (enable) {
1416 /* IPMODIFY should be updated only when filter_hash updating */
1417 ret = ftrace_hash_ipmodify_update(ops, new_hash);
1418 if (ret < 0) {
1419 free_ftrace_hash(new_hash);
1420 return ret;
1421 }
1422 }
1423
1406 /* 1424 /*
1407 * Remove the current set, update the hash and add 1425 * Remove the current set, update the hash and add
1408 * them back. 1426 * them back.
@@ -1767,6 +1785,114 @@ static void ftrace_hash_rec_enable_modify(struct ftrace_ops *ops,
1767 ftrace_hash_rec_update_modify(ops, filter_hash, 1); 1785 ftrace_hash_rec_update_modify(ops, filter_hash, 1);
1768} 1786}
1769 1787
1788/*
1789 * Try to update IPMODIFY flag on each ftrace_rec. Return 0 if it is OK
1790 * or no-needed to update, -EBUSY if it detects a conflict of the flag
1791 * on a ftrace_rec, and -EINVAL if the new_hash tries to trace all recs.
1792 * Note that old_hash and new_hash has below meanings
1793 * - If the hash is NULL, it hits all recs (if IPMODIFY is set, this is rejected)
1794 * - If the hash is EMPTY_HASH, it hits nothing
1795 * - Anything else hits the recs which match the hash entries.
1796 */
1797static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops,
1798 struct ftrace_hash *old_hash,
1799 struct ftrace_hash *new_hash)
1800{
1801 struct ftrace_page *pg;
1802 struct dyn_ftrace *rec, *end = NULL;
1803 int in_old, in_new;
1804
1805 /* Only update if the ops has been registered */
1806 if (!(ops->flags & FTRACE_OPS_FL_ENABLED))
1807 return 0;
1808
1809 if (!(ops->flags & FTRACE_OPS_FL_IPMODIFY))
1810 return 0;
1811
1812 /*
1813 * Since the IPMODIFY is a very address sensitive action, we do not
1814 * allow ftrace_ops to set all functions to new hash.
1815 */
1816 if (!new_hash || !old_hash)
1817 return -EINVAL;
1818
1819 /* Update rec->flags */
1820 do_for_each_ftrace_rec(pg, rec) {
1821 /* We need to update only differences of filter_hash */
1822 in_old = !!ftrace_lookup_ip(old_hash, rec->ip);
1823 in_new = !!ftrace_lookup_ip(new_hash, rec->ip);
1824 if (in_old == in_new)
1825 continue;
1826
1827 if (in_new) {
1828 /* New entries must ensure no others are using it */
1829 if (rec->flags & FTRACE_FL_IPMODIFY)
1830 goto rollback;
1831 rec->flags |= FTRACE_FL_IPMODIFY;
1832 } else /* Removed entry */
1833 rec->flags &= ~FTRACE_FL_IPMODIFY;
1834 } while_for_each_ftrace_rec();
1835
1836 return 0;
1837
1838rollback:
1839 end = rec;
1840
1841 /* Roll back what we did above */
1842 do_for_each_ftrace_rec(pg, rec) {
1843 if (rec == end)
1844 goto err_out;
1845
1846 in_old = !!ftrace_lookup_ip(old_hash, rec->ip);
1847 in_new = !!ftrace_lookup_ip(new_hash, rec->ip);
1848 if (in_old == in_new)
1849 continue;
1850
1851 if (in_new)
1852 rec->flags &= ~FTRACE_FL_IPMODIFY;
1853 else
1854 rec->flags |= FTRACE_FL_IPMODIFY;
1855 } while_for_each_ftrace_rec();
1856
1857err_out:
1858 return -EBUSY;
1859}
1860
1861static int ftrace_hash_ipmodify_enable(struct ftrace_ops *ops)
1862{
1863 struct ftrace_hash *hash = ops->func_hash->filter_hash;
1864
1865 if (ftrace_hash_empty(hash))
1866 hash = NULL;
1867
1868 return __ftrace_hash_update_ipmodify(ops, EMPTY_HASH, hash);
1869}
1870
1871/* Disabling always succeeds */
1872static void ftrace_hash_ipmodify_disable(struct ftrace_ops *ops)
1873{
1874 struct ftrace_hash *hash = ops->func_hash->filter_hash;
1875
1876 if (ftrace_hash_empty(hash))
1877 hash = NULL;
1878
1879 __ftrace_hash_update_ipmodify(ops, hash, EMPTY_HASH);
1880}
1881
1882static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops,
1883 struct ftrace_hash *new_hash)
1884{
1885 struct ftrace_hash *old_hash = ops->func_hash->filter_hash;
1886
1887 if (ftrace_hash_empty(old_hash))
1888 old_hash = NULL;
1889
1890 if (ftrace_hash_empty(new_hash))
1891 new_hash = NULL;
1892
1893 return __ftrace_hash_update_ipmodify(ops, old_hash, new_hash);
1894}
1895
1770static void print_ip_ins(const char *fmt, unsigned char *p) 1896static void print_ip_ins(const char *fmt, unsigned char *p)
1771{ 1897{
1772 int i; 1898 int i;
@@ -2436,6 +2562,15 @@ static int ftrace_startup(struct ftrace_ops *ops, int command)
2436 */ 2562 */
2437 ops->flags |= FTRACE_OPS_FL_ENABLED | FTRACE_OPS_FL_ADDING; 2563 ops->flags |= FTRACE_OPS_FL_ENABLED | FTRACE_OPS_FL_ADDING;
2438 2564
2565 ret = ftrace_hash_ipmodify_enable(ops);
2566 if (ret < 0) {
2567 /* Rollback registration process */
2568 __unregister_ftrace_function(ops);
2569 ftrace_start_up--;
2570 ops->flags &= ~FTRACE_OPS_FL_ENABLED;
2571 return ret;
2572 }
2573
2439 ftrace_hash_rec_enable(ops, 1); 2574 ftrace_hash_rec_enable(ops, 1);
2440 2575
2441 ftrace_startup_enable(command); 2576 ftrace_startup_enable(command);
@@ -2464,6 +2599,8 @@ static int ftrace_shutdown(struct ftrace_ops *ops, int command)
2464 */ 2599 */
2465 WARN_ON_ONCE(ftrace_start_up < 0); 2600 WARN_ON_ONCE(ftrace_start_up < 0);
2466 2601
2602 /* Disabling ipmodify never fails */
2603 ftrace_hash_ipmodify_disable(ops);
2467 ftrace_hash_rec_disable(ops, 1); 2604 ftrace_hash_rec_disable(ops, 1);
2468 2605
2469 ops->flags &= ~FTRACE_OPS_FL_ENABLED; 2606 ops->flags &= ~FTRACE_OPS_FL_ENABLED;
@@ -3058,9 +3195,10 @@ static int t_show(struct seq_file *m, void *v)
3058 if (iter->flags & FTRACE_ITER_ENABLED) { 3195 if (iter->flags & FTRACE_ITER_ENABLED) {
3059 struct ftrace_ops *ops = NULL; 3196 struct ftrace_ops *ops = NULL;
3060 3197
3061 seq_printf(m, " (%ld)%s", 3198 seq_printf(m, " (%ld)%s%s",
3062 ftrace_rec_count(rec), 3199 ftrace_rec_count(rec),
3063 rec->flags & FTRACE_FL_REGS ? " R" : " "); 3200 rec->flags & FTRACE_FL_REGS ? " R" : " ",
3201 rec->flags & FTRACE_FL_IPMODIFY ? " I" : " ");
3064 if (rec->flags & FTRACE_FL_TRAMP_EN) { 3202 if (rec->flags & FTRACE_FL_TRAMP_EN) {
3065 ops = ftrace_find_tramp_ops_any(rec); 3203 ops = ftrace_find_tramp_ops_any(rec);
3066 if (ops) 3204 if (ops)