On 01/17/2019 02:16 AM, Alexei Starovoitov wrote:
> On Thu, Jan 17, 2019 at 01:21:32AM +0100, Daniel Borkmann wrote:
>> On 01/17/2019 12:30 AM, Alexei Starovoitov wrote:
>>> On Thu, Jan 17, 2019 at 12:16:44AM +0100, Daniel Borkmann wrote:
>>>> On 01/16/2019 11:48 PM, Daniel Borkmann wrote:
>>>>> On 01/16/2019 06:08 AM, Alexei Starovoitov wrote:
>>>> [...]
>>>>>> @@ -6096,6 +6226,11 @@ static int do_check(struct bpf_verifier_env *env)
>>>>>>                                          return -EINVAL;
>>>>>>                                  }
>>>>>>  
>>>>>> +                                if (env->cur_state->active_spin_lock) {
>>>>>> +                                        verbose(env, "bpf_spin_unlock 
>>>>>> is missing\n");
>>>>>> +                                        return -EINVAL;
>>>>>> +                                }
>>>>>> +
>>>>>>                                  if (state->curframe) {
>>>>>>                                          /* exit from nested function */
>>>>>>                                          env->prev_insn_idx = 
>>>>>> env->insn_idx;
>>>>>
>>>>> I think if I'm not mistaken there should still be a possibility for 
>>>>> causing a
>>>>> deadlock, namely if in the middle of the critical section I'm using an 
>>>>> LD_ABS
>>>>> or LD_IND instruction with oob index such that I cause an implicit return >>>>> 0
>>>>> while lock is held. At least I don't see this being caught, probably also 
>>>>> for
>>>>> such case a test_verifier snippet would be good.
>>>>>
>>>>> Wouldn't we also need to mark queued spinlock functions as notrace such 
>>>>> that
>>>>> e.g. from kprobe one cannot attach to these causing a deadlock?
>>>>
>>>> I think there may be another problem: haven't verified, but it might be 
>>>> possible
>>>> at least from reading the code that I have two programs which share a 
>>>> common
>>>> array/hash with spin_lock in BTF provided. Program A is properly using 
>>>> spin_lock
>>>> as in one of your examples. Program B is using map in map with inner map 
>>>> being
>>>> that same map using spin_lock. When we return that fake inner_map_meta as
>>>> reg->map_ptr then we can bypass any read/write restrictions into spin_lock 
>>>> area
>>>> which is normally prevented by verifier. Meaning, map in map needs to be 
>>>> made
>>>> aware of spin_lock case as well.
>>>
>>> 2nd great catch. thanks!
>>> Indeed inner_map_meta doesn't preserve all the fields from struct bpf_map.
>>> It seems long term we'll be able to support spin_lock in inner map too,
>>> but for now I'll disable it.
>>
>> There's also one more potential issue in pruning I _think_. In regsafe() we
>> make the basic assumption that for PTR_TO_MAP_VALUE id has been zeroed which
>> is true up to here, and as such we prune state not taking id into account.
>> The only other case we have is PTR_TO_SOCKET{,_OR_NULL} which only allows
>> for exact matches. Potentially there could be a case where you have two map
>> pointers from different branches but with same basic map properties read/
>> writing map data, and in first run for PTR_TO_MAP_VALUE w/o spin_lock path
>> it was considered safe such that we would get a match in regsafe() as well
>> and could potentially prune the access? I guess definitely worth adding such
>> test case to test_verifier to make sure.
> 
> Hmm. Something to think about for sure.
> I belive if (old->active_spin_lock != cur->active_spin_lock) check
> protects from all cases where spin_lock-ed paths are mixed with non-spin.
> Like going through non-locked ld/st of map_value in the first pass through
> the prog and then jumping half way into that pass after taking spin_lock
> to trigger regsafe().
> I cannot quite see how to construct such test without triggering
> old->active_spin_lock != cur->active_spin_lock
> before reaching regsafe().
> But I will keep thinking.
> If you have more concrete description for the test please suggest.

Was thinking something like this, in very rough pseudo code:

Prog A (normal spin lock use of mapA):

  val = bpf_map_lookup_elem(&mapA, &key);
  if (val) {
    bpf_spin_lock(&val->lock);
    [...]
    bpf_spin_unlock(&val->lock);
  }

Prog B:

  if (non_const_condition_A) {
    map_ptr = &mapB;   // mapB is normal array map with same
                       // properties as mapA, but no BTF and
                       // thus no spinlock use.
  } else {
    map_ptr = &mapA;
  }
  val = bpf_map_lookup_elem(&map_ptr, &key);
  map_ptr = 0; // clear map reg to match for
               // both verification paths
  if (val) {
    // turning val into PTR_TO_MAP_VALUE
    if (non_const_condition_B) {
      // write into memory area of spin_lock;
      // first path with mapB is considered
      // safe (since map with no spin_lock so
      // write into this area allowed);
      // now when verifier is checking the
      // non_const_condition_A's else path
      // with mapA, then non_const_condition_B
      // has pruning checkpoint and is going
      // to compare reg with PTR_TO_MAP_VALUE;
      // since id is not considered I /think/
      // verifier would find it (wrongly) safe
      // as well.
    }
  }

Wdyt?

Thanks,
Daniel

Reply via email to