History

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

Masami Hiramatsu committed 11 years ago

Commit f8b8be81 parent a0177843 files · +159 −4Browse files

Showing the changes to kernel/trace (1 of 3 files). Show all

Unified · Split

kernel/trace/ftrace.c+140−2
@@ -1358,6 +1358,9 @@ ftrace_hash_rec_disable_modify(struct ftrace_ops *ops, int filter_hash); · 1357 unmodified lines
13581358 static void
13591359 ftrace_hash_rec_enable_modify(struct ftrace_ops *ops, int filter_hash);
13601360
1361+static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops,
1362+ struct ftrace_hash *new_hash);
1363+
13611364 static int
13621365 ftrace_hash_move(struct ftrace_ops *ops, int enable,
13631366 struct ftrace_hash **dst, struct ftrace_hash *src)
@@ -1368,8 +1371,13 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable, · 4 unmodified lines
13681371 struct ftrace_hash *new_hash;
13691372 int size = src->count;
13701373 int bits = 0;
1374+ int ret;
13711375 int i;
13721376
1377+ /* Reject setting notrace hash on IPMODIFY ftrace_ops */
1378+ if (ops->flags & FTRACE_OPS_FL_IPMODIFY && !enable)
1379+ return -EINVAL;
1380+
13731381 /*
13741382 * If the new source is empty, just free dst and assign it
13751383 * the empty_hash.
@@ -1403,6 +1411,16 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable, · 27 unmodified lines
14031411 }
14041412
14051413 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+
14061424 /*
14071425 * Remove the current set, update the hash and add
14081426 * them back.
@@ -1767,6 +1785,114 @@ static void ftrace_hash_rec_enable_modify(struct ftrace_ops *ops, · 358 unmodified lines
17671785 ftrace_hash_rec_update_modify(ops, filter_hash, 1);
17681786 }
17691787
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+
17701896 static void print_ip_ins(const char *fmt, unsigned char *p)
17711897 {
17721898 int i;
@@ -2436,6 +2562,15 @@ static int ftrace_startup(struct ftrace_ops *ops, int command) · 663 unmodified lines
24362562 */
24372563 ops->flags |= FTRACE_OPS_FL_ENABLED | FTRACE_OPS_FL_ADDING;
24382564
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+
24392574 ftrace_hash_rec_enable(ops, 1);
24402575
24412576 ftrace_startup_enable(command);
@@ -2464,6 +2599,8 @@ static int ftrace_shutdown(struct ftrace_ops *ops, int command) · 22 unmodified lines
24642599 */
24652600 WARN_ON_ONCE(ftrace_start_up < 0);
24662601
2602+ /* Disabling ipmodify never fails */
2603+ ftrace_hash_ipmodify_disable(ops);
24672604 ftrace_hash_rec_disable(ops, 1);
24682605
24692606 ops->flags &= ~FTRACE_OPS_FL_ENABLED;
@@ -3058,9 +3195,10 @@ static int t_show(struct seq_file *m, void *v) · 588 unmodified lines
30583195 if (iter->flags & FTRACE_ITER_ENABLED) {
30593196 struct ftrace_ops *ops = NULL;
30603197
3061− seq_printf(m, " (%ld)%s",
3198+ seq_printf(m, " (%ld)%s%s",
30623199 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" : " ");
30643202 if (rec->flags & FTRACE_FL_TRAMP_EN) {
30653203 ops = ftrace_find_tramp_ops_any(rec);
30663204 if (ops)

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t