[cfe-dev] Automatic variable initialization and copy_from_user()

Florian Hahn via cfe-dev cfe-dev at lists.llvm.org
Tue Feb 11 04:47:02 PST 2020



> On Feb 11, 2020, at 12:38, Dmitry Vyukov <dvyukov at google.com> wrote:
> 
> On Tue, Feb 11, 2020 at 1:31 PM Florian Hahn <florian_hahn at apple.com> wrote:
>> 
>> Hi,
>> 
>>> On Feb 11, 2020, at 11:13, Alexander Potapenko <glider at google.com> wrote:
>>> It would help to mark copy_from_user() as a function that initializes
>>> one of these arguments, but only if it does not fail. We also need to
>>> tell the compiler about the size of that argument, so that the
>>> function prototype will look along the lines of:
>>> 
>>> unsigned long copy_from_user (void *  to, const void __user *  from,
>>> unsigned long  n)
>>> __attribute__((param_sizeof(1, 3)) __attribute__((param_init_if_ret0(1)));
>>> 
>>> , where param_sizeof(1, 3) means that parameter #3 contains the size
>>> of parameter #1, and param_init_if_ret0(1) means that parameter #1 is
>>> initialized iff the function returns 0.
>>> 
>>> This looks quite clumsy, but maybe can be made more elegant and
>>> general enough to handle other cases.
>>> 
>>> Does the idea make sense to you? Maybe you've seen other cases in
>>> which a similar optimization can be applied?
>>> To people working on DSE right now: do you think it's possible to
>>> perform such an optimization assuming that we know about the behavior
>>> of copy_from_user()?
>> 
>> 
>> Yes, I think MemorySSA backed DSE should be able to handle this (it’s still WIP, but the first patch just landed). The key question is how to convey the fact that a function fully initializes `input` along some paths. The MemorySSA-backed DSE patch series will eliminate the first memset in the example below, if there’s no read access on the error path.
>> 
>> From the DSE perspective, it would be easiest if Clang could inject something like an intrinsic that marks `input` as initialized on the success path (e.g. inserts a call to the not-yet existing @llvm.initialized(%ptr, size)). IMO it seems appropriate to handle this at the Clang level, as it seems quite language/attribute-specific. As a stepping stone, it might be feasible to provide a builtin to the user to specify that an object is initialized along a certain path.
>> 
>> 
>> declare void @llvm.memset.p0i8.i64(i8* nocapture, i8, i64, i1) noun-wind
>> declare i1 @cond() readnone
>> declare void @use(i8*)
>> 
>> define void @test19(i32* noalias %P) {
>> entry:
>>  %obj = alloca [10 x i8]
>>  %ptr = bitcast [10 x i8]* %obj to i8*
>>  call void @llvm.memset.p0i8.i64(i8* %ptr, i8 0, i64 10, i1 false)
>>  %succ = call i1 @cond()
>>  br i1 %succ, label %copy, label %err
>> 
>> copy:
>>  call void @llvm.memset.p0i8.i64(i8* %ptr, i8 0, i64 10, i1 false)
>>  call void @use(i8* %ptr)
>>  br label %exit
>> 
>> err:
>>  ;call void @use(i8* %ptr)
>>  ret void
>> 
>> exit:
>>  ret void
>> }
> 
> One alternative for completeness. There is a version of copy_from_user
> that always initializes the whole range (if part is not copied from
> userspace, it's zeroed). Handling such a version is much easier,
> right. However, some versions don't do this (for performance?). Maybe
> we could make _all_ copy_from_user's initialize whole range always iff
> memory initialization is enabled. Then we could add a simpler
> attribute for clang that will convey that copy_from_user initializes
> the memory.

That would also work from the DSE side of things. I’m not sure if/how functions that are marked to initialize a pointer are handled, but adding that to DSE should be easy. One more thing to keep in mind: if we just rely on function attributes, that might lead to missed eliminations if copy_from_user gets inlined and we fail to determine that it initialize a parameter from its contents (e.g. due to it using inline-assembly)

Cheers,
Florian


More information about the cfe-dev mailing list