Re: [PATCH v2 17/18] perf header: Fix various error path memory leaks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sun, Oct 8, 2023 at 11:57 PM Namhyung Kim <namhyung@xxxxxxxxxx> wrote:
>
> On Thu, Oct 5, 2023 at 4:09 PM Ian Rogers <irogers@xxxxxxxxxx> wrote:
> >
> > Memory leaks were detected by clang-tidy.
> >
> > Signed-off-by: Ian Rogers <irogers@xxxxxxxxxx>
> > ---
> >  tools/perf/util/header.c | 63 ++++++++++++++++++++++++----------------
> >  1 file changed, 38 insertions(+), 25 deletions(-)
> >
> > diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
> > index d812e1e371a7..41b78e40b22b 100644
> > --- a/tools/perf/util/header.c
> > +++ b/tools/perf/util/header.c
> > @@ -2598,8 +2598,10 @@ static int process_cpu_topology(struct feat_fd *ff, void *data __maybe_unused)
> >                         goto error;
> >
> >                 /* include a NULL character at the end */
> > -               if (strbuf_add(&sb, str, strlen(str) + 1) < 0)
> > +               if (strbuf_add(&sb, str, strlen(str) + 1) < 0) {
> > +                       free(str);
> >                         goto error;
> > +               }
> >                 size += string_size(str);
> >                 free(str);
> >         }
> > @@ -2617,8 +2619,10 @@ static int process_cpu_topology(struct feat_fd *ff, void *data __maybe_unused)
> >                         goto error;
> >
> >                 /* include a NULL character at the end */
> > -               if (strbuf_add(&sb, str, strlen(str) + 1) < 0)
> > +               if (strbuf_add(&sb, str, strlen(str) + 1) < 0) {
> > +                       free(str);
> >                         goto error;
> > +               }
> >                 size += string_size(str);
> >                 free(str);
> >         }
> > @@ -2681,8 +2685,10 @@ static int process_cpu_topology(struct feat_fd *ff, void *data __maybe_unused)
> >                         goto error;
> >
> >                 /* include a NULL character at the end */
> > -               if (strbuf_add(&sb, str, strlen(str) + 1) < 0)
> > +               if (strbuf_add(&sb, str, strlen(str) + 1) < 0) {
> > +                       free(str);
> >                         goto error;
> > +               }
> >                 size += string_size(str);
> >                 free(str);
> >         }
>
> For these cases, it'd be simpler to free it in the error path.

It was a slightly larger change to do it that way, but I'll alter it.
The issue is not getting a double free on str, which is most easily
handled by switching frees to zfrees.


