@@ -1803,6 +1803,7 @@ static int copy_verifier_state(struct bpf_verifier_state *dst_state,
18031803 dst_state->first_insn_idx = src->first_insn_idx;
18041804 dst_state->last_insn_idx = src->last_insn_idx;
18051805 dst_state->dfs_depth = src->dfs_depth;
1806+ dst_state->used_as_loop_entry = src->used_as_loop_entry;
18061807 for (i = 0; i <= src->curframe; i++) {
18071808 dst = dst_state->frame[i];
18081809 if (!dst) {
@@ -1845,11 +1846,176 @@ static bool same_callsites(struct bpf_verifier_state *a, struct bpf_verifier_sta
18451846 return true;
18461847}
18471848
1849+ /* Open coded iterators allow back-edges in the state graph in order to
1850+ * check unbounded loops that iterators.
1851+ *
1852+ * In is_state_visited() it is necessary to know if explored states are
1853+ * part of some loops in order to decide whether non-exact states
1854+ * comparison could be used:
1855+ * - non-exact states comparison establishes sub-state relation and uses
1856+ * read and precision marks to do so, these marks are propagated from
1857+ * children states and thus are not guaranteed to be final in a loop;
1858+ * - exact states comparison just checks if current and explored states
1859+ * are identical (and thus form a back-edge).
1860+ *
1861+ * Paper "A New Algorithm for Identifying Loops in Decompilation"
1862+ * by Tao Wei, Jian Mao, Wei Zou and Yu Chen [1] presents a convenient
1863+ * algorithm for loop structure detection and gives an overview of
1864+ * relevant terminology. It also has helpful illustrations.
1865+ *
1866+ * [1] https://api.semanticscholar.org/CorpusID:15784067
1867+ *
1868+ * We use a similar algorithm but because loop nested structure is
1869+ * irrelevant for verifier ours is significantly simpler and resembles
1870+ * strongly connected components algorithm from Sedgewick's textbook.
1871+ *
1872+ * Define topmost loop entry as a first node of the loop traversed in a
1873+ * depth first search starting from initial state. The goal of the loop
1874+ * tracking algorithm is to associate topmost loop entries with states
1875+ * derived from these entries.
1876+ *
1877+ * For each step in the DFS states traversal algorithm needs to identify
1878+ * the following situations:
1879+ *
1880+ * initial initial initial
1881+ * | | |
1882+ * V V V
1883+ * ... ... .---------> hdr
1884+ * | | | |
1885+ * V V | V
1886+ * cur .-> succ | .------...
1887+ * | | | | | |
1888+ * V | V | V V
1889+ * succ '-- cur | ... ...
1890+ * | | |
1891+ * | V V
1892+ * | succ <- cur
1893+ * | |
1894+ * | V
1895+ * | ...
1896+ * | |
1897+ * '----'
1898+ *
1899+ * (A) successor state of cur (B) successor state of cur or it's entry
1900+ * not yet traversed are in current DFS path, thus cur and succ
1901+ * are members of the same outermost loop
1902+ *
1903+ * initial initial
1904+ * | |
1905+ * V V
1906+ * ... ...
1907+ * | |
1908+ * V V
1909+ * .------... .------...
1910+ * | | | |
1911+ * V V V V
1912+ * .-> hdr ... ... ...
1913+ * | | | | |
1914+ * | V V V V
1915+ * | succ <- cur succ <- cur
1916+ * | | |
1917+ * | V V
1918+ * | ... ...
1919+ * | | |
1920+ * '----' exit
1921+ *
1922+ * (C) successor state of cur is a part of some loop but this loop
1923+ * does not include cur or successor state is not in a loop at all.
1924+ *
1925+ * Algorithm could be described as the following python code:
1926+ *
1927+ * traversed = set() # Set of traversed nodes
1928+ * entries = {} # Mapping from node to loop entry
1929+ * depths = {} # Depth level assigned to graph node
1930+ * path = set() # Current DFS path
1931+ *
1932+ * # Find outermost loop entry known for n
1933+ * def get_loop_entry(n):
1934+ * h = entries.get(n, None)
1935+ * while h in entries and entries[h] != h:
1936+ * h = entries[h]
1937+ * return h
1938+ *
1939+ * # Update n's loop entry if h's outermost entry comes
1940+ * # before n's outermost entry in current DFS path.
1941+ * def update_loop_entry(n, h):
1942+ * n1 = get_loop_entry(n) or n
1943+ * h1 = get_loop_entry(h) or h
1944+ * if h1 in path and depths[h1] <= depths[n1]:
1945+ * entries[n] = h1
1946+ *
1947+ * def dfs(n, depth):
1948+ * traversed.add(n)
1949+ * path.add(n)
1950+ * depths[n] = depth
1951+ * for succ in G.successors(n):
1952+ * if succ not in traversed:
1953+ * # Case A: explore succ and update cur's loop entry
1954+ * # only if succ's entry is in current DFS path.
1955+ * dfs(succ, depth + 1)
1956+ * h = get_loop_entry(succ)
1957+ * update_loop_entry(n, h)
1958+ * else:
1959+ * # Case B or C depending on `h1 in path` check in update_loop_entry().
1960+ * update_loop_entry(n, succ)
1961+ * path.remove(n)
1962+ *
1963+ * To adapt this algorithm for use with verifier:
1964+ * - use st->branch == 0 as a signal that DFS of succ had been finished
1965+ * and cur's loop entry has to be updated (case A), handle this in
1966+ * update_branch_counts();
1967+ * - use st->branch > 0 as a signal that st is in the current DFS path;
1968+ * - handle cases B and C in is_state_visited();
1969+ * - update topmost loop entry for intermediate states in get_loop_entry().
1970+ */
1971+ static struct bpf_verifier_state *get_loop_entry(struct bpf_verifier_state *st)
1972+ {
1973+ struct bpf_verifier_state *topmost = st->loop_entry, *old;
1974+
1975+ while (topmost && topmost->loop_entry && topmost != topmost->loop_entry)
1976+ topmost = topmost->loop_entry;
1977+ /* Update loop entries for intermediate states to avoid this
1978+ * traversal in future get_loop_entry() calls.
1979+ */
1980+ while (st && st->loop_entry != topmost) {
1981+ old = st->loop_entry;
1982+ st->loop_entry = topmost;
1983+ st = old;
1984+ }
1985+ return topmost;
1986+ }
1987+
1988+ static void update_loop_entry(struct bpf_verifier_state *cur, struct bpf_verifier_state *hdr)
1989+ {
1990+ struct bpf_verifier_state *cur1, *hdr1;
1991+
1992+ cur1 = get_loop_entry(cur) ?: cur;
1993+ hdr1 = get_loop_entry(hdr) ?: hdr;
1994+ /* The head1->branches check decides between cases B and C in
1995+ * comment for get_loop_entry(). If hdr1->branches == 0 then
1996+ * head's topmost loop entry is not in current DFS path,
1997+ * hence 'cur' and 'hdr' are not in the same loop and there is
1998+ * no need to update cur->loop_entry.
1999+ */
2000+ if (hdr1->branches && hdr1->dfs_depth <= cur1->dfs_depth) {
2001+ cur->loop_entry = hdr;
2002+ hdr->used_as_loop_entry = true;
2003+ }
2004+ }
2005+
18482006static void update_branch_counts(struct bpf_verifier_env *env, struct bpf_verifier_state *st)
18492007{
18502008 while (st) {
18512009 u32 br = --st->branches;
18522010
2011+ /* br == 0 signals that DFS exploration for 'st' is finished,
2012+ * thus it is necessary to update parent's loop entry if it
2013+ * turned out that st is a part of some loop.
2014+ * This is a part of 'case A' in get_loop_entry() comment.
2015+ */
2016+ if (br == 0 && st->parent && st->loop_entry)
2017+ update_loop_entry(st->parent, st->loop_entry);
2018+
18532019 /* WARN_ON(br > 1) technically makes sense here,
18542020 * but see comment in push_stack(), hence:
18552021 */
@@ -16650,10 +16816,11 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
1665016816{
1665116817 struct bpf_verifier_state_list *new_sl;
1665216818 struct bpf_verifier_state_list *sl, **pprev;
16653- struct bpf_verifier_state *cur = env->cur_state, *new;
16819+ struct bpf_verifier_state *cur = env->cur_state, *new, *loop_entry ;
1665416820 int i, j, n, err, states_cnt = 0;
1665516821 bool force_new_state = env->test_state_freq || is_force_checkpoint(env, insn_idx);
1665616822 bool add_new_state = force_new_state;
16823+ bool force_exact;
1665716824
1665816825 /* bpf progs typically have pruning point every 4 instructions
1665916826 * http://vger.kernel.org/bpfconf2019.html#session-1
@@ -16748,8 +16915,10 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
1674816915 */
1674916916 spi = __get_spi(iter_reg->off + iter_reg->var_off.value);
1675016917 iter_state = &func(env, iter_reg)->stack[spi].spilled_ptr;
16751- if (iter_state->iter.state == BPF_ITER_STATE_ACTIVE)
16918+ if (iter_state->iter.state == BPF_ITER_STATE_ACTIVE) {
16919+ update_loop_entry(cur, &sl->state);
1675216920 goto hit;
16921+ }
1675316922 }
1675416923 goto skip_inf_loop_check;
1675516924 }
@@ -16780,7 +16949,36 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
1678016949 add_new_state = false;
1678116950 goto miss;
1678216951 }
16783- if (states_equal(env, &sl->state, cur, false)) {
16952+ /* If sl->state is a part of a loop and this loop's entry is a part of
16953+ * current verification path then states have to be compared exactly.
16954+ * 'force_exact' is needed to catch the following case:
16955+ *
16956+ * initial Here state 'succ' was processed first,
16957+ * | it was eventually tracked to produce a
16958+ * V state identical to 'hdr'.
16959+ * .---------> hdr All branches from 'succ' had been explored
16960+ * | | and thus 'succ' has its .branches == 0.
16961+ * | V
16962+ * | .------... Suppose states 'cur' and 'succ' correspond
16963+ * | | | to the same instruction + callsites.
16964+ * | V V In such case it is necessary to check
16965+ * | ... ... if 'succ' and 'cur' are states_equal().
16966+ * | | | If 'succ' and 'cur' are a part of the
16967+ * | V V same loop exact flag has to be set.
16968+ * | succ <- cur To check if that is the case, verify
16969+ * | | if loop entry of 'succ' is in current
16970+ * | V DFS path.
16971+ * | ...
16972+ * | |
16973+ * '----'
16974+ *
16975+ * Additional details are in the comment before get_loop_entry().
16976+ */
16977+ loop_entry = get_loop_entry(&sl->state);
16978+ force_exact = loop_entry && loop_entry->branches > 0;
16979+ if (states_equal(env, &sl->state, cur, force_exact)) {
16980+ if (force_exact)
16981+ update_loop_entry(cur, loop_entry);
1678416982hit:
1678516983 sl->hit_cnt++;
1678616984 /* reached equivalent register/stack state,
@@ -16829,7 +17027,8 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
1682917027 * speed up verification
1683017028 */
1683117029 *pprev = sl->next;
16832- if (sl->state.frame[0]->regs[0].live & REG_LIVE_DONE) {
17030+ if (sl->state.frame[0]->regs[0].live & REG_LIVE_DONE &&
17031+ !sl->state.used_as_loop_entry) {
1683317032 u32 br = sl->state.branches;
1683417033
1683517034 WARN_ONCE(br,
0 commit comments