diff --git a/cmd/malloc.c b/cmd/malloc.c
index 7de421c0ae8..94d0f9427d6 100644
--- a/cmd/malloc.c
+++ b/cmd/malloc.c
@@ -24,6 +24,7 @@ static int do_malloc_info(struct cmd_tbl *cmdtp, int flag, int argc,
 
 	printf("total bytes   = %s\n", format_size(buf, info.total_bytes));
 	printf("in use bytes  = %s\n", format_size(buf, info.in_use_bytes));
+	printf("largest free  = %s\n", format_size(buf, malloc_largest_free()));
 	printf("malloc count  = %lu\n", info.malloc_count);
 	printf("free count    = %lu\n", info.free_count);
 	printf("realloc count = %lu\n", info.realloc_count);
diff --git a/common/dlmalloc.c b/common/dlmalloc.c
index 895aa215228..6d0bdc5b64e 100644
--- a/common/dlmalloc.c
+++ b/common/dlmalloc.c
@@ -7900,6 +7900,35 @@ void malloc_leak_check_free(struct malloc_leak_snap *snap)
 	snap->count = 0;
 }
 
+size_t malloc_largest_free(void)
+{
+	size_t largest = 0;
+	mstate m = gm;
+	msegmentptr s;
+
+	if (m->topsize > largest)
+		largest = m->topsize;
+	if (m->dvsize > largest)
+		largest = m->dvsize;
+
+	for (s = &m->seg; s != 0; s = s->next) {
+		mchunkptr q = align_as_chunk(s->base);
+
+		while (segment_holds(s, q) && q != m->top &&
+		       q->head != FENCEPOST_HEAD) {
+			if (!is_inuse(q)) {
+				size_t sz = chunksize(q);
+
+				if (sz > largest)
+					largest = sz;
+			}
+			q = next_chunk(q);
+		}
+	}
+
+	return largest > CHUNK_OVERHEAD ? largest - CHUNK_OVERHEAD : 0;
+}
+
 int initf_malloc(void)
 {
 #if CONFIG_IS_ENABLED(SYS_MALLOC_F)
diff --git a/include/malloc.h b/include/malloc.h
index 787ec999f5e..599440246b4 100644
--- a/include/malloc.h
+++ b/include/malloc.h
@@ -892,6 +892,19 @@ static inline size_t malloc_mcheck_count(void) { return 0; }
  */
 size_t malloc_chunk_size(void *ptr);
 
+/**
+ * malloc_largest_free() - Return the size of the largest free chunk
+ *
+ * Walks the heap and returns the largest contiguous free region that
+ * malloc() could currently hand out, minus the per-chunk dlmalloc
+ * overhead. Any mcheck header/canary overhead still comes off the top
+ * of the caller's request. Useful for tests that want to allocate "as
+ * much as possible" without tripping over fragmentation.
+ *
+ * Return: approximate largest request bytes malloc() would satisfy
+ */
+size_t malloc_largest_free(void);
+
 /**
  * malloc_mcheck_hdr_size() - Return the size of the mcheck header
  *
diff --git a/test/cmd/malloc.c b/test/cmd/malloc.c
index 812f7530b79..e84033b3ce9 100644
--- a/test/cmd/malloc.c
+++ b/test/cmd/malloc.c
@@ -25,6 +25,7 @@ static int cmd_test_malloc_info(struct unit_test_state *uts)
 	ut_assertok(run_command("malloc info", 0));
 	ut_assert_nextlinen("total bytes   = ");
 	ut_assert_nextlinen("in use bytes  = ");
+	ut_assert_nextlinen("largest free  = ");
 	ut_assert_nextlinen("malloc count  = ");
 	ut_assert_nextlinen("free count    = ");
 	ut_assert_nextlinen("realloc count = ");
