diff mbox series

[v2] ARM: dma-mapping: fix potential uninitialized return

Message ID 20181128185910.5778-1-nathanj439@gmail.com (mailing list archive)
State New, archived
Headers show
Series [v2] ARM: dma-mapping: fix potential uninitialized return | expand

Commit Message

Nathan Jones Nov. 28, 2018, 6:59 p.m. UTC
If neither of the if() statements fire then the return value is
uninitialized. In the worst case it returns 0 which means the caller
will think the function succeeded.

Fixes: 1655cf8829d8 ("ARM: dma-mapping: Remove traces of NOMMU code")
Signed-off-by: Nathan Jones <nathanj439@gmail.com>
---
 arch/arm/mm/dma-mapping.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Comments

Vladimir Murzin Nov. 29, 2018, 9:50 a.m. UTC | #1
On 11/28/18 6:59 PM, Nathan Jones wrote:
> If neither of the if() statements fire then the return value is
> uninitialized. In the worst case it returns 0 which means the caller
> will think the function succeeded.

"ret" is updated indirectly via:

        if (dma_mmap_from_dev_coherent(dev, vma, cpu_addr, size, &ret))
                return ret;

I assume that it was found with static analyzer or like this, in such case
please, provide output produced by the tool.

> 
> Fixes: 1655cf8829d8 ("ARM: dma-mapping: Remove traces of NOMMU code")

I'll leave it up to Russell.

Cheers
Vladimir

