Skip to content

cli: fix memory leak in volume status detail (#4292)#4675

Open
ThalesBarretto wants to merge 1 commit into
gluster:develfrom
ThalesBarretto:issue-4292-memory-leak
Open

cli: fix memory leak in volume status detail (#4292)#4675
ThalesBarretto wants to merge 1 commit into
gluster:develfrom
ThalesBarretto:issue-4292-memory-leak

Conversation

@ThalesBarretto

@ThalesBarretto ThalesBarretto commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Ownership model

cli_volume_status_t has seven pointer fields with two different ownership models:

Owned (heap-allocated, caller must free):

Borrowed (point into dict internals, released by dict_unref at out:):

  • fs_name, mount_options, device, inode_size — all from dict_get_str

The bug

status is declared once outside the loop with = {0}. Each iteration overwrites the owned pointers with new heap allocations. Before this fix, only pid_str was freed inside the loop body and only brick at the out: label. The other two owned fields (free, total) were overwritten each iteration — previous values lost, two strings leaked per brick. The reporter's ASan trace confirms 144 bytes in 4 allocations (2 calls × 2 bricks).

What the fix does

  1. free and total freed in the loop — matching what was already done for pid_str.

  2. pid_str, free, total freed at out: — covers the two goto out error paths mid-iteration (gf_asprintf failure and cli_get_detail_status failure) where the current iteration's allocations haven't been freed yet.

  3. Explicit NULLing after each in-loop GF_FREE — on the normal path the loop finishes, execution falls through cont: to out:, which calls GF_FREE on the same pointers. Without NULLing that's a double-free; with it, GF_FREE(NULL) is a no-op.

Fixes: #4292

@ThalesBarretto ThalesBarretto force-pushed the issue-4292-memory-leak branch from 60d6193 to 3690dca Compare April 28, 2026 21:02
@ThalesBarretto ThalesBarretto changed the title cli: fix memory leak in volume status detail cli: fix memory leak in volume status detail (#4292) Apr 28, 2026
@ThalesBarretto ThalesBarretto force-pushed the issue-4292-memory-leak branch 2 times, most recently from 37abfec to b4b9d5a Compare April 28, 2026 21:27
cli_volume_status_t has seven pointer fields, but they have two
different ownership models:

  Owned (heap-allocated, caller must free):
    - brick        — GF_MALLOC before the loop
    - pid_str      — gf_asprintf each iteration
    - free, total  — gf_uint64_2human_readable inside cli_get_detail_status

  Borrowed (point into dict internals, released by dict_unref at out:):
    - fs_name, mount_options, device, inode_size — dict_get_str

The status variable is declared once outside the loop with = {0}.
Each iteration overwrites the owned pointers with new allocations.
Before this fix, only pid_str was freed inside the loop body and
only brick was freed at the out: label.  The other two owned
pointers (free, total) were simply overwritten each iteration — the
previous values were lost, leaking two strings per brick.

The fix adds three things:

1. GF_FREE for free and total inside the loop, matching what was
   already done for pid_str.

2. GF_FREE for pid_str, free, and total at the out: label, covering
   the two goto-out error paths mid-iteration (gf_asprintf failure
   at line 7592 and cli_get_detail_status failure at line 7597)
   where the current iteration's allocations have not been freed yet.

3. Explicit NULLing after each in-loop GF_FREE.  On the normal path
   the loop finishes, execution falls through cont: to out:, which
   calls GF_FREE on the same pointers again.  Without NULLing, that
   would be a double-free.  With it, GF_FREE(NULL) is a no-op.

Signed-off-by: Thales Antunes de Oliveira Barretto <thales.barretto.git@gmail.com>
@ThalesBarretto ThalesBarretto force-pushed the issue-4292-memory-leak branch from b4b9d5a to 18e96ed Compare April 28, 2026 22:21
@ThalesBarretto ThalesBarretto marked this pull request as ready for review April 28, 2026 22:22
@ThalesBarretto

Copy link
Copy Markdown
Contributor Author

@sanjurakonde would you mind taking a look here, i think this one is pretty straightforward

@gglluukk

gglluukk commented May 26, 2026

Copy link
Copy Markdown

There is no need for:

  1. NULLing: status.pid_str = NULL;
  2. FREEinig: GF_FREE(status.free); and GF_FREE(status.total); as they aren't any MALLOCated
  3. The only patch according to your AI's logic is needed at line 7597 before goto out;:
           if ((cmd & GF_CLI_STATUS_DETAIL)) {
            ret = cli_get_detail_status(dict, i, &status);
            if (ret) {
                GF_FREE(status.pid_str); // <--
                goto out;
            }
  1. The CLI is merely a command-line utility and is not part of the core storage logic, so bugs in it do not necessarily affect the storage engine itself.

Note that generating AI slop wastes electricity and the time of real human developers.

@ThalesBarretto

Copy link
Copy Markdown
Contributor Author

There is no need for:

  1. NULLing: status.pid_str = NULL;
  2. FREEinig: GF_FREE(status.free); and GF_FREE(status.total); as they aren't any MALLOCated
  3. The only patch according to your AI's logic is needed at line 7597 before goto out;:
           if ((cmd & GF_CLI_STATUS_DETAIL)) {
            ret = cli_get_detail_status(dict, i, &status);
            if (ret) {
                GF_FREE(status.pid_str); // <--
                goto out;
            }
  1. The CLI is merely a command-line utility and is not part of the core storage logic, so bugs in it do not necessarily affect the storage engine itself.

Note that generating AI slop wastes electricity and the time of real human developers.

Thanks for your interest in glusterfs, your contributions are welcome, feel free to propose a patch

@ThalesBarretto

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Let me address each point with code references.

On point 2status.free and status.total are heap-allocated. The chain is:

They must be freed by the caller, unlike the four dict_get_str fields (fs_name, mount_options, device, inode_size) which are borrowed from the dict and released by dict_unref.

On point 3 — freeing pid_str only before the goto out at line 7597 covers one error path, but misses the main bug: the per-iteration leak of free and total on the normal (non-error) path. Each successful iteration overwrites those pointers with new gf_asprintf allocations without freeing the previous ones — the old references are lost and the memory becomes unreachable. That is the leaked memory the ASan trace in #4292 reports.

On point 1 — the NULLing fixes a second, separate issue. After GF_FREE(status.pid_str) in the loop body, the pointer still holds the address of freed memory — it dangles. On the normal path, execution falls straight through from the loop end at line 7610 to out: at line 7618 (no return or goto between them). The out: label calls GF_FREE on that same dangling pointer — a double-free. NULLing after each in-loop GF_FREE makes the second free a no-op. Without it, the in-loop frees would fix the leak but introduce a double-free: one memory safety bug traded for another.

On point 4 — agreed, the CLI is short-lived and this is not a critical storage bug. It is a correctness fix for a reported issue (#4292) with an ASan trace.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MemoryLeak in Glusterfs

2 participants