| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/26 12:35 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"MEMCG",
"INET",
"MPTCP"
],
"FocusSymbols": [
"sk_memcg_budget_sync",
"sk_memcg_budget_shrink",
"sk_memcg_budget_release",
"__sk_charge",
"tcp_rcvbuf_grow",
"tcp_set_rcvlowat",
"mptcp_set_rcvlowat",
"__mptcp_inherit_memcg"
],
"KMSANReasoning": "The patch series refactors memory cgroup (memcg) accounting for TCP and MPTCP sockets, shifting from per-allocation charges (__sk_mem_raise_allocated / __sk_mem_reduce_allocated) to upfront budget-based charging against sk_sndbuf, sk_rcvbuf, and sk_reserved_mem via sk_memcg_budget_sync/shrink/release.\n\nEvaluation against KMSAN vs KASAN:\n1. No uninitialized memory usage or branching on uninitialized data: The newly added struct sock members (sk_memcg_budget and sk_memcg_budget_lock) are allocated with __GFP_ZERO in sk_alloc() and explicitly initialized in sk_clone(). All local variables in the new helper functions (sk_memcg_budget_pages, sk_memcg_budget_sync, sk_memcg_budget_shrink, sk_memcg_budget_release) are explicitly initialized before use.\n2. No kernel-to-user data leaks or uninitialized padding exposure: sk_memcg_budget is strictly internal socket accounting state and is not copied to userspace via getsockopt, sock_diag, ioctl, netlink, or BPF.\n3. No altered bounds or buffer sizes for userspace copies: The changes alter memcg accounting metadata calculations without changing buffer indexing, payload copying, or packet data handling.\n4. Any potential issues introduced by this patch (such as spinlock concurrency bugs, memcg refcounting / accounting underflows, or socket lifecycle races) are covered by KASAN, LOCKDEP, and existing kernel assertions rather than KMSAN.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies core socket, TCP, and MPTCP memory accounting by introducing upfront socket budget charging to memcg (covering send buffer, receive buffer, and SO_RESERVE_MEM) rather than charging per-allocation. It introduces new locking (sk_memcg_budget_lock), synchronization helpers (sk_memcg_budget_sync, sk_memcg_budget_shrink, sk_memcg_budget_release), and modifies socket option and buffer growth handling across TCP and MPTCP. These changes in core networking and memory management execute concurrently across user threads, softirqs, and RCU callbacks, making them prime targets for fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/26 12:35 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 74c8ef3e38a9935e88db6f422e1c29693a16522c\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Sep 26 12:35:55 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst\nindex 86a2a0099178e..db31966657bde 100644\n--- a/Documentation/admin-guide/cgroup-v2.rst\n+++ b/Documentation/admin-guide/cgroup-v2.rst\n@@ -1309,7 +1309,11 @@ following types of memory usages are tracked.\n \n - Kernel data structures such as dentries and inodes.\n \n-- TCP socket buffers.\n+- TCP socket buffers. These are charged upfront for the socket's\n+ established memory budget (send buffer, receive buffer and\n+ SO_RESERVE_MEM reservation) when the budget is created or resized,\n+ and returned when the budget shrinks or the socket is closed; the\n+ charge is not tied to per-packet buffer usage.\n \n The above list may expand in the future for better coverage.\n \ndiff --git a/include/net/sock.h b/include/net/sock.h\nindex 60ea55dc18854..88fe6d93f7579 100644\n--- a/include/net/sock.h\n+++ b/include/net/sock.h\n@@ -458,6 +458,15 @@ struct sock {\n \t__cacheline_group_begin(sock_read_rxtx);\n \tint\t\t\tsk_err;\n \tstruct socket\t\t*sk_socket;\n+\t/* Pages of the socket's memory budget (sndbuf + rcvbuf +\n+\t * SO_RESERVE_MEM) currently charged to the memcg. Paired with\n+\t * every mem_cgroup_sk_charge()/mem_cgroup_sk_uncharge() done on\n+\t * behalf of this socket, so it can be refunded exactly once.\n+\t * Guarded by sk_memcg_budget_lock; the charge/uncharge run\n+\t * outside the lock (they may sleep/reclaim).\n+\t */\n+\tint\t\t\tsk_memcg_budget;\n+\tspinlock_t\t\tsk_memcg_budget_lock;\n #ifdef CONFIG_MEMCG\n \tstruct mem_cgroup\t*sk_memcg;\n #endif\n@@ -1553,6 +1562,9 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind);\n int __sk_mem_schedule(struct sock *sk, int size, int kind);\n void __sk_mem_reduce_allocated(struct sock *sk, int amount);\n void __sk_mem_reclaim(struct sock *sk, int amount);\n+bool sk_memcg_budget_sync(struct sock *sk, gfp_t gfp);\n+void sk_memcg_budget_shrink(struct sock *sk);\n+void sk_memcg_budget_release(struct sock *sk);\n \n #define SK_MEM_SEND\t0\n #define SK_MEM_RECV\t1\n@@ -1622,6 +1634,12 @@ static inline void sk_mem_reclaim(struct sock *sk)\n \tif (!sk_has_account(sk))\n \t\treturn;\n \n+\t/* Return the part of the memcg budget charge that a budget\n+\t * shrink (by any writer) no longer backs. Never charges, so\n+\t * it is safe to call locklessly from skb destructors.\n+\t */\n+\tsk_memcg_budget_shrink(sk);\n+\n \treclaimable = sk-\u003esk_forward_alloc - sk_unused_reserved_mem(sk);\n \n \tif (reclaimable \u003e= (int)PAGE_SIZE)\n@@ -2640,6 +2658,9 @@ static inline void sk_stream_moderate_sndbuf(struct sock *sk)\n \tval = max_t(u32, val, sk_unused_reserved_mem(sk));\n \n \tWRITE_ONCE(sk-\u003esk_sndbuf, max_t(u32, val, SOCK_MIN_SNDBUF));\n+\n+\t/* The sndbuf shrink released part of the memcg budget charge. */\n+\tsk_memcg_budget_shrink(sk);\n }\n \n /**\ndiff --git a/net/core/sock.c b/net/core/sock.c\nindex 2948dffcc3e1b..d3374e563a9a0 100644\n--- a/net/core/sock.c\n+++ b/net/core/sock.c\n@@ -986,6 +986,9 @@ static void __sock_set_rcvbuf(struct sock *sk, int val)\n \t */\n \tWRITE_ONCE(sk-\u003esk_rcvbuf, max_t(int, val * 2, SOCK_MIN_RCVBUF));\n \n+\t/* Charge the new budget to the memcg (or refund the released one). */\n+\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n+\n \tif (sock) {\n \t\tconst struct proto_ops *ops = READ_ONCE(sock-\u003eops);\n \n@@ -1026,12 +1029,13 @@ static void sock_release_reserved_memory(struct sock *sk, int bytes)\n \tWARN_ON(bytes \u003e sk-\u003esk_reserved_mem);\n \tWRITE_ONCE(sk-\u003esk_reserved_mem, sk-\u003esk_reserved_mem - bytes);\n \tsk_mem_reclaim(sk);\n+\t/* The released reservation shrank the memcg budget charge. */\n+\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n }\n \n static int sock_reserve_memory(struct sock *sk, int bytes)\n {\n \tlong allocated;\n-\tbool charged;\n \tint pages;\n \n \tif (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))\n@@ -1042,12 +1046,6 @@ static int sock_reserve_memory(struct sock *sk, int bytes)\n \n \tpages = sk_mem_pages(bytes);\n \n-\t/* pre-charge to memcg */\n-\tcharged = mem_cgroup_sk_charge(sk, pages,\n-\t\t\t\t GFP_KERNEL | __GFP_RETRY_MAYFAIL);\n-\tif (!charged)\n-\t\treturn -ENOMEM;\n-\n \tif (sk-\u003esk_bypass_prot_mem)\n \t\tgoto success;\n \n@@ -1060,16 +1058,29 @@ static int sock_reserve_memory(struct sock *sk, int bytes)\n \t */\n \tif (allocated \u003e sk_prot_mem_limits(sk, 1)) {\n \t\tsk_memory_allocated_sub(sk, pages);\n-\t\tmem_cgroup_sk_uncharge(sk, pages);\n \t\treturn -ENOMEM;\n \t}\n \n success:\n-\tsk_forward_alloc_add(sk, pages \u003c\u003c PAGE_SHIFT);\n-\n+\t/* The reservation joins the socket budget, so publish it before\n+\t * syncing the memcg charge and roll it back if the sync fails.\n+\t * The forward_alloc credit is committed last, after all fallible\n+\t * steps: softirq reclaim may fold it back into the protocol\n+\t * counter, and that cannot be rolled back.\n+\t */\n \tWRITE_ONCE(sk-\u003esk_reserved_mem,\n \t\t sk-\u003esk_reserved_mem + (pages \u003c\u003c PAGE_SHIFT));\n \n+\tif (!sk_memcg_budget_sync(sk, GFP_KERNEL | __GFP_RETRY_MAYFAIL)) {\n+\t\tWRITE_ONCE(sk-\u003esk_reserved_mem,\n+\t\t\t sk-\u003esk_reserved_mem - (pages \u003c\u003c PAGE_SHIFT));\n+\t\tif (!sk-\u003esk_bypass_prot_mem)\n+\t\t\tsk_memory_allocated_sub(sk, pages);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tsk_forward_alloc_add(sk, pages \u003c\u003c PAGE_SHIFT);\n+\n \treturn 0;\n }\n \n@@ -1349,6 +1360,10 @@ int sk_setsockopt(struct sock *sk, int level, int optname,\n \t\tsk-\u003esk_userlocks |= SOCK_SNDBUF_LOCK;\n \t\tWRITE_ONCE(sk-\u003esk_sndbuf,\n \t\t\t max_t(int, val * 2, SOCK_MIN_SNDBUF));\n+\t\t/* Charge the new budget to the memcg (or refund the\n+\t\t * released one).\n+\t\t */\n+\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n \t\t/* Wake up sending tasks if we upped the value. */\n \t\tsk-\u003esk_write_space(sk);\n \t\tbreak;\n@@ -2319,6 +2334,7 @@ struct sock *sk_alloc(struct net *net, int family, gfp_t priority,\n \n \t\tsk-\u003esk_kern_sock = kern;\n \t\tsock_lock_init(sk);\n+\t\tspin_lock_init(\u0026sk-\u003esk_memcg_budget_lock);\n \n \t\tsk-\u003esk_net_refcnt = kern ? 0 : 1;\n \t\tif (likely(sk-\u003esk_net_refcnt)) {\n@@ -2356,6 +2372,12 @@ static void __sk_destruct(struct rcu_head *head)\n \tif (sk-\u003esk_destruct)\n \t\tsk-\u003esk_destruct(sk);\n \n+\t/* All queues are purged and no reference is left: return the\n+\t * remaining memcg budget charge exactly once, before the memcg\n+\t * association is dropped by sk_prot_free() below.\n+\t */\n+\tsk_memcg_budget_release(sk);\n+\n \tfilter = rcu_dereference_check(sk-\u003esk_filter,\n \t\t\t\t refcount_read(\u0026sk-\u003esk_wmem_alloc) == 0);\n \tif (filter) {\n@@ -2535,6 +2557,12 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,\n \tnewsk-\u003esk_wmem_queued\t= 0;\n \tnewsk-\u003esk_forward_alloc = 0;\n \tnewsk-\u003esk_reserved_mem = 0;\n+\t/* The child has not charged anything to the memcg yet: it will be\n+\t * charged for its budget at accept() time (__sk_charge()).\n+\t * sock_copy() copied the parent's lock bytes: re-init the lock.\n+\t */\n+\tnewsk-\u003esk_memcg_budget = 0;\n+\tspin_lock_init(\u0026newsk-\u003esk_memcg_budget_lock);\n \tDEBUG_NET_WARN_ON_ONCE(newsk-\u003esk_drop_counters);\n \tsk_drops_reset(newsk);\n \tnewsk-\u003esk_send_head\t= NULL;\n@@ -3334,15 +3362,12 @@ EXPORT_SYMBOL(sk_wait_data);\n *\n *\tSimilar to __sk_mem_schedule(), but does not update sk_forward_alloc.\n *\n- *\tUnlike the globally shared limits among the sockets under same protocol,\n- *\tconsuming the budget of a memcg won't have direct effect on other ones.\n- *\tSo be optimistic about memcg's tolerance, and leave the callers to decide\n- *\twhether or not to raise allocated through sk_under_memory_pressure() or\n- *\tits variants.\n+ *\tCharges the global protocol counter only. The memcg is charged for\n+ *\tthe socket's full memory budget when that budget is established or\n+ *\tresized (see sk_memcg_budget_sync()), not per allocation.\n */\n int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)\n {\n-\tbool memcg_enabled = false, charged = false;\n \tstruct proto *prot = sk-\u003esk_prot;\n \tlong allocated = 0;\n \n@@ -3351,13 +3376,6 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)\n \t\tallocated = sk_memory_allocated(sk);\n \t}\n \n-\tif (mem_cgroup_sk_enabled(sk)) {\n-\t\tmemcg_enabled = true;\n-\t\tcharged = mem_cgroup_sk_charge(sk, amt, gfp_memcg_charge());\n-\t\tif (!charged)\n-\t\t\tgoto suppress_allocation;\n-\t}\n-\n \tif (!allocated)\n \t\treturn 1;\n \n@@ -3428,13 +3446,8 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)\n \t\t/* Fail only if socket is _under_ its sndbuf.\n \t\t * In this case we cannot block, so that we have to fail.\n \t\t */\n-\t\tif (sk-\u003esk_wmem_queued + size \u003e= sk-\u003esk_sndbuf) {\n-\t\t\t/* Force charge with __GFP_NOFAIL */\n-\t\t\tif (memcg_enabled \u0026\u0026 !charged)\n-\t\t\t\tmem_cgroup_sk_charge(sk, amt,\n-\t\t\t\t\t\t gfp_memcg_charge() | __GFP_NOFAIL);\n+\t\tif (sk-\u003esk_wmem_queued + size \u003e= sk-\u003esk_sndbuf)\n \t\t\treturn 1;\n-\t\t}\n \t}\n \n \ttrace_sock_exceed_buf_limit(sk, prot, allocated, kind);\n@@ -3442,9 +3455,6 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)\n \tif (allocated)\n \t\tsk_memory_allocated_sub(sk, amt);\n \n-\tif (charged)\n-\t\tmem_cgroup_sk_uncharge(sk, amt);\n-\n \treturn 0;\n }\n \n@@ -3475,13 +3485,12 @@ EXPORT_SYMBOL(__sk_mem_schedule);\n *\t@sk: socket\n *\t@amount: number of quanta\n *\n- *\tSimilar to __sk_mem_reclaim(), but does not update sk_forward_alloc\n+ *\tSimilar to __sk_mem_reclaim(), but does not update sk_forward_alloc.\n+ *\tOnly returns pages to the global protocol counter; the memcg charge\n+ *\tfollows the socket budget, see sk_memcg_budget_sync().\n */\n void __sk_mem_reduce_allocated(struct sock *sk, int amount)\n {\n-\tif (mem_cgroup_sk_enabled(sk))\n-\t\tmem_cgroup_sk_uncharge(sk, amount);\n-\n \tif (sk-\u003esk_bypass_prot_mem)\n \t\treturn;\n \n@@ -3505,20 +3514,150 @@ void __sk_mem_reclaim(struct sock *sk, int amount)\n }\n EXPORT_SYMBOL(__sk_mem_reclaim);\n \n-void __sk_charge(struct sock *sk, gfp_t gfp)\n+/* Pages of the socket's currently established memory budget: the send\n+ * and receive buffers plus the SO_RESERVE_MEM reservation.\n+ */\n+static int sk_memcg_budget_pages(const struct sock *sk)\n {\n-\tint amt;\n+\tlong budget = READ_ONCE(sk-\u003esk_sndbuf) + READ_ONCE(sk-\u003esk_rcvbuf) +\n+\t\t READ_ONCE(sk-\u003esk_reserved_mem);\n \n-\tgfp |= __GFP_NOFAIL;\n-\tif (mem_cgroup_from_sk(sk)) {\n-\t\t/* The socket has not been accepted yet, no need\n-\t\t * to look at newsk-\u003esk_wmem_queued.\n+\treturn (int)DIV_ROUND_UP(budget, PAGE_SIZE);\n+}\n+\n+/**\n+ *\tsk_memcg_budget_sync - charge the socket budget to the memcg\n+ *\t@sk: socket\n+ *\t@gfp: reclaim mode for the memcg charge\n+ *\n+ *\tMake the memcg charge match the socket's current budget: charge the\n+ *\tdifference when the budget grew, refund it when the budget shrank.\n+ *\n+ *\tGrowth: the memcg is charged first and only a successful charge is\n+ *\taccounted in sk-\u003esk_memcg_budget. A failed charge leaves the tracker\n+ *\tunchanged: the new budget is used uncharged (the safe direction) and\n+ *\ta later call retries the whole difference.\n+ *\n+ *\tConcurrency: budget growth usually runs under the socket lock, but\n+ *\tsome setsockopt(2) paths run without it, and shrinks may run\n+ *\tlocklessly (sk_mem_reclaim() from skb destructors), so the tracker\n+ *\tis a plain int guarded by sk_memcg_budget_lock. The lock is a leaf\n+ *\tlock taken with _bh (sk_memcg_budget_release() runs from RCU\n+ * callbacks); the memcg charge and uncharge run outside of it because\n+ *\tthey may sleep (memcg reclaim). Charging before publishing the\n+ *\ttracker, and refunding only the excess claimed under the lock, keep\n+ *\tevery charged page accounted in the tracker exactly once and every\n+ *\ttracker page refundable exactly once: concurrent growths can\n+ *\ttransiently over-count (the next shrink refunds the excess), while\n+ *\tcumulative refunds can never exceed cumulative charges, so the\n+ *\tmemcg balance can not underflow.\n+ *\n+ *\tReturns false when a growth charge failed.\n+ */\n+bool sk_memcg_budget_sync(struct sock *sk, gfp_t gfp)\n+{\n+\tint want, have, delta;\n+\n+\tif (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))\n+\t\treturn true;\n+\n+\t/* The snapshot is unlocked on purpose: the memcg charge below\n+\t * may sleep, so the lock cannot be held across it, and a\n+\t * lock-coherent snapshot would go stale the same way.\n+\t */\n+\twant = sk_memcg_budget_pages(sk);\n+\thave = READ_ONCE(sk-\u003esk_memcg_budget);\n+\n+\tif (want \u003e have) {\n+\t\tdelta = want - have;\n+\t\tif (!mem_cgroup_sk_charge(sk, delta, gfp))\n+\t\t\treturn false;\n+\n+\t\t/* Add the full @delta unconditionally: every charged page\n+\t\t * must land in the tracker exactly once, so the release\n+\t\t * can refund it exactly once. @have may be stale, the\n+\t\t * resulting over-count is refunded by the next shrink.\n \t\t */\n-\t\tamt = sk_mem_pages(sk-\u003esk_forward_alloc +\n-\t\t\t\t atomic_read(\u0026sk-\u003esk_rmem_alloc));\n-\t\tif (amt)\n-\t\t\tmem_cgroup_sk_charge(sk, amt, gfp);\n+\t\tspin_lock_bh(\u0026sk-\u003esk_memcg_budget_lock);\n+\t\tWRITE_ONCE(sk-\u003esk_memcg_budget, sk-\u003esk_memcg_budget + delta);\n+\t\tspin_unlock_bh(\u0026sk-\u003esk_memcg_budget_lock);\n+\t} else if (want \u003c have) {\n+\t\tsk_memcg_budget_shrink(sk);\n+\t}\n+\n+\treturn true;\n+}\n+EXPORT_SYMBOL(sk_memcg_budget_sync);\n+\n+/**\n+ *\tsk_memcg_budget_shrink - refund budget shrinks to the memcg\n+ *\t@sk: socket\n+ *\n+ *\tReturn the part of the memcg charge that is no longer backed by the\n+ *\tsocket's budget. Never charges and never fails, so it is safe to\n+ *\tcall from arbitrary (including lockless softirq) contexts, e.g.\n+ *\tfrom sk_mem_reclaim() to catch budget shrinks done by writers the\n+ *\tbudget sync is not hooked into.\n+ */\n+void sk_memcg_budget_shrink(struct sock *sk)\n+{\n+\tint want, refund = 0;\n+\n+\tif (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))\n+\t\treturn;\n+\n+\t/* Claim the excess under the lock, then refund it outside:\n+\t * the memcg uncharge may sleep, and the claim guarantees each\n+\t * tracker page is refunded exactly once. @want follows the\n+\t * socket buffers, which are protected by the socket lock, not\n+\t * the budget lock; a claim made stale by a concurrent budget\n+\t * update is corrected by the next sync.\n+\t */\n+\tspin_lock_bh(\u0026sk-\u003esk_memcg_budget_lock);\n+\twant = sk_memcg_budget_pages(sk);\n+\tif (want \u003c sk-\u003esk_memcg_budget) {\n+\t\trefund = sk-\u003esk_memcg_budget - want;\n+\t\tWRITE_ONCE(sk-\u003esk_memcg_budget, want);\n \t}\n+\tspin_unlock_bh(\u0026sk-\u003esk_memcg_budget_lock);\n+\n+\tif (refund)\n+\t\tmem_cgroup_sk_uncharge(sk, refund);\n+}\n+EXPORT_SYMBOL(sk_memcg_budget_shrink);\n+\n+/**\n+ *\tsk_memcg_budget_release - return the socket's memcg budget charge\n+ *\t@sk: socket\n+ *\n+ *\tReturn the whole remaining budget charge of the socket to its memcg\n+ *\tand reset the tracker. Used when the socket dies (__sk_destruct(),\n+ *\tno budget change can happen anymore, the refund is exactly the\n+ * amount that was charged) and when the memcg association of the\n+ *\tsocket moves (the charge is re-established against the new memcg).\n+ */\n+void sk_memcg_budget_release(struct sock *sk)\n+{\n+\tint refund;\n+\n+\tspin_lock_bh(\u0026sk-\u003esk_memcg_budget_lock);\n+\trefund = sk-\u003esk_memcg_budget;\n+\tWRITE_ONCE(sk-\u003esk_memcg_budget, 0);\n+\tspin_unlock_bh(\u0026sk-\u003esk_memcg_budget_lock);\n+\n+\tif (refund)\n+\t\tmem_cgroup_sk_uncharge(sk, refund);\n+}\n+EXPORT_SYMBOL(sk_memcg_budget_release);\n+\n+void __sk_charge(struct sock *sk, gfp_t gfp)\n+{\n+\tgfp |= __GFP_NOFAIL;\n+\n+\t/* Charge the child's full memory budget to its memcg: the budget\n+\t * covers whatever the child queued before being accepted.\n+\t */\n+\tsk_memcg_budget_sync(sk, gfp);\n \n \tkmem_cache_charge(sk, gfp);\n }\n@@ -3760,6 +3899,10 @@ void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)\n \tsk-\u003esk_allocation\t=\tGFP_KERNEL;\n \tsk-\u003esk_rcvbuf\t\t=\tREAD_ONCE(sysctl_rmem_default);\n \tsk-\u003esk_sndbuf\t\t=\tREAD_ONCE(sysctl_wmem_default);\n+\t/* The socket's memory budget starts here: charge it to the memcg\n+\t * upfront. Protocols raising the default buffers later re-sync.\n+\t */\n+\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n \tsk-\u003esk_state\t\t=\tTCP_CLOSE;\n \tsk-\u003esk_use_task_frag\t=\ttrue;\n \tsk_set_socket(sk, sock);\n@@ -4581,6 +4724,8 @@ static int __init sock_struct_check(void)\n \n \tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_err);\n \tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_socket);\n+\tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg_budget);\n+\tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg_budget_lock);\n #ifdef CONFIG_MEMCG\n \tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg);\n #endif\ndiff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c\nindex 3ac4856852794..629ce7dc3032d 100644\n--- a/net/ipv4/tcp.c\n+++ b/net/ipv4/tcp.c\n@@ -471,6 +471,10 @@ void tcp_init_sock(struct sock *sk)\n \n \tWRITE_ONCE(sk-\u003esk_sndbuf, READ_ONCE(sock_net(sk)-\u003eipv4.sysctl_tcp_wmem[1]));\n \tWRITE_ONCE(sk-\u003esk_rcvbuf, READ_ONCE(sock_net(sk)-\u003eipv4.sysctl_tcp_rmem[1]));\n+\t/* The default buffers grew from the generic sock_init_data()\n+\t * values: charge the difference to the memcg.\n+\t */\n+\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n \ttcp_scaling_ratio_init(sk);\n \n \tset_bit(SOCK_SUPPORT_ZC, \u0026sk-\u003esk_socket-\u003eflags);\n@@ -917,9 +921,9 @@ void sk_forced_mem_schedule(struct sock *sk, int size)\n \tamt = sk_mem_pages(delta);\n \tsk_forward_alloc_add(sk, amt \u003c\u003c PAGE_SHIFT);\n \n-\tif (mem_cgroup_sk_enabled(sk))\n-\t\tmem_cgroup_sk_charge(sk, amt, gfp_memcg_charge() | __GFP_NOFAIL);\n-\n+\t/* Only the global protocol counter: the memcg side follows the\n+\t * socket budget (sk_memcg_budget_sync()).\n+\t */\n \tif (sk-\u003esk_bypass_prot_mem)\n \t\treturn;\n \n@@ -1850,6 +1854,7 @@ int tcp_set_rcvlowat(struct sock *sk, int val)\n \tspace = tcp_space_from_win(sk, val);\n \tif (space \u003e sk-\u003esk_rcvbuf) {\n \t\tWRITE_ONCE(sk-\u003esk_rcvbuf, space);\n+\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n \n \t\tif (tp-\u003ewindow_clamp \u0026\u0026 tp-\u003ewindow_clamp \u003c val)\n \t\t\tWRITE_ONCE(tp-\u003ewindow_clamp, val);\ndiff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c\nindex 892ff256e2352..9fdb2c66821b7 100644\n--- a/net/ipv4/tcp_input.c\n+++ b/net/ipv4/tcp_input.c\n@@ -629,9 +629,11 @@ static void tcp_sndbuf_expand(struct sock *sk)\n \tsndmem = ca_ops-\u003esndbuf_expand ? ca_ops-\u003esndbuf_expand(sk) : 2;\n \tsndmem *= nr_segs * per_mss;\n \n-\tif (sk-\u003esk_sndbuf \u003c sndmem)\n+\tif (sk-\u003esk_sndbuf \u003c sndmem) {\n \t\tWRITE_ONCE(sk-\u003esk_sndbuf,\n \t\t\t min(sndmem, READ_ONCE(sock_net(sk)-\u003eipv4.sysctl_tcp_wmem[2])));\n+\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n+\t}\n }\n \n /* 2. Tuning advertised window (window_clamp, rcv_ssthresh)\n@@ -791,6 +793,7 @@ static void tcp_clamp_window(struct sock *sk)\n \t sk_memory_allocated(sk) \u003c sk_prot_mem_limits(sk, 0)) {\n \t\tWRITE_ONCE(sk-\u003esk_rcvbuf,\n \t\t\t min(atomic_read(\u0026sk-\u003esk_rmem_alloc), rmem2));\n+\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n \t}\n \tif (atomic_read(\u0026sk-\u003esk_rmem_alloc) \u003e sk-\u003esk_rcvbuf)\n \t\ttp-\u003ercv_ssthresh = min(tp-\u003ewindow_clamp, 2U * tp-\u003eadvmss);\n@@ -948,6 +951,7 @@ void tcp_rcvbuf_grow(struct sock *sk, u32 newval)\n \trcvbuf = min_t(u32, tcp_space_from_win(sk, rcvwin), cap);\n \tif (rcvbuf \u003e sk-\u003esk_rcvbuf) {\n \t\tWRITE_ONCE(sk-\u003esk_rcvbuf, rcvbuf);\n+\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n \t\t/* Make the window clamp follow along. */\n \t\tWRITE_ONCE(tp-\u003ewindow_clamp,\n \t\t\t tcp_win_from_space(sk, rcvbuf));\n@@ -6055,8 +6059,10 @@ static bool tcp_should_expand_sndbuf(struct sock *sk)\n \t\t * it never goes below SOCK_MIN_SNDBUF.\n \t\t * See sk_stream_moderate_sndbuf() for more details.\n \t\t */\n-\t\tif (unused_mem \u003e SOCK_MIN_SNDBUF)\n+\t\tif (unused_mem \u003e SOCK_MIN_SNDBUF) {\n \t\t\tWRITE_ONCE(sk-\u003esk_sndbuf, unused_mem);\n+\t\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n+\t\t}\n \n \t\treturn false;\n \t}\ndiff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c\nindex e89a69ab927c9..bcc59e673b9bf 100644\n--- a/net/mptcp/protocol.c\n+++ b/net/mptcp/protocol.c\n@@ -238,6 +238,7 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval)\n \trcvbuf = min_t(u32, mptcp_space_from_win(sk, rcvwin), cap);\n \tif (rcvbuf \u003e sk-\u003esk_rcvbuf) {\n \t\tWRITE_ONCE(sk-\u003esk_rcvbuf, rcvbuf);\n+\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n \t\treturn true;\n \t}\n \treturn false;\n@@ -791,12 +792,6 @@ static void __mptcp_add_backlog(struct sock *sk,\n \n account:\n \tWRITE_ONCE(msk-\u003ebacklog_len, msk-\u003ebacklog_len + delta);\n-\n-\t/* Possibly not accept()ed yet, keep track of memory not CG\n-\t * accounted, mptcp_graft_subflows() will handle it.\n-\t */\n-\tif (!mem_cgroup_from_sk(ssk))\n-\t\tmsk-\u003ebacklog_unaccounted += delta;\n }\n \n static bool __mptcp_move_skbs_from_subflow(struct mptcp_sock *msk,\n@@ -2318,12 +2313,6 @@ static bool mptcp_can_spool_backlog(struct sock *sk, struct list_head *skbs)\n {\n \tstruct mptcp_sock *msk = mptcp_sk(sk);\n \n-\t/* After CG initialization, subflows should never add skb before\n-\t * gaining the CG themself.\n-\t */\n-\tDEBUG_NET_WARN_ON_ONCE(msk-\u003ebacklog_unaccounted \u0026\u0026 sk-\u003esk_socket \u0026\u0026\n-\t\t\t mem_cgroup_from_sk(sk));\n-\n \tif (list_empty(\u0026msk-\u003ebacklog_list))\n \t\treturn false;\n \n@@ -3255,6 +3244,10 @@ static int mptcp_init_sock(struct sock *sk)\n \tsk_sockets_allocated_inc(sk);\n \tsk-\u003esk_rcvbuf = READ_ONCE(net-\u003eipv4.sysctl_tcp_rmem[1]);\n \tsk-\u003esk_sndbuf = READ_ONCE(net-\u003eipv4.sysctl_tcp_wmem[1]);\n+\t/* The default buffers grew from the generic sock_init_data()\n+\t * values: charge the difference to the memcg.\n+\t */\n+\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n \tsk-\u003esk_write_space = sk_stream_write_space;\n \n \treturn 0;\n@@ -4332,10 +4325,7 @@ static void mptcp_graft_subflows(struct sock *sk)\n \t\tLIST_HEAD(join_list);\n \n \t\t/* Subflows joining after __inet_accept() will get the\n-\t\t * mem CG properly initialized at mptcp_finish_join() time,\n-\t\t * but subflows pending in join_list need explicit\n-\t\t * initialization before flushing `backlog_unaccounted`\n-\t\t * or MPTCP can later unexpectedly observe unaccounted memory.\n+\t\t * mem CG properly initialized at mptcp_finish_join() time.\n \t\t */\n \t\tmptcp_data_lock(sk);\n \t\tlist_splice_init(\u0026msk-\u003ejoin_list, \u0026join_list);\n@@ -4364,26 +4354,6 @@ static void mptcp_graft_subflows(struct sock *sk)\n unlock:\n \t\trelease_sock(ssk);\n \t}\n-\n-\tif (mem_cgroup_sk_enabled(sk)) {\n-\t\tgfp_t gfp = GFP_KERNEL | __GFP_NOFAIL;\n-\t\tint amt;\n-\n-\t\t/* Account the backlog memory; prior accept() is aware of\n-\t\t * fwd and rmem only.\n-\t\t */\n-\t\tmptcp_data_lock(sk);\n-\t\tamt = sk_mem_pages(sk-\u003esk_forward_alloc +\n-\t\t\t\t msk-\u003ebacklog_unaccounted +\n-\t\t\t\t atomic_read(\u0026sk-\u003esk_rmem_alloc)) -\n-\t\t sk_mem_pages(sk-\u003esk_forward_alloc +\n-\t\t\t\t atomic_read(\u0026sk-\u003esk_rmem_alloc));\n-\t\tmsk-\u003ebacklog_unaccounted = 0;\n-\t\tmptcp_data_unlock(sk);\n-\n-\t\tif (amt)\n-\t\t\tmem_cgroup_sk_charge(sk, amt, gfp);\n-\t}\n }\n \n static int mptcp_stream_accept(struct socket *sock, struct socket *newsock,\ndiff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h\nindex 0384d6a023f9d..66ff8c232d8c9 100644\n--- a/net/mptcp/protocol.h\n+++ b/net/mptcp/protocol.h\n@@ -377,7 +377,6 @@ struct mptcp_sock {\n \n \tstruct list_head backlog_list;\t/* protected by the data lock */\n \tu32\t\tbacklog_len;\n-\tu32\t\tbacklog_unaccounted;\n };\n \n #define mptcp_data_lock(sk) spin_lock_bh(\u0026(sk)-\u003esk_lock.slock)\n@@ -1041,6 +1040,7 @@ static inline void __mptcp_sync_sndbuf(struct sock *sk)\n \n \t/* the msk max wmem limit is \u003cnr_subflows\u003e * tcp wmem[2] */\n \tWRITE_ONCE(sk-\u003esk_sndbuf, new_sndbuf);\n+\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n \tmptcp_write_space(sk);\n }\n \ndiff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c\nindex fcf6feb2a9eb5..05d9dafa8c071 100644\n--- a/net/mptcp/sockopt.c\n+++ b/net/mptcp/sockopt.c\n@@ -70,6 +70,7 @@ static int mptcp_get_int_option(struct mptcp_sock *msk, sockptr_t optval,\n static void __mptcp_subflow_set_rcvbuf(struct sock *ssk, int val)\n {\n \tWRITE_ONCE(ssk-\u003esk_rcvbuf, val);\n+\tsk_memcg_budget_sync(ssk, gfp_memcg_charge());\n \ttcp_set_rcvbuf(ssk, val);\n }\n \n@@ -1656,12 +1657,14 @@ int mptcp_set_rcvlowat(struct sock *sk, int val)\n \n \t/* propagate the rcvbuf changes to all the subflows */\n \tWRITE_ONCE(sk-\u003esk_rcvbuf, space);\n+\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\n \tmptcp_for_each_subflow(mptcp_sk(sk), subflow) {\n \t\tstruct sock *ssk = mptcp_subflow_tcp_sock(subflow);\n \t\tbool slow;\n \n \t\tslow = lock_sock_fast(ssk);\n \t\tWRITE_ONCE(ssk-\u003esk_rcvbuf, space);\n+\t\tsk_memcg_budget_sync(ssk, gfp_memcg_charge());\n \t\tWRITE_ONCE(tcp_sk(ssk)-\u003ewindow_clamp, val);\n \t\tunlock_sock_fast(ssk, slow);\n \t}\ndiff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c\nindex f0a6725d2c376..340eb5b2cacb8 100644\n--- a/net/mptcp/subflow.c\n+++ b/net/mptcp/subflow.c\n@@ -1735,6 +1735,11 @@ void __mptcp_inherit_memcg(struct sock *sk, struct sock *ssk, gfp_t gfp)\n \tif (!mem_cgroup_sockets_enabled || !sk-\u003esk_socket)\n \t\treturn;\n \n+\t/* The subflow's budget charge went to its previous memcg: return\n+\t * it before the memcg association moves to the msk's one, the\n+\t * __sk_charge() below re-charges the budget there.\n+\t */\n+\tsk_memcg_budget_release(ssk);\n \tmem_cgroup_sk_inherit(sk, ssk);\n \t__sk_charge(ssk, gfp);\n }\ndiff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c\nindex 3a84d068fbf36..54dae37291c31 100644\n--- a/tools/testing/selftests/cgroup/test_memcontrol.c\n+++ b/tools/testing/selftests/cgroup/test_memcontrol.c\n@@ -1335,9 +1335,12 @@ static int tcp_client(const char *cgroup, unsigned short port)\n \tchar servport[6];\n \tint retries = 0x10; /* nice round number */\n \tint sk, ret;\n-\tlong allocated;\n+\tlong allocated, sock_pre;\n \n \tallocated = cg_read_long(cgroup, \"memory.current\");\n+\tsock_pre = cg_read_key_long(cgroup, \"memory.stat\", \"sock \");\n+\tif (sock_pre \u003c 0)\n+\t\treturn KSFT_FAIL;\n \tsnprintf(servport, sizeof(servport), \"%hd\", port);\n \tret = getaddrinfo(server, servport, NULL, \u0026ai);\n \tif (ret)\n@@ -1365,8 +1368,12 @@ static int tcp_client(const char *cgroup, unsigned short port)\n \t\tif (current \u003c 0 || sock \u003c 0)\n \t\t\tgoto close_sk;\n \n-\t\t/* exclude the memory not related to socket connection */\n-\t\tif (values_close(current - allocated, sock, 10)) {\n+\t\t/* exclude the memory not related to socket connection;\n+\t\t * compare the growth of both counters since the baseline,\n+\t\t * as the listening socket may already hold socket memory\n+\t\t * (e.g. its pre-charged memory budget) at the baseline.\n+\t\t */\n+\t\tif (values_close(current - allocated, sock - sock_pre, 10)) {\n \t\t\tret = KSFT_PASS;\n \t\t\tbreak;\n \t\t}\n@@ -1384,8 +1391,9 @@ static int tcp_client(const char *cgroup, unsigned short port)\n * The test forks a TCP server listens on a random port between 1000\n * and 61000. Once it gets a client connection, it starts writing to\n * its socket.\n- * The TCP client interleaves reads from the socket with check whether\n- * memory.current and memory.stat.sock are similar.\n+ * The TCP client interleaves reads from the socket with checking whether\n+ * the growth of memory.current and memory.stat.sock since the baseline\n+ * are similar.\n */\n static int test_memcg_sock(const char *root)\n {\n"
} |
| 2/1 | 2026/09/26 12:35 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"MEMCG",
"INET",
"MPTCP"
],
"FocusSymbols": [
"sk_memcg_budget_sync",
"sk_memcg_budget_shrink",
"sk_memcg_budget_release",
"__sk_charge",
"tcp_rcvbuf_grow",
"tcp_set_rcvlowat",
"mptcp_set_rcvlowat",
"__mptcp_inherit_memcg"
],
"Reasoning": "The patch modifies core socket, TCP, and MPTCP memory accounting by introducing upfront socket budget charging to memcg (covering send buffer, receive buffer, and SO_RESERVE_MEM) rather than charging per-allocation. It introduces new locking (sk_memcg_budget_lock), synchronization helpers (sk_memcg_budget_sync, sk_memcg_budget_shrink, sk_memcg_budget_release), and modifies socket option and buffer growth handling across TCP and MPTCP. These changes in core networking and memory management execute concurrently across user threads, softirqs, and RCU callbacks, making them prime targets for fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 74c8ef3e38a9935e88db6f422e1c29693a16522c
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Sep 26 12:35:55 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index 86a2a0099178e..db31966657bde 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -1309,7 +1309,11 @@ following types of memory usages are tracked.
- Kernel data structures such as dentries and inodes.
-- TCP socket buffers.
+- TCP socket buffers. These are charged upfront for the socket's
+ established memory budget (send buffer, receive buffer and
+ SO_RESERVE_MEM reservation) when the budget is created or resized,
+ and returned when the budget shrinks or the socket is closed; the
+ charge is not tied to per-packet buffer usage.
The above list may expand in the future for better coverage.
diff --git a/include/net/sock.h b/include/net/sock.h
index 60ea55dc18854..88fe6d93f7579 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -458,6 +458,15 @@ struct sock {
__cacheline_group_begin(sock_read_rxtx);
int sk_err;
struct socket *sk_socket;
+ /* Pages of the socket's memory budget (sndbuf + rcvbuf +
+ * SO_RESERVE_MEM) currently charged to the memcg. Paired with
+ * every mem_cgroup_sk_charge()/mem_cgroup_sk_uncharge() done on
+ * behalf of this socket, so it can be refunded exactly once.
+ * Guarded by sk_memcg_budget_lock; the charge/uncharge run
+ * outside the lock (they may sleep/reclaim).
+ */
+ int sk_memcg_budget;
+ spinlock_t sk_memcg_budget_lock;
#ifdef CONFIG_MEMCG
struct mem_cgroup *sk_memcg;
#endif
@@ -1553,6 +1562,9 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind);
int __sk_mem_schedule(struct sock *sk, int size, int kind);
void __sk_mem_reduce_allocated(struct sock *sk, int amount);
void __sk_mem_reclaim(struct sock *sk, int amount);
+bool sk_memcg_budget_sync(struct sock *sk, gfp_t gfp);
+void sk_memcg_budget_shrink(struct sock *sk);
+void sk_memcg_budget_release(struct sock *sk);
#define SK_MEM_SEND 0
#define SK_MEM_RECV 1
@@ -1622,6 +1634,12 @@ static inline void sk_mem_reclaim(struct sock *sk)
if (!sk_has_account(sk))
return;
+ /* Return the part of the memcg budget charge that a budget
+ * shrink (by any writer) no longer backs. Never charges, so
+ * it is safe to call locklessly from skb destructors.
+ */
+ sk_memcg_budget_shrink(sk);
+
reclaimable = sk->sk_forward_alloc - sk_unused_reserved_mem(sk);
if (reclaimable >= (int)PAGE_SIZE)
@@ -2640,6 +2658,9 @@ static inline void sk_stream_moderate_sndbuf(struct sock *sk)
val = max_t(u32, val, sk_unused_reserved_mem(sk));
WRITE_ONCE(sk->sk_sndbuf, max_t(u32, val, SOCK_MIN_SNDBUF));
+
+ /* The sndbuf shrink released part of the memcg budget charge. */
+ sk_memcg_budget_shrink(sk);
}
/**
diff --git a/net/core/sock.c b/net/core/sock.c
index 2948dffcc3e1b..d3374e563a9a0 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -986,6 +986,9 @@ static void __sock_set_rcvbuf(struct sock *sk, int val)
*/
WRITE_ONCE(sk->sk_rcvbuf, max_t(int, val * 2, SOCK_MIN_RCVBUF));
+ /* Charge the new budget to the memcg (or refund the released one). */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
+
if (sock) {
const struct proto_ops *ops = READ_ONCE(sock->ops);
@@ -1026,12 +1029,13 @@ static void sock_release_reserved_memory(struct sock *sk, int bytes)
WARN_ON(bytes > sk->sk_reserved_mem);
WRITE_ONCE(sk->sk_reserved_mem, sk->sk_reserved_mem - bytes);
sk_mem_reclaim(sk);
+ /* The released reservation shrank the memcg budget charge. */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
}
static int sock_reserve_memory(struct sock *sk, int bytes)
{
long allocated;
- bool charged;
int pages;
if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))
@@ -1042,12 +1046,6 @@ static int sock_reserve_memory(struct sock *sk, int bytes)
pages = sk_mem_pages(bytes);
- /* pre-charge to memcg */
- charged = mem_cgroup_sk_charge(sk, pages,
- GFP_KERNEL | __GFP_RETRY_MAYFAIL);
- if (!charged)
- return -ENOMEM;
-
if (sk->sk_bypass_prot_mem)
goto success;
@@ -1060,16 +1058,29 @@ static int sock_reserve_memory(struct sock *sk, int bytes)
*/
if (allocated > sk_prot_mem_limits(sk, 1)) {
sk_memory_allocated_sub(sk, pages);
- mem_cgroup_sk_uncharge(sk, pages);
return -ENOMEM;
}
success:
- sk_forward_alloc_add(sk, pages << PAGE_SHIFT);
-
+ /* The reservation joins the socket budget, so publish it before
+ * syncing the memcg charge and roll it back if the sync fails.
+ * The forward_alloc credit is committed last, after all fallible
+ * steps: softirq reclaim may fold it back into the protocol
+ * counter, and that cannot be rolled back.
+ */
WRITE_ONCE(sk->sk_reserved_mem,
sk->sk_reserved_mem + (pages << PAGE_SHIFT));
+ if (!sk_memcg_budget_sync(sk, GFP_KERNEL | __GFP_RETRY_MAYFAIL)) {
+ WRITE_ONCE(sk->sk_reserved_mem,
+ sk->sk_reserved_mem - (pages << PAGE_SHIFT));
+ if (!sk->sk_bypass_prot_mem)
+ sk_memory_allocated_sub(sk, pages);
+ return -ENOMEM;
+ }
+
+ sk_forward_alloc_add(sk, pages << PAGE_SHIFT);
+
return 0;
}
@@ -1349,6 +1360,10 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
WRITE_ONCE(sk->sk_sndbuf,
max_t(int, val * 2, SOCK_MIN_SNDBUF));
+ /* Charge the new budget to the memcg (or refund the
+ * released one).
+ */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
/* Wake up sending tasks if we upped the value. */
sk->sk_write_space(sk);
break;
@@ -2319,6 +2334,7 @@ struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
sk->sk_kern_sock = kern;
sock_lock_init(sk);
+ spin_lock_init(&sk->sk_memcg_budget_lock);
sk->sk_net_refcnt = kern ? 0 : 1;
if (likely(sk->sk_net_refcnt)) {
@@ -2356,6 +2372,12 @@ static void __sk_destruct(struct rcu_head *head)
if (sk->sk_destruct)
sk->sk_destruct(sk);
+ /* All queues are purged and no reference is left: return the
+ * remaining memcg budget charge exactly once, before the memcg
+ * association is dropped by sk_prot_free() below.
+ */
+ sk_memcg_budget_release(sk);
+
filter = rcu_dereference_check(sk->sk_filter,
refcount_read(&sk->sk_wmem_alloc) == 0);
if (filter) {
@@ -2535,6 +2557,12 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
newsk->sk_wmem_queued = 0;
newsk->sk_forward_alloc = 0;
newsk->sk_reserved_mem = 0;
+ /* The child has not charged anything to the memcg yet: it will be
+ * charged for its budget at accept() time (__sk_charge()).
+ * sock_copy() copied the parent's lock bytes: re-init the lock.
+ */
+ newsk->sk_memcg_budget = 0;
+ spin_lock_init(&newsk->sk_memcg_budget_lock);
DEBUG_NET_WARN_ON_ONCE(newsk->sk_drop_counters);
sk_drops_reset(newsk);
newsk->sk_send_head = NULL;
@@ -3334,15 +3362,12 @@ EXPORT_SYMBOL(sk_wait_data);
*
* Similar to __sk_mem_schedule(), but does not update sk_forward_alloc.
*
- * Unlike the globally shared limits among the sockets under same protocol,
- * consuming the budget of a memcg won't have direct effect on other ones.
- * So be optimistic about memcg's tolerance, and leave the callers to decide
- * whether or not to raise allocated through sk_under_memory_pressure() or
- * its variants.
+ * Charges the global protocol counter only. The memcg is charged for
+ * the socket's full memory budget when that budget is established or
+ * resized (see sk_memcg_budget_sync()), not per allocation.
*/
int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
{
- bool memcg_enabled = false, charged = false;
struct proto *prot = sk->sk_prot;
long allocated = 0;
@@ -3351,13 +3376,6 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
allocated = sk_memory_allocated(sk);
}
- if (mem_cgroup_sk_enabled(sk)) {
- memcg_enabled = true;
- charged = mem_cgroup_sk_charge(sk, amt, gfp_memcg_charge());
- if (!charged)
- goto suppress_allocation;
- }
-
if (!allocated)
return 1;
@@ -3428,13 +3446,8 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
/* Fail only if socket is _under_ its sndbuf.
* In this case we cannot block, so that we have to fail.
*/
- if (sk->sk_wmem_queued + size >= sk->sk_sndbuf) {
- /* Force charge with __GFP_NOFAIL */
- if (memcg_enabled && !charged)
- mem_cgroup_sk_charge(sk, amt,
- gfp_memcg_charge() | __GFP_NOFAIL);
+ if (sk->sk_wmem_queued + size >= sk->sk_sndbuf)
return 1;
- }
}
trace_sock_exceed_buf_limit(sk, prot, allocated, kind);
@@ -3442,9 +3455,6 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
if (allocated)
sk_memory_allocated_sub(sk, amt);
- if (charged)
- mem_cgroup_sk_uncharge(sk, amt);
-
return 0;
}
@@ -3475,13 +3485,12 @@ EXPORT_SYMBOL(__sk_mem_schedule);
* @sk: socket
* @amount: number of quanta
*
- * Similar to __sk_mem_reclaim(), but does not update sk_forward_alloc
+ * Similar to __sk_mem_reclaim(), but does not update sk_forward_alloc.
+ * Only returns pages to the global protocol counter; the memcg charge
+ * follows the socket budget, see sk_memcg_budget_sync().
*/
void __sk_mem_reduce_allocated(struct sock *sk, int amount)
{
- if (mem_cgroup_sk_enabled(sk))
- mem_cgroup_sk_uncharge(sk, amount);
-
if (sk->sk_bypass_prot_mem)
return;
@@ -3505,20 +3514,150 @@ void __sk_mem_reclaim(struct sock *sk, int amount)
}
EXPORT_SYMBOL(__sk_mem_reclaim);
-void __sk_charge(struct sock *sk, gfp_t gfp)
+/* Pages of the socket's currently established memory budget: the send
+ * and receive buffers plus the SO_RESERVE_MEM reservation.
+ */
+static int sk_memcg_budget_pages(const struct sock *sk)
{
- int amt;
+ long budget = READ_ONCE(sk->sk_sndbuf) + READ_ONCE(sk->sk_rcvbuf) +
+ READ_ONCE(sk->sk_reserved_mem);
- gfp |= __GFP_NOFAIL;
- if (mem_cgroup_from_sk(sk)) {
- /* The socket has not been accepted yet, no need
- * to look at newsk->sk_wmem_queued.
+ return (int)DIV_ROUND_UP(budget, PAGE_SIZE);
+}
+
+/**
+ * sk_memcg_budget_sync - charge the socket budget to the memcg
+ * @sk: socket
+ * @gfp: reclaim mode for the memcg charge
+ *
+ * Make the memcg charge match the socket's current budget: charge the
+ * difference when the budget grew, refund it when the budget shrank.
+ *
+ * Growth: the memcg is charged first and only a successful charge is
+ * accounted in sk->sk_memcg_budget. A failed charge leaves the tracker
+ * unchanged: the new budget is used uncharged (the safe direction) and
+ * a later call retries the whole difference.
+ *
+ * Concurrency: budget growth usually runs under the socket lock, but
+ * some setsockopt(2) paths run without it, and shrinks may run
+ * locklessly (sk_mem_reclaim() from skb destructors), so the tracker
+ * is a plain int guarded by sk_memcg_budget_lock. The lock is a leaf
+ * lock taken with _bh (sk_memcg_budget_release() runs from RCU
+ * callbacks); the memcg charge and uncharge run outside of it because
+ * they may sleep (memcg reclaim). Charging before publishing the
+ * tracker, and refunding only the excess claimed under the lock, keep
+ * every charged page accounted in the tracker exactly once and every
+ * tracker page refundable exactly once: concurrent growths can
+ * transiently over-count (the next shrink refunds the excess), while
+ * cumulative refunds can never exceed cumulative charges, so the
+ * memcg balance can not underflow.
+ *
+ * Returns false when a growth charge failed.
+ */
+bool sk_memcg_budget_sync(struct sock *sk, gfp_t gfp)
+{
+ int want, have, delta;
+
+ if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))
+ return true;
+
+ /* The snapshot is unlocked on purpose: the memcg charge below
+ * may sleep, so the lock cannot be held across it, and a
+ * lock-coherent snapshot would go stale the same way.
+ */
+ want = sk_memcg_budget_pages(sk);
+ have = READ_ONCE(sk->sk_memcg_budget);
+
+ if (want > have) {
+ delta = want - have;
+ if (!mem_cgroup_sk_charge(sk, delta, gfp))
+ return false;
+
+ /* Add the full @delta unconditionally: every charged page
+ * must land in the tracker exactly once, so the release
+ * can refund it exactly once. @have may be stale, the
+ * resulting over-count is refunded by the next shrink.
*/
- amt = sk_mem_pages(sk->sk_forward_alloc +
- atomic_read(&sk->sk_rmem_alloc));
- if (amt)
- mem_cgroup_sk_charge(sk, amt, gfp);
+ spin_lock_bh(&sk->sk_memcg_budget_lock);
+ WRITE_ONCE(sk->sk_memcg_budget, sk->sk_memcg_budget + delta);
+ spin_unlock_bh(&sk->sk_memcg_budget_lock);
+ } else if (want < have) {
+ sk_memcg_budget_shrink(sk);
+ }
+
+ return true;
+}
+EXPORT_SYMBOL(sk_memcg_budget_sync);
+
+/**
+ * sk_memcg_budget_shrink - refund budget shrinks to the memcg
+ * @sk: socket
+ *
+ * Return the part of the memcg charge that is no longer backed by the
+ * socket's budget. Never charges and never fails, so it is safe to
+ * call from arbitrary (including lockless softirq) contexts, e.g.
+ * from sk_mem_reclaim() to catch budget shrinks done by writers the
+ * budget sync is not hooked into.
+ */
+void sk_memcg_budget_shrink(struct sock *sk)
+{
+ int want, refund = 0;
+
+ if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))
+ return;
+
+ /* Claim the excess under the lock, then refund it outside:
+ * the memcg uncharge may sleep, and the claim guarantees each
+ * tracker page is refunded exactly once. @want follows the
+ * socket buffers, which are protected by the socket lock, not
+ * the budget lock; a claim made stale by a concurrent budget
+ * update is corrected by the next sync.
+ */
+ spin_lock_bh(&sk->sk_memcg_budget_lock);
+ want = sk_memcg_budget_pages(sk);
+ if (want < sk->sk_memcg_budget) {
+ refund = sk->sk_memcg_budget - want;
+ WRITE_ONCE(sk->sk_memcg_budget, want);
}
+ spin_unlock_bh(&sk->sk_memcg_budget_lock);
+
+ if (refund)
+ mem_cgroup_sk_uncharge(sk, refund);
+}
+EXPORT_SYMBOL(sk_memcg_budget_shrink);
+
+/**
+ * sk_memcg_budget_release - return the socket's memcg budget charge
+ * @sk: socket
+ *
+ * Return the whole remaining budget charge of the socket to its memcg
+ * and reset the tracker. Used when the socket dies (__sk_destruct(),
+ * no budget change can happen anymore, the refund is exactly the
+ * amount that was charged) and when the memcg association of the
+ * socket moves (the charge is re-established against the new memcg).
+ */
+void sk_memcg_budget_release(struct sock *sk)
+{
+ int refund;
+
+ spin_lock_bh(&sk->sk_memcg_budget_lock);
+ refund = sk->sk_memcg_budget;
+ WRITE_ONCE(sk->sk_memcg_budget, 0);
+ spin_unlock_bh(&sk->sk_memcg_budget_lock);
+
+ if (refund)
+ mem_cgroup_sk_uncharge(sk, refund);
+}
+EXPORT_SYMBOL(sk_memcg_budget_release);
+
+void __sk_charge(struct sock *sk, gfp_t gfp)
+{
+ gfp |= __GFP_NOFAIL;
+
+ /* Charge the child's full memory budget to its memcg: the budget
+ * covers whatever the child queued before being accepted.
+ */
+ sk_memcg_budget_sync(sk, gfp);
kmem_cache_charge(sk, gfp);
}
@@ -3760,6 +3899,10 @@ void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)
sk->sk_allocation = GFP_KERNEL;
sk->sk_rcvbuf = READ_ONCE(sysctl_rmem_default);
sk->sk_sndbuf = READ_ONCE(sysctl_wmem_default);
+ /* The socket's memory budget starts here: charge it to the memcg
+ * upfront. Protocols raising the default buffers later re-sync.
+ */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
sk->sk_state = TCP_CLOSE;
sk->sk_use_task_frag = true;
sk_set_socket(sk, sock);
@@ -4581,6 +4724,8 @@ static int __init sock_struct_check(void)
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_err);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_socket);
+ CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg_budget);
+ CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg_budget_lock);
#ifdef CONFIG_MEMCG
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg);
#endif
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 3ac4856852794..629ce7dc3032d 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -471,6 +471,10 @@ void tcp_init_sock(struct sock *sk)
WRITE_ONCE(sk->sk_sndbuf, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_wmem[1]));
WRITE_ONCE(sk->sk_rcvbuf, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_rmem[1]));
+ /* The default buffers grew from the generic sock_init_data()
+ * values: charge the difference to the memcg.
+ */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
tcp_scaling_ratio_init(sk);
set_bit(SOCK_SUPPORT_ZC, &sk->sk_socket->flags);
@@ -917,9 +921,9 @@ void sk_forced_mem_schedule(struct sock *sk, int size)
amt = sk_mem_pages(delta);
sk_forward_alloc_add(sk, amt << PAGE_SHIFT);
- if (mem_cgroup_sk_enabled(sk))
- mem_cgroup_sk_charge(sk, amt, gfp_memcg_charge() | __GFP_NOFAIL);
-
+ /* Only the global protocol counter: the memcg side follows the
+ * socket budget (sk_memcg_budget_sync()).
+ */
if (sk->sk_bypass_prot_mem)
return;
@@ -1850,6 +1854,7 @@ int tcp_set_rcvlowat(struct sock *sk, int val)
space = tcp_space_from_win(sk, val);
if (space > sk->sk_rcvbuf) {
WRITE_ONCE(sk->sk_rcvbuf, space);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
if (tp->window_clamp && tp->window_clamp < val)
WRITE_ONCE(tp->window_clamp, val);
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 892ff256e2352..9fdb2c66821b7 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -629,9 +629,11 @@ static void tcp_sndbuf_expand(struct sock *sk)
sndmem = ca_ops->sndbuf_expand ? ca_ops->sndbuf_expand(sk) : 2;
sndmem *= nr_segs * per_mss;
- if (sk->sk_sndbuf < sndmem)
+ if (sk->sk_sndbuf < sndmem) {
WRITE_ONCE(sk->sk_sndbuf,
min(sndmem, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_wmem[2])));
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
+ }
}
/* 2. Tuning advertised window (window_clamp, rcv_ssthresh)
@@ -791,6 +793,7 @@ static void tcp_clamp_window(struct sock *sk)
sk_memory_allocated(sk) < sk_prot_mem_limits(sk, 0)) {
WRITE_ONCE(sk->sk_rcvbuf,
min(atomic_read(&sk->sk_rmem_alloc), rmem2));
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
}
if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf)
tp->rcv_ssthresh = min(tp->window_clamp, 2U * tp->advmss);
@@ -948,6 +951,7 @@ void tcp_rcvbuf_grow(struct sock *sk, u32 newval)
rcvbuf = min_t(u32, tcp_space_from_win(sk, rcvwin), cap);
if (rcvbuf > sk->sk_rcvbuf) {
WRITE_ONCE(sk->sk_rcvbuf, rcvbuf);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
/* Make the window clamp follow along. */
WRITE_ONCE(tp->window_clamp,
tcp_win_from_space(sk, rcvbuf));
@@ -6055,8 +6059,10 @@ static bool tcp_should_expand_sndbuf(struct sock *sk)
* it never goes below SOCK_MIN_SNDBUF.
* See sk_stream_moderate_sndbuf() for more details.
*/
- if (unused_mem > SOCK_MIN_SNDBUF)
+ if (unused_mem > SOCK_MIN_SNDBUF) {
WRITE_ONCE(sk->sk_sndbuf, unused_mem);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
+ }
return false;
}
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index e89a69ab927c9..bcc59e673b9bf 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -238,6 +238,7 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval)
rcvbuf = min_t(u32, mptcp_space_from_win(sk, rcvwin), cap);
if (rcvbuf > sk->sk_rcvbuf) {
WRITE_ONCE(sk->sk_rcvbuf, rcvbuf);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
return true;
}
return false;
@@ -791,12 +792,6 @@ static void __mptcp_add_backlog(struct sock *sk,
account:
WRITE_ONCE(msk->backlog_len, msk->backlog_len + delta);
-
- /* Possibly not accept()ed yet, keep track of memory not CG
- * accounted, mptcp_graft_subflows() will handle it.
- */
- if (!mem_cgroup_from_sk(ssk))
- msk->backlog_unaccounted += delta;
}
static bool __mptcp_move_skbs_from_subflow(struct mptcp_sock *msk,
@@ -2318,12 +2313,6 @@ static bool mptcp_can_spool_backlog(struct sock *sk, struct list_head *skbs)
{
struct mptcp_sock *msk = mptcp_sk(sk);
- /* After CG initialization, subflows should never add skb before
- * gaining the CG themself.
- */
- DEBUG_NET_WARN_ON_ONCE(msk->backlog_unaccounted && sk->sk_socket &&
- mem_cgroup_from_sk(sk));
-
if (list_empty(&msk->backlog_list))
return false;
@@ -3255,6 +3244,10 @@ static int mptcp_init_sock(struct sock *sk)
sk_sockets_allocated_inc(sk);
sk->sk_rcvbuf = READ_ONCE(net->ipv4.sysctl_tcp_rmem[1]);
sk->sk_sndbuf = READ_ONCE(net->ipv4.sysctl_tcp_wmem[1]);
+ /* The default buffers grew from the generic sock_init_data()
+ * values: charge the difference to the memcg.
+ */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
sk->sk_write_space = sk_stream_write_space;
return 0;
@@ -4332,10 +4325,7 @@ static void mptcp_graft_subflows(struct sock *sk)
LIST_HEAD(join_list);
/* Subflows joining after __inet_accept() will get the
- * mem CG properly initialized at mptcp_finish_join() time,
- * but subflows pending in join_list need explicit
- * initialization before flushing `backlog_unaccounted`
- * or MPTCP can later unexpectedly observe unaccounted memory.
+ * mem CG properly initialized at mptcp_finish_join() time.
*/
mptcp_data_lock(sk);
list_splice_init(&msk->join_list, &join_list);
@@ -4364,26 +4354,6 @@ static void mptcp_graft_subflows(struct sock *sk)
unlock:
release_sock(ssk);
}
-
- if (mem_cgroup_sk_enabled(sk)) {
- gfp_t gfp = GFP_KERNEL | __GFP_NOFAIL;
- int amt;
-
- /* Account the backlog memory; prior accept() is aware of
- * fwd and rmem only.
- */
- mptcp_data_lock(sk);
- amt = sk_mem_pages(sk->sk_forward_alloc +
- msk->backlog_unaccounted +
- atomic_read(&sk->sk_rmem_alloc)) -
- sk_mem_pages(sk->sk_forward_alloc +
- atomic_read(&sk->sk_rmem_alloc));
- msk->backlog_unaccounted = 0;
- mptcp_data_unlock(sk);
-
- if (amt)
- mem_cgroup_sk_charge(sk, amt, gfp);
- }
}
static int mptcp_stream_accept(struct socket *sock, struct socket *newsock,
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 0384d6a023f9d..66ff8c232d8c9 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -377,7 +377,6 @@ struct mptcp_sock {
struct list_head backlog_list; /* protected by the data lock */
u32 backlog_len;
- u32 backlog_unaccounted;
};
#define mptcp_data_lock(sk) spin_lock_bh(&(sk)->sk_lock.slock)
@@ -1041,6 +1040,7 @@ static inline void __mptcp_sync_sndbuf(struct sock *sk)
/* the msk max wmem limit is <nr_subflows> * tcp wmem[2] */
WRITE_ONCE(sk->sk_sndbuf, new_sndbuf);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
mptcp_write_space(sk);
}
diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c
index fcf6feb2a9eb5..05d9dafa8c071 100644
--- a/net/mptcp/sockopt.c
+++ b/net/mptcp/sockopt.c
@@ -70,6 +70,7 @@ static int mptcp_get_int_option(struct mptcp_sock *msk, sockptr_t optval,
static void __mptcp_subflow_set_rcvbuf(struct sock *ssk, int val)
{
WRITE_ONCE(ssk->sk_rcvbuf, val);
+ sk_memcg_budget_sync(ssk, gfp_memcg_charge());
tcp_set_rcvbuf(ssk, val);
}
@@ -1656,12 +1657,14 @@ int mptcp_set_rcvlowat(struct sock *sk, int val)
/* propagate the rcvbuf changes to all the subflows */
WRITE_ONCE(sk->sk_rcvbuf, space);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
mptcp_for_each_subflow(mptcp_sk(sk), subflow) {
struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
bool slow;
slow = lock_sock_fast(ssk);
WRITE_ONCE(ssk->sk_rcvbuf, space);
+ sk_memcg_budget_sync(ssk, gfp_memcg_charge());
WRITE_ONCE(tcp_sk(ssk)->window_clamp, val);
unlock_sock_fast(ssk, slow);
}
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index f0a6725d2c376..340eb5b2cacb8 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -1735,6 +1735,11 @@ void __mptcp_inherit_memcg(struct sock *sk, struct sock *ssk, gfp_t gfp)
if (!mem_cgroup_sockets_enabled || !sk->sk_socket)
return;
+ /* The subflow's budget charge went to its previous memcg: return
+ * it before the memcg association moves to the msk's one, the
+ * __sk_charge() below re-charges the budget there.
+ */
+ sk_memcg_budget_release(ssk);
mem_cgroup_sk_inherit(sk, ssk);
__sk_charge(ssk, gfp);
}
diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c
index 3a84d068fbf36..54dae37291c31 100644
--- a/tools/testing/selftests/cgroup/test_memcontrol.c
+++ b/tools/testing/selftests/cgroup/test_memcontrol.c
@@ -1335,9 +1335,12 @@ static int tcp_client(const char *cgroup, unsigned short port)
char servport[6];
int retries = 0x10; /* nice round number */
int sk, ret;
- long allocated;
+ long allocated, sock_pre;
allocated = cg_read_long(cgroup, "memory.current");
+ sock_pre = cg_read_key_long(cgroup, "memory.stat", "sock ");
+ if (sock_pre < 0)
+ return KSFT_FAIL;
snprintf(servport, sizeof(servport), "%hd", port);
ret = getaddrinfo(server, servport, NULL, &ai);
if (ret)
@@ -1365,8 +1368,12 @@ static int tcp_client(const char *cgroup, unsigned short port)
if (current < 0 || sock < 0)
goto close_sk;
- /* exclude the memory not related to socket connection */
- if (values_close(current - allocated, sock, 10)) {
+ /* exclude the memory not related to socket connection;
+ * compare the growth of both counters since the baseline,
+ * as the listening socket may already hold socket memory
+ * (e.g. its pre-charged memory budget) at the baseline.
+ */
+ if (values_close(current - allocated, sock - sock_pre, 10)) {
ret = KSFT_PASS;
break;
}
@@ -1384,8 +1391,9 @@ static int tcp_client(const char *cgroup, unsigned short port)
* The test forks a TCP server listens on a random port between 1000
* and 61000. Once it gets a client connection, it starts writing to
* its socket.
- * The TCP client interleaves reads from the socket with check whether
- * memory.current and memory.stat.sock are similar.
+ * The TCP client interleaves reads from the socket with checking whether
+ * the growth of memory.current and memory.stat.sock since the baseline
+ * are similar.
*/
static int test_memcg_sock(const char *root)
{
|
| 3/2 | 2026/09/26 12:35 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 13106 output: 30 thoughts: 1166 Thoughts: **Worthiness Determination: Memcg Socket Budgeting Patch** Okay, let's break this down. My initial impression is that this patch is a significant overhaul, right in an area we *love* to fuzz: networking and memory management. The fact that it touches `Documentation/admin-guide/cgroup-v2.rst` immediately flags a potential interaction with other subsystems. Reviewing the diff, I can see this introduces a new memory budgeting scheme for sockets tied into memcg. We're talking about a fundamental shift from per-allocation charging to upfront budgeting. That's a classic area to look at - changes to core memory accounting often expose subtle race conditions or unexpected interactions. The mentions of `sk_memcg_budget`, `sk_memcg_budget_lock`, and the associated sync/shrink/release functions immediately catch my eye. We have spinlocks and potentially complex interactions. This is prime real estate for concurrency bugs. The impact is broad. It's not just some obscure driver; it hits core socket infrastructure (`net/core/sock.c`), TCP (`net/ipv4/tcp.c`), and even MPTCP (`net/mptcp/`). These are all highly reachable and heavily fuzzed areas in syzkaller. Standard virtualized environments will exercise this code path. Let's apply the 'WorthFuzzing' criteria. * **Core Subsystems?** Yes, absolutely. Core networking and memory management (memcg) are present. * **Refactorings, State Management, and Locking?** Check. We see state changes, `sk_memcg_budget_lock`, and related locking. Memory accounting changes, and synchronization. This is a refactoring, right down to the memcg charge change. * **Complex Concurrency?** Yes. The commit message itself hints at nuanced locking and concurrency logic. "The snapshot is unlocked on purpose... spin_lock_bh... cumulative refunds can never exceed cumulative charges, so the memcg balance can not underflow". This means races and concurrency problems are highly likely. So, 'WorthFuzzing' is definitely TRUE. This goes on the list. The next step is to nail down the specifics: EnableConfigs and FocusSymbols. Now it's time to check the code and Kconfig using grepper or read-file. |
| 4/2 | 2026/09/26 12:36 | tool | grepper |
0mArgs:{
"Expression": "mem_cgroup_sk_enabled",
"PathPrefix": "include/"
}Results: {
"Output": "include/net/proto_memory.h=29=static inline bool sk_under_memory_pressure(const struct sock *sk)\n--\ninclude/net/proto_memory.h-33-\ninclude/net/proto_memory.h:34:\tif (mem_cgroup_sk_enabled(sk) \u0026\u0026\ninclude/net/proto_memory.h-35-\t mem_cgroup_sk_under_memory_pressure(sk))\n--\ninclude/net/sock.h=2715=static inline struct mem_cgroup *mem_cgroup_from_sk(const struct sock *sk)\n--\ninclude/net/sock.h-2719-\ninclude/net/sock.h:2720:static inline bool mem_cgroup_sk_enabled(const struct sock *sk)\ninclude/net/sock.h-2721-{\n--\ninclude/net/sock.h=2746=static inline struct mem_cgroup *mem_cgroup_from_sk(const struct sock *sk)\n--\ninclude/net/sock.h-2750-\ninclude/net/sock.h:2751:static inline bool mem_cgroup_sk_enabled(const struct sock *sk)\ninclude/net/sock.h-2752-{\n--\ninclude/net/tcp.h=299=static inline bool tcp_under_memory_pressure(const struct sock *sk)\ninclude/net/tcp.h-300-{\ninclude/net/tcp.h:301:\tif (mem_cgroup_sk_enabled(sk) \u0026\u0026\ninclude/net/tcp.h-302-\t mem_cgroup_sk_under_memory_pressure(sk))\n"
} |
| 5/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 15220 output: 36 thoughts: 27 Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own: **Investigating the `mem_cgroup_sk_enabled` Variable** Right, so I'm curious about this `mem_cgroup_sk_enabled` thing. My first instinct is to get a solid definition, a grounding, you know? Let's check the source code, specifically `include/net/sock.h`. I need to understand what this variable represents, its data type, and how it relates to networking and memory cgroups. I'm hoping the header file will give me the building blocks I need for context before I look for usage and implications elsewhere. Time to open that file and get to the bottom of this. |
| 6/2 | 2026/09/26 12:36 | tool | read-file |
0mArgs:{
"File": "include/net/sock.h",
"FirstLine": 2715,
"LineCount": 45
}Results: {
"Contents": "2715:\tstatic inline struct mem_cgroup *mem_cgroup_from_sk(const struct sock *sk)\n2716:\t{\n2717:\t\treturn sk-\u003esk_memcg;\n2718:\t}\n2719:\t\n2720:\tstatic inline bool mem_cgroup_sk_enabled(const struct sock *sk)\n2721:\t{\n2722:\t\treturn mem_cgroup_sockets_enabled \u0026\u0026 mem_cgroup_from_sk(sk);\n2723:\t}\n2724:\t\n2725:\tstatic inline bool mem_cgroup_sk_under_memory_pressure(const struct sock *sk)\n2726:\t{\n2727:\t\tstruct mem_cgroup *memcg = mem_cgroup_from_sk(sk);\n2728:\t\n2729:\t#ifdef CONFIG_MEMCG_V1\n2730:\t\tif (!cgroup_subsys_on_dfl(memory_cgrp_subsys))\n2731:\t\t\treturn !!memcg-\u003etcpmem_pressure;\n2732:\t#endif /* CONFIG_MEMCG_V1 */\n2733:\t\n2734:\t\tdo {\n2735:\t\t\tif (time_before64(get_jiffies_64(),\n2736:\t\t\t\t\t mem_cgroup_get_socket_pressure(memcg))) {\n2737:\t\t\t\tmemcg_memory_event(mem_cgroup_from_sk(sk),\n2738:\t\t\t\t\t\t MEMCG_SOCK_THROTTLED);\n2739:\t\t\t\treturn true;\n2740:\t\t\t}\n2741:\t\t} while ((memcg = parent_mem_cgroup(memcg)));\n2742:\t\n2743:\t\treturn false;\n2744:\t}\n2745:\t#else\n2746:\tstatic inline struct mem_cgroup *mem_cgroup_from_sk(const struct sock *sk)\n2747:\t{\n2748:\t\treturn NULL;\n2749:\t}\n2750:\t\n2751:\tstatic inline bool mem_cgroup_sk_enabled(const struct sock *sk)\n2752:\t{\n2753:\t\treturn false;\n2754:\t}\n2755:\t\n2756:\tstatic inline bool mem_cgroup_sk_under_memory_pressure(const struct sock *sk)\n2757:\t{\n2758:\t\treturn false;\n2759:\t}\n"
} |
| 7/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 16111 output: 36 thoughts: 47 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person: **Navigating the Net/Sock.h Landscape** Alright, I'm zeroing in on this particular area. My attention was immediately drawn to line 2715: I see that `#ifdef CONFIG_MEMCG` just precedes it. This immediately triggers a mental flag. Given the nature of my work, and the potential implications of memory control groups on socket behavior, my instinct is to dig deeper right here. To get a handle on what's actually happening, I need to understand the surrounding context. Therefore, I need to check lines 2700-2720 of `include/net/sock.h` right away. I need to understand the logical flow around that specific conditional. I need to understand the implications of the CONFIG_MEMCG preprocessor directive. I anticipate this will reveal the underlying mechanism. |
| 8/2 | 2026/09/26 12:36 | tool | read-file |
0mArgs:{
"File": "include/net/sock.h",
"FirstLine": 2700,
"LineCount": 20
}Results: {
"Contents": "2700:\t{\n2701:\t\treturn __sock_writeable(sk, refcount_read(\u0026sk-\u003esk_wmem_alloc));\n2702:\t}\n2703:\t\n2704:\tstatic inline gfp_t gfp_any(void)\n2705:\t{\n2706:\t\treturn in_softirq() ? GFP_ATOMIC : GFP_KERNEL;\n2707:\t}\n2708:\t\n2709:\tstatic inline gfp_t gfp_memcg_charge(void)\n2710:\t{\n2711:\t\treturn in_softirq() ? GFP_ATOMIC : GFP_KERNEL;\n2712:\t}\n2713:\t\n2714:\t#ifdef CONFIG_MEMCG\n2715:\tstatic inline struct mem_cgroup *mem_cgroup_from_sk(const struct sock *sk)\n2716:\t{\n2717:\t\treturn sk-\u003esk_memcg;\n2718:\t}\n2719:\t\n"
} |
| 9/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 16678 output: 22 thoughts: 61 Thoughts: Okay, let's break this down. **Investigating Memory Cgroup Socket Behavior** Right, so I'm digging into memory cgroups and socket interactions. My immediate thought is, let's see how `mem_cgroup_sk_enabled(sk)` behaves. That function *should* be the key. And the first thing that jumps out is that it's going to return `false` if `CONFIG_MEMCG` isn't even enabled in the kernel config. That's a fundamental prerequisite, a point easily overlooked if you're not paying close attention to the build. Now, the next logical question is, what controls the overall *enabling* of memory cgroups for sockets? I need to understand what flips the switch for `mem_cgroup_sockets_enabled`. It's not enough to know if a socket *is* managed; I need to know *when* that management is activated. That's the core of the issue, and where I need to focus my efforts. I need to trace this in the kernel source to see *how* `mem_cgroup_sockets_enabled` gets set or unset. That's the next step, so let's start with checking the implementation. |
| 10/2 | 2026/09/26 12:36 | tool | grepper |
0mArgs:{
"Expression": "mem_cgroup_sockets_enabled"
}Results: {
"Output": "include/linux/memcontrol.h=1633=extern struct static_key_false memcg_sockets_enabled_key;\ninclude/linux/memcontrol.h:1634:#define mem_cgroup_sockets_enabled static_branch_unlikely(\u0026memcg_sockets_enabled_key)\ninclude/linux/memcontrol.h-1635-\n--\ninclude/linux/memcontrol.h=1683=static inline int shrinker_id(struct shrinker *shrinker)\n--\ninclude/linux/memcontrol.h-1687-#else\ninclude/linux/memcontrol.h:1688:#define mem_cgroup_sockets_enabled 0\ninclude/linux/memcontrol.h-1689-\n--\ninclude/net/sock.h=2720=static inline bool mem_cgroup_sk_enabled(const struct sock *sk)\ninclude/net/sock.h-2721-{\ninclude/net/sock.h:2722:\treturn mem_cgroup_sockets_enabled \u0026\u0026 mem_cgroup_from_sk(sk);\ninclude/net/sock.h-2723-}\n--\nmm/memcontrol.c=5565=void mem_cgroup_sk_alloc(struct sock *sk)\n--\nmm/memcontrol.c-5568-\nmm/memcontrol.c:5569:\tif (!mem_cgroup_sockets_enabled)\nmm/memcontrol.c-5570-\t\treturn;\n--\nnet/ipv4/af_inet.c=760=void __inet_accept(struct socket *sock, struct socket *newsock, struct sock *newsk)\nnet/ipv4/af_inet.c-761-{\nnet/ipv4/af_inet.c:762:\tif (mem_cgroup_sockets_enabled) {\nnet/ipv4/af_inet.c-763-\t\tmem_cgroup_sk_alloc(newsk);\n--\nnet/mptcp/protocol.c=4319=static void mptcp_graft_subflows(struct sock *sk)\n--\nnet/mptcp/protocol.c-4323-\nnet/mptcp/protocol.c:4324:\tif (mem_cgroup_sockets_enabled) {\nnet/mptcp/protocol.c-4325-\t\tLIST_HEAD(join_list);\n--\nnet/mptcp/subflow.c=1732=void __mptcp_inherit_memcg(struct sock *sk, struct sock *ssk, gfp_t gfp)\n--\nnet/mptcp/subflow.c-1734-\t/* Only if the msk has been accepted already (and not orphaned).*/\nnet/mptcp/subflow.c:1735:\tif (!mem_cgroup_sockets_enabled || !sk-\u003esk_socket)\nnet/mptcp/subflow.c-1736-\t\treturn;\n--\nnet/mptcp/subflow.c=1763=static void mptcp_attach_cgroup(struct sock *parent, struct sock *child)\n--\nnet/mptcp/subflow.c-1765-\t__mptcp_inherit_cgrp_data(parent, child);\nnet/mptcp/subflow.c:1766:\tif (mem_cgroup_sockets_enabled)\nnet/mptcp/subflow.c-1767-\t\tmem_cgroup_sk_inherit(parent, child);\n"
} |
| 11/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 17892 output: 22 thoughts: 18 Thoughts: Okay, let's dive into this. Here's my thought process on tracking down `memcg_sockets_enabled_key`: **Tracing the Activation of `memcg_sockets_enabled_key`** Right, so I need to find out where this `memcg_sockets_enabled_key` is being activated, or even *how* it's being used. I'll need to start by tracing its usage within the kernel source code. I'm thinking I'll begin with a straightforward `grep` or similar search. I need to understand the logic flow surrounding it. Is it a compile-time option? A run-time configuration? Is it part of some more extensive initialization routine, or is it triggered by some specific event? Understanding the surrounding context is crucial. Once I've found the usage, I'll analyze the surrounding code to determine the conditions under which the activation occurs. I'll be looking for any dependencies, configuration flags, or kernel parameters that influence its state. Ultimately, I need to know *why* and *when* this key becomes enabled, as this will illuminate its overall role. I suspect this will be interesting, based on my past experience with these sorts of kernel parameters. |
| 12/2 | 2026/09/26 12:36 | tool | grepper |
0mArgs:{
"Expression": "memcg_sockets_enabled_key"
}Results: {
"Output": "include/linux/memcontrol.h=1631=struct sock;\ninclude/linux/memcontrol.h-1632-#ifdef CONFIG_MEMCG\ninclude/linux/memcontrol.h:1633:extern struct static_key_false memcg_sockets_enabled_key;\ninclude/linux/memcontrol.h:1634:#define mem_cgroup_sockets_enabled static_branch_unlikely(\u0026memcg_sockets_enabled_key)\ninclude/linux/memcontrol.h-1635-\n--\nmm/memcontrol-v1.c=1965=static int memcg_update_tcp_max(struct mem_cgroup *memcg, unsigned long max)\n--\nmm/memcontrol-v1.c-1991-\t\t */\nmm/memcontrol-v1.c:1992:\t\tstatic_branch_inc(\u0026memcg_sockets_enabled_key);\nmm/memcontrol-v1.c-1993-\t\tmemcg-\u003etcpmem_active = true;\n--\nmm/memcontrol.c=4218=mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)\n--\nmm/memcontrol.c-4262-\tif (memcg_on_dfl \u0026\u0026 !cgroup_memory_nosocket)\nmm/memcontrol.c:4263:\t\tstatic_branch_inc(\u0026memcg_sockets_enabled_key);\nmm/memcontrol.c-4264-\n--\nmm/memcontrol.c=4383=static void mem_cgroup_css_free(struct cgroup_subsys_state *css)\n--\nmm/memcontrol.c-4392-\tif (cgroup_subsys_on_dfl(memory_cgrp_subsys) \u0026\u0026 !cgroup_memory_nosocket)\nmm/memcontrol.c:4393:\t\tstatic_branch_dec(\u0026memcg_sockets_enabled_key);\nmm/memcontrol.c-4394-\nmm/memcontrol.c-4395-\tif (!cgroup_subsys_on_dfl(memory_cgrp_subsys) \u0026\u0026 memcg1_tcpmem_active(memcg))\nmm/memcontrol.c:4396:\t\tstatic_branch_dec(\u0026memcg_sockets_enabled_key);\nmm/memcontrol.c-4397-\n--\nmm/memcontrol.c=5513=void mem_cgroup_migrate(struct folio *old, struct folio *new)\n--\nmm/memcontrol.c-5561-\nmm/memcontrol.c:5562:DEFINE_STATIC_KEY_FALSE(memcg_sockets_enabled_key);\nmm/memcontrol.c:5563:EXPORT_SYMBOL(memcg_sockets_enabled_key);\nmm/memcontrol.c-5564-\n"
} |
| 13/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 18894 output: 26 thoughts: 86 Thoughts: Okay, here's my summary, speaking as the expert I am: **Initial Configuration Dive** Right, let's get down to brass tacks. I need to understand this new configuration landscape, especially regarding changes to socket accounting. My immediate thought is to confirm what `CONFIG_MEMCG` is set to because, as I understand it, that enables cgroup v2 memory controller socket accounting. I want to make sure the accounting is working as expected. Then, since I know the MPTCP code base has seen modifications, specifically in `net/mptcp`, I should verify the `CONFIG_MPTCP` setting. The file changes I saw included updates in `net/mptcp/protocol.c`, `net/mptcp/sockopt.c`, and `net/mptcp/subflow.c`. That gives me a roadmap to understand how everything links. Finally, I'll review the Kconfig file for MPTCP itself to fully grasp how this is all configured and its implications. |
| 14/2 | 2026/09/26 12:36 | tool | grepper |
0mArgs:{
"Expression": "config MPTCP",
"PathPrefix": "net/"
}Results: {
"Output": "net/mptcp/Kconfig-1-\nnet/mptcp/Kconfig:2:config MPTCP\nnet/mptcp/Kconfig-3-\tbool \"MPTCP: Multipath TCP\"\n--\nnet/mptcp/Kconfig=16=config INET_MPTCP_DIAG\n--\nnet/mptcp/Kconfig-19-\nnet/mptcp/Kconfig:20:config MPTCP_IPV6\nnet/mptcp/Kconfig-21-\tbool \"MPTCP: IPv6 support for Multipath TCP\"\n--\nnet/mptcp/Kconfig-24-\nnet/mptcp/Kconfig:25:config MPTCP_KUNIT_TEST\nnet/mptcp/Kconfig-26-\ttristate \"This builds the MPTCP KUnit tests\" if !KUNIT_ALL_TESTS\n"
} |
| 15/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 19444 output: 34 thoughts: 19 Thoughts: Here's a summary of my thought process, keeping in mind my expert understanding: **Navigating the Network Stack** Okay, so I need to dive into the Multipath TCP configuration options. My instinct tells me that the place to start is the `Kconfig` file. I'm thinking specifically about the `net/mptcp/` directory within the kernel source tree. Let's start with a look around the first line of the file, because that's usually where the top-level configuration statements are made, and I expect I'll find the general configuration header and perhaps some top-level include statements there. This will give me a good lay of the land, helping me understand the overall structure and dependency relationships within the MPTCP configuration. This should provide a decent starting point for understanding how the core features of Multipath TCP are enabled or disabled and their dependencies. |
| 16/2 | 2026/09/26 12:36 | tool | read-file |
0mArgs:{
"File": "net/mptcp/Kconfig",
"FirstLine": 1,
"LineCount": 20
}Results: {
"Contents": " 1:\t\n 2:\tconfig MPTCP\n 3:\t\tbool \"MPTCP: Multipath TCP\"\n 4:\t\tdepends on INET\n 5:\t\tselect SKB_EXTENSIONS\n 6:\t\tselect CRYPTO_LIB_SHA256\n 7:\t\tselect CRYPTO_LIB_UTILS\n 8:\t\thelp\n 9:\t\t Multipath TCP (MPTCP) connections send and receive data over multiple\n 10:\t\t subflows in order to utilize multiple network paths. Each subflow\n 11:\t\t uses the TCP protocol, and TCP options carry header information for\n 12:\t\t MPTCP.\n 13:\t\n 14:\tif MPTCP\n 15:\t\n 16:\tconfig INET_MPTCP_DIAG\n 17:\t\tdepends on INET_DIAG\n 18:\t\tdef_tristate INET_DIAG\n 19:\t\n 20:\tconfig MPTCP_IPV6\n"
} |
| 17/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 19929 output: 25 thoughts: 24 Thoughts: Here's my take on those thoughts: **MPTCP, INET, and MEMCG: A Quick Drill Down** Okay, so I'm thinking about MPTCP – and the obvious connection is INET. That's the baseline, the foundation. MPTCP *has* to rely on that. But then my mind takes a quick detour... what about memory control groups? MEMCGs could potentially play a role in how MPTCP manages resources, especially in containerized environments. It makes sense to quickly double-check the kernel's configuration options to see if there's any explicit link or configuration item in `init/Kconfig` that addresses MPTCP's interaction with MEMCG. Gotta confirm if this connection is built in, an ancillary concern, or perhaps entirely independent. Better safe than sorry. |
| 18/2 | 2026/09/26 12:36 | tool | grepper |
0mArgs:{
"Expression": "config MEMCG",
"PathPrefix": "init/"
}Results: {
"Output": "init/Kconfig=1075=config CGROUP_FAVOR_DYNMODS\n--\ninit/Kconfig-1084-\ninit/Kconfig:1085:config MEMCG\ninit/Kconfig-1086-\tbool \"Memory controller\"\n--\ninit/Kconfig-1093-\ninit/Kconfig:1094:config MEMCG_NMI_UNSAFE\ninit/Kconfig-1095-\tbool\n--\ninit/Kconfig-1100-\ninit/Kconfig:1101:config MEMCG_NMI_SAFETY_REQUIRES_ATOMIC\ninit/Kconfig-1102-\tbool\n--\ninit/Kconfig-1107-\ninit/Kconfig:1108:config MEMCG_V1\ninit/Kconfig-1109-\tbool \"Legacy cgroup v1 memory controller\"\n"
} |
| 19/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20379 output: 27 thoughts: 1471 Thoughts: **Focusing on Memory Control for Sockets** Okay, let's break this down. We're looking at a patch that introduces memory control within the kernel, specifically targeting sockets and their interaction with memory cgroups (memcg). The presence of `CONFIG_MEMCG`, `CONFIG_INET`, and `CONFIG_MPTCP` suggests this is about socket memory management in a networked environment. The inclusion of `INET` naturally implies `TCP`. The goal is to identify the critical functions that should guide fuzzing efforts, adhering to the "FocusSymbols" guidelines. The core principle is to avoid generic, ubiquitous functions that would dilute fuzzing efforts. Instead, we need to target specific feature logic and entry points. Looking at the symbol modifications, the newly introduced functions related to memcg are clearly the focus: `sk_memcg_budget_sync`, `sk_memcg_budget_shrink`, and `sk_memcg_budget_release`. These are the heart of the changes, handling memory charging, shrinking, and release related to memcg. The memory reservation functions `sock_reserve_memory` and `sock_release_reserved_memory` are also directly altered, making them important to consider. Additionally, we need to consider altered functions involved in buffer management: `tcp_sndbuf_expand`, `tcp_rcvbuf_grow`, `tcp_clamp_window`, and `mptcp_rcvbuf_grow`. These are more specific to TCP and MPTCP, respectively. `__mptcp_inherit_memcg` is also relevant, as it handles memcg inheritance for subflows. Now, let's identify what to *exclude*. Functions like `sk_alloc`, `sk_clone`, `__sk_destruct`, `sock_init_data_uid` are too generic; they're called on *every* socket creation and destruction. Similarly, `sk_setsockopt` is a general socket option handler. `__sk_mem_raise_allocated` and `__sk_mem_reduce_allocated` are also very ubiquitous, being used at every skb allocation and deallocation. While changes might exist, their hotness could dilute the fuzzing. `tcp_set_rcvlowat` and `mptcp_set_rcvlowat` are less critical, though could be added if needed, but the primary focus is still the new functions. So, the key functions to include for fuzzing should be: `sk_memcg_budget_sync`, `sk_memcg_budget_shrink`, `sk_memcg_budget_release`, `sock_reserve_memory`, `sock_release_reserved_memory`, `tcp_sndbuf_expand`, `tcp_rcvbuf_grow`, `tcp_clamp_window`, `mptcp_rcvbuf_grow`, and `__mptcp_inherit_memcg`. The new memcg functions are particularly important as they are the core logic and entry points for the feature. We've verified that the crucial memcg functions are not static and are properly exported for symbol access. The callers of the reserve and release functions in `sock.c` will be worth examining too. This should allow for a focused fuzzing campaign targeting the critical memory control changes. |
| 20/2 | 2026/09/26 12:36 | tool | grepper |
0mArgs:{
"Expression": "sock_reserve_memory",
"PathPrefix": "net/"
}Results: {
"Output": "net/core/sock.c=1024=static void sock_release_reserved_memory(struct sock *sk, int bytes)\n--\nnet/core/sock.c-1035-\nnet/core/sock.c:1036:static int sock_reserve_memory(struct sock *sk, int bytes)\nnet/core/sock.c-1037-{\n--\nnet/core/sock.c=1206=int sk_setsockopt(struct sock *sk, int level, int optname,\n--\nnet/core/sock.c-1687-\t\telse\nnet/core/sock.c:1688:\t\t\tret = sock_reserve_memory(sk, delta);\nnet/core/sock.c-1689-\t\tbreak;\n"
} |
| 21/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 22772 output: 21 thoughts: 293 Thoughts: **Investigating Buffer Management in Network Stack** Okay, so I'm focusing on buffer management, specifically how memory reservation works. I'm starting with `sock_reserve_memory` and `sock_release_reserved_memory` within `sock.c`. These are the core functions for `SO_RESERVE_MEM`, and even if the compiler inlines them into `sk_setsockopt`, I know they're fundamentally linked to this specific socket option. Next, I need to look at how this impacts the TCP and MPTCP stack, specifically `tcp_sndbuf_expand`, `tcp_clamp_window`, `tcp_rcvbuf_grow`, and `tcp_should_expand_sndbuf`. The key here is determining their scope. Are these global functions accessible throughout the kernel, or are they static, limiting their scope within their respective files? In `net/ipv4/tcp_input.c`, I see `tcp_sndbuf_expand`, `tcp_clamp_window`, and `tcp_should_expand_sndbuf` are indeed static, while `tcp_rcvbuf_grow` is non-static. This tells me that `tcp_rcvbuf_grow` is more generally accessible, perhaps part of a larger, more generic buffering mechanism or part of the public API. Now I'll shift my attention to the MPTCP side, checking `net/mptcp/protocol.c`. Here, `mptcp_rcvbuf_grow`, `mptcp_graft_subflows` are static, and `__mptcp_inherit_memcg` is non-static. I'm thinking I'll need to examine `tcp_rcvbuf_grow` and how it is used. This will help with understanding potential dependencies. |
| 22/2 | 2026/09/26 12:36 | tool | grepper |
0mArgs:{
"Expression": "tcp_rcvbuf_grow"
}Results: {
"Output": "Documentation/networking/net_cachelines/netns_ipv4_sysctl.rst=106=u8 sysctl_tcp_no_ssthresh_metrics_save TCP_LAST_ACK/tcp_...\nDocumentation/networking/net_cachelines/netns_ipv4_sysctl.rst:107:u8 sysctl_tcp_moderate_rcvbuf read_mostly tcp_rcvbuf_grow()\nDocumentation/networking/net_cachelines/netns_ipv4_sysctl.rst:108:u32 sysctl_tcp_rcvbuf_low_rtt read_mostly tcp_rcvbuf_grow()\nDocumentation/networking/net_cachelines/netns_ipv4_sysctl.rst-109-u8 sysctl_tcp_shrink_window read_mostly read_mostly __tcp_select_wind...\n--\ninclude/net/tcp.h=395=void tcp_rcv_established(struct sock *sk, struct sk_buff *skb);\ninclude/net/tcp.h:396:void tcp_rcvbuf_grow(struct sock *sk, u32 newval);\ninclude/net/tcp.h-397-void tcp_rcv_space_adjust(struct sock *sk);\n--\ninclude/trace/events/mptcp.h=164=TRACE_EVENT(subflow_check_data_avail,\n--\ninclude/trace/events/mptcp.h-186-\ninclude/trace/events/mptcp.h:187:TRACE_EVENT(mptcp_rcvbuf_grow,\ninclude/trace/events/mptcp.h-188-\n--\ninclude/trace/events/tcp.h=200=DEFINE_EVENT(tcp_event_sk, tcp_rcv_space_adjust,\n--\ninclude/trace/events/tcp.h-206-\ninclude/trace/events/tcp.h:207:TRACE_EVENT(tcp_rcvbuf_grow,\ninclude/trace/events/tcp.h-208-\n--\nnet/ipv4/tcp_input.c=896=static inline void tcp_rcv_rtt_measure_ts(struct sock *sk,\n--\nnet/ipv4/tcp_input.c-913-\nnet/ipv4/tcp_input.c:914:void tcp_rcvbuf_grow(struct sock *sk, u32 newval)\nnet/ipv4/tcp_input.c-915-{\n--\nnet/ipv4/tcp_input.c=964=void tcp_rcv_space_adjust(struct sock *sk)\n--\nnet/ipv4/tcp_input.c-989-\nnet/ipv4/tcp_input.c:990:\ttrace_tcp_rcvbuf_grow(sk, time);\nnet/ipv4/tcp_input.c-991-\nnet/ipv4/tcp_input.c:992:\ttcp_rcvbuf_grow(sk, copied);\nnet/ipv4/tcp_input.c-993-\n--\nnet/ipv4/tcp_input.c=5385=static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb)\n--\nnet/ipv4/tcp_input.c-5532-\tif (sk-\u003esk_socket)\nnet/ipv4/tcp_input.c:5533:\t\ttcp_rcvbuf_grow(sk, tp-\u003ercvq_space.space);\nnet/ipv4/tcp_input.c-5534-}\n--\nnet/mptcp/protocol.c=202=static bool mptcp_ooo_try_coalesce(struct mptcp_sock *msk, struct sk_buff *to,\n--\nnet/mptcp/protocol.c-210-\nnet/mptcp/protocol.c:211:/* \"inspired\" by tcp_rcvbuf_grow(), main difference:\nnet/mptcp/protocol.c-212- * - mptcp does not maintain a msk-level window clamp\n--\nnet/mptcp/protocol.c-214- */\nnet/mptcp/protocol.c:215:static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval)\nnet/mptcp/protocol.c-216-{\n--\nnet/mptcp/protocol.c=2234=static void mptcp_rcv_space_adjust(struct mptcp_sock *msk, int copied)\n--\nnet/mptcp/protocol.c-2262-\nnet/mptcp/protocol.c:2263:\ttrace_mptcp_rcvbuf_grow(sk, time);\nnet/mptcp/protocol.c:2264:\tif (mptcp_rcvbuf_grow(sk, copied)) {\nnet/mptcp/protocol.c-2265-\t\t/* Make subflows follow along. If we do not do this, we\n--\nnet/mptcp/protocol.c-2277-\t\t\tif (tcp_sk(ssk)-\u003ercvq_space.space)\nnet/mptcp/protocol.c:2278:\t\t\t\ttcp_rcvbuf_grow(ssk, copied);\nnet/mptcp/protocol.c-2279-\t\t\tunlock_sock_fast(ssk, slow);\n"
} |
| 23/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24809 output: 22 thoughts: 41 Thoughts: Here's my summary, as though I'm thinking through this process: **Investigating TCP Receive Buffer Growth** Okay, so `tcp_rcvbuf_grow` isn't static. That immediately tells me it's likely intended to be used externally to `tcp_input.c`. That's a key piece of information. Since it's visible, someone's probably calling it from outside that file. Now, I need to understand *how* it's being used and, more importantly, *why*. My next step is clearly going to be to dig into `tcp_set_rcvlowat`. Let's see what that function does and where it fits into the bigger picture of receive buffer management. I'm betting that function's the key to this particular thread of inquiry. |
| 24/2 | 2026/09/26 12:36 | tool | grepper |
0mArgs:{
"Expression": "tcp_set_rcvlowat"
}Results: {
"Output": "include/net/tcp.h=513=int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,\ninclude/net/tcp.h-514-\t\tint flags);\ninclude/net/tcp.h:515:int tcp_set_rcvlowat(struct sock *sk, int val);\ninclude/net/tcp.h-516-void tcp_set_rcvbuf(struct sock *sk, int val);\n--\nnet/ipv4/af_inet.c=1057=const struct proto_ops inet_stream_ops = {\n--\nnet/ipv4/af_inet.c-1087-#endif\nnet/ipv4/af_inet.c:1088:\t.set_rcvlowat\t = tcp_set_rcvlowat,\nnet/ipv4/af_inet.c-1089-\t.set_rcvbuf\t = tcp_set_rcvbuf,\n--\nnet/ipv4/tcp.c=1830=int tcp_peek_len(struct socket *sock)\n--\nnet/ipv4/tcp.c-1835-/* Make sure sk_rcvbuf is big enough to satisfy SO_RCVLOWAT hint */\nnet/ipv4/tcp.c:1836:int tcp_set_rcvlowat(struct sock *sk, int val)\nnet/ipv4/tcp.c-1837-{\n--\nnet/ipv6/af_inet6.c=656=const struct proto_ops inet6_stream_ops = {\n--\nnet/ipv6/af_inet6.c-686-#endif\nnet/ipv6/af_inet6.c:687:\t.set_rcvlowat\t = tcp_set_rcvlowat,\nnet/ipv6/af_inet6.c-688-\t.set_rcvbuf\t = tcp_set_rcvbuf,\n--\nnet/mptcp/protocol.c=4697=static const struct proto_ops mptcp_stream_ops = {\n--\nnet/mptcp/protocol.c-4715-\t.mmap\t\t = sock_no_mmap,\nnet/mptcp/protocol.c:4716:\t.set_rcvlowat\t = mptcp_set_rcvlowat,\nnet/mptcp/protocol.c-4717-\t.read_sock\t = mptcp_read_sock,\n--\nnet/mptcp/protocol.c=4806=static const struct proto_ops mptcp_v6_stream_ops = {\n--\nnet/mptcp/protocol.c-4827-#endif\nnet/mptcp/protocol.c:4828:\t.set_rcvlowat\t = mptcp_set_rcvlowat,\nnet/mptcp/protocol.c-4829-\t.read_sock\t = mptcp_read_sock,\n--\nnet/mptcp/protocol.h=870=static inline bool mptcp_epollin_ready(const struct sock *sk)\n--\nnet/mptcp/protocol.h-883-\nnet/mptcp/protocol.h:884:int mptcp_set_rcvlowat(struct sock *sk, int val);\nnet/mptcp/protocol.h-885-\n--\nnet/mptcp/sockopt.c=1607=void mptcp_sockopt_sync_locked(struct mptcp_sock *msk, struct sock *ssk)\n--\nnet/mptcp/sockopt.c-1630- */\nnet/mptcp/sockopt.c:1631:int mptcp_set_rcvlowat(struct sock *sk, int val)\nnet/mptcp/sockopt.c-1632-{\n"
} |
| 25/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 26014 output: 18 thoughts: 169 Thoughts: **Analyzing Socket Memory Management in the Kernel** Okay, so I'm digging into the kernel's socket memory management, and I'm seeing a pattern. It looks like both `tcp_set_rcvlowat` and `mptcp_set_rcvlowat`, despite being in different modules, are essentially doing the same thing: implementing the `.set_rcvlowat` proto operation. Interesting that they have separate entry points for this, I'll need to look at why that is. And hey, `tcp_rcvbuf_grow` is also not static, which is worth noting. I'm particularly interested in memory control groups (memcgs) here, and I'm spotting the introduction of several new, non-static functions in `net/core/sock.c` specifically for that: `sk_memcg_budget_sync`, `sk_memcg_budget_shrink`, and `sk_memcg_budget_release`. This is the core of where the memcg integration is happening, and is a good place to start. I also noticed that `__mptcp_inherit_memcg` in `net/mptcp/subflow.c` is non-static, so the Multipath TCP code is also engaging in memcg management. I'm going to have to dive into that more later. Now, I want to see how these memcg interactions tie into the more general memory management. I'm going to check the ubiquity of `__sk_charge` to see how that integrates. Where is it being called? I need to understand the full picture of how these socket operations manage memory consumption and how they interact with memcgs. |
| 26/2 | 2026/09/26 12:36 | tool | grepper |
0mArgs:{
"Expression": "__sk_charge"
}Results: {
"Output": "include/net/sock.h=1662=static inline void sk_mem_uncharge(struct sock *sk, int size)\n--\ninclude/net/sock.h-1669-\ninclude/net/sock.h:1670:void __sk_charge(struct sock *sk, gfp_t gfp);\ninclude/net/sock.h-1671-\n--\nnet/core/sock.c=2502=struct sock *sk_clone(const struct sock *sk, const gfp_t priority,\n--\nnet/core/sock.c-2560-\t/* The child has not charged anything to the memcg yet: it will be\nnet/core/sock.c:2561:\t * charged for its budget at accept() time (__sk_charge()).\nnet/core/sock.c-2562-\t * sock_copy() copied the parent's lock bytes: re-init the lock.\n--\nnet/core/sock.c=3651=EXPORT_SYMBOL(sk_memcg_budget_release);\nnet/core/sock.c-3652-\nnet/core/sock.c:3653:void __sk_charge(struct sock *sk, gfp_t gfp)\nnet/core/sock.c-3654-{\n--\nnet/ipv4/af_inet.c=760=void __inet_accept(struct socket *sock, struct socket *newsock, struct sock *newsk)\n--\nnet/ipv4/af_inet.c-763-\t\tmem_cgroup_sk_alloc(newsk);\nnet/ipv4/af_inet.c:764:\t\t__sk_charge(newsk, GFP_KERNEL);\nnet/ipv4/af_inet.c-765-\t}\n--\nnet/mptcp/subflow.c=1732=void __mptcp_inherit_memcg(struct sock *sk, struct sock *ssk, gfp_t gfp)\n--\nnet/mptcp/subflow.c-1739-\t * it before the memcg association moves to the msk's one, the\nnet/mptcp/subflow.c:1740:\t * __sk_charge() below re-charges the budget there.\nnet/mptcp/subflow.c-1741-\t */\n--\nnet/mptcp/subflow.c-1743-\tmem_cgroup_sk_inherit(sk, ssk);\nnet/mptcp/subflow.c:1744:\t__sk_charge(ssk, gfp);\nnet/mptcp/subflow.c-1745-}\n"
} |
| 27/2 | 2026/09/26 12:36 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 27222 output: 266 thoughts: 944 Thoughts: **Analyzing Socket Memory Accounting for Fuzzing** Okay, so I've been digging into this code, and it's looking like `__sk_charge` is surprisingly specific. It's *only* called from `__inet_accept` and `__mptcp_inherit_memcg`, which means it's not a general memory allocator, but rather a dedicated function for memcg socket buffer charging specifically during accept or MPTCP subflow inheritance. Its implementation has been changed to `sk_memcg_budget_sync` and `kmem_cache_charge(sk, gfp)`, so I'm interested in how these are called too. Now, which symbols are the most relevant for fuzzing? I think `sk_memcg_budget_sync`, `sk_memcg_budget_shrink`, `sk_memcg_budget_release`, and the `__sk_charge` function itself are top contenders. Also, the functions `tcp_rcvbuf_grow`, `tcp_set_rcvlowat`, `mptcp_set_rcvlowat` and `__mptcp_inherit_memcg`, which are more specific to TCP and MPTCP buffer management and settings. I thought about `tcp_init_sock` and `mptcp_init_sock`, but those are too broad because they're called every time a TCP socket is created. `sock_reserve_memory` looked promising, but it's a `static` function, so it's probably inlined. I've verified that all of the key symbols are non-static. The first four are in `sock.c` and the others are in `tcp_input.c`, `tcp.c`, `sockopt.c`, and `subflow.c`, so I'm confident about their location. Configuration-wise, it's pretty straightforward: we need `CONFIG_MEMCG` for the memcg accounting, `CONFIG_INET` for the TCP/IPv4 base, and `CONFIG_MPTCP` for the MPTCP changes. So, stripping the `CONFIG_` prefix, we need `MEMCG`, `INET`, and `MPTCP`. And the requirements: The patch changes how TCP and MPTCP socket memory is accounted for cgroups, charging the established memory budget upfront (sndbuf, rcvbuf, SO_RESERVE_MEM) instead of per packet buffer allocation. It introduces primitives and functions for synchronization. Core networking and memory management paths are modified, involve concurrent budget updates, making them valuable targets to uncover races, memory accounting mismatches, or deadlocks. I think I've got a good handle on this now. I'm ready to call `set-results`. |
| 28/2 | 2026/09/26 12:36 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"MEMCG",
"INET",
"MPTCP"
],
"FocusSymbols": [
"sk_memcg_budget_sync",
"sk_memcg_budget_shrink",
"sk_memcg_budget_release",
"__sk_charge",
"tcp_rcvbuf_grow",
"tcp_set_rcvlowat",
"mptcp_set_rcvlowat",
"__mptcp_inherit_memcg"
],
"Reasoning": "The patch modifies core socket, TCP, and MPTCP memory accounting by introducing upfront socket budget charging to memcg (covering send buffer, receive buffer, and SO_RESERVE_MEM) rather than charging per-allocation. It introduces new locking (sk_memcg_budget_lock), synchronization helpers (sk_memcg_budget_sync, sk_memcg_budget_shrink, sk_memcg_budget_release), and modifies socket option and buffer growth handling across TCP and MPTCP. These changes in core networking and memory management execute concurrently across user threads, softirqs, and RCU callbacks, making them prime targets for fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"MEMCG",
"INET",
"MPTCP"
],
"FocusSymbols": [
"sk_memcg_budget_sync",
"sk_memcg_budget_shrink",
"sk_memcg_budget_release",
"__sk_charge",
"tcp_rcvbuf_grow",
"tcp_set_rcvlowat",
"mptcp_set_rcvlowat",
"__mptcp_inherit_memcg"
],
"Reasoning": "The patch modifies core socket, TCP, and MPTCP memory accounting by introducing upfront socket budget charging to memcg (covering send buffer, receive buffer, and SO_RESERVE_MEM) rather than charging per-allocation. It introduces new locking (sk_memcg_budget_lock), synchronization helpers (sk_memcg_budget_sync, sk_memcg_budget_shrink, sk_memcg_budget_release), and modifies socket option and buffer growth handling across TCP and MPTCP. These changes in core networking and memory management execute concurrently across user threads, softirqs, and RCU callbacks, making them prime targets for fuzzing.",
"WorthFuzzing": true
} |
| 29/1 | 2026/09/26 12:36 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 30/2 | 2026/09/26 12:36 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch series refactors memory cgroup (memcg) accounting for TCP and MPTCP sockets, shifting from per-allocation charges (__sk_mem_raise_allocated / __sk_mem_reduce_allocated) to upfront budget-based charging against sk_sndbuf, sk_rcvbuf, and sk_reserved_mem via sk_memcg_budget_sync/shrink/release.\n\nEvaluation against KMSAN vs KASAN:\n1. No uninitialized memory usage or branching on uninitialized data: The newly added struct sock members (sk_memcg_budget and sk_memcg_budget_lock) are allocated with __GFP_ZERO in sk_alloc() and explicitly initialized in sk_clone(). All local variables in the new helper functions (sk_memcg_budget_pages, sk_memcg_budget_sync, sk_memcg_budget_shrink, sk_memcg_budget_release) are explicitly initialized before use.\n2. No kernel-to-user data leaks or uninitialized padding exposure: sk_memcg_budget is strictly internal socket accounting state and is not copied to userspace via getsockopt, sock_diag, ioctl, netlink, or BPF.\n3. No altered bounds or buffer sizes for userspace copies: The changes alter memcg accounting metadata calculations without changing buffer indexing, payload copying, or packet data handling.\n4. Any potential issues introduced by this patch (such as spinlock concurrency bugs, memcg refcounting / accounting underflows, or socket lifecycle races) are covered by KASAN, LOCKDEP, and existing kernel assertions rather than KMSAN.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 74c8ef3e38a9935e88db6f422e1c29693a16522c
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Sep 26 12:35:55 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index 86a2a0099178e..db31966657bde 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -1309,7 +1309,11 @@ following types of memory usages are tracked.
- Kernel data structures such as dentries and inodes.
-- TCP socket buffers.
+- TCP socket buffers. These are charged upfront for the socket's
+ established memory budget (send buffer, receive buffer and
+ SO_RESERVE_MEM reservation) when the budget is created or resized,
+ and returned when the budget shrinks or the socket is closed; the
+ charge is not tied to per-packet buffer usage.
The above list may expand in the future for better coverage.
diff --git a/include/net/sock.h b/include/net/sock.h
index 60ea55dc18854..88fe6d93f7579 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -458,6 +458,15 @@ struct sock {
__cacheline_group_begin(sock_read_rxtx);
int sk_err;
struct socket *sk_socket;
+ /* Pages of the socket's memory budget (sndbuf + rcvbuf +
+ * SO_RESERVE_MEM) currently charged to the memcg. Paired with
+ * every mem_cgroup_sk_charge()/mem_cgroup_sk_uncharge() done on
+ * behalf of this socket, so it can be refunded exactly once.
+ * Guarded by sk_memcg_budget_lock; the charge/uncharge run
+ * outside the lock (they may sleep/reclaim).
+ */
+ int sk_memcg_budget;
+ spinlock_t sk_memcg_budget_lock;
#ifdef CONFIG_MEMCG
struct mem_cgroup *sk_memcg;
#endif
@@ -1553,6 +1562,9 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind);
int __sk_mem_schedule(struct sock *sk, int size, int kind);
void __sk_mem_reduce_allocated(struct sock *sk, int amount);
void __sk_mem_reclaim(struct sock *sk, int amount);
+bool sk_memcg_budget_sync(struct sock *sk, gfp_t gfp);
+void sk_memcg_budget_shrink(struct sock *sk);
+void sk_memcg_budget_release(struct sock *sk);
#define SK_MEM_SEND 0
#define SK_MEM_RECV 1
@@ -1622,6 +1634,12 @@ static inline void sk_mem_reclaim(struct sock *sk)
if (!sk_has_account(sk))
return;
+ /* Return the part of the memcg budget charge that a budget
+ * shrink (by any writer) no longer backs. Never charges, so
+ * it is safe to call locklessly from skb destructors.
+ */
+ sk_memcg_budget_shrink(sk);
+
reclaimable = sk->sk_forward_alloc - sk_unused_reserved_mem(sk);
if (reclaimable >= (int)PAGE_SIZE)
@@ -2640,6 +2658,9 @@ static inline void sk_stream_moderate_sndbuf(struct sock *sk)
val = max_t(u32, val, sk_unused_reserved_mem(sk));
WRITE_ONCE(sk->sk_sndbuf, max_t(u32, val, SOCK_MIN_SNDBUF));
+
+ /* The sndbuf shrink released part of the memcg budget charge. */
+ sk_memcg_budget_shrink(sk);
}
/**
diff --git a/net/core/sock.c b/net/core/sock.c
index 2948dffcc3e1b..d3374e563a9a0 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -986,6 +986,9 @@ static void __sock_set_rcvbuf(struct sock *sk, int val)
*/
WRITE_ONCE(sk->sk_rcvbuf, max_t(int, val * 2, SOCK_MIN_RCVBUF));
+ /* Charge the new budget to the memcg (or refund the released one). */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
+
if (sock) {
const struct proto_ops *ops = READ_ONCE(sock->ops);
@@ -1026,12 +1029,13 @@ static void sock_release_reserved_memory(struct sock *sk, int bytes)
WARN_ON(bytes > sk->sk_reserved_mem);
WRITE_ONCE(sk->sk_reserved_mem, sk->sk_reserved_mem - bytes);
sk_mem_reclaim(sk);
+ /* The released reservation shrank the memcg budget charge. */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
}
static int sock_reserve_memory(struct sock *sk, int bytes)
{
long allocated;
- bool charged;
int pages;
if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))
@@ -1042,12 +1046,6 @@ static int sock_reserve_memory(struct sock *sk, int bytes)
pages = sk_mem_pages(bytes);
- /* pre-charge to memcg */
- charged = mem_cgroup_sk_charge(sk, pages,
- GFP_KERNEL | __GFP_RETRY_MAYFAIL);
- if (!charged)
- return -ENOMEM;
-
if (sk->sk_bypass_prot_mem)
goto success;
@@ -1060,16 +1058,29 @@ static int sock_reserve_memory(struct sock *sk, int bytes)
*/
if (allocated > sk_prot_mem_limits(sk, 1)) {
sk_memory_allocated_sub(sk, pages);
- mem_cgroup_sk_uncharge(sk, pages);
return -ENOMEM;
}
success:
- sk_forward_alloc_add(sk, pages << PAGE_SHIFT);
-
+ /* The reservation joins the socket budget, so publish it before
+ * syncing the memcg charge and roll it back if the sync fails.
+ * The forward_alloc credit is committed last, after all fallible
+ * steps: softirq reclaim may fold it back into the protocol
+ * counter, and that cannot be rolled back.
+ */
WRITE_ONCE(sk->sk_reserved_mem,
sk->sk_reserved_mem + (pages << PAGE_SHIFT));
+ if (!sk_memcg_budget_sync(sk, GFP_KERNEL | __GFP_RETRY_MAYFAIL)) {
+ WRITE_ONCE(sk->sk_reserved_mem,
+ sk->sk_reserved_mem - (pages << PAGE_SHIFT));
+ if (!sk->sk_bypass_prot_mem)
+ sk_memory_allocated_sub(sk, pages);
+ return -ENOMEM;
+ }
+
+ sk_forward_alloc_add(sk, pages << PAGE_SHIFT);
+
return 0;
}
@@ -1349,6 +1360,10 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
WRITE_ONCE(sk->sk_sndbuf,
max_t(int, val * 2, SOCK_MIN_SNDBUF));
+ /* Charge the new budget to the memcg (or refund the
+ * released one).
+ */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
/* Wake up sending tasks if we upped the value. */
sk->sk_write_space(sk);
break;
@@ -2319,6 +2334,7 @@ struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
sk->sk_kern_sock = kern;
sock_lock_init(sk);
+ spin_lock_init(&sk->sk_memcg_budget_lock);
sk->sk_net_refcnt = kern ? 0 : 1;
if (likely(sk->sk_net_refcnt)) {
@@ -2356,6 +2372,12 @@ static void __sk_destruct(struct rcu_head *head)
if (sk->sk_destruct)
sk->sk_destruct(sk);
+ /* All queues are purged and no reference is left: return the
+ * remaining memcg budget charge exactly once, before the memcg
+ * association is dropped by sk_prot_free() below.
+ */
+ sk_memcg_budget_release(sk);
+
filter = rcu_dereference_check(sk->sk_filter,
refcount_read(&sk->sk_wmem_alloc) == 0);
if (filter) {
@@ -2535,6 +2557,12 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
newsk->sk_wmem_queued = 0;
newsk->sk_forward_alloc = 0;
newsk->sk_reserved_mem = 0;
+ /* The child has not charged anything to the memcg yet: it will be
+ * charged for its budget at accept() time (__sk_charge()).
+ * sock_copy() copied the parent's lock bytes: re-init the lock.
+ */
+ newsk->sk_memcg_budget = 0;
+ spin_lock_init(&newsk->sk_memcg_budget_lock);
DEBUG_NET_WARN_ON_ONCE(newsk->sk_drop_counters);
sk_drops_reset(newsk);
newsk->sk_send_head = NULL;
@@ -3334,15 +3362,12 @@ EXPORT_SYMBOL(sk_wait_data);
*
* Similar to __sk_mem_schedule(), but does not update sk_forward_alloc.
*
- * Unlike the globally shared limits among the sockets under same protocol,
- * consuming the budget of a memcg won't have direct effect on other ones.
- * So be optimistic about memcg's tolerance, and leave the callers to decide
- * whether or not to raise allocated through sk_under_memory_pressure() or
- * its variants.
+ * Charges the global protocol counter only. The memcg is charged for
+ * the socket's full memory budget when that budget is established or
+ * resized (see sk_memcg_budget_sync()), not per allocation.
*/
int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
{
- bool memcg_enabled = false, charged = false;
struct proto *prot = sk->sk_prot;
long allocated = 0;
@@ -3351,13 +3376,6 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
allocated = sk_memory_allocated(sk);
}
- if (mem_cgroup_sk_enabled(sk)) {
- memcg_enabled = true;
- charged = mem_cgroup_sk_charge(sk, amt, gfp_memcg_charge());
- if (!charged)
- goto suppress_allocation;
- }
-
if (!allocated)
return 1;
@@ -3428,13 +3446,8 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
/* Fail only if socket is _under_ its sndbuf.
* In this case we cannot block, so that we have to fail.
*/
- if (sk->sk_wmem_queued + size >= sk->sk_sndbuf) {
- /* Force charge with __GFP_NOFAIL */
- if (memcg_enabled && !charged)
- mem_cgroup_sk_charge(sk, amt,
- gfp_memcg_charge() | __GFP_NOFAIL);
+ if (sk->sk_wmem_queued + size >= sk->sk_sndbuf)
return 1;
- }
}
trace_sock_exceed_buf_limit(sk, prot, allocated, kind);
@@ -3442,9 +3455,6 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
if (allocated)
sk_memory_allocated_sub(sk, amt);
- if (charged)
- mem_cgroup_sk_uncharge(sk, amt);
-
return 0;
}
@@ -3475,13 +3485,12 @@ EXPORT_SYMBOL(__sk_mem_schedule);
* @sk: socket
* @amount: number of quanta
*
- * Similar to __sk_mem_reclaim(), but does not update sk_forward_alloc
+ * Similar to __sk_mem_reclaim(), but does not update sk_forward_alloc.
+ * Only returns pages to the global protocol counter; the memcg charge
+ * follows the socket budget, see sk_memcg_budget_sync().
*/
void __sk_mem_reduce_allocated(struct sock *sk, int amount)
{
- if (mem_cgroup_sk_enabled(sk))
- mem_cgroup_sk_uncharge(sk, amount);
-
if (sk->sk_bypass_prot_mem)
return;
@@ -3505,20 +3514,150 @@ void __sk_mem_reclaim(struct sock *sk, int amount)
}
EXPORT_SYMBOL(__sk_mem_reclaim);
-void __sk_charge(struct sock *sk, gfp_t gfp)
+/* Pages of the socket's currently established memory budget: the send
+ * and receive buffers plus the SO_RESERVE_MEM reservation.
+ */
+static int sk_memcg_budget_pages(const struct sock *sk)
{
- int amt;
+ long budget = READ_ONCE(sk->sk_sndbuf) + READ_ONCE(sk->sk_rcvbuf) +
+ READ_ONCE(sk->sk_reserved_mem);
- gfp |= __GFP_NOFAIL;
- if (mem_cgroup_from_sk(sk)) {
- /* The socket has not been accepted yet, no need
- * to look at newsk->sk_wmem_queued.
+ return (int)DIV_ROUND_UP(budget, PAGE_SIZE);
+}
+
+/**
+ * sk_memcg_budget_sync - charge the socket budget to the memcg
+ * @sk: socket
+ * @gfp: reclaim mode for the memcg charge
+ *
+ * Make the memcg charge match the socket's current budget: charge the
+ * difference when the budget grew, refund it when the budget shrank.
+ *
+ * Growth: the memcg is charged first and only a successful charge is
+ * accounted in sk->sk_memcg_budget. A failed charge leaves the tracker
+ * unchanged: the new budget is used uncharged (the safe direction) and
+ * a later call retries the whole difference.
+ *
+ * Concurrency: budget growth usually runs under the socket lock, but
+ * some setsockopt(2) paths run without it, and shrinks may run
+ * locklessly (sk_mem_reclaim() from skb destructors), so the tracker
+ * is a plain int guarded by sk_memcg_budget_lock. The lock is a leaf
+ * lock taken with _bh (sk_memcg_budget_release() runs from RCU
+ * callbacks); the memcg charge and uncharge run outside of it because
+ * they may sleep (memcg reclaim). Charging before publishing the
+ * tracker, and refunding only the excess claimed under the lock, keep
+ * every charged page accounted in the tracker exactly once and every
+ * tracker page refundable exactly once: concurrent growths can
+ * transiently over-count (the next shrink refunds the excess), while
+ * cumulative refunds can never exceed cumulative charges, so the
+ * memcg balance can not underflow.
+ *
+ * Returns false when a growth charge failed.
+ */
+bool sk_memcg_budget_sync(struct sock *sk, gfp_t gfp)
+{
+ int want, have, delta;
+
+ if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))
+ return true;
+
+ /* The snapshot is unlocked on purpose: the memcg charge below
+ * may sleep, so the lock cannot be held across it, and a
+ * lock-coherent snapshot would go stale the same way.
+ */
+ want = sk_memcg_budget_pages(sk);
+ have = READ_ONCE(sk->sk_memcg_budget);
+
+ if (want > have) {
+ delta = want - have;
+ if (!mem_cgroup_sk_charge(sk, delta, gfp))
+ return false;
+
+ /* Add the full @delta unconditionally: every charged page
+ * must land in the tracker exactly once, so the release
+ * can refund it exactly once. @have may be stale, the
+ * resulting over-count is refunded by the next shrink.
*/
- amt = sk_mem_pages(sk->sk_forward_alloc +
- atomic_read(&sk->sk_rmem_alloc));
- if (amt)
- mem_cgroup_sk_charge(sk, amt, gfp);
+ spin_lock_bh(&sk->sk_memcg_budget_lock);
+ WRITE_ONCE(sk->sk_memcg_budget, sk->sk_memcg_budget + delta);
+ spin_unlock_bh(&sk->sk_memcg_budget_lock);
+ } else if (want < have) {
+ sk_memcg_budget_shrink(sk);
+ }
+
+ return true;
+}
+EXPORT_SYMBOL(sk_memcg_budget_sync);
+
+/**
+ * sk_memcg_budget_shrink - refund budget shrinks to the memcg
+ * @sk: socket
+ *
+ * Return the part of the memcg charge that is no longer backed by the
+ * socket's budget. Never charges and never fails, so it is safe to
+ * call from arbitrary (including lockless softirq) contexts, e.g.
+ * from sk_mem_reclaim() to catch budget shrinks done by writers the
+ * budget sync is not hooked into.
+ */
+void sk_memcg_budget_shrink(struct sock *sk)
+{
+ int want, refund = 0;
+
+ if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk))
+ return;
+
+ /* Claim the excess under the lock, then refund it outside:
+ * the memcg uncharge may sleep, and the claim guarantees each
+ * tracker page is refunded exactly once. @want follows the
+ * socket buffers, which are protected by the socket lock, not
+ * the budget lock; a claim made stale by a concurrent budget
+ * update is corrected by the next sync.
+ */
+ spin_lock_bh(&sk->sk_memcg_budget_lock);
+ want = sk_memcg_budget_pages(sk);
+ if (want < sk->sk_memcg_budget) {
+ refund = sk->sk_memcg_budget - want;
+ WRITE_ONCE(sk->sk_memcg_budget, want);
}
+ spin_unlock_bh(&sk->sk_memcg_budget_lock);
+
+ if (refund)
+ mem_cgroup_sk_uncharge(sk, refund);
+}
+EXPORT_SYMBOL(sk_memcg_budget_shrink);
+
+/**
+ * sk_memcg_budget_release - return the socket's memcg budget charge
+ * @sk: socket
+ *
+ * Return the whole remaining budget charge of the socket to its memcg
+ * and reset the tracker. Used when the socket dies (__sk_destruct(),
+ * no budget change can happen anymore, the refund is exactly the
+ * amount that was charged) and when the memcg association of the
+ * socket moves (the charge is re-established against the new memcg).
+ */
+void sk_memcg_budget_release(struct sock *sk)
+{
+ int refund;
+
+ spin_lock_bh(&sk->sk_memcg_budget_lock);
+ refund = sk->sk_memcg_budget;
+ WRITE_ONCE(sk->sk_memcg_budget, 0);
+ spin_unlock_bh(&sk->sk_memcg_budget_lock);
+
+ if (refund)
+ mem_cgroup_sk_uncharge(sk, refund);
+}
+EXPORT_SYMBOL(sk_memcg_budget_release);
+
+void __sk_charge(struct sock *sk, gfp_t gfp)
+{
+ gfp |= __GFP_NOFAIL;
+
+ /* Charge the child's full memory budget to its memcg: the budget
+ * covers whatever the child queued before being accepted.
+ */
+ sk_memcg_budget_sync(sk, gfp);
kmem_cache_charge(sk, gfp);
}
@@ -3760,6 +3899,10 @@ void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)
sk->sk_allocation = GFP_KERNEL;
sk->sk_rcvbuf = READ_ONCE(sysctl_rmem_default);
sk->sk_sndbuf = READ_ONCE(sysctl_wmem_default);
+ /* The socket's memory budget starts here: charge it to the memcg
+ * upfront. Protocols raising the default buffers later re-sync.
+ */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
sk->sk_state = TCP_CLOSE;
sk->sk_use_task_frag = true;
sk_set_socket(sk, sock);
@@ -4581,6 +4724,8 @@ static int __init sock_struct_check(void)
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_err);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_socket);
+ CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg_budget);
+ CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg_budget_lock);
#ifdef CONFIG_MEMCG
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg);
#endif
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 3ac4856852794..629ce7dc3032d 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -471,6 +471,10 @@ void tcp_init_sock(struct sock *sk)
WRITE_ONCE(sk->sk_sndbuf, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_wmem[1]));
WRITE_ONCE(sk->sk_rcvbuf, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_rmem[1]));
+ /* The default buffers grew from the generic sock_init_data()
+ * values: charge the difference to the memcg.
+ */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
tcp_scaling_ratio_init(sk);
set_bit(SOCK_SUPPORT_ZC, &sk->sk_socket->flags);
@@ -917,9 +921,9 @@ void sk_forced_mem_schedule(struct sock *sk, int size)
amt = sk_mem_pages(delta);
sk_forward_alloc_add(sk, amt << PAGE_SHIFT);
- if (mem_cgroup_sk_enabled(sk))
- mem_cgroup_sk_charge(sk, amt, gfp_memcg_charge() | __GFP_NOFAIL);
-
+ /* Only the global protocol counter: the memcg side follows the
+ * socket budget (sk_memcg_budget_sync()).
+ */
if (sk->sk_bypass_prot_mem)
return;
@@ -1850,6 +1854,7 @@ int tcp_set_rcvlowat(struct sock *sk, int val)
space = tcp_space_from_win(sk, val);
if (space > sk->sk_rcvbuf) {
WRITE_ONCE(sk->sk_rcvbuf, space);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
if (tp->window_clamp && tp->window_clamp < val)
WRITE_ONCE(tp->window_clamp, val);
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 892ff256e2352..9fdb2c66821b7 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -629,9 +629,11 @@ static void tcp_sndbuf_expand(struct sock *sk)
sndmem = ca_ops->sndbuf_expand ? ca_ops->sndbuf_expand(sk) : 2;
sndmem *= nr_segs * per_mss;
- if (sk->sk_sndbuf < sndmem)
+ if (sk->sk_sndbuf < sndmem) {
WRITE_ONCE(sk->sk_sndbuf,
min(sndmem, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_wmem[2])));
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
+ }
}
/* 2. Tuning advertised window (window_clamp, rcv_ssthresh)
@@ -791,6 +793,7 @@ static void tcp_clamp_window(struct sock *sk)
sk_memory_allocated(sk) < sk_prot_mem_limits(sk, 0)) {
WRITE_ONCE(sk->sk_rcvbuf,
min(atomic_read(&sk->sk_rmem_alloc), rmem2));
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
}
if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf)
tp->rcv_ssthresh = min(tp->window_clamp, 2U * tp->advmss);
@@ -948,6 +951,7 @@ void tcp_rcvbuf_grow(struct sock *sk, u32 newval)
rcvbuf = min_t(u32, tcp_space_from_win(sk, rcvwin), cap);
if (rcvbuf > sk->sk_rcvbuf) {
WRITE_ONCE(sk->sk_rcvbuf, rcvbuf);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
/* Make the window clamp follow along. */
WRITE_ONCE(tp->window_clamp,
tcp_win_from_space(sk, rcvbuf));
@@ -6055,8 +6059,10 @@ static bool tcp_should_expand_sndbuf(struct sock *sk)
* it never goes below SOCK_MIN_SNDBUF.
* See sk_stream_moderate_sndbuf() for more details.
*/
- if (unused_mem > SOCK_MIN_SNDBUF)
+ if (unused_mem > SOCK_MIN_SNDBUF) {
WRITE_ONCE(sk->sk_sndbuf, unused_mem);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
+ }
return false;
}
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index e89a69ab927c9..bcc59e673b9bf 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -238,6 +238,7 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval)
rcvbuf = min_t(u32, mptcp_space_from_win(sk, rcvwin), cap);
if (rcvbuf > sk->sk_rcvbuf) {
WRITE_ONCE(sk->sk_rcvbuf, rcvbuf);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
return true;
}
return false;
@@ -791,12 +792,6 @@ static void __mptcp_add_backlog(struct sock *sk,
account:
WRITE_ONCE(msk->backlog_len, msk->backlog_len + delta);
-
- /* Possibly not accept()ed yet, keep track of memory not CG
- * accounted, mptcp_graft_subflows() will handle it.
- */
- if (!mem_cgroup_from_sk(ssk))
- msk->backlog_unaccounted += delta;
}
static bool __mptcp_move_skbs_from_subflow(struct mptcp_sock *msk,
@@ -2318,12 +2313,6 @@ static bool mptcp_can_spool_backlog(struct sock *sk, struct list_head *skbs)
{
struct mptcp_sock *msk = mptcp_sk(sk);
- /* After CG initialization, subflows should never add skb before
- * gaining the CG themself.
- */
- DEBUG_NET_WARN_ON_ONCE(msk->backlog_unaccounted && sk->sk_socket &&
- mem_cgroup_from_sk(sk));
-
if (list_empty(&msk->backlog_list))
return false;
@@ -3255,6 +3244,10 @@ static int mptcp_init_sock(struct sock *sk)
sk_sockets_allocated_inc(sk);
sk->sk_rcvbuf = READ_ONCE(net->ipv4.sysctl_tcp_rmem[1]);
sk->sk_sndbuf = READ_ONCE(net->ipv4.sysctl_tcp_wmem[1]);
+ /* The default buffers grew from the generic sock_init_data()
+ * values: charge the difference to the memcg.
+ */
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
sk->sk_write_space = sk_stream_write_space;
return 0;
@@ -4332,10 +4325,7 @@ static void mptcp_graft_subflows(struct sock *sk)
LIST_HEAD(join_list);
/* Subflows joining after __inet_accept() will get the
- * mem CG properly initialized at mptcp_finish_join() time,
- * but subflows pending in join_list need explicit
- * initialization before flushing `backlog_unaccounted`
- * or MPTCP can later unexpectedly observe unaccounted memory.
+ * mem CG properly initialized at mptcp_finish_join() time.
*/
mptcp_data_lock(sk);
list_splice_init(&msk->join_list, &join_list);
@@ -4364,26 +4354,6 @@ static void mptcp_graft_subflows(struct sock *sk)
unlock:
release_sock(ssk);
}
-
- if (mem_cgroup_sk_enabled(sk)) {
- gfp_t gfp = GFP_KERNEL | __GFP_NOFAIL;
- int amt;
-
- /* Account the backlog memory; prior accept() is aware of
- * fwd and rmem only.
- */
- mptcp_data_lock(sk);
- amt = sk_mem_pages(sk->sk_forward_alloc +
- msk->backlog_unaccounted +
- atomic_read(&sk->sk_rmem_alloc)) -
- sk_mem_pages(sk->sk_forward_alloc +
- atomic_read(&sk->sk_rmem_alloc));
- msk->backlog_unaccounted = 0;
- mptcp_data_unlock(sk);
-
- if (amt)
- mem_cgroup_sk_charge(sk, amt, gfp);
- }
}
static int mptcp_stream_accept(struct socket *sock, struct socket *newsock,
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 0384d6a023f9d..66ff8c232d8c9 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -377,7 +377,6 @@ struct mptcp_sock {
struct list_head backlog_list; /* protected by the data lock */
u32 backlog_len;
- u32 backlog_unaccounted;
};
#define mptcp_data_lock(sk) spin_lock_bh(&(sk)->sk_lock.slock)
@@ -1041,6 +1040,7 @@ static inline void __mptcp_sync_sndbuf(struct sock *sk)
/* the msk max wmem limit is <nr_subflows> * tcp wmem[2] */
WRITE_ONCE(sk->sk_sndbuf, new_sndbuf);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
mptcp_write_space(sk);
}
diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c
index fcf6feb2a9eb5..05d9dafa8c071 100644
--- a/net/mptcp/sockopt.c
+++ b/net/mptcp/sockopt.c
@@ -70,6 +70,7 @@ static int mptcp_get_int_option(struct mptcp_sock *msk, sockptr_t optval,
static void __mptcp_subflow_set_rcvbuf(struct sock *ssk, int val)
{
WRITE_ONCE(ssk->sk_rcvbuf, val);
+ sk_memcg_budget_sync(ssk, gfp_memcg_charge());
tcp_set_rcvbuf(ssk, val);
}
@@ -1656,12 +1657,14 @@ int mptcp_set_rcvlowat(struct sock *sk, int val)
/* propagate the rcvbuf changes to all the subflows */
WRITE_ONCE(sk->sk_rcvbuf, space);
+ sk_memcg_budget_sync(sk, gfp_memcg_charge());
mptcp_for_each_subflow(mptcp_sk(sk), subflow) {
struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
bool slow;
slow = lock_sock_fast(ssk);
WRITE_ONCE(ssk->sk_rcvbuf, space);
+ sk_memcg_budget_sync(ssk, gfp_memcg_charge());
WRITE_ONCE(tcp_sk(ssk)->window_clamp, val);
unlock_sock_fast(ssk, slow);
}
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index f0a6725d2c376..340eb5b2cacb8 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -1735,6 +1735,11 @@ void __mptcp_inherit_memcg(struct sock *sk, struct sock *ssk, gfp_t gfp)
if (!mem_cgroup_sockets_enabled || !sk->sk_socket)
return;
+ /* The subflow's budget charge went to its previous memcg: return
+ * it before the memcg association moves to the msk's one, the
+ * __sk_charge() below re-charges the budget there.
+ */
+ sk_memcg_budget_release(ssk);
mem_cgroup_sk_inherit(sk, ssk);
__sk_charge(ssk, gfp);
}
diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c
index 3a84d068fbf36..54dae37291c31 100644
--- a/tools/testing/selftests/cgroup/test_memcontrol.c
+++ b/tools/testing/selftests/cgroup/test_memcontrol.c
@@ -1335,9 +1335,12 @@ static int tcp_client(const char *cgroup, unsigned short port)
char servport[6];
int retries = 0x10; /* nice round number */
int sk, ret;
- long allocated;
+ long allocated, sock_pre;
allocated = cg_read_long(cgroup, "memory.current");
+ sock_pre = cg_read_key_long(cgroup, "memory.stat", "sock ");
+ if (sock_pre < 0)
+ return KSFT_FAIL;
snprintf(servport, sizeof(servport), "%hd", port);
ret = getaddrinfo(server, servport, NULL, &ai);
if (ret)
@@ -1365,8 +1368,12 @@ static int tcp_client(const char *cgroup, unsigned short port)
if (current < 0 || sock < 0)
goto close_sk;
- /* exclude the memory not related to socket connection */
- if (values_close(current - allocated, sock, 10)) {
+ /* exclude the memory not related to socket connection;
+ * compare the growth of both counters since the baseline,
+ * as the listening socket may already hold socket memory
+ * (e.g. its pre-charged memory budget) at the baseline.
+ */
+ if (values_close(current - allocated, sock - sock_pre, 10)) {
ret = KSFT_PASS;
break;
}
@@ -1384,8 +1391,9 @@ static int tcp_client(const char *cgroup, unsigned short port)
* The test forks a TCP server listens on a random port between 1000
* and 61000. Once it gets a client connection, it starts writing to
* its socket.
- * The TCP client interleaves reads from the socket with check whether
- * memory.current and memory.stat.sock are similar.
+ * The TCP client interleaves reads from the socket with checking whether
+ * the growth of memory.current and memory.stat.sock since the baseline
+ * are similar.
*/
static int test_memcg_sock(const char *root)
{
|
| 31/3 | 2026/09/26 12:36 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 12318 output: 31 thoughts: 547 Thoughts: **Analyzing the Patch for KMSAN Fuzzing Suitability** Okay, let's break this down. My goal is to determine if this patch warrants firing up a dedicated KMSAN fuzzing session. This means meticulously examining the changes, weighing them against KMSAN and KASAN criteria. First, let's get a handle on the scope. It looks like the patch encompasses documentation updates, specifically concerning cgroup v2, which is good housekeeping. The meat of it, however, seems to be a significant alteration to socket memory cgroup accounting, particularly for TCP and MPTCP buffers. Instead of per-allocation charging with `__sk_mem_raise_allocated` and friends, there's now an upfront budgeting system based on `sk_sndbuf`, `sk_rcvbuf`, and `sk_reserved_mem`. This has the potential to touch a lot of code, and change a lot of behaviour, so this immediately grabs my attention. Specifically, it introduces some new fields in `struct sock`: `sk_memcg_budget` and `sk_memcg_budget_lock`, which are initialized during `sk_alloc()` and `sk_clone()`. That `sk_alloc()` is worth a closer look, as it's the gateway for how the `sock` structure itself is allocated, using either `kzalloc` or `kmem_cache_alloc`. I need to confirm where that is, in `sock.c`. New functions have also been introduced: `sk_memcg_budget_pages`, `sk_memcg_budget_sync`, `sk_memcg_budget_shrink`, and `sk_memcg_budget_release`. This signals some new memory management routines and interactions. And these new functions are sprinkled throughout core networking code: `net/core/sock.c`, `net/ipv4/tcp.c`, `net/ipv4/tcp_input.c`, `net/mptcp/protocol.c`, `net/mptcp/sockopt.c`, and `net/mptcp/subflow.c`. That's a wide spread! Finally, there are modifications to selftests in `tools/testing/selftests/cgroup/test_memcontrol.c`. That's at least a small positive, implying some regression testing is being implemented. Now, that upfront budgeting is interesting from a KMSAN perspective. Any change in memory accounting can have the potential for memory leaks or accounting issues. This change touches core network functions, which makes it even more important. It's a bit more intricate than your typical allocation/free change. I'm going to dig into the `sk_alloc` and the new budget functions to see where the KMSAN potential lies. I suspect there's a good chance that KMSAN might be useful here, but I need to understand this new memory budget logic more completely. |
| 32/3 | 2026/09/26 12:37 | tool | grepper |
0mArgs:{
"Expression": "sk_alloc\\(",
"PathPrefix": "net/core/sock.c"
}Results: {
"Output": "net/core/sock.c=2253=static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority,\n--\nnet/core/sock.c-2269-\tif (sk != NULL) {\nnet/core/sock.c:2270:\t\tif (security_sk_alloc(sk, family, priority))\nnet/core/sock.c-2271-\t\t\tgoto out_free;\n--\nnet/core/sock.c=2289=static void sk_prot_free(struct proto *prot, struct sock *sk)\n--\nnet/core/sock.c-2317- */\nnet/core/sock.c:2318:struct sock *sk_alloc(struct net *net, int family, gfp_t priority,\nnet/core/sock.c-2319-\t\t struct proto *prot, int kern)\n--\nnet/core/sock.c-2351-\nnet/core/sock.c:2352:\t\tmem_cgroup_sk_alloc(sk);\nnet/core/sock.c:2353:\t\tcgroup_sk_alloc(\u0026sk-\u003esk_cgrp_data);\nnet/core/sock.c-2354-\t\tsock_update_classid(\u0026sk-\u003esk_cgrp_data);\n"
} |
| 33/3 | 2026/09/26 12:37 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 13841 output: 36 thoughts: 11 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience and presented in the first person: **Deep Dive into `sk_prot_alloc`** Right, let's get down to brass tacks and take a closer look at `sk_prot_alloc`. I need to assess its behavior. I want to understand what's happening at the protocol socket allocation level and make sure nothing is amiss. It's time to dive into that function and scrutinize its mechanisms. What are the potential areas for optimization, or perhaps even subtle vulnerabilities, in the allocation process itself? I need to analyze how it interacts with the underlying kernel structures. I will evaluate the flow and determine if it does what I expect. Any strange results or areas to investigate will be noted. |
| 34/3 | 2026/09/26 12:37 | tool | read-file |
0mArgs:{
"File": "net/core/sock.c",
"FirstLine": 2250,
"LineCount": 35
}Results: {
"Contents": "2250:\t#endif\n2251:\t}\n2252:\t\n2253:\tstatic struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority,\n2254:\t\t\tint family)\n2255:\t{\n2256:\t\tstruct sock *sk;\n2257:\t\tstruct kmem_cache *slab;\n2258:\t\n2259:\t\tslab = prot-\u003eslab;\n2260:\t\tif (slab != NULL) {\n2261:\t\t\tsk = kmem_cache_alloc(slab, priority \u0026 ~__GFP_ZERO);\n2262:\t\t\tif (!sk)\n2263:\t\t\t\treturn sk;\n2264:\t\t\tif (want_init_on_alloc(priority))\n2265:\t\t\t\tsk_prot_clear_nulls(sk, prot-\u003eobj_size);\n2266:\t\t} else\n2267:\t\t\tsk = kmalloc(prot-\u003eobj_size, priority);\n2268:\t\n2269:\t\tif (sk != NULL) {\n2270:\t\t\tif (security_sk_alloc(sk, family, priority))\n2271:\t\t\t\tgoto out_free;\n2272:\t\n2273:\t\t\tif (!try_module_get(prot-\u003eowner))\n2274:\t\t\t\tgoto out_free_sec;\n2275:\t\t}\n2276:\t\n2277:\t\treturn sk;\n2278:\t\n2279:\tout_free_sec:\n2280:\t\tsecurity_sk_free(sk);\n2281:\tout_free:\n2282:\t\tif (slab != NULL)\n2283:\t\t\tkmem_cache_free(slab, sk);\n2284:\t\telse\n"
} |
| 35/3 | 2026/09/26 12:37 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 14518 output: 36 thoughts: 25 Thoughts: Okay, let's dive into this. I'm focusing on lines 2310-2345 within `net/core/sock.c`. My immediate thought is, "What's the context here? What are we looking at within this critical networking core file?" I'm expecting this section to deal with fundamental socket operations. Perhaps this deals with socket creation, management, or some critical data handling related to network traffic. This could include aspects related to buffering, connection state transitions, or maybe even error handling within the socket layer. I know this section is crucial for understanding the underpinnings of how network connections are set up and managed within the Linux kernel, so I'll need to pay close attention to any subtle details. I'm keen to understand any changes that are different from my previous studies. |
| 36/3 | 2026/09/26 12:37 | tool | read-file |
0mArgs:{
"File": "net/core/sock.c",
"FirstLine": 2310,
"LineCount": 45
}Results: {
"Contents": "2310:\t/**\n2311:\t *\tsk_alloc - All socket objects are allocated here\n2312:\t *\t@net: the applicable net namespace\n2313:\t *\t@family: protocol family\n2314:\t *\t@priority: for allocation (%GFP_KERNEL, %GFP_ATOMIC, etc)\n2315:\t *\t@prot: struct proto associated with this new sock instance\n2316:\t *\t@kern: is this to be a kernel socket?\n2317:\t */\n2318:\tstruct sock *sk_alloc(struct net *net, int family, gfp_t priority,\n2319:\t\t\t struct proto *prot, int kern)\n2320:\t{\n2321:\t\tstruct sock *sk;\n2322:\t\n2323:\t\tsk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);\n2324:\t\tif (sk) {\n2325:\t\t\tsk-\u003esk_family = family;\n2326:\t\t\t/*\n2327:\t\t\t * See comment in struct sock definition to understand\n2328:\t\t\t * why we need sk_prot_creator -acme\n2329:\t\t\t */\n2330:\t\t\tsk-\u003esk_prot = sk-\u003esk_prot_creator = prot;\n2331:\t\n2332:\t\t\tif (READ_ONCE(net-\u003ecore.sysctl_bypass_prot_mem))\n2333:\t\t\t\tsk-\u003esk_bypass_prot_mem = 1;\n2334:\t\n2335:\t\t\tsk-\u003esk_kern_sock = kern;\n2336:\t\t\tsock_lock_init(sk);\n2337:\t\t\tspin_lock_init(\u0026sk-\u003esk_memcg_budget_lock);\n2338:\t\n2339:\t\t\tsk-\u003esk_net_refcnt = kern ? 0 : 1;\n2340:\t\t\tif (likely(sk-\u003esk_net_refcnt)) {\n2341:\t\t\t\tget_net_track(net, \u0026sk-\u003ens_tracker, priority);\n2342:\t\t\t\tsock_inuse_add(net, 1);\n2343:\t\t\t} else {\n2344:\t\t\t\tnet_passive_inc(net);\n2345:\t\t\t\t__netns_tracker_alloc(net, \u0026sk-\u003ens_tracker,\n2346:\t\t\t\t\t\t false, priority);\n2347:\t\t\t}\n2348:\t\n2349:\t\t\tsock_net_set(sk, net);\n2350:\t\t\trefcount_set(\u0026sk-\u003esk_wmem_alloc, SK_WMEM_ALLOC_BIAS);\n2351:\t\n2352:\t\t\tmem_cgroup_sk_alloc(sk);\n2353:\t\t\tcgroup_sk_alloc(\u0026sk-\u003esk_cgrp_data);\n2354:\t\t\tsock_update_classid(\u0026sk-\u003esk_cgrp_data);\n"
} |
| 37/3 | 2026/09/26 12:37 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 15500 output: 31 thoughts: 284 Thoughts: **Analyzing Socket Memory Accounting** Okay, let's see, my expert eye immediately caught something interesting on line 2323, that `sk` allocation using `sk_prot_alloc`. I see that the `__GFP_ZERO` flag is used. Interesting. So, the kernel is explicitly zeroing out the allocated `struct sock`. Now, digging deeper, in the `sk_clone()` function, I observe that several fields are explicitly initialized, and of particular interest is the initialization of memory accounting related fields. Specifically, `sk_wmem_queued`, `sk_forward_alloc`, and `sk_reserved_mem` are all zeroed out, as is `sk_memcg_budget`! The code even goes so far as to initialize the spinlock `sk_memcg_budget_lock`. This strongly suggests that a new socket, when cloned, begins with a clean slate regarding memory usage, with the memory charge potentially occurring later at `accept()` via `__sk_charge()`. The comment clarifies that it isn't charged during the cloning process, but later on when it is actually accepted, which makes sense. I would expect this kind of behavior, but it's good to see it explicitly confirmed here. To be thorough, let's explore if there are other instances of `struct sock` creation or cloning where this memory accounting behavior might be relevant. I'll search for calls to `sock_copy` or `sk_clone`, to get a more comprehensive picture. |
| 38/3 | 2026/09/26 12:37 | tool | grepper |
0mArgs:{
"Expression": "sk_clone\\(",
"PathPrefix": "net/core/sock.c"
}Results: {
"Output": "net/core/sock.c=2225=static void sock_copy(struct sock *nsk, const struct sock *osk)\n--\nnet/core/sock.c-2248-\tnsk-\u003esk_security = sptr;\nnet/core/sock.c:2249:\tsecurity_sk_clone(osk, nsk);\nnet/core/sock.c-2250-#endif\n--\nnet/core/sock.c=2467=static void sk_init_common(struct sock *sk)\n--\nnet/core/sock.c-2501- */\nnet/core/sock.c:2502:struct sock *sk_clone(const struct sock *sk, const gfp_t priority,\nnet/core/sock.c-2503-\t\t bool lock)\n--\nnet/core/sock.c-2578-\nnet/core/sock.c:2579:\tcgroup_sk_clone(\u0026newsk-\u003esk_cgrp_data);\nnet/core/sock.c-2580-\n"
} |
| 39/3 | 2026/09/26 12:37 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 16404 output: 32 thoughts: 27 Thoughts: Here's my take on the thought process, tailored for an expert audience and presented as a first-person summary: **Analyzing the Impact of the Patch** Okay, so I've just confirmed that the `sk_clone` function was definitely modified by the patch we're reviewing. That's the starting point. Now, the critical question is always about the side effects, and in this context, that means carefully scrutinizing the interactions, specifically the handling of related locks. Given the change to `sk_clone`, my immediate focus shifts to the fate of `sk_clone_lock`. Did the patch introduce any modifications or interactions with that particular lock? Did the patch's implementation inadvertently change the locking semantics, or did the patch interact with the lock in new or interesting ways? Now let's dive deep into the code and see how `sk_clone_lock` and the rest of the relevant functions have been changed. |
| 40/3 | 2026/09/26 12:37 | tool | grepper |
0mArgs:{
"Expression": "sk_clone_lock",
"PathPrefix": "include/net/sock.h"
}Results: {
"Output": "include/net/sock.h=1862=struct sock *sk_clone(const struct sock *sk, const gfp_t priority, bool lock);\ninclude/net/sock.h-1863-\ninclude/net/sock.h:1864:static inline struct sock *sk_clone_lock(const struct sock *sk, const gfp_t priority)\ninclude/net/sock.h-1865-{\n"
} |
| 41/3 | 2026/09/26 12:37 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 16772 output: 20 thoughts: 114 Thoughts: Okay, here's my understanding, considering I already know the landscape of socket structures and memory control groups (memcgs). **Analyzing Socket Cloning and Memcg Budgeting** Right, so `sk_clone_lock` simply seems to be a wrapper around `sk_clone`. It's calling the standard clone function, passing the socket, a priority, and telling it to clone with the 'true' flag, probably for a specific purpose. Nothing unexpected there, that's straightforward. Now, my real concern is around `sk_memcg_budget`. I need to understand its scope and potential impact on user space. The important question now is, does `sk_memcg_budget` or any data derived from it, ever leak or get propagated to user space? I have to be thorough here. I need to think about all the possible paths for information leakage. I need to check: * Do any `getsockopt` calls expose it? * Is it being sent over `netlink` messages? * How about `sock_diag`? That's a common area for diagnostic info. * Are there any entries in `/proc` or `/sysfs` that might reveal this information? * And critically, is it being exposed through BPF programs? That could be a significant vulnerability. My next step is to methodically search the code and related documentation for any evidence of this data being copied or exposed through any of these avenues. I need to be absolutely certain before I make any conclusions. |
| 42/3 | 2026/09/26 12:37 | tool | grepper |
0mArgs:{
"Expression": "sk_memcg_budget"
}Results: {
"Output": "include/net/sock.h=365=struct sock {\n--\ninclude/net/sock.h-464-\t * behalf of this socket, so it can be refunded exactly once.\ninclude/net/sock.h:465:\t * Guarded by sk_memcg_budget_lock; the charge/uncharge run\ninclude/net/sock.h-466-\t * outside the lock (they may sleep/reclaim).\ninclude/net/sock.h-467-\t */\ninclude/net/sock.h:468:\tint\t\t\tsk_memcg_budget;\ninclude/net/sock.h:469:\tspinlock_t\t\tsk_memcg_budget_lock;\ninclude/net/sock.h-470-#ifdef CONFIG_MEMCG\n--\ninclude/net/sock.h=1564=void __sk_mem_reclaim(struct sock *sk, int amount);\ninclude/net/sock.h:1565:bool sk_memcg_budget_sync(struct sock *sk, gfp_t gfp);\ninclude/net/sock.h:1566:void sk_memcg_budget_shrink(struct sock *sk);\ninclude/net/sock.h:1567:void sk_memcg_budget_release(struct sock *sk);\ninclude/net/sock.h-1568-\n--\ninclude/net/sock.h=1630=static inline void sk_mem_reclaim(struct sock *sk)\n--\ninclude/net/sock.h-1640-\t */\ninclude/net/sock.h:1641:\tsk_memcg_budget_shrink(sk);\ninclude/net/sock.h-1642-\n--\ninclude/net/sock.h=2650=static inline void sk_stream_moderate_sndbuf(struct sock *sk)\n--\ninclude/net/sock.h-2662-\t/* The sndbuf shrink released part of the memcg budget charge. */\ninclude/net/sock.h:2663:\tsk_memcg_budget_shrink(sk);\ninclude/net/sock.h-2664-}\n--\nnet/core/sock.c=967=static void __sock_set_rcvbuf(struct sock *sk, int val)\n--\nnet/core/sock.c-989-\t/* Charge the new budget to the memcg (or refund the released one). */\nnet/core/sock.c:990:\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/core/sock.c-991-\n--\nnet/core/sock.c=1024=static void sock_release_reserved_memory(struct sock *sk, int bytes)\n--\nnet/core/sock.c-1032-\t/* The released reservation shrank the memcg budget charge. */\nnet/core/sock.c:1033:\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/core/sock.c-1034-}\n--\nnet/core/sock.c=1036=static int sock_reserve_memory(struct sock *sk, int bytes)\n--\nnet/core/sock.c-1073-\nnet/core/sock.c:1074:\tif (!sk_memcg_budget_sync(sk, GFP_KERNEL | __GFP_RETRY_MAYFAIL)) {\nnet/core/sock.c-1075-\t\tWRITE_ONCE(sk-\u003esk_reserved_mem,\n--\nnet/core/sock.c=1206=int sk_setsockopt(struct sock *sk, int level, int optname,\n--\nnet/core/sock.c-1365-\t\t */\nnet/core/sock.c:1366:\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/core/sock.c-1367-\t\t/* Wake up sending tasks if we upped the value. */\n--\nnet/core/sock.c=2318=struct sock *sk_alloc(struct net *net, int family, gfp_t priority,\n--\nnet/core/sock.c-2336-\t\tsock_lock_init(sk);\nnet/core/sock.c:2337:\t\tspin_lock_init(\u0026sk-\u003esk_memcg_budget_lock);\nnet/core/sock.c-2338-\n--\nnet/core/sock.c=2366=static void __sk_destruct(struct rcu_head *head)\n--\nnet/core/sock.c-2378-\t */\nnet/core/sock.c:2379:\tsk_memcg_budget_release(sk);\nnet/core/sock.c-2380-\n--\nnet/core/sock.c=2502=struct sock *sk_clone(const struct sock *sk, const gfp_t priority,\n--\nnet/core/sock.c-2563-\t */\nnet/core/sock.c:2564:\tnewsk-\u003esk_memcg_budget = 0;\nnet/core/sock.c:2565:\tspin_lock_init(\u0026newsk-\u003esk_memcg_budget_lock);\nnet/core/sock.c-2566-\tDEBUG_NET_WARN_ON_ONCE(newsk-\u003esk_drop_counters);\n--\nnet/core/sock.c=3354=EXPORT_SYMBOL(sk_wait_data);\n--\nnet/core/sock.c-3366- *\tthe socket's full memory budget when that budget is established or\nnet/core/sock.c:3367: *\tresized (see sk_memcg_budget_sync()), not per allocation.\nnet/core/sock.c-3368- */\n--\nnet/core/sock.c=3481=EXPORT_SYMBOL(__sk_mem_schedule);\n--\nnet/core/sock.c-3489- *\tOnly returns pages to the global protocol counter; the memcg charge\nnet/core/sock.c:3490: *\tfollows the socket budget, see sk_memcg_budget_sync().\nnet/core/sock.c-3491- */\n--\nnet/core/sock.c=3515=EXPORT_SYMBOL(__sk_mem_reclaim);\n--\nnet/core/sock.c-3519- */\nnet/core/sock.c:3520:static int sk_memcg_budget_pages(const struct sock *sk)\nnet/core/sock.c-3521-{\n--\nnet/core/sock.c-3528-/**\nnet/core/sock.c:3529: *\tsk_memcg_budget_sync - charge the socket budget to the memcg\nnet/core/sock.c-3530- *\t@sk: socket\n--\nnet/core/sock.c-3536- *\tGrowth: the memcg is charged first and only a successful charge is\nnet/core/sock.c:3537: *\taccounted in sk-\u003esk_memcg_budget. A failed charge leaves the tracker\nnet/core/sock.c-3538- *\tunchanged: the new budget is used uncharged (the safe direction) and\n--\nnet/core/sock.c-3543- *\tlocklessly (sk_mem_reclaim() from skb destructors), so the tracker\nnet/core/sock.c:3544: *\tis a plain int guarded by sk_memcg_budget_lock. The lock is a leaf\nnet/core/sock.c:3545: *\tlock taken with _bh (sk_memcg_budget_release() runs from RCU\nnet/core/sock.c-3546- * callbacks); the memcg charge and uncharge run outside of it because\n--\nnet/core/sock.c-3556- */\nnet/core/sock.c:3557:bool sk_memcg_budget_sync(struct sock *sk, gfp_t gfp)\nnet/core/sock.c-3558-{\n--\nnet/core/sock.c-3567-\t */\nnet/core/sock.c:3568:\twant = sk_memcg_budget_pages(sk);\nnet/core/sock.c:3569:\thave = READ_ONCE(sk-\u003esk_memcg_budget);\nnet/core/sock.c-3570-\n--\nnet/core/sock.c-3580-\t\t */\nnet/core/sock.c:3581:\t\tspin_lock_bh(\u0026sk-\u003esk_memcg_budget_lock);\nnet/core/sock.c:3582:\t\tWRITE_ONCE(sk-\u003esk_memcg_budget, sk-\u003esk_memcg_budget + delta);\nnet/core/sock.c:3583:\t\tspin_unlock_bh(\u0026sk-\u003esk_memcg_budget_lock);\nnet/core/sock.c-3584-\t} else if (want \u003c have) {\nnet/core/sock.c:3585:\t\tsk_memcg_budget_shrink(sk);\nnet/core/sock.c-3586-\t}\n--\nnet/core/sock.c-3589-}\nnet/core/sock.c:3590:EXPORT_SYMBOL(sk_memcg_budget_sync);\nnet/core/sock.c-3591-\nnet/core/sock.c-3592-/**\nnet/core/sock.c:3593: *\tsk_memcg_budget_shrink - refund budget shrinks to the memcg\nnet/core/sock.c-3594- *\t@sk: socket\n--\nnet/core/sock.c-3601- */\nnet/core/sock.c:3602:void sk_memcg_budget_shrink(struct sock *sk)\nnet/core/sock.c-3603-{\n--\nnet/core/sock.c-3615-\t */\nnet/core/sock.c:3616:\tspin_lock_bh(\u0026sk-\u003esk_memcg_budget_lock);\nnet/core/sock.c:3617:\twant = sk_memcg_budget_pages(sk);\nnet/core/sock.c:3618:\tif (want \u003c sk-\u003esk_memcg_budget) {\nnet/core/sock.c:3619:\t\trefund = sk-\u003esk_memcg_budget - want;\nnet/core/sock.c:3620:\t\tWRITE_ONCE(sk-\u003esk_memcg_budget, want);\nnet/core/sock.c-3621-\t}\nnet/core/sock.c:3622:\tspin_unlock_bh(\u0026sk-\u003esk_memcg_budget_lock);\nnet/core/sock.c-3623-\n--\nnet/core/sock.c-3626-}\nnet/core/sock.c:3627:EXPORT_SYMBOL(sk_memcg_budget_shrink);\nnet/core/sock.c-3628-\nnet/core/sock.c-3629-/**\nnet/core/sock.c:3630: *\tsk_memcg_budget_release - return the socket's memcg budget charge\nnet/core/sock.c-3631- *\t@sk: socket\n--\nnet/core/sock.c-3638- */\nnet/core/sock.c:3639:void sk_memcg_budget_release(struct sock *sk)\nnet/core/sock.c-3640-{\n--\nnet/core/sock.c-3642-\nnet/core/sock.c:3643:\tspin_lock_bh(\u0026sk-\u003esk_memcg_budget_lock);\nnet/core/sock.c:3644:\trefund = sk-\u003esk_memcg_budget;\nnet/core/sock.c:3645:\tWRITE_ONCE(sk-\u003esk_memcg_budget, 0);\nnet/core/sock.c:3646:\tspin_unlock_bh(\u0026sk-\u003esk_memcg_budget_lock);\nnet/core/sock.c-3647-\n--\nnet/core/sock.c-3650-}\nnet/core/sock.c:3651:EXPORT_SYMBOL(sk_memcg_budget_release);\nnet/core/sock.c-3652-\nnet/core/sock.c=3653=void __sk_charge(struct sock *sk, gfp_t gfp)\n--\nnet/core/sock.c-3659-\t */\nnet/core/sock.c:3660:\tsk_memcg_budget_sync(sk, gfp);\nnet/core/sock.c-3661-\n--\nnet/core/sock.c=3892=void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)\n--\nnet/core/sock.c-3904-\t */\nnet/core/sock.c:3905:\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/core/sock.c-3906-\tsk-\u003esk_state\t\t=\tTCP_CLOSE;\n--\nnet/core/sock.c=4707=static int __init sock_struct_check(void)\n--\nnet/core/sock.c-4726-\tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_socket);\nnet/core/sock.c:4727:\tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg_budget);\nnet/core/sock.c:4728:\tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg_budget_lock);\nnet/core/sock.c-4729-#ifdef CONFIG_MEMCG\n--\nnet/ipv4/tcp.c=420=void tcp_init_sock(struct sock *sk)\n--\nnet/ipv4/tcp.c-476-\t */\nnet/ipv4/tcp.c:477:\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/ipv4/tcp.c-478-\ttcp_scaling_ratio_init(sk);\n--\nnet/ipv4/tcp.c=913=void sk_forced_mem_schedule(struct sock *sk, int size)\n--\nnet/ipv4/tcp.c-924-\t/* Only the global protocol counter: the memcg side follows the\nnet/ipv4/tcp.c:925:\t * socket budget (sk_memcg_budget_sync()).\nnet/ipv4/tcp.c-926-\t */\n--\nnet/ipv4/tcp.c=1836=int tcp_set_rcvlowat(struct sock *sk, int val)\n--\nnet/ipv4/tcp.c-1856-\t\tWRITE_ONCE(sk-\u003esk_rcvbuf, space);\nnet/ipv4/tcp.c:1857:\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/ipv4/tcp.c-1858-\n--\nnet/ipv4/tcp_input.c=605=static void tcp_sndbuf_expand(struct sock *sk)\n--\nnet/ipv4/tcp_input.c-634-\t\t\t min(sndmem, READ_ONCE(sock_net(sk)-\u003eipv4.sysctl_tcp_wmem[2])));\nnet/ipv4/tcp_input.c:635:\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/ipv4/tcp_input.c-636-\t}\n--\nnet/ipv4/tcp_input.c=780=static void tcp_clamp_window(struct sock *sk)\n--\nnet/ipv4/tcp_input.c-795-\t\t\t min(atomic_read(\u0026sk-\u003esk_rmem_alloc), rmem2));\nnet/ipv4/tcp_input.c:796:\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/ipv4/tcp_input.c-797-\t}\n--\nnet/ipv4/tcp_input.c=914=void tcp_rcvbuf_grow(struct sock *sk, u32 newval)\n--\nnet/ipv4/tcp_input.c-953-\t\tWRITE_ONCE(sk-\u003esk_rcvbuf, rcvbuf);\nnet/ipv4/tcp_input.c:954:\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/ipv4/tcp_input.c-955-\t\t/* Make the window clamp follow along. */\n--\nnet/ipv4/tcp_input.c=6044=static bool tcp_should_expand_sndbuf(struct sock *sk)\n--\nnet/ipv4/tcp_input.c-6063-\t\t\tWRITE_ONCE(sk-\u003esk_sndbuf, unused_mem);\nnet/ipv4/tcp_input.c:6064:\t\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/ipv4/tcp_input.c-6065-\t\t}\n--\nnet/mptcp/protocol.c=215=static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval)\n--\nnet/mptcp/protocol.c-240-\t\tWRITE_ONCE(sk-\u003esk_rcvbuf, rcvbuf);\nnet/mptcp/protocol.c:241:\t\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/mptcp/protocol.c-242-\t\treturn true;\n--\nnet/mptcp/protocol.c=3217=static int mptcp_init_sock(struct sock *sk)\n--\nnet/mptcp/protocol.c-3249-\t */\nnet/mptcp/protocol.c:3250:\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/mptcp/protocol.c-3251-\tsk-\u003esk_write_space = sk_stream_write_space;\n--\nnet/mptcp/protocol.h=1025=static inline void __mptcp_sync_sndbuf(struct sock *sk)\n--\nnet/mptcp/protocol.h-1042-\tWRITE_ONCE(sk-\u003esk_sndbuf, new_sndbuf);\nnet/mptcp/protocol.h:1043:\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/mptcp/protocol.h-1044-\tmptcp_write_space(sk);\n--\nnet/mptcp/sockopt.c=70=static void __mptcp_subflow_set_rcvbuf(struct sock *ssk, int val)\n--\nnet/mptcp/sockopt.c-72-\tWRITE_ONCE(ssk-\u003esk_rcvbuf, val);\nnet/mptcp/sockopt.c:73:\tsk_memcg_budget_sync(ssk, gfp_memcg_charge());\nnet/mptcp/sockopt.c-74-\ttcp_set_rcvbuf(ssk, val);\n--\nnet/mptcp/sockopt.c=1631=int mptcp_set_rcvlowat(struct sock *sk, int val)\n--\nnet/mptcp/sockopt.c-1659-\tWRITE_ONCE(sk-\u003esk_rcvbuf, space);\nnet/mptcp/sockopt.c:1660:\tsk_memcg_budget_sync(sk, gfp_memcg_charge());\nnet/mptcp/sockopt.c-1661-\tmptcp_for_each_subflow(mptcp_sk(sk), subflow) {\n--\nnet/mptcp/sockopt.c-1666-\t\tWRITE_ONCE(ssk-\u003esk_rcvbuf, space);\nnet/mptcp/sockopt.c:1667:\t\tsk_memcg_budget_sync(ssk, gfp_memcg_charge());\nnet/mptcp/sockopt.c-1668-\t\tWRITE_ONCE(tcp_sk(ssk)-\u003ewindow_clamp, val);\n--\nnet/mptcp/subflow.c=1732=void __mptcp_inherit_memcg(struct sock *sk, struct sock *ssk, gfp_t gfp)\n--\nnet/mptcp/subflow.c-1741-\t */\nnet/mptcp/subflow.c:1742:\tsk_memcg_budget_release(ssk);\nnet/mptcp/subflow.c-1743-\tmem_cgroup_sk_inherit(sk, ssk);\n"
} |
| 43/3 | 2026/09/26 12:37 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 22479 output: 30 thoughts: 135 Thoughts: Okay, here's my understanding, framed as my internal thought process: **Internal Sock Accounting vs. User-Space Representation** Right, let's break this down. That `sk_memcg_budget` field within the `struct sock`... that's purely an internal thing. It's an integer used for internal accounting, and it's protected by that `sk_memcg_budget_lock` – so far, so good. It’s never exposed directly to user space. But, I need to understand how this relates to what users can see via memory statistics and cgroup files, particularly `memory.stat` and related cgroup filesystem entries. So, cgroup files like `memory.stat` *do* show memcg stats, including things like the `sock` count. But how is that `sock` figure computed? It looks like `cg_read_key_long(cgroup, "memory.stat", "sock ")` is how we read it. That function's going to read formatted text; ASCII numbers. These numbers are based on statistics counters that are, presumably, kept up-to-date by the memcg system. The important part is: the stats are separate from the internal accounting! I need to dig into how the `mem_cgroup_sk_charge` and `mem_cgroup_sk_uncharge` functions work. They're where the rubber meets the road. These must be the functions that actually manipulate the underlying counters that *feed* the cgroup statistics that user space sees. Gotta understand how the internal budget interacts with those charging and uncharging mechanisms. |
| 44/3 | 2026/09/26 12:37 | tool | grepper |
0mArgs:{
"Expression": "mem_cgroup_sk_charge",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/memcontrol.c=5596=void mem_cgroup_sk_inherit(const struct sock *sk, struct sock *newsk)\n--\nmm/memcontrol.c-5612-/**\nmm/memcontrol.c:5613: * mem_cgroup_sk_charge - charge socket memory\nmm/memcontrol.c-5614- * @sk: socket in memcg to charge\n--\nmm/memcontrol.c-5620- */\nmm/memcontrol.c:5621:bool mem_cgroup_sk_charge(const struct sock *sk, unsigned int nr_pages,\nmm/memcontrol.c-5622-\t\t\t gfp_t gfp_mask)\n"
} |
| 45/3 | 2026/09/26 12:37 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 23175 output: 35 thoughts: 29 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, and written as if they are my own: **Diving into the Socket Memory Accounting** Alright, I need to get a handle on the memory accounting within the cgroup context for sockets. My immediate thought is, let's drill down into the implementation details of `mem_cgroup_sk_charge` and `mem_cgroup_sk_uncharge`. These are the functions responsible for recording and releasing memory used by sockets within a memory cgroup, and I want to verify their precise behaviour. What's the allocation path, and how are these functions integrated into the core socket operations? I'm particularly interested in understanding how these routines prevent memory over-charges or under-charges. I need to know how these functions interact with the page cache, and whether there are any race conditions lurking in the code. Any edge cases related to socket life-cycle need to be taken into account; such as how the accounting is handled at socket close, or when a socket transitions between states. Ultimately, I need to confirm whether the accounting is robust and doesn't introduce any subtle memory leaks or inaccurate reporting. |
| 46/3 | 2026/09/26 12:37 | tool | read-file |
0mArgs:{
"File": "mm/memcontrol.c",
"FirstLine": 5613,
"LineCount": 40
}Results: {
"Contents": "5613:\t * mem_cgroup_sk_charge - charge socket memory\n5614:\t * @sk: socket in memcg to charge\n5615:\t * @nr_pages: number of pages to charge\n5616:\t * @gfp_mask: reclaim mode\n5617:\t *\n5618:\t * Charges @nr_pages to @memcg. Returns %true if the charge fit within\n5619:\t * @memcg's configured limit, %false if it doesn't.\n5620:\t */\n5621:\tbool mem_cgroup_sk_charge(const struct sock *sk, unsigned int nr_pages,\n5622:\t\t\t\t gfp_t gfp_mask)\n5623:\t{\n5624:\t\tstruct mem_cgroup *memcg = mem_cgroup_from_sk(sk);\n5625:\t\n5626:\t\tif (!cgroup_subsys_on_dfl(memory_cgrp_subsys))\n5627:\t\t\treturn memcg1_charge_skmem(memcg, nr_pages, gfp_mask);\n5628:\t\n5629:\t\tif (try_charge_memcg(memcg, gfp_mask, nr_pages) == 0) {\n5630:\t\t\tmod_memcg_state(memcg, MEMCG_SOCK, nr_pages);\n5631:\t\t\treturn true;\n5632:\t\t}\n5633:\t\n5634:\t\treturn false;\n5635:\t}\n5636:\t\n5637:\t/**\n5638:\t * mem_cgroup_sk_uncharge - uncharge socket memory\n5639:\t * @sk: socket in memcg to uncharge\n5640:\t * @nr_pages: number of pages to uncharge\n5641:\t */\n5642:\tvoid mem_cgroup_sk_uncharge(const struct sock *sk, unsigned int nr_pages)\n5643:\t{\n5644:\t\tstruct mem_cgroup *memcg = mem_cgroup_from_sk(sk);\n5645:\t\n5646:\t\tif (!cgroup_subsys_on_dfl(memory_cgrp_subsys)) {\n5647:\t\t\tmemcg1_uncharge_skmem(memcg, nr_pages);\n5648:\t\t\treturn;\n5649:\t\t}\n5650:\t\n5651:\t\tmod_memcg_state(memcg, MEMCG_SOCK, -nr_pages);\n5652:\t\n"
} |
| 47/3 | 2026/09/26 12:37 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24154 output: 383 thoughts: 1249 Thoughts: **Uninitialized Memory Risk Assessment** Okay, so I've been through this code, which essentially batches socket memory cgroup accounting. First, let's establish: `mem_cgroup_sk_charge` is just updating the memcg page counter. Now, my mind goes straight to the potential for uninitialized memory issues. I've always been thorough in my assessment, so I've meticulously reviewed the standard criteria: 1. **Kernel-to-User Space Leaks:** There is *zero* risk here. We're not sending any uninitialized kernel structures to user space via the usual vectors (copy\_to\_user, netlink, etc.). The new fields, `sk_memcg_budget` and `sk_memcg_budget_lock` are internal to `struct sock`. Even the `sock_diag` code, which *does* construct netlink attributes, does it by copying *specific* fields, *never* the raw `struct sock` bytes. We're safe on this front. 2. **Conditional Logic/Branching on Uninitialized Variables:** Every single variable introduced in the new functions is initialized before use. `sk_memcg_budget` is explicitly initialized to zero in `sk_alloc` (due to `__GFP_ZERO`) and `sk_clone`. Spinlocks are initialized with `spin_lock_init`. The local variables within `sk_memcg_budget_sync`, `sk_memcg_budget_shrink`, and `sk_memcg_budget_release` are all either assigned or initialized before ever being read. No worries here. 3. **Complex Data Structures:** No new complex data structures are being allocated or initialized. This patch is about changing the accounting *method*, not introducing new data. 4. **Bounds Checks/Uninitialized Bytes:** The code *doesn't* introduce any changes that would allow the access of uninitialized bytes within existing buffers. This patch deals with the *budgeting* of socket memory, not buffer sizes or accesses. So, from an uninitialized memory perspective, it's a clean bill of health. But, as we know, that's not the whole story. What *could* go wrong? Well, these are the risks I see: * Concurrency issues, like race conditions between `sk_memcg_budget_sync`, `sk_memcg_budget_shrink`, and `sk_memcg_budget_release`. * Locking problems: spinlocks, deadlocks, and issues caught by LOCKDEP. * Memcg accounting imbalances: mismatches, underflows, leaks. * Use-after-free or refcounting errors involving the socket or its memcg. KASAN and refcount\_t will catch these. * Out of bounds / buffer overflows. KASAN again. However, all of these are *not* uninitialized memory issues, per se. The standard KASAN and LOCKDEP builds will cover the rest. And KMSAN, of course, detects reads of uninitialized memory (stack, heap, page allocations) and kernel-to-user memory leaks. We don't have any of *those* here. I've done a very deep dive, reviewing the changes to the selftest, documentation, header files, and the core implementation. It's all consistent: we replace individual memcg charges with budget syncing, and that's it. Nothing to trigger a KMSAN requirement. Therefore, NeedsKMSAN should be false. I'm ready to move on. Let's prepare the tool call to `set-results`. |
| 48/3 | 2026/09/26 12:37 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series refactors memory cgroup (memcg) accounting for TCP and MPTCP sockets, shifting from per-allocation charges (__sk_mem_raise_allocated / __sk_mem_reduce_allocated) to upfront budget-based charging against sk_sndbuf, sk_rcvbuf, and sk_reserved_mem via sk_memcg_budget_sync/shrink/release.\n\nEvaluation against KMSAN vs KASAN:\n1. No uninitialized memory usage or branching on uninitialized data: The newly added struct sock members (sk_memcg_budget and sk_memcg_budget_lock) are allocated with __GFP_ZERO in sk_alloc() and explicitly initialized in sk_clone(). All local variables in the new helper functions (sk_memcg_budget_pages, sk_memcg_budget_sync, sk_memcg_budget_shrink, sk_memcg_budget_release) are explicitly initialized before use.\n2. No kernel-to-user data leaks or uninitialized padding exposure: sk_memcg_budget is strictly internal socket accounting state and is not copied to userspace via getsockopt, sock_diag, ioctl, netlink, or BPF.\n3. No altered bounds or buffer sizes for userspace copies: The changes alter memcg accounting metadata calculations without changing buffer indexing, payload copying, or packet data handling.\n4. Any potential issues introduced by this patch (such as spinlock concurrency bugs, memcg refcounting / accounting underflows, or socket lifecycle races) are covered by KASAN, LOCKDEP, and existing kernel assertions rather than KMSAN.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series refactors memory cgroup (memcg) accounting for TCP and MPTCP sockets, shifting from per-allocation charges (__sk_mem_raise_allocated / __sk_mem_reduce_allocated) to upfront budget-based charging against sk_sndbuf, sk_rcvbuf, and sk_reserved_mem via sk_memcg_budget_sync/shrink/release.\n\nEvaluation against KMSAN vs KASAN:\n1. No uninitialized memory usage or branching on uninitialized data: The newly added struct sock members (sk_memcg_budget and sk_memcg_budget_lock) are allocated with __GFP_ZERO in sk_alloc() and explicitly initialized in sk_clone(). All local variables in the new helper functions (sk_memcg_budget_pages, sk_memcg_budget_sync, sk_memcg_budget_shrink, sk_memcg_budget_release) are explicitly initialized before use.\n2. No kernel-to-user data leaks or uninitialized padding exposure: sk_memcg_budget is strictly internal socket accounting state and is not copied to userspace via getsockopt, sock_diag, ioctl, netlink, or BPF.\n3. No altered bounds or buffer sizes for userspace copies: The changes alter memcg accounting metadata calculations without changing buffer indexing, payload copying, or packet data handling.\n4. Any potential issues introduced by this patch (such as spinlock concurrency bugs, memcg refcounting / accounting underflows, or socket lifecycle races) are covered by KASAN, LOCKDEP, and existing kernel assertions rather than KMSAN.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|