> Signed-off-by: Nathan Jones <nathanj439@gmail.com>
> ---
>  arch/arm/mm/dma-mapping.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
> index 661fe48ab78d..78de138aa66d 100644
> --- a/arch/arm/mm/dma-mapping.c
> +++ b/arch/arm/mm/dma-mapping.c
> @@ -829,7 +829,7 @@ static int __arm_dma_mmap(struct device *dev, struct vm_area_struct *vma,
>  		 void *cpu_addr, dma_addr_t dma_addr, size_t size,
>  		 unsigned long attrs)
>  {
> -	int ret;
> +	int ret = -ENXIO;
>  	unsigned long nr_vma_pages = vma_pages(vma);
>  	unsigned long nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
>  	unsigned long pfn = dma_to_pfn(dev, dma_addr);
>
Vladimir Murzin Nov. 29, 2018, 10:11 a.m. UTC | #2
On 11/29/18 9:50 AM, Vladimir Murzin wrote:
> On 11/28/18 6:59 PM, Nathan Jones wrote:
>> If neither of the if() statements fire then the return value is
>> uninitialized. In the worst case it returns 0 which means the caller
>> will think the function succeeded.
> 
> "ret" is updated indirectly via:
> 
>         if (dma_mmap_from_dev_coherent(dev, vma, cpu_addr, size, &ret))
>                 return ret;

Ok. I've had a look how dma_mmap_from_dev_coherent() is implemented and it
looks like "ret" is not updated if dev doesn't have reserved memory.
It looks like arm64 also might be affected by this as well.

So, would it be better to update dma_mmap_from_dev_coherent() with

diff --git a/kernel/dma/coherent.c b/kernel/dma/coherent.c
index 597d408..0273ec4 100644
--- a/kernel/dma/coherent.c
+++ b/kernel/dma/coherent.c
@@ -282,6 +282,8 @@ int dma_release_from_global_coherent(int order, void *vaddr)
 static int __dma_mmap_from_coherent(struct dma_coherent_mem *mem,
                struct vm_area_struct *vma, void *vaddr, size_t size, int *ret)
 {
+       *ret = -ENXIO;
+
        if (mem && vaddr >= mem->virt_base && vaddr + size <=
                   (mem->virt_base + (mem->size << PAGE_SHIFT))) {
                unsigned long off = vma->vm_pgoff;
@@ -289,7 +291,6 @@ static int __dma_mmap_from_coherent(struct dma_coherent_mem *mem,
                int user_count = vma_pages(vma);
                int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
 
-               *ret = -ENXIO;
                if (off < count && user_count <= count - off) {
                        unsigned long pfn = mem->pfn_base + start + off;
                        *ret = remap_pfn_range(vma, vma->vm_start, pfn,


Cheers
Vladimir

> 
> I assume that it was found with static analyzer or like this, in such case
> please, provide output produced by the tool.
> 
>>
>> Fixes: 1655cf8829d8 ("ARM: dma-mapping: Remove traces of NOMMU code")
> 
> I'll leave it up to Russell.
> 
> Cheers
> Vladimir
> 
>> Signed-off-by: Nathan Jones <nathanj439@gmail.com>
>> ---
>>  arch/arm/mm/dma-mapping.c | 2 +-
>>  1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
>> index 661fe48ab78d..78de138aa66d 100644
>> --- a/arch/arm/mm/dma-mapping.c
>> +++ b/arch/arm/mm/dma-mapping.c
>> @@ -829,7 +829,7 @@ static int __arm_dma_mmap(struct device *dev, struct vm_area_struct *vma,
>>  		 void *cpu_addr, dma_addr_t dma_addr, size_t size,
>>  		 unsigned long attrs)
>>  {
>> -	int ret;
>> +	int ret = -ENXIO;
>>  	unsigned long nr_vma_pages = vma_pages(vma);
>>  	unsigned long nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
>>  	unsigned long pfn = dma_to_pfn(dev, dma_addr);
>>
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
Russell King (Oracle) Nov. 29, 2018, 10:14 a.m. UTC | #3
On Thu, Nov 29, 2018 at 09:50:59AM +0000, Vladimir Murzin wrote:
> On 11/28/18 6:59 PM, Nathan Jones wrote:
> > If neither of the if() statements fire then the return value is
> > uninitialized. In the worst case it returns 0 which means the caller
> > will think the function succeeded.
> 
> "ret" is updated indirectly via:
> 
>         if (dma_mmap_from_dev_coherent(dev, vma, cpu_addr, size, &ret))
>                 return ret;

I'm afraid you are wrong - "ret" really is used uninitialised.

dma_mmap_from_dev_coherent() calls __dma_mmap_from_coherent(), and
there is a top-function-level if statement:

static int __dma_mmap_from_coherent(struct dma_coherent_mem *mem,
                struct vm_area_struct *vma, void *vaddr, size_t size, int *ret)
{
        if (mem && vaddr >= mem->virt_base && vaddr + size <=
                   (mem->virt_base + (mem->size << PAGE_SHIFT))) {
		... path that sets *ret ...
		return 1;
        }
        return 0;
}

If that if statement is true, then yes, ret will be initialised, and
dma_mmap_from_dev_coherent() will return 1.  We will take that
"return ret" statement in __arm_dma_mmap() and everything is fine.

If that if statement is false, then the function returns zero without
setting "ret" to anything, and we continue processing __arm_dma_mmap().
The next statements are:

        if (off < nr_pages && nr_vma_pages <= (nr_pages - off)) {
                ret = remap_pfn_range(vma, vma->vm_start,
                                      pfn + off,
                                      vma->vm_end - vma->vm_start,
                                      vma->vm_page_prot);
        }

        return ret;

So, if _this_ if statement is also false, then we get to "return ret"
but ret has not been initialised by anything.

Therefore, 'ret' does need initialisation, since as of your commit,
there is a path through the code where it is used uninitialised.

Hence, Nathan's patch is correct.
Russell King (Oracle) Nov. 29, 2018, 10:22 a.m. UTC | #4
On Thu, Nov 29, 2018 at 10:11:45AM +0000, Vladimir Murzin wrote:
> On 11/29/18 9:50 AM, Vladimir Murzin wrote:
> > On 11/28/18 6:59 PM, Nathan Jones wrote:
> >> If neither of the if() statements fire then the return value is
> >> uninitialized. In the worst case it returns 0 which means the caller
> >> will think the function succeeded.
> > 
> > "ret" is updated indirectly via:
> > 
> >         if (dma_mmap_from_dev_coherent(dev, vma, cpu_addr, size, &ret))
> >                 return ret;
> 
> Ok. I've had a look how dma_mmap_from_dev_coherent() is implemented and it
> looks like "ret" is not updated if dev doesn't have reserved memory.
> It looks like arm64 also might be affected by this as well.
> 
> So, would it be better to update dma_mmap_from_dev_coherent() with

I don't think so - if we were to remove the call to
dma_mmap_from_dev_coherent(), it reintroduces the bug.

Quite why we have dma_mmap_from_dev_coherent() returning a 0/1 and
error code via pointer I'm really not sure.

	ret = dma_mmap_from_dev_coherent(...);
	if (ret)
		return ret > 0 ? 0 : ret;

and have dma_mmap_from_dev_coherent() return -ve for errnos, 1 if
mapped via the coherent pool mechanism, or 0 otherwise.

The down-side is that 'ret' would be zero for the follow-on code,
which would need explicit initialisation - but at least it's then
obvious what is going on.
Nathan Jones Nov. 29, 2018, 2:58 p.m. UTC | #5
On Thu, Nov 29, 2018 at 4:51 AM Vladimir Murzin <vladimir.murzin@arm.com> wrote:
>
> On 11/28/18 6:59 PM, Nathan Jones wrote:
> > If neither of the if() statements fire then the return value is
> > uninitialized. In the worst case it returns 0 which means the caller
> > will think the function succeeded.
>
> "ret" is updated indirectly via:
>
>         if (dma_mmap_from_dev_coherent(dev, vma, cpu_addr, size, &ret))
>                 return ret;
>
> I assume that it was found with static analyzer or like this, in such case
> please, provide output produced by the tool.

No, I had some bad code which was passing a wrong length and receiving the
strange error code.

>
> >
> > Fixes: 1655cf8829d8 ("ARM: dma-mapping: Remove traces of NOMMU code")
>
> I'll leave it up to Russell.
>
> Cheers
> Vladimir
>
> > Signed-off-by: Nathan Jones <nathanj439@gmail.com>
> > ---
> >  arch/arm/mm/dma-mapping.c | 2 +-
> >  1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
> > index 661fe48ab78d..78de138aa66d 100644
> > --- a/arch/arm/mm/dma-mapping.c
> > +++ b/arch/arm/mm/dma-mapping.c
> > @@ -829,7 +829,7 @@ static int __arm_dma_mmap(struct device *dev, struct vm_area_struct *vma,
> >                void *cpu_addr, dma_addr_t dma_addr, size_t size,
> >                unsigned long attrs)
> >  {
> > -     int ret;
> > +     int ret = -ENXIO;
> >       unsigned long nr_vma_pages = vma_pages(vma);
> >       unsigned long nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
> >       unsigned long pfn = dma_to_pfn(dev, dma_addr);
> >
>
Robin Murphy Nov. 29, 2018, 3:17 p.m. UTC | #6
On 28/11/2018 18:59, Nathan Jones wrote:
> If neither of the if() statements fire then the return value is
> uninitialized. In the worst case it returns 0 which means the caller
> will think the function succeeded.

Looking at the referenced commit, the original initialisation of ret 
must have actually belonged to the MMU code anyway, since all the NOMMU 
path did was overwrite it unconditionally. Thus the patch itself and the 
fixes tag both look like the right thing to do.

Reviewed-by: Robin Murphy <robin.murphy@arm.com>

> Fixes: 1655cf8829d8 ("ARM: dma-mapping: Remove traces of NOMMU code")
> Signed-off-by: Nathan Jones <nathanj439@gmail.com>
> ---
>   arch/arm/mm/dma-mapping.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
> index 661fe48ab78d..78de138aa66d 100644
> --- a/arch/arm/mm/dma-mapping.c
> +++ b/arch/arm/mm/dma-mapping.c
> @@ -829,7 +829,7 @@ static int __arm_dma_mmap(struct device *dev, struct vm_area_struct *vma,
>   		 void *cpu_addr, dma_addr_t dma_addr, size_t size,
>   		 unsigned long attrs)
>   {
> -	int ret;
> +	int ret = -ENXIO;
>   	unsigned long nr_vma_pages = vma_pages(vma);
>   	unsigned long nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
>   	unsigned long pfn = dma_to_pfn(dev, dma_addr);
>
Russell King (Oracle) Nov. 29, 2018, 3:24 p.m. UTC | #7
On Thu, Nov 29, 2018 at 09:58:46AM -0500, Nathan Jones wrote:
> No, I had some bad code which was passing a wrong length and receiving the
> strange error code.

While adding some networking documentation, I tripped over the comments
at the bottom of Documentation/networking/netdev-FAQ.rst which seem
very good at guiding what should be in the commit message, specifically:

  If your change is a bug fix, make sure your commit log indicates the
  end-user visible symptom, the underlying reason as to why it happens,
  and then if necessary, explain why the fix proposed is the best way to
  get things done.

This is actually rather important, as we may need to look back at a
commit, and end up wondering what it was about.  A case in point is the
patch that added the /proc/<PID>/syscall interface:

    /proc/PID/syscall

    This adds /proc/PID/syscall and /proc/PID/task/TID/syscall magic files.
    These use task_current_syscall() to show the task's current system call
    number and argument registers, stack pointer and PC.  For a task blocked
    but not in a syscall, the file shows "-1" in place of the syscall number,
    followed by only the SP and PC.  For a task that's not blocked, it shows
    "running".

This doesn't say what the purpose of this new user interface is, why it
is needed, nor is there documentation describing its behaviour (such as
what happens if the thread is being traced.)  The covering message for
the series omitted to talk about this new interface.  So we're now at
the position where we have a bug reported against this interface, and
no one knows what the right behaviour is really supposed to be.

The commit message describes mostly what we can gather from reading the
patch, and some of the behaviour but is entirely insufficient - we're
left scratching our heads as to what the behaviour should be for
restarted syscalls.

So, augmenting your commit message with something like:

"While trying to use the dma_mmap_*() interface, it was noticed that
 this interface returns strange values when passed an incorrect
 length."

would be nice.

Thanks.
Christoph Hellwig Nov. 29, 2018, 4:26 p.m. UTC | #8
On Thu, Nov 29, 2018 at 10:22:59AM +0000, Russell King - ARM Linux wrote:
> I don't think so - if we were to remove the call to
> dma_mmap_from_dev_coherent(), it reintroduces the bug.
> 
> Quite why we have dma_mmap_from_dev_coherent() returning a 0/1 and
> error code via pointer I'm really not sure.
> 
> 	ret = dma_mmap_from_dev_coherent(...);
> 	if (ret)
> 		return ret > 0 ? 0 : ret;
> 
> and have dma_mmap_from_dev_coherent() return -ve for errnos, 1 if
> mapped via the coherent pool mechanism, or 0 otherwise.
> 
> The down-side is that 'ret' would be zero for the follow-on code,
> which would need explicit initialisation - but at least it's then
> obvious what is going on.

The above would be better than the current calling conventions, which
are horrible.  But the magic positive error code also tends to lead
to subtle errors sometimes.  My preference for this pattern is something
like:

	bool mapped;

	ret = dma_mmap_from_dev_coherent(..., &mapped);
	if (ret || mapped)
		return ret;
Vladimir Murzin Nov. 30, 2018, 9 a.m. UTC | #9
On 11/29/18 2:58 PM, Nathan Jones wrote:
> On Thu, Nov 29, 2018 at 4:51 AM Vladimir Murzin <vladimir.murzin@arm.com> wrote:
>>
>> On 11/28/18 6:59 PM, Nathan Jones wrote:
>>> If neither of the if() statements fire then the return value is
>>> uninitialized. In the worst case it returns 0 which means the caller
>>> will think the function succeeded.
>>
>> "ret" is updated indirectly via:
>>
>>         if (dma_mmap_from_dev_coherent(dev, vma, cpu_addr, size, &ret))
>>                 return ret;
>>
>> I assume that it was found with static analyzer or like this, in such case
>> please, provide output produced by the tool.
> 
> No, I had some bad code which was passing a wrong length and receiving the
> strange error code.

Sorry for dragging you into that. If you put statement above into commit log,
feel free to add

Acked-by: Vladimir Murzin <vladimir.murzin@arm.com>

Thanks
Vladimir

> 
>>
>>>
>>> Fixes: 1655cf8829d8 ("ARM: dma-mapping: Remove traces of NOMMU code")
>>
>> I'll leave it up to Russell.
>>
>> Cheers
>> Vladimir
>>
>>> Signed-off-by: Nathan Jones <nathanj439@gmail.com>
>>> ---
>>>  arch/arm/mm/dma-mapping.c | 2 +-
>>>  1 file changed, 1 insertion(+), 1 deletion(-)
>>>
>>> diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
>>> index 661fe48ab78d..78de138aa66d 100644
>>> --- a/arch/arm/mm/dma-mapping.c
>>> +++ b/arch/arm/mm/dma-mapping.c
>>> @@ -829,7 +829,7 @@ static int __arm_dma_mmap(struct device *dev, struct vm_area_struct *vma,
>>>                void *cpu_addr, dma_addr_t dma_addr, size_t size,
>>>                unsigned long attrs)
>>>  {
>>> -     int ret;
>>> +     int ret = -ENXIO;
>>>       unsigned long nr_vma_pages = vma_pages(vma);
>>>       unsigned long nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
>>>       unsigned long pfn = dma_to_pfn(dev, dma_addr);
>>>
>>
>
diff mbox series

Patch

diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
index 661fe48ab78d..78de138aa66d 100644
--- a/arch/arm/mm/dma-mapping.c
+++ b/arch/arm/mm/dma-mapping.c
@@ -829,7 +829,7 @@  static int __arm_dma_mmap(struct device *dev, struct vm_area_struct *vma,
 		 void *cpu_addr, dma_addr_t dma_addr, size_t size,
 		 unsigned long attrs)
 {
-	int ret;
+	int ret = -ENXIO;
 	unsigned long nr_vma_pages = vma_pages(vma);
 	unsigned long nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
 	unsigned long pfn = dma_to_pfn(dev, dma_addr);