>
>
> > @@ -2736,10 +2742,9 @@ static int process_numa_topology(struct feat_fd *ff, void *data __maybe_unused)
> >                         goto error;
> >
> >                 n->map = perf_cpu_map__new(str);
> > +               free(str);
> >                 if (!n->map)
> >                         goto error;
> > -
> > -               free(str);
> >         }
> >         ff->ph->env.nr_numa_nodes = nr;
> >         ff->ph->env.numa_nodes = nodes;
> > @@ -2913,10 +2918,10 @@ static int process_cache(struct feat_fd *ff, void *data __maybe_unused)
> >                 return -1;
> >
> >         for (i = 0; i < cnt; i++) {
> > -               struct cpu_cache_level c;
> > +               struct cpu_cache_level *c = &caches[i];
> >
> >                 #define _R(v)                                           \
> > -                       if (do_read_u32(ff, &c.v))\
> > +                       if (do_read_u32(ff, &c->v))                     \
> >                                 goto out_free_caches;                   \
> >
> >                 _R(level)
> > @@ -2926,22 +2931,25 @@ static int process_cache(struct feat_fd *ff, void *data __maybe_unused)
> >                 #undef _R
> >
> >                 #define _R(v)                                   \
> > -                       c.v = do_read_string(ff);               \
> > -                       if (!c.v)                               \
> > -                               goto out_free_caches;
> > +                       c->v = do_read_string(ff);              \
> > +                       if (!c->v)                              \
> > +                               goto out_free_caches;           \
> >
> >                 _R(type)
> >                 _R(size)
> >                 _R(map)
> >                 #undef _R
> > -
> > -               caches[i] = c;
> >         }
> >
> >         ff->ph->env.caches = caches;
> >         ff->ph->env.caches_cnt = cnt;
> >         return 0;
> >  out_free_caches:
> > +       for (i = 0; i < cnt; i++) {
> > +               free(caches[i].type);
> > +               free(caches[i].size);
> > +               free(caches[i].map);
> > +       }
> >         free(caches);
> >         return -1;
> >  }
>
> Looks ok.
>
>
> > @@ -3585,18 +3593,16 @@ static int perf_header__adds_write(struct perf_header *header,
> >                                    struct feat_copier *fc)
> >  {
> >         int nr_sections;
> > -       struct feat_fd ff;
> > +       struct feat_fd ff = {
> > +               .fd  = fd,
> > +               .ph = header,
> > +       };
>
> I'm fine with this change.
>
>
> >         struct perf_file_section *feat_sec, *p;
> >         int sec_size;
> >         u64 sec_start;
> >         int feat;
> >         int err;
> >
> > -       ff = (struct feat_fd){
> > -               .fd  = fd,
> > -               .ph = header,
> > -       };
> > -
> >         nr_sections = bitmap_weight(header->adds_features, HEADER_FEAT_BITS);
> >         if (!nr_sections)
> >                 return 0;
> > @@ -3623,6 +3629,7 @@ static int perf_header__adds_write(struct perf_header *header,
> >         err = do_write(&ff, feat_sec, sec_size);
> >         if (err < 0)
> >                 pr_debug("failed to write feature section\n");
> > +       free(ff.buf);
>
> But it looks like false alarams.  Since the feat_fd has fd
> and no buf, it won't allocate the buffer in do_write() and
> just use __do_write_fd().  So no need to free the buf.

This code looks like it has had a half-done optimization applied to it
- why have >1 buffer? why are we making the code's life hard? In
__do_write_buf there is a test that can be true even when a buf is
provided:
https://git.kernel.org/pub/scm/linux/kernel/git/perf/perf-tools-next.git/tree/tools/perf/util/header.c?h=perf-tools-next#n125
```
if (ff->size < new_size) {
        addr = realloc(ff->buf, new_size);
        if (!addr)
                return -ENOMEM;
        ff->buf = addr;
```
In the case clang-tidy (I'll put the analysis below) has determined
that new_size may be greater than size (I believe the intent in the
code is they both evaluate to 0) and this causes the memory leak being
fixed here. I'll add a TODO comment in v3 but things like 'buf' are
opaque and not intention revealing in the code, which makes any kind
of interpretation hard.

I think again this is a good signal that there is worthwhile
simplification/cleanup in this code.

Thanks,
Ian

```
tools/perf/util/header.c:3628:6: warning: Potential leak of memory
pointed to by 'ff.buf' [clang-analyzer-unix.Malloc]
        err = do_write(&ff, feat_sec, sec_size);
            ^
tools/perf/util/header.c:3778:9: note: Calling 'perf_session__do_write_header'
        return perf_session__do_write_header(session, evlist, fd, true, fc);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:3675:2: note: Loop condition is false.
Execution continues on line 3685
        evlist__for_each_entry(session->evlist, evsel) {
        ^
tools/perf/util/evlist.h:276:2: note: expanded from macro
'evlist__for_each_entry'
        __evlist__for_each_entry(&(evlist)->core.entries, evsel)
        ^
tools/perf/util/evlist.h:268:9: note: expanded from macro
'__evlist__for_each_entry'
        list_for_each_entry(evsel, list, core.node)
        ^
tools/include/linux/list.h:458:2: note: expanded from macro
'list_for_each_entry'
        for (pos = list_first_entry(head, typeof(*pos), member);        \
        ^
tools/perf/util/header.c:3687:2: note: Loop condition is false.
Execution continues on line 3711
        evlist__for_each_entry(evlist, evsel) {
        ^
tools/perf/util/evlist.h:276:2: note: expanded from macro
'evlist__for_each_entry'
        __evlist__for_each_entry(&(evlist)->core.entries, evsel)
        ^
tools/perf/util/evlist.h:268:9: note: expanded from macro
'__evlist__for_each_entry'
        list_for_each_entry(evsel, list, core.node)
        ^
tools/include/linux/list.h:458:2: note: expanded from macro
'list_for_each_entry'
        for (pos = list_first_entry(head, typeof(*pos), member);        \
        ^
tools/perf/util/header.c:3711:6: note: Assuming field 'data_offset' is
not equal to 0
        if (!header->data_offset)
            ^~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:3711:2: note: Taking false branch
        if (!header->data_offset)
        ^
tools/perf/util/header.c:3715:6: note: 'at_exit' is true
        if (at_exit) {
            ^~~~~~~
tools/perf/util/header.c:3715:2: note: Taking true branch
        if (at_exit) {
        ^
tools/perf/util/header.c:3716:9: note: Calling 'perf_header__adds_write'
                err = perf_header__adds_write(header, evlist, fd, fc);
                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:3606:6: note: Assuming 'nr_sections' is not equal
 to 0
        if (!nr_sections)
            ^~~~~~~~~~~~
tools/perf/util/header.c:3606:2: note: Taking false branch
        if (!nr_sections)
        ^
tools/perf/util/header.c:3610:6: note: Assuming 'feat_sec' is not equal to
 NULL
        if (feat_sec == NULL)
            ^~~~~~~~~~~~~~~~
tools/perf/util/header.c:3610:2: note: Taking false branch
        if (feat_sec == NULL)
        ^
tools/perf/util/header.c:3618:2: note: Assuming 'feat' is < HEADER_FEAT_BITS
        for_each_set_bit(feat, header->adds_features, HEADER_FEAT_BITS) {
        ^
tools/include/linux/bitops.h:54:7: note: expanded from macro 'for_each_set_bit'
             (bit) < (size);                                    \
             ^~~~~~~~~~~~~~
tools/perf/util/header.c:3618:2: note: Loop condition is true.
Entering loop body
        for_each_set_bit(feat, header->adds_features, HEADER_FEAT_BITS) {
        ^
tools/include/linux/bitops.h:53:2: note: expanded from macro 'for_each_set_bit'
        for ((bit) = find_first_bit((addr), (size));            \
        ^
tools/perf/util/header.c:3619:3: note: Taking true branch
                if (do_write_feat(&ff, feat, &p, evlist, fc))
                ^
tools/perf/util/header.c:3618:2: note: Assuming 'feat' is >= HEADER_FEAT_BITS
        for_each_set_bit(feat, header->adds_features, HEADER_FEAT_BITS) {
tools/include/linux/bitops.h:54:7: note: expanded from macro 'for_each_set_bit'
             (bit) < (size);                                    \
             ^~~~~~~~~~~~~~
tools/perf/util/header.c:3618:2: note: Loop condition is false.
Execution continues on line 3623
        for_each_set_bit(feat, header->adds_features, HEADER_FEAT_BITS) {
        ^
tools/include/linux/bitops.h:53:2: note: expanded from macro 'for_each_set_bit'
        for ((bit) = find_first_bit((addr), (size));            \
        ^
tools/perf/util/header.c:3628:8: note: Calling 'do_write'
        err = do_write(&ff, feat_sec, sec_size);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:141:6: note: Assuming field 'buf' is non-null
        if (!ff->buf)
            ^~~~~~~~
tools/perf/util/header.c:141:2: note: Taking false branch
        if (!ff->buf)
        ^
tools/perf/util/header.c:143:9: note: Calling '__do_write_buf'
        return __do_write_buf(ff, buf, size);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:117:6: note: Assuming the condition is false
        if (size + ff->offset > max_size)
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:117:2: note: Taking false branch
        if (size + ff->offset > max_size)
        ^
tools/perf/util/header.c:120:9: note: Assuming the condition is true
        while (size > (new_size - ff->offset))
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:120:2: note: Loop condition is true.
Entering loop body
        while (size > (new_size - ff->offset))
        ^
tools/perf/util/header.c:120:9: note: Assuming the condition is false
        while (size > (new_size - ff->offset))
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:120:2: note: Loop condition is false.
Execution continues on line 122
        while (size > (new_size - ff->offset))
        ^
tools/perf/util/header.c:122:13: note: Assuming '_min1' is >= '_min2'
        new_size = min(max_size, new_size);
                   ^
tools/include/linux/kernel.h:53:2: note: expanded from macro 'min'
        _min1 < _min2 ? _min1 : _min2; })
        ^~~~~~~~~~~~~
tools/perf/util/header.c:122:13: note: '?' condition is false
        new_size = min(max_size, new_size);
                   ^
tools/include/linux/kernel.h:53:2: note: expanded from macro 'min'
        _min1 < _min2 ? _min1 : _min2; })
        ^
tools/perf/util/header.c:124:6: note: Assuming 'new_size' is > field 'size'
        if (ff->size < new_size) {
            ^~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:124:2: note: Taking true branch
        if (ff->size < new_size) {
        ^
tools/perf/util/header.c:125:10: note: Memory is allocated
                addr = realloc(ff->buf, new_size);
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:126:7: note: Assuming 'addr' is non-null
                if (!addr)
                    ^~~~~
tools/perf/util/header.c:126:3: note: Taking false branch
                if (!addr)
                ^
tools/perf/util/header.c:143:9: note: Returned allocated memory
        return __do_write_buf(ff, buf, size);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:3628:8: note: Returned allocated memory
        err = do_write(&ff, feat_sec, sec_size);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tools/perf/util/header.c:3628:6: note: Potential leak of memory
pointed to by 'ff.buf'
        err = do_write(&ff, feat_sec, sec_size);
```


> Thanks,
> Namhyung
>
>
> >         free(feat_sec);
> >         return err;
> >  }
> > @@ -3630,11 +3637,11 @@ static int perf_header__adds_write(struct perf_header *header,
> >  int perf_header__write_pipe(int fd)
> >  {
> >         struct perf_pipe_file_header f_header;
> > -       struct feat_fd ff;
> > +       struct feat_fd ff = {
> > +               .fd = fd,
> > +       };
> >         int err;
> >
> > -       ff = (struct feat_fd){ .fd = fd };
> > -
> >         f_header = (struct perf_pipe_file_header){
> >                 .magic     = PERF_MAGIC,
> >                 .size      = sizeof(f_header),
> > @@ -3645,7 +3652,7 @@ int perf_header__write_pipe(int fd)
> >                 pr_debug("failed to write perf pipe header\n");
> >                 return err;
> >         }
> > -
> > +       free(ff.buf);
> >         return 0;
> >  }
> >
> > @@ -3658,11 +3665,12 @@ static int perf_session__do_write_header(struct perf_session *session,
> >         struct perf_file_attr   f_attr;
> >         struct perf_header *header = &session->header;
> >         struct evsel *evsel;
> > -       struct feat_fd ff;
> > +       struct feat_fd ff = {
> > +               .fd = fd,
> > +       };
> >         u64 attr_offset;
> >         int err;
> >
> > -       ff = (struct feat_fd){ .fd = fd};
> >         lseek(fd, sizeof(f_header), SEEK_SET);
> >
> >         evlist__for_each_entry(session->evlist, evsel) {
> > @@ -3670,6 +3678,7 @@ static int perf_session__do_write_header(struct perf_session *session,
> >                 err = do_write(&ff, evsel->core.id, evsel->core.ids * sizeof(u64));
> >                 if (err < 0) {
> >                         pr_debug("failed to write perf header\n");
> > +                       free(ff.buf);
> >                         return err;
> >                 }
> >         }
> > @@ -3695,6 +3704,7 @@ static int perf_session__do_write_header(struct perf_session *session,
> >                 err = do_write(&ff, &f_attr, sizeof(f_attr));
> >                 if (err < 0) {
> >                         pr_debug("failed to write perf header attribute\n");
> > +                       free(ff.buf);
> >                         return err;
> >                 }
> >         }
> > @@ -3705,8 +3715,10 @@ static int perf_session__do_write_header(struct perf_session *session,
> >
> >         if (at_exit) {
> >                 err = perf_header__adds_write(header, evlist, fd, fc);
> > -               if (err < 0)
> > +               if (err < 0) {
> > +                       free(ff.buf);
> >                         return err;
> > +               }
> >         }
> >
> >         f_header = (struct perf_file_header){
> > @@ -3728,6 +3740,7 @@ static int perf_session__do_write_header(struct perf_session *session,
> >
> >         lseek(fd, 0, SEEK_SET);
> >         err = do_write(&ff, &f_header, sizeof(f_header));
> > +       free(ff.buf);
> >         if (err < 0) {
> >                 pr_debug("failed to write perf header\n");
> >                 return err;
> > --
> > 2.42.0.609.gbb76f46606-goog
> >





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux