diff options
author | Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> | 2014-11-21 05:25:16 -0500 |
---|---|---|
committer | Steven Rostedt <rostedt@goodmis.org> | 2014-11-21 14:42:10 -0500 |
commit | f8b8be8a310a55856fd2c369dade08088d85df3b (patch) | |
tree | 4c427a2981e4b66392b5bf9d7f8988479040150c /kernel/trace | |
parent | a017784f1b236cbc42ce83b4345a667c21113481 (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.c | 142 |
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); | |||
1358 | static void | 1358 | static void |
1359 | ftrace_hash_rec_enable_modify(struct ftrace_ops *ops, int filter_hash); | 1359 | ftrace_hash_rec_enable_modify(struct ftrace_ops *ops, int filter_hash); |
1360 | 1360 | ||
1361 | static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops, | ||
1362 | struct ftrace_hash *new_hash); | ||
1363 | |||
1361 | static int | 1364 | static int |
1362 | ftrace_hash_move(struct ftrace_ops *ops, int enable, | 1365 | ftrace_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 | ||
1405 | update: | 1413 | update: |
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 | */ | ||
1797 | static 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 | |||
1838 | rollback: | ||
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 | |||
1857 | err_out: | ||
1858 | return -EBUSY; | ||
1859 | } | ||
1860 | |||
1861 | static 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 */ | ||
1872 | static 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 | |||
1882 | static 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 | |||
1770 | static void print_ip_ins(const char *fmt, unsigned char *p) | 1896 | static 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) |