| 0/0 |
2026/07/30 09:10 |
flow |
repro |
22m
Error:
|
| 1/1 |
2026/07/30 09:10 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/3735123449433f9e6f9fb5b55ddd9994cbcf15f0]
|
| 2/1 |
2026/07/30 09:10 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/846948e4048290a8bb370cf967150a5c7ab49e94]
|
| 3/1 |
2026/07/30 09:10 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/07/30 09:11 |
agent |
crash-repro-finder |
14m
Model:
Results: map[ReproSyz:r0 = syz_usb_connect(0x0, 0x0, 0x0, 0x0)
syz_usb_control_io(r0, 0x0, 0x0)
Sandbox:none]
Instruction:
You are an expert in Linux kernel fuzzing. Your goal is to write a syzkaller program to trigger a specific bug.
Execution sandboxes ('Sandbox' output field):
- "none": Runs test processes as root without Linux namespace isolation (PID, Net, IPC, User).
- "setuid": Runs test processes under unprivileged user accounts (drops root privileges and capabilities).
- "namespace": Runs test processes inside isolated Linux namespaces
(CLONE_NEWNS, CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWPID, CLONE_NEWNET,
CLONE_NEWUSER) with isolated network devices (loopback, tun, etc.).
Use this for network, IPC, or namespace bugs.
- "android": Simulates Android application privilege and SELinux restrictions (drops privileges to Android UIDs/GIDs).
Document about syzkaller program syntax:
===
# Program syntax
Syzkaller uses a compact domain-specific language (DSL) for programs
to log executed programs, test its code, and persist programs in the
corpus. This page provides a brief description of the corresponding
syntax. Some useful information can also be found in the
[existing examples](/sys/linux/test) and in the program
[deserialization code](/prog/encoding.go).
Together with execution options, the DSL provides everything that
syz-executor needs to run a program.
For example, consider the program:
```
r0 = syz_open_dev$loop(&(0x7f00000011c0), 0x0, 0x0)
r1 = openat$6lowpan_control(0xffffffffffffff9c, &(0x7f00000000c0), 0x2, 0x0)
ioctl$LOOP_SET_FD(r0, 0x4c00, r1)
```
Each line in this program describes a particular syscall invocation,
with the first two calls saving the result in temporary variables `r0`
and `r1`, which are passed to the third call.
```
line = assignment | call
assignment = variable " = " call
call = syscall-name "(" [arg ["," arg]*] ")" ["(" [call-prop ["," call-prop*] ")"]
arg = "nil" | "AUTO" | const-arg | resource-arg | result-arg | pointer-arg | string-arg | struct-arg | array-arg | union-arg
const-arg = integer
resource-arg = variable ["/" hex-integer] ["+" hex-integer]
result-arg = "<" variable "=>" arg
pointer-arg = "&" pointer-arg-addr ["=ANY"] "=" arg
pointer-arg-addr = "AUTO" | "(" pointer-addr ["/" region-size] ")"
string-arg = "'" escaped-string "'" | "\"" escaped-string "\"" | "\"$" escaped-string "\""
struct-arg = "{" [arg ["," arg]*] "}"
array-arg = "[" [arg ["," arg]*] "]"
union-arg = "@" field-name ["=" arg]
call-prop = prop-name ": " prop-value
variable = "r" dec-integer
pointer-addr = integer
region-size = integer
integer = dec-integer | oct-integer | "0x" hex-integer
```
Programs may also contain blank lines and comments.
```
# Obtain a file handle
r0 = openat(0xffffffffffffff9c, &AUTO='./file1\x00', 0x42, 0x1ff)
# Perform a write operation
write(r0, &AUTO="01010101", 0x4)
```
### Memory management
Memory management is performed by syzkaller itself. It will allocate
virtual memory regions of the necessary size and set the final values
of pointer arguments.
By using the `AUTO` keyword, programs can give syzkaller the full
control over storing the data. This may be convenient e.g. when a
parameter must be passed by reference, but the exact location of its
value is not of particular importance.
```
r1 = syz_genetlink_get_family_id$nl80211(&AUTO='nl80211\x00', 0xffffffffffffffff)
ioctl$sock_SIOCGIFINDEX_80211(r0, 0x8933, &AUTO={'wlan0\x00', <r2=>0x0})
```
Alternatively, some data can be "anchored" to specific addresses. It
may be especially important when a memory region must be shared
between multiple calls. In this case, pointer addresses must be given
at the 0x7f0000000000 offset. Before the actual execution, syzkaller
will adjust pointers to the start of the actual mmap'ed region.
### Call properties
Call properties specify extra information about how a specific call
must be executed. Each call within a program has its own set of call
properties. If no properties are provided, syzkaller takes the default
ones.
Currently, syzkaller supports the following call properties.
#### Fault injection
Syntax: `fail_nth: N`.
It takes an integer (base 10) argument `N`. If the argument is
non-negative, a fault will be injected into the `N`-th occasion.
```
r0 = openat$6lowpan_control(0xffffffffffffff9c, &(0x7f00000000c0), 0x2, 0x0)
ioctl$LOOP_SET_FD(r0, 0x4c00, r0) (fail_nth: 5)
```
#### Async
Syntax: `async`.
Instructs `syz-executor` not to wait until the call completes and
to proceed immediately to the next call.
```
r0 = openat(0xffffffffffffff9c, &AUTO='./file1\x00', 0x42, 0x1ff)
write(r0, &AUTO="01010101", 0x4) (async)
read(r0, &AUTO=""/4, 0x4)
close(r0)
```
When setting `async` flags be aware of the following considerations:
* Such programs should only be executed in threaded mode (i.e. `-threaded`
flag must be passed to `syz-executor`.
* Each `async` call is executed in a separate thread and there's a
limited number of available threads (`kMaxThreads = 16`).
* If an `async` call produces a resource, keep in mind that some other call
might take it as input and `syz-executor` will just pass 0 if the resource-
producing call has not finished by that time.
===
Document about syzlang system call descriptions syntax:
===
# Syscall description language
aka `syzlang` (`[siΛzΛlΓ¦Εg]`)
Pseudo-formal grammar of syscall description:
```
syscallname "(" [arg ["," arg]*] ")" [type] ["(" attribute* ")"]
arg = argname type
argname = identifier
type = typename [ "[" type-options "]" ]
typename = "const" | "intN" | "intptr" | "flags" | "array" | "ptr" |
"string" | "filename" | "glob" | "len" |
"bytesize" | "bytesizeN" | "bitsize" | "vma" | "proc" |
"compressed_image"
type-options = [type-opt ["," type-opt]]
```
common type-options include:
```
"opt" - the argument is optional (like mmap fd argument, or accept peer argument)
```
rest of the type-options are type-specific:
```
"const": integer constant, type-options:
value, underlying type (one of "intN", "intptr")
"intN"/"intptr": an integer without a particular meaning, type-options:
either an optional range of values (e.g. "5:10", or "100:200")
or a reference to flags description (see below),
or a single value
optionally followed by an alignment parameter if using a range
"flags": a set of values, type-options:
reference to flags description (see below), underlying int type (e.g. "int32")
"array": a variable/fixed-length array, type-options:
type of elements, optional size (fixed "5", or ranged "5:10", boundaries inclusive)
"ptr"/"ptr64": a pointer to an object, type-options:
direction (in/out/inout); type of the object
ptr64 has size of 8 bytes regardless of target pointer size
"string": a zero-terminated memory buffer (no pointer indirection implied), type-options:
either a string value in quotes for constant strings (e.g. "foo" or `deadbeef` for hex literal),
or a reference to string flags (special value `filename` produces file names),
optionally followed by a buffer size (string values will be padded with \x00 to that size)
"stringnoz": a non-zero-terminated memory buffer (no pointer indirection implied), type-options:
either a string value in quotes for constant strings (e.g. "foo" or `deadbeef` for hex literal),
or a reference to string flags,
"glob": glob pattern to match on the target files, type-options:
a pattern string in quotes (syntax: https://golang.org/pkg/path/filepath/#Match)
(e.g. "/sys/" or "/sys/**/*"),
or include exclude glob too (e.g. "/sys/**/*:-/sys/power/state")
"fmt": a string representation of an integer (not zero-terminated), type-options:
format (one of "dec", "hex", "oct") and the value (a resource, int, flags or proc)
the resulting data is always fixed-size (formatted as "%020llu", "0x%016llx" or "%023llo", respectively)
"len": length of another field (for array it is number of elements), type-options:
argname of the object
"bytesize": similar to "len", but always denotes the size in bytes, type-options:
argname of the object
"bitsize": similar to "len", but always denotes the size in bits, type-options:
argname of the object
"offsetof": offset of the field from the beginning of the parent struct, type-options:
field
"vma"/"vma64": a pointer to a set of pages (used as input for mmap/munmap/mremap/madvise), type-options:
optional number of pages (e.g. vma[7]), or a range of pages (e.g. vma[2-4])
vma64 has size of 8 bytes regardless of target pointer size
"proc": per process int (see description below), type-options:
value range start, how many values per process, underlying type
"compressed_image": zlib-compressed disk image
syscalls accepting compressed images must be marked with `no_generate`
and `no_minimize` call attributes. if the content of the decompressed image
can be checked by a `fsck`-like command, use the `fsck` syscall attribute
"text": machine code of the specified type, type-options:
text type (x86_real, x86_16, x86_32, x86_64, arm64)
"void": type with static size 0
mostly useful inside of templates and varlen unions, can't be syscall argument
```
flags/len/flags also have trailing underlying type type-option when used in structs/unions/pointers.
Flags are described as:
```
flagname = const ["," const]*
```
or for string flags as:
```
flagname = "\"" literal "\"" ["," "\"" literal "\""]*
```
Call attributes are:
```
"disabled": the call will not be used in fuzzing; useful to temporary disable some calls
or prohibit particular argument combinations.
"timeout[N]": additional execution timeout (in ms) for the call on top of some default value.
"prog_timeout[N]": additional execution timeout (in ms) for the whole program if it contains this call;
if a program contains several such calls, the max value is used.
"ignore_return": ignore return value of this syscall in fallback feedback; need to be used for calls
that don't return fixed error codes but rather something else (e.g. the current time).
"breaks_returns": ignore return values of all subsequent calls in the program in fallback feedback (can't be trusted).
"no_generate": do not try to generate this syscall, i.e. use only seed descriptions to produce it.
"no_minimize": do not modify instances of this syscall when trying to minimize a crashing program.
"no_squash": do not attempt to pass squashed arguments to this syscall.
Without that, the fuzzer will sometimes attempt to replace complex structures with arrays of bytes,
possibly triggering interesting mutations, but also making programs hard to reason about.
"fsck": the content of the compressed buffer argument for this syscall is a file system and the
string argument is a fsck-like command that will be called to verify the filesystem.
"remote_cover": wait longer to collect remote coverage for this call.
"kfuzz_test": the call is a kfuzztest target.
"snapshot": the call is enabled by default only in snapshot fuzzing mode, but can also be enabled in
the non-snasphot mode when listed in "enable_syscalls" with its full name (as opposed to a wildcard match).
It can also always be disabled via "disable_syscalls".
The attribute is generally used to mark calls that are not safe to execute in non-snapshot mode
(can lead to false positives, or lost connections to test machines.
```
## Ints
`int8`, `int16`, `int32` and `int64` denote an integer of the corresponding size.
`intptr` denotes a pointer-sized integer, i.e. C `long` type.
By appending `be` suffix (e.g. `int16be`) integers become big-endian.
It's possible to specify a range of values for an integer in the format of `int32[0:100]` or `int32[0:4096, 512]` for a 512-aligned int.
Integers can also take a reference to flags description or a value as its first type-option.
In that case, the alignment parameter is not supported.
To denote a bitfield of size N use `int64:N`.
It's possible to use these various kinds of ints as base types for `const`, `flags`, `len` and `proc`.
```
example_struct {
f0 int8 # random 1-byte integer
f1 const[0x42, int16be] # const 2-byte integer with value 0x4200 (big-endian 0x42)
f2 int32[0:100] # random 4-byte integer with values from 0 to 100 inclusive
f3 int32[1:10, 2] # random 4-byte integer with values {1, 3, 5, 7, 9}
f4 int64:20 # random 20-bit bitfield
f5 int8[10] # const 1-byte integer with value 10
f6 int32[flagname] # random 4-byte integer from the set of values referenced by flagname
}
```
## Structs
Structs are described as:
```
structname "{" "\n"
(fieldname type ("(" fieldattribute* ")")? (if[expression])? "\n")+
"}" ("[" attribute* "]")?
```
Fields can have attributes specified in parentheses after the field, independent
of their type. `in/out/inout` attribute specify per-field direction, for example:
```
foo {
field0 const[1, int32] (in)
field1 int32 (inout)
field2 fd (out)
}
```
You may specify conditions that determine whether a field will be included:
```
foo {
field0 int32
field1 int32 (if[value[field0] == 0x1])
}
```
See [the corresponding section](syscall_descriptions_syntax.md#conditional-fields)
for more details.
`out_overlay` attribute allows to have separate input and output layouts for the struct.
Fields before the `out_overlay` field are input, fields starting from `out_overlay` are output.
Input and output fields overlap in memory (both start from the beginning of the struct in memory).
For example:
```
foo {
in0 const[1, int32]
in1 flags[bar, int8]
in2 ptr[in, string]
out0 fd (out_overlay)
out1 int32
}
```
Structs can have attributes specified in square brackets after the struct.
Attributes are:
- `packed`: the struct does not have paddings between fields and has alignment 1; this is similar to GNU C `__attribute__((packed))`; struct alignment can be overridden with `align` attribute
- `align[N]`: the struct has alignment N and padded up to multiple of `N`; contents of the padding are unspecified (though, frequently are zeros); similar to GNU C `__attribute__((aligned(N)))`
- `size[N]`: the struct is padded up to the specified size `N`; contents of the padding are unspecified (though, frequently are zeros)
## Unions
Unions are described as:
```
unionname "[" "\n"
(fieldname type (if[expression])? "\n")+
"]" ("[" attribute* "]")?
```
During fuzzing, syzkaller randomly picks one of the union options.
You may also specify conditions that determine whether the corresponding
option may or may not be selected, depending on values of other fields. See
[the corresponding section](syscall_descriptions_syntax.md#conditional-fields)
for more details.
Unions can have attributes specified in square brackets after the union.
Attributes are:
- `varlen`: union size is the size of the particular chosen option (not statically known); without this attribute unions are statically sized as maximum of all options (similar to C unions)
- `size[N]`: the union is padded up to the specified size `N`; contents of the padding are unspecified (though, frequently are zeros)
## Resources
Resources represent values that need to be passed from output of one syscall to input of another syscall. For example, `close` syscall requires an input value (fd) previously returned by `open` or `pipe` syscall. To achieve this, `fd` is declared as a resource. This is a way of modelling dependencies between syscalls, as defining a syscall as the producer of a resource and another syscall as the consumer defines a loose sense of ordering between them. Resources are described as:
```
"resource" identifier "[" underlying_type "]" [ ":" const ("," const)* ]
```
`underlying_type` is either one of `int8`, `int16`, `int32`, `int64`, `intptr` or another resource (which models inheritance, for example, a socket is a subtype of fd). The optional set of constants represent resource special values, for example, `0xffffffffffffffff` (-1) for "no fd", or `AT_FDCWD` for "the current dir". Special values are used once in a while as resource values. If no special values specified, special value of `0` is used. Resources can then be used as types, for example:
```
resource fd[int32]: 0xffffffffffffffff, AT_FDCWD, 1000000
resource sock[fd]
resource sock_unix[sock]
socket(...) sock
accept(fd sock, ...) sock
listen(fd sock, backlog int32)
```
Resources don't have to be necessarily returned by a syscall. They can be used as any other data type. For example:
```
resource my_resource[int32]
request_producer(..., arg ptr[out, my_resource])
request_consumer(..., arg ptr[inout, test_struct])
test_struct {
...
attr my_resource
}
```
For more complex producer/consumer scenarios, field attributes can be utilized.
For example:
```
resource my_resource_1[int32]
resource my_resource_2[int32]
request_produce1_consume2(..., arg ptr[inout, test_struct])
test_struct {
...
field0 my_resource_1 (out)
field1 my_resource_2 (in)
}
```
Each resource type must be "produced" (used as an output) by at least one syscall
(outside of unions and optional pointers) and "consumed" (used as an input)
by at least one syscall.
## Type Aliases
Complex types that are often repeated can be given short type aliases using the
following syntax:
```
type identifier underlying_type
```
For example:
```
type signalno int32[0:65]
type net_port proc[20000, 4, int16be]
```
Then, type alias can be used instead of the underlying type in any contexts.
Underlying type needs to be described as if it's a struct field, that is,
with the base type if it's required. However, type alias can be used as syscall
arguments as well. Underlying types are currently restricted to integer types,
`ptr`, `ptr64`, `const`, `flags` and `proc` types.
There are some builtin type aliases:
```
type bool8 int8[0:1]
type bool16 int16[0:1]
type bool32 int32[0:1]
type bool64 int64[0:1]
type boolptr intptr[0:1]
type fileoff[BASE] BASE
type filename string[filename]
type buffer[DIR] ptr[DIR, array[int8]]
```
## Type Templates
Type templates can be declared as follows:
```
type buffer[DIR] ptr[DIR, array[int8]]
type fileoff[BASE] BASE
type nlattr[TYPE, PAYLOAD] {
nla_len len[parent, int16]
nla_type const[TYPE, int16]
payload PAYLOAD
} [align_4]
```
and later used as follows:
```
syscall(a buffer[in], b fileoff[int64], c ptr[in, nlattr[FOO, int32]])
```
There is builtin type template `optional` defined as:
```
type optional[T] [
val T
void void
] [varlen]
```
## Length
You can specify length of a particular field in struct or a named argument by
using `len`, `bytesize` and `bitsize` types, for example:
```
write(fd fd, buf ptr[in, array[int8]], count len[buf])
sock_fprog {
len len[filter, int16]
filter ptr[in, array[sock_filter]]
}
```
If `len`'s argument is a pointer, then the length of the pointee argument is used.
To denote the length of a field in N-byte words use `bytesizeN`, possible values
for N are 1, 2, 4 and 8.
To denote the length of the parent struct, you can use `len[parent, int8]`.
To denote the length of the higher level parent when structs are embedded into
one another, you can specify the type name of the particular parent:
```
s1 {
f0 len[s2] # length of s2
}
s2 {
f0 s1
f1 array[int32]
f2 len[parent, int32]
}
```
`len` argument can also be a path expression which allows more complex
addressing. Path expressions are similar to C field references, but also allow
referencing parent and sibling elements. A special reference `syscall` used
in the beginning of the path allows to refer directly to the syscall arguments.
For example:
```
s1 {
a ptr[in, s2]
b ptr[in, s3]
c array[int8]
}
s2 {
d array[int8]
}
s3 {
# This refers to the array c in the parent s1.
e len[s1:c, int32]
# This refers to the array d in the sibling s2.
f len[s1:a:d, int32]
# This refers to the array k in the child s4.
g len[i:j, int32]
# This refers to syscall argument l.
h len[syscall:l, int32]
i ptr[in, s4]
}
s4 {
j array[int8]
}
foo(k ptr[in, s1], l ptr[in, array[int8]])
```
## Proc
The `proc` type can be used to denote per process integers.
The idea is to have a separate range of values for each executor, so they don't interfere.
The simplest example is a port number.
The `proc[20000, 4, int16be]` type means that we want to generate an `int16be`
integer starting from `20000` and assign `4` values for each process.
As a result the executor number `n` will get values in the `[20000 + n * 4, 20000 + (n + 1) * 4)` range.
## Integer Constants
Integer constants can be specified as decimal literals, as `0x`-prefixed
hex literals, as `'`-surrounded char literals, or as symbolic constants
extracted from kernel headers or defined by `define` directives. For example:
```
foo(a const[10], b const[-10])
foo(a const[0xabcd])
foo(a int8['a':'z'])
foo(a const[PATH_MAX])
foo(a int32[PATH_MAX])
foo(a ptr[in, array[int8, MY_PATH_MAX]])
define MY_PATH_MAX PATH_MAX + 2
```
## Conditional fields
### In structures
In syzlang, it's possible to specify a condition for every struct field that
determines whether the field should be included or omitted:
```
header_fields {
magic const[0xabcd, int16]
haveInteger int8
} [packed]
packet {
header header_fields
integer int64 (if[value[header:haveInteger] == 0x1])
body array[int8]
} [packed]
some_call(a ptr[in, packet])
```
In this example, the `packet` structure will include the field `integer` only
if `header.haveInteger == 1`. In memory, `packet` will have the following
layout:
| header.magic = 0xabcd | header.haveInteger = 0x1 | integer | body |
| --------------------- | ------------------------ | ------- | ---- |
That corresponds to e.g. the following program:
```
some_call(&AUTO={{AUTO, 0x1}, @value=0xabcd, []})
```
If `header.haveInteger` is not `1`, syzkaller will just pretend that the field
`integer` does not exist.
```
some_call(&AUTO={{AUTO, 0x0}, @void, []})
```
| header.magic = 0xabcd | header.haveInteger = 0x0 | body |
| --------------------- | ------------------------ | ---- |
Every conditional field is assumed to be of variable length and so is the struct
to which this field belongs.
When a variable length field appears in the middle of a structure, the structure
must be marked with `[packed].`
Conditions on bitfields are prohibited:
```
struct {
f0 int
f1 int:3 (if[value[f0] == 0x1]) # It will not compile.
}
```
But you may reference bitfields in your conditions:
```
struct {
f0 int:1
f1 int:7
f2 int (if[value[f0] == value[f1]])
} [packed]
```
### In unions
Let's consider the following example.
```
struct {
type int
body alternatives
}
alternatives [
int int64 (if[value[struct:type] == 0x1])
arr array[int64, 5] (if[value[struct:type] == 0x2])
default int32
] [varlen]
some_call(a ptr[in, struct])
```
In this case, the union option will be selected depending on the value of the
`type` field. For example, if `type` is `0x1`, then it can be either `int` or
`default`:
```
some_call(&AUTO={0x1, @int=0x123})
some_call(&AUTO={0x1, @default=0x123})
```
If `type` is `0x2`, it can be either `arr` or `default`.
If `type` is neither `0x1` nor `0x2`, syzkaller may only select `default`:
```
some_call(&AUTO={0x0, @default=0xabcd})
```
To ensure that a union can always be constructed, the last union field **must always
have no condition**.
Thus, the following definition would fail to compile:
```
alternatives [
int int64 (if[value[struct:type] == 0x1])
arr array[int64, 5] (if[value[struct:type] == 0x1])
] [varlen]
```
During prog mutation and generation syzkaller will select a random union field
whose condition is satisfied.
### Expression syntax
Currently, only `==`, `!=`, `&` and `||` operators are supported. However, the
functionality was designed in such a way that adding more operators is easy.
Feel free to file a GitHub issue or write us an email in case it's needed.
Expressions are evaluated as `int64` values. If the final result of an
expression is not 0, it's assumed to be satisfied.
If you want to reference a field's value, you can do it via
`value[path:to:field]`, which is similar to the `len[]` argument.
```
sub_struct {
f0 int
# Reference a field in a parent struct.
f1 int (if[value[struct:f2]]) # Same as if[value[struct:f2] != 0].
}
struct {
f2 int
f3 sub_struct
f4 int (if[value[f2] == 0x2]) # Reference a sibling field.
f5 int (if[value[f3:f0] == 0x1]) # Reference a nested field.
f6 int (if[value[f3:f0] == 0x1 || value[f3:f0] == 0x2]) # Reference a nested field which either equals to 0x1 or 0x2.
} [packed]
call(a ptr[in, struct])
```
The referenced field must be of integer type and there must be no
conditional fields in the path to it. For example, the following
descriptions will not compile.
```
struct {
f0 int
f1 int (if[value[f0] == 0x1])
f2 int (if[value[f1] == 0x1])
}
```
You may also reference constants in expressions:
```
struct {
f0 int
f1 int
f2 int (if[value[f0] & SOME_CONST == OTHER_CONST])
}
```
## Meta
Description files can also contain `meta` directives that specify meta-information for the whole file.
```
meta noextract
```
Tells `make extract` to not extract constants for this file.
Though, `syz-extract` can still be invoked manually on this file.
```
meta arches["arch1", "arch2"]
```
Restricts this file only to the given set of architectures.
`make extract` and `make generate` will not use it on other architectures.
## Misc
Description files also contain `include` directives that refer to Linux kernel header files,
`incdir` directives that refer to custom Linux kernel header directories
and `define` directives that define symbolic constant values.
The syzkaller executor defines some [pseudo system calls](./pseudo_syscalls.md)
that can be used as any other syscall in a description file. These pseudo
system calls expand to literal C code and can perform user-defined
custom actions. You can find some examples in
[executor/common_linux.h](../executor/common_linux.h).
Also see [tips](syscall_descriptions.md#tips) on writing good descriptions.
===
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
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:
Bug title: kernel BUG in register_netdevice
The bug report to reproduce:
------------[ cut here ]------------
kernel BUG at net/core/dev.c:11340!
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
CPU: 0 UID: 0 PID: 7785 Comm: syz.5.435 Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/16/2026
RIP: 0010:register_netdevice+0x1233/0x25a0 net/core/dev.c:11340
Code: 0f 0b e8 60 ff 7a f8 48 8d 3d 69 50 a2 07 ba 47 2c 00 00 48 c7 c6 80 59 16 8d 67 48 0f b9 3a e9 a5 ee ff ff e8 3e ff 7a f8 90 <0f> 0b e8 36 ff 7a f8 90 0f 0b 48 8b 5c 24 18 48 8b 6c 24 20 e8 24
RSP: 0018:ffffc9000271f7c8 EFLAGS: 00010283
RAX: 0000000000027807 RBX: 1ffff920004e3f02 RCX: ffffc9000fd95000
RDX: 0000000000080000 RSI: ffffffff898f3fe2 RDI: ffff888044ca8000
RBP: ffff888067696000 R08: 0000000000000001 R09: 0000000000000000
R10: 0000000000000001 R11: 0000000000000000 R12: 0000000000000001
R13: ffff8880223b6000 R14: ffff888075d439b0 R15: ffff888067696000
FS: 00007f0b786416c0(0000) GS:ffff888123dde000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007ffd3d259f88 CR3: 0000000074345000 CR4: 0000000000350ef0
Call Trace:
<TASK>
register_netdev+0x34/0x50 net/core/dev.c:11562
gphonet_register_netdev drivers/usb/gadget/function/f_phonet.c:727 [inline]
pn_bind+0x149/0x5b0 drivers/usb/gadget/function/f_phonet.c:511
usb_add_function+0x219/0x890 drivers/usb/gadget/composite.c:333
configfs_composite_bind+0xd83/0x1960 drivers/usb/gadget/configfs.c:1802
gadget_bind_driver+0x28c/0xbf0 drivers/usb/gadget/udc/core.c:1662
call_driver_probe drivers/base/dd.c:628 [inline]
really_probe+0x241/0xa60 drivers/base/dd.c:706
__driver_probe_device+0x20e/0x450 drivers/base/dd.c:868
driver_probe_device+0x4a/0x140 drivers/base/dd.c:898
__driver_attach+0x21f/0x5b0 drivers/base/dd.c:1292
bus_for_each_dev+0x13e/0x1d0 drivers/base/bus.c:383
bus_add_driver+0x305/0x5b0 drivers/base/bus.c:763
driver_register+0x1e2/0x360 drivers/base/driver.c:174
usb_gadget_register_driver_owner+0x132/0x210 drivers/usb/gadget/udc/core.c:1752
gadget_dev_desc_UDC_store+0x1b0/0x2e0 drivers/usb/gadget/configfs.c:300
flush_write_buffer fs/configfs/file.c:207 [inline]
configfs_write_iter+0x302/0x4e0 fs/configfs/file.c:229
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x6ac/0x1050 fs/read_write.c:687
ksys_write+0x12a/0x250 fs/read_write.c:739
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x115/0x870 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f0b7779de99
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f0b78641028 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007f0b77a25fa0 RCX: 00007f0b7779de99
RDX: 000000000000000b RSI: 0000200000000380 RDI: 0000000000000003
RBP: 00007f0b77833eaf R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f0b77a26038 R14: 00007f0b77a25fa0 R15: 00007ffceb9c2f78
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:register_netdevice+0x1233/0x25a0 net/core/dev.c:11340
Code: 0f 0b e8 60 ff 7a f8 48 8d 3d 69 50 a2 07 ba 47 2c 00 00 48 c7 c6 80 59 16 8d 67 48 0f b9 3a e9 a5 ee ff ff e8 3e ff 7a f8 90 <0f> 0b e8 36 ff 7a f8 90 0f 0b 48 8b 5c 24 18 48 8b 6c 24 20 e8 24
RSP: 0018:ffffc9000271f7c8 EFLAGS: 00010283
RAX: 0000000000027807 RBX: 1ffff920004e3f02 RCX: ffffc9000fd95000
RDX: 0000000000080000 RSI: ffffffff898f3fe2 RDI: ffff888044ca8000
RBP: ffff888067696000 R08: 0000000000000001 R09: 0000000000000000
R10: 0000000000000001 R11: 0000000000000000 R12: 0000000000000001
R13: ffff8880223b6000 R14: ffff888075d439b0 R15: ffff888067696000
FS: 00007f0b786416c0(0000) GS:ffff888123dde000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007ffd1eb92fcc CR3: 0000000074345000 CR4: 0000000000350ef0
The list of existing description files:
acpi_thermal_rel.txt
aio.txt
auto.txt
binfmt.txt
bpf.txt
bpf_prog.txt
bpf_trace.txt
cgroup.txt
damon.txt
dev_ashmem.txt
dev_bifrost.txt
dev_binder.txt
dev_binderfs.txt
dev_block.txt
dev_bsg.txt
dev_bus_usb.txt
dev_camx.txt
dev_cdrom.txt
dev_cec.txt
dev_char_usb.txt
dev_comedi.txt
dev_dma_heap.txt
dev_dri.txt
dev_dsp.txt
dev_dvb_demux.txt
dev_dvb_dvr.txt
dev_dvb_frontend.txt
dev_fb.txt
dev_floppy.txt
dev_hidraw.txt
dev_i2c.txt
dev_i915.txt
dev_img_rogue.txt
dev_infiniband_rdma.txt
dev_infiniband_rdma_cm.txt
dev_input.txt
dev_iommu.txt
dev_kvm.txt
dev_kvm_amd64.txt
dev_kvm_arm64.txt
dev_kvm_extra.txt
dev_kvm_riscv64.txt
dev_loop.txt
dev_mali.txt
dev_media.txt
dev_msm.txt
dev_msr.txt
dev_mtd.txt
dev_nbd.txt
dev_net_tun.txt
dev_panthor.txt
dev_ppp.txt
dev_ptmx.txt
dev_ptp.txt
dev_qat_adf_ctl.txt
dev_qrtr_tun.txt
dev_random.txt
dev_rfkill.txt
dev_rtc.txt
dev_sequencer.txt
dev_sg.txt
dev_snapshot.txt
dev_snd_control.txt
dev_snd_hw.txt
dev_snd_midi.txt
dev_snd_pcm.txt
dev_snd_seq.txt
dev_snd_timer.txt
dev_sr.txt
dev_sw_sync.txt
dev_tee.txt
dev_tlk_device.txt
dev_trusty.txt
dev_udmabuf.txt
dev_uhid.txt
dev_uinput.txt
dev_usb_hiddev.txt
dev_usbmon.txt
dev_userio.txt
dev_vfio.txt
dev_vga_arbiter.txt
dev_vhci.txt
dev_video4linux.txt
dev_video4linux_vim2m.txt
dev_virtual_nci.txt
dev_vtpm.txt
fanotify.txt
filesystem.txt
fs_9p.txt
fs_fuse.txt
fs_incfs.txt
fs_ioctl.txt
fs_ioctl_autofs.txt
fs_ioctl_btrfs.txt
fs_ioctl_ext4.txt
fs_ioctl_f2fs.txt
fs_ioctl_fat.txt
fs_ioctl_fscrypt.txt
fs_ioctl_fsverity.txt
fs_ioctl_nilfs2.txt
fs_ioctl_ocfs2.txt
fs_ioctl_xfs.txt
futex.txt
hafnium.txt
inotify.txt
io_uring.txt
ipc.txt
key.txt
kfuzztest.txt
l2cap.txt
landlock.txt
lsm.txt
namespaces.txt
net_80211.txt
netfilter.txt
netfilter_arp.txt
netfilter_bridge.txt
netfilter_ipv4.txt
netfilter_ipv6.txt
netfilter_ipvs.txt
netfilter_targets.txt
pagemap_ioctl.txt
perf.txt
prctl.txt
quotactl.txt
seccomp.txt
security_apparmor.txt
security_selinux.txt
security_smack.txt
socket.txt
socket_alg.txt
socket_ax25.txt
socket_bluetooth.txt
socket_bluetooth_defs_default.txt
socket_bluetooth_defs_mips.txt
socket_caif.txt
socket_can.txt
socket_ieee802154.txt
socket_inet.txt
socket_inet6.txt
socket_inet_dccp.txt
socket_inet_icmp.txt
socket_inet_igmp.txt
socket_inet_l2tp.txt
socket_inet_sctp.txt
socket_inet_tcp.txt
socket_inet_udp.txt
socket_ip_tunnel.txt
socket_isdn.txt
socket_isdn_defs_386_arm.txt
socket_isdn_defs_cmd_default.txt
socket_isdn_defs_common_default.txt
socket_isdn_defs_mips.txt
socket_kcm.txt
socket_key.txt
socket_llc.txt
socket_netlink.txt
socket_netlink_audit.txt
socket_netlink_crypto.txt
socket_netlink_generic.txt
socket_netlink_generic_80211.txt
socket_netlink_generic_batadv.txt
socket_netlink_generic_devlink.txt
socket_netlink_generic_ethtool.txt
socket_netlink_generic_fou.txt
socket_netlink_generic_gtp.txt
socket_netlink_generic_mptcp.txt
socket_netlink_generic_net_dm.txt
socket_netlink_generic_netlabel.txt
socket_netlink_generic_nfc.txt
socket_netlink_generic_seg6.txt
socket_netlink_generic_smc.txt
socket_netlink_generic_team.txt
socket_netlink_generic_wireguard.txt
socket_netlink_netfilter.txt
socket_netlink_netfilter_acct.txt
socket_netlink_netfilter_conntrack.txt
socket_netlink_netfilter_conntrack_exp.txt
socket_netlink_netfilter_conntrack_helper.txt
socket_netlink_netfilter_ipset.txt
socket_netlink_netfilter_log.txt
socket_netlink_netfilter_nft_compat.txt
socket_netlink_netfilter_nftables.txt
socket_netlink_netfilter_osf.txt
socket_netlink_netfilter_queue.txt
socket_netlink_netfilter_timeout.txt
socket_netlink_rdma.txt
socket_netlink_route.txt
socket_netlink_route_sched.txt
socket_netlink_route_sched_retired.txt
socket_netlink_sock_diag.txt
socket_netlink_xfrm.txt
socket_netrom.txt
socket_nfc.txt
socket_nvme_of_tcp.txt
socket_packet.txt
socket_phonet.txt
socket_pppox.txt
socket_pppox_defs_386_arm.txt
socket_pppox_defs_default.txt
socket_pppox_defs_mips.txt
socket_qipcrtr.txt
socket_rds.txt
socket_rose.txt
socket_rxrpc.txt
socket_tipc.txt
socket_tipc_netlink.txt
socket_unix.txt
socket_vnet.txt
socket_x25.txt
socket_xdp.txt
sys.txt
ublk.txt
uffd.txt
usbip.txt
vmw_vmci.txt
vnet.txt
vnet_mptcp.txt
vusb.txt
watch_queue.txt
xattr.txt
|
| 5/2 |
2026/07/30 09:11 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 17168
output: 27
thoughts: 293
Thoughts: **Analyzing a Syzkaller Target for `register_netdevice`**
Okay, so the goal is to create a syzkaller program to trigger a bug within `register_netdevice`. The bug report clearly points towards an issue in `net/core/dev.c` at line 11340. The stack trace is particularly interesting because it traces through the USB gadget configfs, specifically the `phonet` function within `f_phonet.c`. We're seeing calls leading from `register_netdev` down into `gphonet_register_netdev`, then into `pn_bind` within the `f_phonet` driver, and ultimately down through the USB gadget core and configfs mechanisms. It looks like the error is originating from how `f_phonet` is registering its network device, likely in the context of the USB gadget. My next step is to examine the code at `net/core/dev.c:11340` to understand the exact nature of the kernel panic. This will tell me what conditions need to be set up to reproduce the bug within syzkaller. The stack trace will provide valuable clues about how to set up the USB gadget, configfs, and `f_phonet` drivers within the syzkaller program. This should be an interesting hunt.
|
| 6/2 |
2026/07/30 09:11 |
tool |
git-log |
0m
Args: map[Count:1 PathPrefix:net/core/dev.c]
Results: map[Output:6860b467f569 xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
]
|
| 7/2 |
2026/07/30 09:11 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 17810
output: 37
thoughts: 24
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Kernel Investigation Strategy**
Okay, I'm getting bogged down here. Instead of chasing this down a rabbit hole, let's step back and be strategic. The symptoms strongly suggest something within the network device initialization or core handling. I know there's a good chance that the problem lies close to the device driver level. I've got to start with the essentials, and the most logical place to begin is deep within the core. Let me focus on the core logic and not any of the specific driver logic, at least at first. Time to zero in on `net/core/dev.c`. I'm going to look at that file. Line 11340 sounds like a good starting point. Let's see what that portion of code is doing, and if it's handling the scenario in the way I expect. That should offer me a good jumping-off point for debugging.
|
| 8/2 |
2026/07/30 09:11 |
tool |
read-file |
0m
Args: map[File:net/core/dev.c FirstLine:11330 LineCount:20]
Results: map[Contents:11330: struct net *net = dev_net(dev);
11331:
11332: BUILD_BUG_ON(sizeof(netdev_features_t) * BITS_PER_BYTE <
11333: NETDEV_FEATURE_COUNT);
11334: BUG_ON(dev_boot_phase);
11335: ASSERT_RTNL();
11336:
11337: might_sleep();
11338:
11339: /* When net_device's are persistent, this will be fatal. */
11340: BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
11341: BUG_ON(!net);
11342:
11343: ret = ethtool_check_ops(dev->ethtool_ops);
11344: if (ret)
11345: return ret;
11346:
11347: /* rss ctx ID 0 is reserved for the default context, start from 1 */
11348: xa_init_flags(&dev->ethtool->rss_ctx, XA_FLAGS_ALLOC1);
11349: mutex_init(&dev->ethtool->rss_lock);
]
|
| 9/2 |
2026/07/30 09:11 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 18432
output: 43
thoughts: 153
Thoughts: **Debugging a Network Device Registration Issue**
Okay, so I'm looking at this bug, `BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);`. It seems pretty clear what's happening. The system is trying to register a network device, but the `net_device` struct (`dev`) has a `reg_state` that's not `NETREG_UNINITIALIZED`. That means it's already registered or, at least, in the middle of being registered or unregistered. That's a classic race condition or improper use of the registration functions.
The call trace tells the story: `gphonet_register_netdev` is the culprit, called inlined at `f_phonet.c:727`. This function is being called from `pn_bind` at `f_phonet.c:511`. So it appears `pn_bind` is where the flawed logic exists. I'll need to dig into `drivers/usb/gadget/function/f_phonet.c` to see how a `net_device` could possibly be registered twice, or even attempt to register while in an intermediate state. Time to check the code and see what `pn_bind` and `gphonet_register_netdev` are actually doing, and what they *should* be doing. I'll be looking for synchronization issues and any missed checks on the device's registration state.
|
| 10/2 |
2026/07/30 09:11 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:500 LineCount:30]
Results: map[Contents: 500: phonet_opts = container_of(f->fi, struct f_phonet_opts, func_inst);
501:
502: /*
503: * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
504: * configurations are bound in sequence with list_for_each_entry,
505: * in each configuration its functions are bound in sequence
506: * with list_for_each_entry, so we assume no race condition
507: * with regard to phonet_opts->bound access
508: */
509: if (!phonet_opts->bound) {
510: gphonet_set_gadget(phonet_opts->net, gadget);
511: status = gphonet_register_netdev(phonet_opts->net);
512: if (status)
513: return status;
514: phonet_opts->bound = true;
515: }
516:
517: /* Reserve interface IDs */
518: status = usb_interface_id(c, f);
519: if (status < 0)
520: goto err;
521: pn_control_intf_desc.bInterfaceNumber = status;
522: pn_union_desc.bMasterInterface0 = status;
523:
524: status = usb_interface_id(c, f);
525: if (status < 0)
526: goto err;
527: pn_data_nop_intf_desc.bInterfaceNumber = status;
528: pn_data_intf_desc.bInterfaceNumber = status;
529: pn_union_desc.bSlaveInterface0 = status;
]
|
| 11/2 |
2026/07/30 09:11 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 19445
output: 43
thoughts: 64
Thoughts: Here's my summary, as the expert:
**Investigating a Network Interface Registration**
Okay, so I'm looking at this code and something's bugging me. I'm seeing that `phonet_opts->bound` is set to true *after* `gphonet_register_netdev` is called. That feels a littleβ¦unconventional. My immediate concern is what happens if `pn_bind` fails later on, say because `usb_interface_id` bails out. The code then jumps to `err`, which is a common pattern, but I need to understand the implications of this particular sequence. My focus is now honed on what happens at the `err` label to fully grasp the ramifications of this design. I need to know how the cleanup and state management are handled in that error path to ensure we're not left with a partially initialized, or incorrectly-bound, network device. Gotta dig deeper into that `err` section.
|
| 12/2 |
2026/07/30 09:11 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:570 LineCount:20]
Results: map[Contents: 570: INFO(cdev, "USB CDC Phonet function\n");
571: INFO(cdev, "using %s, OUT %s, IN %s\n", cdev->gadget->name,
572: fp->out_ep->name, fp->in_ep->name);
573: return 0;
574:
575: err_req:
576: for (i = 0; i < phonet_rxq_size && fp->out_reqv[i]; i++)
577: usb_ep_free_request(fp->out_ep, fp->out_reqv[i]);
578: usb_free_all_descriptors(f);
579: err:
580: ERROR(cdev, "USB CDC Phonet: cannot autoconfigure\n");
581: return status;
582: }
583:
584: static inline struct f_phonet_opts *to_f_phonet_opts(struct config_item *item)
585: {
586: return container_of(to_config_group(item), struct f_phonet_opts,
587: func_inst.group);
588: }
589:
]
|
| 13/2 |
2026/07/30 09:11 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 20115
output: 38
thoughts: 167
Thoughts: **Debugging a Tricky Network Device Issue**
Okay, so I'm looking at this function and I see a potential problem. If this thing fails, it returns a status code, which is fine, but it *doesn't* unregister the network device, and, crucially, it doesn't reset `phonet_opts->bound` to false. Hmm, okay, interesting.
Now, if `phonet_opts->bound` is true, then the next time `pn_bind` gets called, itβs supposed to skip `gphonet_register_netdev`. That's the expected behavior, but this lack of unregistration is nagging at me.
But, wait... why am I seeing a crash with `BUG_ON(dev->reg_state != NETREG_UNINITIALIZED)` if the network device isnβt unregistered? It's making me scratch my head. I have to figure this out.
Could it be that `phonet_opts->bound` isnβt reset, but the netdev *is* somehow unregistered by another part of the code? Or, what if `phonet_opts->bound` *is* false in this scenario, and the code is somehow incorrectly trying to register an already registered or uninitialized device?
I need to trace the value of `phonet_opts->bound`. Let's dig deeper and see where `phonet_opts->bound` gets set to false. Time to start spelunking through the code and figure out the state of this variable. This is going to require some serious code archaeology.
|
| 14/2 |
2026/07/30 09:11 |
tool |
grepper |
0m
Error:
Args: map[Expression:bound = false PathPrefix:drivers/usb/gadget/function/f_phonet.c]
Results:
|
| 15/2 |
2026/07/30 09:11 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 20668
output: 36
thoughts: 16
Thoughts: Okay, let's dive into this code.
**Investigating the Usage of `phonet_opts->bound`**
Alright, so the task at hand is to understand how `phonet_opts->bound` is being utilized within this system. It's often helpful to begin by tracing the uses of a particular variable or structure member, especially something as potentially crucial as a "bound" parameter. I need to figure out where it's being read, modified, and what logic depends on its value. Depending on the architecture, I would start by using my IDE's "find usages" or "find references" feature, or perhaps a more advanced tool like a static analyzer. From there, I'll examine the surrounding context to grasp how changes to `phonet_opts->bound` influence the overall program flow and behavior of the phonet subsystem. This will quickly reveal any potential dependencies on this bound and its semantic meaning. My intuition suggests that this 'bound' will likely be related to resource allocation, packet size limitations, or possibly some kind of security measure or protocol compliance - but it can also be something completely different. Without additional context, I cannot be certain. The goal is to develop a clear understanding of its role and significance within the larger system.
|
| 16/2 |
2026/07/30 09:11 |
tool |
grepper |
0m
Args: map[Expression:bound PathPrefix:drivers/usb/gadget/function/f_phonet.c]
Results: map[Output:drivers/usb/gadget/function/f_phonet.c=490=static int pn_bind(struct usb_configuration *c, struct usb_function *f)
--
drivers/usb/gadget/function/f_phonet.c-503- * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
drivers/usb/gadget/function/f_phonet.c:504: * configurations are bound in sequence with list_for_each_entry,
drivers/usb/gadget/function/f_phonet.c:505: * in each configuration its functions are bound in sequence
drivers/usb/gadget/function/f_phonet.c-506- * with list_for_each_entry, so we assume no race condition
drivers/usb/gadget/function/f_phonet.c:507: * with regard to phonet_opts->bound access
drivers/usb/gadget/function/f_phonet.c-508- */
drivers/usb/gadget/function/f_phonet.c:509: if (!phonet_opts->bound) {
drivers/usb/gadget/function/f_phonet.c-510- gphonet_set_gadget(phonet_opts->net, gadget);
--
drivers/usb/gadget/function/f_phonet.c-513- return status;
drivers/usb/gadget/function/f_phonet.c:514: phonet_opts->bound = true;
drivers/usb/gadget/function/f_phonet.c-515- }
--
drivers/usb/gadget/function/f_phonet.c=619=static void phonet_free_inst(struct usb_function_instance *f)
--
drivers/usb/gadget/function/f_phonet.c-623- opts = container_of(f, struct f_phonet_opts, func_inst);
drivers/usb/gadget/function/f_phonet.c:624: if (opts->bound)
drivers/usb/gadget/function/f_phonet.c-625- gphonet_cleanup(opts->net);
]
|
| 17/2 |
2026/07/30 09:11 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 21540
output: 43
thoughts: 68
Thoughts: Here's my take on those thoughts:
**Analyzing a Potential Network Device Cleanup Issue**
Okay, so I'm looking at this `phonet_free_inst` function, and it seems like it's calling `gphonet_cleanup(opts->net)` under certain conditions. Specifically, if `opts->bound` is true. Right, that makes sense. We're cleaning up the network device if it's currently bound to something. Now, I remember that `gphonet_cleanup` is the function that actually handles the unregistration of the netdev. That's a key part of the cleanup process.
But here's where my mind starts to race a little. What if `pn_bind` fails? So we *tried* to bind, it didn't work, and then subsequently `pn_unbind` is invoked? We want to make sure the network device isn't left in a bad state. It's important to understand the flow and make sure no resources are leaked or left in an inconsistent state in this failure scenario. I'm going to have to check exactly *how* that situation is handled, and exactly what happens if `pn_bind` fails before `pn_unbind` is called.
|
| 18/2 |
2026/07/30 09:11 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:590 LineCount:30]
Results: map[Contents: 590: static void phonet_attr_release(struct config_item *item)
591: {
592: struct f_phonet_opts *opts = to_f_phonet_opts(item);
593:
594: usb_put_function_instance(&opts->func_inst);
595: }
596:
597: static const struct configfs_item_operations phonet_item_ops = {
598: .release = phonet_attr_release,
599: };
600:
601: static ssize_t f_phonet_ifname_show(struct config_item *item, char *page)
602: {
603: return gether_get_ifname(to_f_phonet_opts(item)->net, page, PAGE_SIZE);
604: }
605:
606: CONFIGFS_ATTR_RO(f_phonet_, ifname);
607:
608: static struct configfs_attribute *phonet_attrs[] = {
609: &f_phonet_attr_ifname,
610: NULL,
611: };
612:
613: static const struct config_item_type phonet_func_type = {
614: .ct_item_ops = &phonet_item_ops,
615: .ct_attrs = phonet_attrs,
616: .ct_owner = THIS_MODULE,
617: };
618:
619: static void phonet_free_inst(struct usb_function_instance *f)
]
|
| 19/2 |
2026/07/30 09:11 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 22388
output: 38
thoughts: 9
Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own:
**Analyzing `pn_unbind`**
Alright, let's see what we're dealing with here. The term `pn_unbind` has popped up, and my expert intuition is immediately activated. This likely relates to some form of binding or unbinding operation, potentially within a networking or inter-process communication context. My brain immediately starts running through possible implementations and their implications. Iβm thinking about what the 'pn' prefix might signify: is it protocol-specific, a particular naming convention within a given library, or something else entirely? Could it be related to a particular publish/subscribe framework? I'm quickly cycling through the common suspects: Proton, perhaps? Or something lower-level, closer to socket manipulation? The critical question now becomes: What exactly is being bound or unbound? Resources? Connections? Handlers? The surrounding code would be critical here to get the full picture. My mind is already sketching out potential scenarios and edge cases. I need more information to really pin this down, but it's got my full attention.
|
| 20/2 |
2026/07/30 09:12 |
tool |
grepper |
0m
Args: map[Expression:pn_unbind PathPrefix:drivers/usb/gadget/function/f_phonet.c]
Results: map[Output:drivers/usb/gadget/function/f_phonet.c=653=static void phonet_free(struct usb_function *f)
--
drivers/usb/gadget/function/f_phonet.c-660-
drivers/usb/gadget/function/f_phonet.c:661:static void pn_unbind(struct usb_configuration *c, struct usb_function *f)
drivers/usb/gadget/function/f_phonet.c-662-{
--
drivers/usb/gadget/function/f_phonet.c=676=static struct usb_function *phonet_alloc(struct usb_function_instance *fi)
--
drivers/usb/gadget/function/f_phonet.c-689- fp->function.bind = pn_bind;
drivers/usb/gadget/function/f_phonet.c:690: fp->function.unbind = pn_unbind;
drivers/usb/gadget/function/f_phonet.c-691- fp->function.set_alt = pn_set_alt;
]
|
| 21/2 |
2026/07/30 09:12 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 22954
output: 43
thoughts: 9
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Initial Assessment: Diving into `pn_unbind`**
Alright, time to dig into this `pn_unbind` function. My initial thought process is to understand its role within the broader system. I'm immediately trying to connect it to the bigger picture β what are its inputs, outputs, side effects, and, crucially, how does it interact with other components? Is it a crucial teardown function? Is it responsible for releasing specific resources? I'm already anticipating questions about its atomicity, thread safety, and potential race conditions. I need to figure out its contract: when is it safe to call this function and under what circumstances should I not call it. I will start by seeing what comments are provided with the code.
|
| 22/2 |
2026/07/30 09:12 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:660 LineCount:20]
Results: map[Contents: 660:
661: static void pn_unbind(struct usb_configuration *c, struct usb_function *f)
662: {
663: struct f_phonet *fp = func_to_pn(f);
664: int i;
665:
666: /* We are already disconnected */
667: if (fp->in_req)
668: usb_ep_free_request(fp->in_ep, fp->in_req);
669: for (i = 0; i < phonet_rxq_size; i++)
670: if (fp->out_reqv[i])
671: usb_ep_free_request(fp->out_ep, fp->out_reqv[i]);
672:
673: usb_free_all_descriptors(f);
674: }
675:
676: static struct usb_function *phonet_alloc(struct usb_function_instance *fi)
677: {
678: struct f_phonet *fp;
679: struct f_phonet_opts *opts;
]
|
| 23/2 |
2026/07/30 09:12 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 23513
output: 38
thoughts: 80
Thoughts: **Analyzing the Connection Management**
Okay, hold on a second. I'm looking at `pn_unbind`, and it doesn't seem to touch `opts->bound` or `opts->net`. That's a little odd. Now, I'm trying to remember the failure cases. If `pn_bind` fails, does `pn_unbind` even get called? Or maybe it isn't? I should check the calling logic again. Right, if `pn_bind` bails out, `usb_add_function` immediately returns an error. Let's make sure I'm following the exact flow. I'm going to jump into `usb_add_function` now.
|
| 24/2 |
2026/07/30 09:12 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/composite.c FirstLine:320 LineCount:30]
Results: map[Contents: 320: goto done;
321:
322: function->config = config;
323: list_add_tail(&function->list, &config->functions);
324:
325: if (function->bind_deactivated) {
326: value = usb_function_deactivate(function);
327: if (value)
328: goto done;
329: }
330:
331: /* REVISIT *require* function->bind? */
332: if (function->bind) {
333: value = function->bind(config, function);
334: if (value < 0) {
335: list_del(&function->list);
336: function->config = NULL;
337: }
338: } else
339: value = 0;
340:
341: /* We allow configurations that don't work at both speeds.
342: * If we run into a lowspeed Linux system, treat it the same
343: * as full speed ... it's the function drivers that will need
344: * to avoid bulk and ISO transfers.
345: */
346: if (!config->fullspeed && function->fs_descriptors)
347: config->fullspeed = true;
348: if (!config->highspeed && function->hs_descriptors)
349: config->highspeed = true;
]
|
| 25/2 |
2026/07/30 09:12 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 24206
output: 40
thoughts: 46
Thoughts: **Analyzing the Binding Failure**
Okay, so if the `function->bind` operation fails, it seems the system is designed to remove that specific function from the list and return the error. This makes sense from a modular design perspective, preventing a cascading failure. If this happens, then I expect that the `configfs_composite_bind` function will also fail, because the underlying function isn't bound. This seems to be the intended behaviour. I should now check the behaviour of `configfs_composite_bind` to confirm my understanding of the error handling and expected flow.
|
| 26/2 |
2026/07/30 09:12 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/configfs.c FirstLine:1790 LineCount:30]
Results: map[Contents:1790: }
1791: cfg->gstrings[i] = NULL;
1792: s = usb_gstrings_attach(&gi->cdev, cfg->gstrings, 1);
1793: if (IS_ERR(s)) {
1794: ret = PTR_ERR(s);
1795: goto err_comp_cleanup;
1796: }
1797: c->iConfiguration = s[0].id;
1798: }
1799:
1800: list_for_each_entry_safe(f, tmp, &cfg->func_list, list) {
1801: list_del(&f->list);
1802: ret = usb_add_function(c, f);
1803: if (ret) {
1804: list_add(&f->list, &cfg->func_list);
1805: goto err_purge_funcs;
1806: }
1807: }
1808: ret = usb_gadget_check_config(cdev->gadget);
1809: if (ret)
1810: goto err_purge_funcs;
1811:
1812: usb_ep_autoconfig_reset(cdev->gadget);
1813: }
1814: if (cdev->use_os_string) {
1815: ret = composite_os_desc_req_prepare(cdev, gadget->ep0);
1816: if (ret)
1817: goto err_purge_funcs;
1818: }
1819:
]
|
| 27/2 |
2026/07/30 09:12 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 24866
output: 40
thoughts: 20
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Debugging a USB Function Driver**
Alright, so I'm deep in the weeds with this USB driver, and I see a critical failure point. If `usb_add_function` returns an error, my immediate focus shifts to the `err_purge_funcs` routine. That's standard practice, really. This isn't a "retry and hope for the best" situation; it's a "clean up the mess we made before things get worse" situation. I'm thinking, "Okay, the function couldn't be added properly β perhaps a resource conflict, a driver issue, something preventing it from integrating with the USB stack. We need to back out cleanly and release whatever allocated resources.
`err_purge_funcs` is probably going to iterate through the partially initialized function structures, undo any partial registration, and deallocate anything that's been set up. The goal here isn't just to return an error code; it's to leave the system in a known, stable state. We're avoiding memory leaks, dangling pointers, and potential USB bus instability. A proper clean-up allows a graceful failover and leaves us free to diagnose the source of the problem. That's crucial. I'll need to double-check that this purge routine is properly implemented and all allocated memory is released and everything is fully unregistered to prevent future conflicts.
|
| 28/2 |
2026/07/30 09:12 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/configfs.c FirstLine:1820 LineCount:30]
Results: map[Contents:1820: usb_ep_autoconfig_reset(cdev->gadget);
1821: return 0;
1822:
1823: err_purge_funcs:
1824: purge_configs_funcs(gi);
1825: err_comp_cleanup:
1826: composite_dev_cleanup(cdev);
1827: return ret;
1828: }
1829:
1830: static void configfs_composite_unbind(struct usb_gadget *gadget)
1831: {
1832: struct usb_composite_dev *cdev;
1833: struct gadget_info *gi;
1834: unsigned long flags;
1835:
1836: /* the gi->lock is hold by the caller */
1837:
1838: cdev = get_gadget_data(gadget);
1839: gi = container_of(cdev, struct gadget_info, cdev);
1840: spin_lock_irqsave(&gi->spinlock, flags);
1841: gi->unbind = 1;
1842: spin_unlock_irqrestore(&gi->spinlock, flags);
1843:
1844: kfree(otg_desc[0]);
1845: otg_desc[0] = NULL;
1846: purge_configs_funcs(gi);
1847: composite_dev_cleanup(cdev);
1848: usb_ep_autoconfig_reset(cdev->gadget);
1849: spin_lock_irqsave(&gi->spinlock, flags);
]
|
| 29/2 |
2026/07/30 09:12 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 25690
output: 40
thoughts: 10
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Analyzing the Call to `purge_configs_funcs`**
Right, so `purge_configs_funcs` has been called. This immediately triggers a mental checklist. I need to figure out *why* this function is being invoked now. Is it part of a scheduled maintenance routine? Did it originate from a user request to reset or clean up configurations? Or perhaps there was a detected anomaly, a potential vulnerability, or an indication of drift, causing a system-level trigger? Context is absolutely key.
I start by thinking about what `purge_configs_funcs` *does* in the first place. What is its remit? Does it apply to all configurations globally, or is it scoped by environment, application, or even specific config elements? Are there any potential cascading effects? What logging is associated with this function? Are there any obvious candidates in logs to start looking at? I have to consider the potential for destructive actions. I need to quickly ascertain the scope and risk of what's happening.
My mind quickly jumps to the possible configuration targets: are we talking about default configs, overrides, runtime-specific values, or perhaps even secrets that could be stored as configurations? What's the process for backing up, auditing, and validating the removal/reset of configuration data? Are there any versioning schemes involved? Does this function support rollbacks or has the rollback been triggered as the reason for the function being called?
Next, I think about the potential impacts. Loss of configuration can mean an application doesn't start, or perhaps starts in an unsafe, degraded state. It could lead to security vulnerabilities if, for example, access controls are unintentionally relaxed. It could also mean data corruption or loss. I need to quickly identify any potential dependencies. Are there any other systems or services that are affected by this configuration?
I need to immediately check the logs, and quickly determine where this call originated. I'm looking for a timestamp, a source code location, and ideally, some kind of unique transaction ID. That's the starting point. I can then trace back through the logs to try to reconstruct the entire process leading up to this point. I'll also be mentally verifying whether the action is documented correctly, or whether the system is acting outside of spec.
Finally, depending on the urgency and severity, I'm already formulating an investigation plan. I need to gather relevant information, analyze the data, and make an assessment. Based on that assessment, I'm prepared to escalate, mitigate, or confirm the expected outcome. It all depends on what the initial investigation uncovers. The goal is to either confirm the successful outcome and potential impact, or contain, reverse, and prevent unexpected configuration changes from impacting operations. The call to `purge_configs_funcs` is, in itself, a trigger.
|
| 30/2 |
2026/07/30 09:12 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/configfs.c FirstLine:1680 LineCount:30]
Results: map[Contents:1680: kfree(gadget_strings);
1681:
1682: return us;
1683: }
1684:
1685: static int configfs_composite_bind(struct usb_gadget *gadget,
1686: struct usb_gadget_driver *gdriver)
1687: {
1688: struct usb_composite_driver *composite = to_cdriver(gdriver);
1689: struct gadget_info *gi = container_of(composite,
1690: struct gadget_info, composite);
1691: struct usb_composite_dev *cdev = &gi->cdev;
1692: struct usb_configuration *c;
1693: struct usb_string *s;
1694: unsigned i;
1695: int ret;
1696:
1697: /* the gi->lock is hold by the caller */
1698: gi->unbind = 0;
1699: cdev->gadget = gadget;
1700: set_gadget_data(gadget, cdev);
1701: ret = composite_dev_prepare(composite, cdev);
1702: if (ret)
1703: return ret;
1704: /* and now the gadget bind */
1705: ret = -EINVAL;
1706:
1707: if (list_empty(&gi->cdev.configs)) {
1708: pr_err("Need at least one configuration in %s.\n",
1709: gi->composite.name);
]
|
| 31/2 |
2026/07/30 09:12 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 26794
output: 36
thoughts: 13
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing the Configuration Purge Functions**
Okay, hold on a second. Before I proceed any further with this line of investigation, I need to take a closer look at `purge_configs_funcs`. My mental model of the system's configuration management has a gap, and I'm pretty sure that the key to resolving it is going to be in understanding how these functions work. Specifically, I need to see how they handle the purging process. Are they batching, transactional, idempotent, or what? Are there any obvious race conditions that are introduced? Or, perhaps they use a more sophisticated approach. I need to understand the function's scope of responsibility within the larger system. I need to understand where the configurations reside, what types of configurations they are, and why they would need purging. Any details on the configurations' structure would also be useful. Knowing the type and nature of the data is key to understanding the purge. I'm going to dig into the definition of `purge_configs_funcs` immediately. I'm expecting some standard cleanup patterns, but I can't know for sure. I must get a clear view on its implementation, or this entire line of inquiry will be built on shaky ground.
|
| 32/2 |
2026/07/30 09:12 |
tool |
grepper |
0m
Args: map[Expression:purge_configs_funcs PathPrefix:drivers/usb/gadget/configfs.c]
Results: map[Output:drivers/usb/gadget/configfs.c=1580=int composite_os_desc_req_prepare(struct usb_composite_dev *cdev,
--
drivers/usb/gadget/configfs.c-1582-
drivers/usb/gadget/configfs.c:1583:static void purge_configs_funcs(struct gadget_info *gi)
drivers/usb/gadget/configfs.c-1584-{
--
drivers/usb/gadget/configfs.c=1685=static int configfs_composite_bind(struct usb_gadget *gadget,
--
drivers/usb/gadget/configfs.c-1823-err_purge_funcs:
drivers/usb/gadget/configfs.c:1824: purge_configs_funcs(gi);
drivers/usb/gadget/configfs.c-1825-err_comp_cleanup:
--
drivers/usb/gadget/configfs.c=1830=static void configfs_composite_unbind(struct usb_gadget *gadget)
--
drivers/usb/gadget/configfs.c-1845- otg_desc[0] = NULL;
drivers/usb/gadget/configfs.c:1846: purge_configs_funcs(gi);
drivers/usb/gadget/configfs.c-1847- composite_dev_cleanup(cdev);
]
|
| 33/2 |
2026/07/30 09:12 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 27473
output: 40
thoughts: 11
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deep in my domain of expertise:
**Diving into `purge_configs_funcs`**
Alright, time to dive into `purge_configs_funcs`. This filename immediately tells me a few things, which starts the gears turning. "Purge" implies removal or cleanup, likely of configurations. "Configs" narrows the scope to system settings, parameters, or definitions β crucial stuff. And "funcs" suggests this isn't just a single script but a module or collection of functions dedicated to that specific task. I anticipate this will be a central piece of any configuration management or lifecycle.
I'm mentally preparing for a deep-dive, anticipating a series of functions that might handle things like:
* **Identifying stale or outdated configurations:** This could involve checking timestamps, comparing versions, looking at resource utilization, or maybe even implementing more complex heuristics based on how long a setting hasn't been touched.
* **Deciding what to purge:** Is this a simple deletion, or are we dealing with archiving, moving to a backup, or notifying someone? The functions likely contain logic to account for dependencies and potential impacts of removing configs.
* **Executing the purge:** This is the core work, physically deleting files, modifying databases, or interacting with configuration services. Expect careful handling of potential errors, rollback mechanisms, and potentially logging for audit purposes.
* **Validation & Verification:** To be robust, the functions must include validation steps, that either succeed or fail to ensure the changes are correct and complete.
* **Integration Points:** I bet there's some kind of API or hook, to be able to be called from other modules, perhaps a way to schedule these operations or trigger them based on events.
I'm expecting some pretty specific design choices, probably around error handling, transactionality, and logging. The more critical the configuration, the more important those aspects will be. I'm especially keen to see how they handle dependencies. This module is likely at the heart of our configuration management. Time to dig in and see how they've tackled this complex piece of the puzzle.
|
| 34/2 |
2026/07/30 09:12 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/configfs.c FirstLine:1583 LineCount:30]
Results: map[Contents:1583: static void purge_configs_funcs(struct gadget_info *gi)
1584: {
1585: struct usb_configuration *c;
1586:
1587: list_for_each_entry(c, &gi->cdev.configs, list) {
1588: struct usb_function *f, *tmp;
1589: struct config_usb_cfg *cfg;
1590:
1591: cfg = container_of(c, struct config_usb_cfg, c);
1592:
1593: list_for_each_entry_safe_reverse(f, tmp, &c->functions, list) {
1594:
1595: list_move(&f->list, &cfg->func_list);
1596: if (f->unbind) {
1597: dev_dbg(&gi->cdev.gadget->dev,
1598: "unbind function '%s'/%p\n",
1599: f->name, f);
1600: f->unbind(c, f);
1601: }
1602: }
1603: c->next_interface_id = 0;
1604: memset(c->interface, 0, sizeof(c->interface));
1605: c->superspeed_plus = 0;
1606: c->superspeed = 0;
1607: c->highspeed = 0;
1608: c->fullspeed = 0;
1609: }
1610: }
1611:
1612: static struct usb_string *
]
|
| 35/2 |
2026/07/30 09:12 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 28433
output: 38
thoughts: 70
Thoughts: **Analyzing Configuration Function Unbinding**
Okay, so `purge_configs_funcs` is responsible for cleaning up configuration functions. My initial thought is that it simply loops through `c->functions` and calls `f->unbind` on each one. That's a pretty standard approach, right?
But hang on a second...something's nagging at me. What if `usb_add_function` *fails*? I seem to recall that the function is removed from `c->functions` within the scope of `usb_add_function`. If that's the case, we have to ensure that `purge_configs_funcs` doesn't try to unbind something that is no longer there. I need to make *absolutely* sure of that. I'm going to revisit the code for `usb_add_function` and re-evaluate the implications of any failure scenarios. It would be a nasty bug if the list contained stale data.
|
| 36/2 |
2026/07/30 09:12 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/composite.c FirstLine:330 LineCount:20]
Results: map[Contents: 330:
331: /* REVISIT *require* function->bind? */
332: if (function->bind) {
333: value = function->bind(config, function);
334: if (value < 0) {
335: list_del(&function->list);
336: function->config = NULL;
337: }
338: } else
339: value = 0;
340:
341: /* We allow configurations that don't work at both speeds.
342: * If we run into a lowspeed Linux system, treat it the same
343: * as full speed ... it's the function drivers that will need
344: * to avoid bulk and ISO transfers.
345: */
346: if (!config->fullspeed && function->fs_descriptors)
347: config->fullspeed = true;
348: if (!config->highspeed && function->hs_descriptors)
349: config->highspeed = true;
]
|
| 37/2 |
2026/07/30 09:12 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 29043
output: 43
thoughts: 338
Thoughts: **My Analysis of Function Binding and Unbinding in the ConfigFS Composite Driver**
Okay, here's what's running through my head as I dissect this code. It appears the system is set up to handle function binding and unbinding within a ConfigFS composite driver, and I'm spotting a potential flaw in the error handling.
If `function->bind` fails, I see that the function is deleted from `config->functions` using `list_del(&function->list)`. Then `usb_add_function` returns an error, and the flow proceeds to `configfs_composite_bind`. In this function, the code iterates through `cfg->func_list` attempting to add each function. Critically, if `usb_add_function` fails, `f` (the problematic function) is *added back* to `cfg->func_list`.
The control then jumps to `err_purge_funcs`, which calls `purge_configs_funcs(gi)`. `purge_configs_funcs` is meant to iterate over `c->functions` and call `f->unbind` for each. But here's the kicker: `f` is no longer in `c->functions` because it was previously removed when the `bind` failed. It's now back in `cfg->func_list`!
So, the key issue is that if `usb_add_function` fails *after* `bind` has run (and failed), the `unbind` is *not* called for the function whose `bind` failed. That's a significant oversight. Generally, a failing `bind` should clean up after itself, but this appears to be the problem.
My immediate question is: should `unbind` even be called after a *failed* `bind`? My gut says yes, particularly if `bind` might have created resources that need cleanup.
Let's dig into the details: The standard pattern is that a failing `bind` is expected to cleanup after itself. However, it looks like `pn_bind` in `f_phonet.c` is not cleaning up correctly after failing `gphonet_register_netdev`. I'm starting to think `pn_bind` must be looked at with more scrutiny.
|
| 38/2 |
2026/07/30 09:13 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:510 LineCount:30]
Results: map[Contents: 510: gphonet_set_gadget(phonet_opts->net, gadget);
511: status = gphonet_register_netdev(phonet_opts->net);
512: if (status)
513: return status;
514: phonet_opts->bound = true;
515: }
516:
517: /* Reserve interface IDs */
518: status = usb_interface_id(c, f);
519: if (status < 0)
520: goto err;
521: pn_control_intf_desc.bInterfaceNumber = status;
522: pn_union_desc.bMasterInterface0 = status;
523:
524: status = usb_interface_id(c, f);
525: if (status < 0)
526: goto err;
527: pn_data_nop_intf_desc.bInterfaceNumber = status;
528: pn_data_intf_desc.bInterfaceNumber = status;
529: pn_union_desc.bSlaveInterface0 = status;
530:
531: /* Reserve endpoints */
532: status = -ENODEV;
533: ep = usb_ep_autoconfig(gadget, &pn_fs_sink_desc);
534: if (!ep)
535: goto err;
536: fp->out_ep = ep;
537:
538: ep = usb_ep_autoconfig(gadget, &pn_fs_source_desc);
539: if (!ep)
]
|
| 39/2 |
2026/07/30 09:13 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 30370
output: 43
thoughts: 677
Thoughts: **Debugging a Potential Netdev Registration Issue in the CDC Phonet Gadget Driver**
Okay, so I'm looking at this CDC Phonet code, and I'm seeing a potential race condition or bug related to netdev registration. Here's what's running through my head:
First, there's this `err` label. If `usb_interface_id` or `usb_ep_autoconfig` fails, we jump there, but it looks like we're not cleaning up properly. We log an error, and return the `status`, but `phonet_opts->bound` is still set to `true`, and the netdev remains registered. That's not ideal.
Then, if `usb_add_function` fails, `f->unbind` isn't called. Then `configfs_composite_bind` fails, and an error is returned. That's fine, but the user can try to bind the gadget again. That calls `pn_bind` again.
Here's where it gets interesting. If `phonet_opts->bound` is true, `pn_bind` skips the `gphonet_register_netdev` call. But if the gadget bind failed, the UDC driver *wasn't* bound. So, if the user unbinds the gadget, `phonet_free_inst` might be called. `phonet_free_inst` will then call `gphonet_cleanup(opts->net)` which unregisters the netdev.
But wait, the BUG! It's in `register_netdevice`, specifically the `BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);`. This means `register_netdevice` is being called on a netdev that's already registered, which is clearly wrong.
Okay, so `phonet_opts->bound` *should* prevent `gphonet_register_netdev` from being called again by `pn_bind`, right? I can see it:
```c
if (!phonet_opts->bound) {
gphonet_set_gadget(phonet_opts->net, gadget);
status = gphonet_register_netdev(phonet_opts->net);
if (status)
return status;
phonet_opts->bound = true;
}
```
If `gphonet_register_netdev` *succeeds*, then `phonet_opts->bound` is set to true. If `pn_bind` fails later, `phonet_opts->bound` *remains* true, meaning on a subsequent call to `pn_bind`, `gphonet_register_netdev` *shouldn't* be called. So, that's good.
So, how can `gphonet_register_netdev` get called again? What am I missing?
Ah! What if `gphonet_register_netdev` *fails*? If it fails, the code returns `status`, and `phonet_opts->bound` remains false. But does it leave the netdev registered? That's the critical question! I need to check the implementation of `gphonet_register_netdev` itself, because if it leaves the netdev in a registered state, we have a problem.
|
| 40/2 |
2026/07/30 09:13 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:720 LineCount:20]
Results: map[Contents: 720: SET_NETDEV_DEV(net, &g->dev);
721: }
722:
723: int gphonet_register_netdev(struct net_device *net)
724: {
725: int status;
726:
727: status = register_netdev(net);
728: if (status)
729: free_netdev(net);
730:
731: return status;
732: }
733:
734: void gphonet_cleanup(struct net_device *dev)
735: {
736: unregister_netdev(dev);
737: }
738:
739: DECLARE_USB_FUNCTION_INIT(phonet, phonet_alloc_inst, phonet_alloc);
]
|
| 41/2 |
2026/07/30 09:13 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 32070
output: 37
thoughts: 362
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability**
Okay, so I'm looking at this code and something's not quite right. If `register_netdev` fails, the code calls `free_netdev(net)`. My initial thought was, "Aha! `free_netdev` *frees* the netdev, but `phonet_opts->net` is still pointing to that freed memory!" That immediately screams "use-after-free" to me. When `pn_bind` gets called again, it's going to call `gphonet_register_netdev` again with the dangling pointer!
But hold on, a use-after-free can manifest in a few ways. The freed memory could have been reallocated, or it could just be corrupted. The crucial point here is that KASAN *is* enabled (`KASAN NOPTI`) in the bug report, yet the kernel didn't report a use-after-free condition. It reported a BUG in `register_netdevice` instead! That's a different beast altogether.
Okay, let's retrace the steps. If `register_netdev` fails, `free_netdev` is called. That makes sense. The `pn_bind` function returns an error, and the user likely tries to bind again. Consequently, `pn_bind` is called a second time. `phonet_opts->bound` is false at this point, so it ends up calling `gphonet_register_netdev` with the pointer to the *freed* `net` device.
So, here's what's likely happening. We're calling `register_netdev` again with this freed device. Since the device is freed, the value of `dev->reg_state` is probably garbage. If `dev->reg_state` is anything other than `NETREG_UNINITIALIZED` (which is zero), it triggers the BUG that we're seeing. It looks like the issue is related to how the state of the device is managed after freeing.
Wait, does `free_netdev` *modify* `dev->reg_state`? That's the missing link. I'll need to check the source code for `free_netdev` to confirm if it modifies the `reg_state`. Knowing if it resets `reg_state` to `NETREG_UNINITIALIZED` is critical.
|
| 42/2 |
2026/07/30 09:13 |
tool |
git-log |
0m
Args: map[Count:1 SourcePath:net/core/dev.c SymbolName:free_netdev]
Results: map[Output:a4c833278144 net: cache snapshot entries for ndo_set_rx_mode_async
]
|
| 43/2 |
2026/07/30 09:13 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 33031
output: 35
thoughts: 10
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with networking and kernel internals:
**Diving into `free_netdev`**
Alright, the next step is to get my head around `free_netdev`. I know this function is crucial, likely the final step in deallocating a network device structure within the kernel. I need to understand exactly what resources are released and in what order. I'm going to quickly skim the code, focusing on the key areas: memory deallocation (likely calls to `kfree` or related functions), interrupt handling disablement or unregistration, the release of associated data structures (like queues, buffers, and control blocks), and finally, any potential locks that need to be released. I expect a thorough cleanup, given the significance of freeing this structure. My initial assumption is that this will be a layered process, starting with the least critical and moving to the most important resources, and I'll be looking for confirmation of that in the code. I'll be especially attentive to any potential race conditions that are addressed by this function. I'm going to start with the top, look for any comments and quickly scan for any structures of interest. I'm excited to see how this function is implemented!
|
| 44/2 |
2026/07/30 09:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:free_netdev]
Results: map[SourceCode:12183: /**
12184: * free_netdev - free network device
12185: * @dev: device
12186: *
12187: * This function does the last stage of destroying an allocated device
12188: * interface. The reference to the device object is released. If this
12189: * is the last reference then it will be freed.Must be called in process
12190: * context.
12191: */
12192: void free_netdev(struct net_device *dev)
12193: {
12194: might_sleep();
12195:
12196: /* When called immediately after register_netdevice() failed the unwind
12197: * handling may still be dismantling the device. Handle that case by
12198: * deferring the free.
12199: */
12200: if (dev->reg_state == NETREG_UNREGISTERING) {
12201: ASSERT_RTNL();
12202: dev->needs_free_netdev = true;
12203: return;
12204: }
12205:
12206: WARN_ON(dev->cfg != dev->cfg_pending);
12207: kfree(dev->cfg);
12208: kfree(dev->ethtool);
12209: netif_free_tx_queues(dev);
12210: netif_free_rx_queues(dev);
12211:
12212: kfree(rcu_dereference_protected(dev->ingress_queue, 1));
12213:
12214: __hw_addr_flush(&dev->rx_mode_addr_cache);
12215:
12216: /* Flush device addresses */
12217: dev_addr_flush(dev);
12218:
12219: netdev_napi_exit(dev);
12220:
12221: netif_del_cpu_rmap(dev);
12222:
12223: ref_tracker_dir_exit(&dev->refcnt_tracker);
12224: #ifdef CONFIG_PCPU_DEV_REFCNT
12225: free_percpu(dev->pcpu_refcnt);
12226: dev->pcpu_refcnt = NULL;
12227: #endif
12228: free_percpu(dev->core_stats);
12229: dev->core_stats = NULL;
12230: free_percpu(dev->xdp_bulkq);
12231: dev->xdp_bulkq = NULL;
12232:
12233: netdev_free_phy_link_topology(dev);
12234:
12235: mutex_destroy(&dev->lock);
12236:
12237: /* Compatibility with error handling in drivers */
12238: if (dev->reg_state == NETREG_UNINITIALIZED ||
12239: dev->reg_state == NETREG_DUMMY) {
12240: kvfree(dev);
12241: return;
12242: }
12243:
12244: BUG_ON(dev->reg_state != NETREG_UNREGISTERED);
12245: WRITE_ONCE(dev->reg_state, NETREG_RELEASED);
12246:
12247: /* will free via device release */
12248: put_device(&dev->dev);
12249: }
SourceFile:net/core/dev.c]
|
| 45/2 |
2026/07/30 09:13 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 34391
output: 43
thoughts: 171
Thoughts: **Debugging a Potential Use-After-Free in Network Device Registration**
Okay, so I'm looking at this code and I'm a bit puzzled. The critical path seems to be: `register_netdev` fails, which sets `dev->reg_state` to `NETREG_UNINITIALIZED`. Then, `free_netdev` is called, and `kvfree(dev)` is executed. That should free the memory associated with `dev`. Now, if KASAN is enabled, I'd *expect* it to catch a use-after-free error later when `register_netdev` attempts to access `dev->reg_state`, because that access should trigger a read on freed memory (specifically the `BUG_ON` check). But the logs aren't showing that.
Why isn't KASAN flagging this? That's the core question. Did I miss something? Maybe the device structure `dev` was somehow reallocated after `free_netdev`? Or is `register_netdev` succeeding but some later operation, like `pn_bind` failing, leading to `free_netdev` and then the subsequent access? Let me go back and re-examine the `pn_bind` code flow again to make sure I haven't missed a crucial detail there. This smells like a subtle timing issue or a race condition, and I need to be methodical in tracking it down.
|
| 46/2 |
2026/07/30 09:13 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:510 LineCount:30]
Results: map[Contents: 510: gphonet_set_gadget(phonet_opts->net, gadget);
511: status = gphonet_register_netdev(phonet_opts->net);
512: if (status)
513: return status;
514: phonet_opts->bound = true;
515: }
516:
517: /* Reserve interface IDs */
518: status = usb_interface_id(c, f);
519: if (status < 0)
520: goto err;
521: pn_control_intf_desc.bInterfaceNumber = status;
522: pn_union_desc.bMasterInterface0 = status;
523:
524: status = usb_interface_id(c, f);
525: if (status < 0)
526: goto err;
527: pn_data_nop_intf_desc.bInterfaceNumber = status;
528: pn_data_intf_desc.bInterfaceNumber = status;
529: pn_union_desc.bSlaveInterface0 = status;
530:
531: /* Reserve endpoints */
532: status = -ENODEV;
533: ep = usb_ep_autoconfig(gadget, &pn_fs_sink_desc);
534: if (!ep)
535: goto err;
536: fp->out_ep = ep;
537:
538: ep = usb_ep_autoconfig(gadget, &pn_fs_source_desc);
539: if (!ep)
]
|
| 47/2 |
2026/07/30 09:13 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 35376
output: 43
thoughts: 601
Thoughts: **Debugging a Phonet Gadget Binding Issue**
Okay, so here's what I'm seeing. The initial flow is: `gphonet_register_netdev` succeeds, and that's supposed to set `phonet_opts->bound` to true. But then, if a *later* part of the binding process fails β let's say `usb_interface_id` β we hit an error path, and the function returns. The thing that's nagging me is that even in this error case, `phonet_opts->bound` is *still* true.
Then `usb_add_function` fails, then `configfs_composite_bind`. Okay, so far, so good β the binding didn't fully happen. The user unbinds the gadget, and if `configfs_composite_bind` fails, then there's an error and the gadget remains unbound. The system calls `configfs_composite_bind` again, which calls `usb_add_function` and subsequently `pn_bind` again.
Here's where it gets interesting: the next time `pn_bind` is called, because `phonet_opts->bound` *should* be true from the initial success, it *skips* `gphonet_register_netdev`. It continues on to `usb_interface_id`, which, if successful, returns 0. That makes sense, right? It only registers the network device the *first* time.
But wait a minute! The crash trace shows that `register_netdev` *is* being called again from `pn_bind` at line 511. Now, if we *skipped* `gphonet_register_netdev` due to `phonet_opts->bound` being true, how is `register_netdev` being called again? It *can't* be being called again! I've confirmed that `gphonet_register_netdev` is called, and that means `phonet_opts->bound` is FALSE.
So it *is* being called from `pn_bind`. This means that somehow `phonet_opts->bound` is FALSE. But if `phonet_opts->bound` is FALSE, then `gphonet_register_netdev` was called. If it failed, it *should* have freed the network device (`netdev`). So next time `pn_bind` is called, it's attempting to call `gphonet_register_netdev` on a freed netdev! But shouldn't that be a use-after-free, a KASAN error? Why isn't KASAN screaming about this? This is what I'm stuck on.
Maybe `register_netdev` didn't actually fail. Maybe `phonet_opts->bound` was set to false *somewhere else*? I need to track down how that could happen. Where is that value ever set to false? It's initialized to false, that's certain. Hang on... `phonet_opts` is allocated, so therefore `phonet_opts->net` is also allocated. This is a vital piece.
|
| 48/2 |
2026/07/30 09:13 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:600 LineCount:50]
Results: map[Contents: 600:
601: static ssize_t f_phonet_ifname_show(struct config_item *item, char *page)
602: {
603: return gether_get_ifname(to_f_phonet_opts(item)->net, page, PAGE_SIZE);
604: }
605:
606: CONFIGFS_ATTR_RO(f_phonet_, ifname);
607:
608: static struct configfs_attribute *phonet_attrs[] = {
609: &f_phonet_attr_ifname,
610: NULL,
611: };
612:
613: static const struct config_item_type phonet_func_type = {
614: .ct_item_ops = &phonet_item_ops,
615: .ct_attrs = phonet_attrs,
616: .ct_owner = THIS_MODULE,
617: };
618:
619: static void phonet_free_inst(struct usb_function_instance *f)
620: {
621: struct f_phonet_opts *opts;
622:
623: opts = container_of(f, struct f_phonet_opts, func_inst);
624: if (opts->bound)
625: gphonet_cleanup(opts->net);
626: else
627: free_netdev(opts->net);
628: kfree(opts);
629: }
630:
631: static struct usb_function_instance *phonet_alloc_inst(void)
632: {
633: struct f_phonet_opts *opts;
634:
635: opts = kzalloc_obj(*opts);
636: if (!opts)
637: return ERR_PTR(-ENOMEM);
638:
639: opts->func_inst.free_func_inst = phonet_free_inst;
640: opts->net = gphonet_setup_default();
641: if (IS_ERR(opts->net)) {
642: struct net_device *net = opts->net;
643: kfree(opts);
644: return ERR_CAST(net);
645: }
646:
647: config_group_init_type_name(&opts->func_inst.group, "",
648: &phonet_func_type);
649:
]
|
| 49/2 |
2026/07/30 09:13 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 37438
output: 35
thoughts: 172
Thoughts: **Uh Oh, I See a Potential Use-After-Free**
Okay, hold on a second. I'm seeing something that's making the hairs on the back of my neck stand up. Let me break this down... `phonet_opts->bound` is initialized to `false`, that makes sense. But if `gphonet_register_netdev` fails, and I recall that's the registration function for a network device, it then calls `free_netdev(net)`. Got it, that frees the `net` struct. Then, `pn_bind` returns the error β that's the function that's failing, which makes sense. Here's where it gets interesting... `phonet_opts->bound` is *still* `false` after the failed registration. Then, if I follow this logic, the code is going to try to bind *again*.
So, `pn_bind` is called a second time. And, lo and behold, `phonet_opts->bound` is still `false`. So... `gphonet_register_netdev` is being called *again*, but now with the previously `free_netdev(net)`. **That's a use-after-free!** That's a textbook example of using freed memory. I should be able to trigger this in a controlled environment, yes?
But, wait... why didn't KASAN (Kernel Address Sanitizer) catch this? That's what it's supposed to do! Did I miss something? Maybe, just maybe, `free_netdev` doesn't actually immediately free the memory, like a lazy free. Let me double-check that `free_netdev` implementation again. I need to be sure. I'm going to dig into the details of that function. I need to figure out exactly what it does with the memory.
|
| 50/2 |
2026/07/30 09:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:free_netdev]
Results: map[SourceCode:12183: /**
12184: * free_netdev - free network device
12185: * @dev: device
12186: *
12187: * This function does the last stage of destroying an allocated device
12188: * interface. The reference to the device object is released. If this
12189: * is the last reference then it will be freed.Must be called in process
12190: * context.
12191: */
12192: void free_netdev(struct net_device *dev)
12193: {
12194: might_sleep();
12195:
12196: /* When called immediately after register_netdevice() failed the unwind
12197: * handling may still be dismantling the device. Handle that case by
12198: * deferring the free.
12199: */
12200: if (dev->reg_state == NETREG_UNREGISTERING) {
12201: ASSERT_RTNL();
12202: dev->needs_free_netdev = true;
12203: return;
12204: }
12205:
12206: WARN_ON(dev->cfg != dev->cfg_pending);
12207: kfree(dev->cfg);
12208: kfree(dev->ethtool);
12209: netif_free_tx_queues(dev);
12210: netif_free_rx_queues(dev);
12211:
12212: kfree(rcu_dereference_protected(dev->ingress_queue, 1));
12213:
12214: __hw_addr_flush(&dev->rx_mode_addr_cache);
12215:
12216: /* Flush device addresses */
12217: dev_addr_flush(dev);
12218:
12219: netdev_napi_exit(dev);
12220:
12221: netif_del_cpu_rmap(dev);
12222:
12223: ref_tracker_dir_exit(&dev->refcnt_tracker);
12224: #ifdef CONFIG_PCPU_DEV_REFCNT
12225: free_percpu(dev->pcpu_refcnt);
12226: dev->pcpu_refcnt = NULL;
12227: #endif
12228: free_percpu(dev->core_stats);
12229: dev->core_stats = NULL;
12230: free_percpu(dev->xdp_bulkq);
12231: dev->xdp_bulkq = NULL;
12232:
12233: netdev_free_phy_link_topology(dev);
12234:
12235: mutex_destroy(&dev->lock);
12236:
12237: /* Compatibility with error handling in drivers */
12238: if (dev->reg_state == NETREG_UNINITIALIZED ||
12239: dev->reg_state == NETREG_DUMMY) {
12240: kvfree(dev);
12241: return;
12242: }
12243:
12244: BUG_ON(dev->reg_state != NETREG_UNREGISTERED);
12245: WRITE_ONCE(dev->reg_state, NETREG_RELEASED);
12246:
12247: /* will free via device release */
12248: put_device(&dev->dev);
12249: }
SourceFile:net/core/dev.c]
|
| 51/2 |
2026/07/30 09:13 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 39096
output: 35
thoughts: 40
Thoughts: Okay, let's see. My first thought is: **Potential Concurrency Pitfall in Network Device Registration**. I'm immediately concerned. I'm thinking, "Hold on a minute, if `register_netdev` fails, there's a possibility that the `dev->reg_state` might be set to `NETREG_UNREGISTERING` *before* the caller can handle the failure. That could lead to a race condition or some other nasty concurrency bug." This needs a closer look. I need to refresh my memory and verify how `register_netdevice` itself handles registration failures and its impact on the `reg_state` variable of the network device. I need to meticulously trace the control flow to ensure that we're properly synchronizing access to `dev->reg_state` and handling all possible failure scenarios in a thread-safe way. I'm going to dig into the source code of `register_netdevice` to confirm my suspicion and identify any potential vulnerabilities. This is important; a subtle bug here could have widespread consequences.
|
| 52/2 |
2026/07/30 09:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:register_netdevice]
Results: map[SourceCode:11318: /**
11319: * register_netdevice() - register a network device
11320: * @dev: device to register
11321: *
11322: * Take a prepared network device structure and make it externally accessible.
11323: * A %NETDEV_REGISTER message is sent to the netdev notifier chain.
11324: * Callers must hold the rtnl lock - you may want register_netdev()
11325: * instead of this.
11326: */
11327: int register_netdevice(struct net_device *dev)
11328: {
11329: int ret;
11330: struct net *net = dev_net(dev);
11331:
11332: BUILD_BUG_ON(sizeof(netdev_features_t) * BITS_PER_BYTE <
11333: NETDEV_FEATURE_COUNT);
11334: BUG_ON(dev_boot_phase);
11335: ASSERT_RTNL();
11336:
11337: might_sleep();
11338:
11339: /* When net_device's are persistent, this will be fatal. */
11340: BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
11341: BUG_ON(!net);
11342:
11343: ret = ethtool_check_ops(dev->ethtool_ops);
11344: if (ret)
11345: return ret;
11346:
11347: /* rss ctx ID 0 is reserved for the default context, start from 1 */
11348: xa_init_flags(&dev->ethtool->rss_ctx, XA_FLAGS_ALLOC1);
11349: mutex_init(&dev->ethtool->rss_lock);
11350:
11351: spin_lock_init(&dev->addr_list_lock);
11352: netdev_set_addr_lockdep_class(dev);
11353:
11354: ret = dev_get_valid_name(net, dev, dev->name);
11355: if (ret < 0)
11356: goto out;
11357:
11358: ret = -ENOMEM;
11359: dev->name_node = netdev_name_node_head_alloc(dev);
11360: if (!dev->name_node)
11361: goto out;
11362:
11363: /* Init, if this function is available */
11364: if (dev->netdev_ops->ndo_init) {
11365: ret = dev->netdev_ops->ndo_init(dev);
11366: if (ret) {
11367: if (ret > 0)
11368: ret = -EIO;
11369: goto err_free_name;
11370: }
11371: }
11372:
11373: if (((dev->hw_features | dev->features) &
11374: NETIF_F_HW_VLAN_CTAG_FILTER) &&
11375: (!dev->netdev_ops->ndo_vlan_rx_add_vid ||
11376: !dev->netdev_ops->ndo_vlan_rx_kill_vid)) {
11377: netdev_WARN(dev, "Buggy VLAN acceleration in driver!\n");
11378: ret = -EINVAL;
11379: goto err_uninit;
11380: }
11381:
11382: if (netdev_need_ops_lock(dev) &&
11383: dev->netdev_ops->ndo_set_rx_mode &&
11384: !dev->netdev_ops->ndo_set_rx_mode_async)
11385: netdev_WARN(dev, "ops-locked drivers should use ndo_set_rx_mode_async\n");
11386:
11387: ret = netdev_do_alloc_pcpu_stats(dev);
11388: if (ret)
11389: goto err_uninit;
11390:
11391: ret = dev_index_reserve(net, dev->ifindex);
11392: if (ret < 0)
11393: goto err_free_pcpu;
11394: dev->ifindex = ret;
11395:
11396: /* Transfer changeable features to wanted_features and enable
11397: * software offloads (GSO and GRO).
11398: */
11399: dev->hw_features |= (NETIF_F_SOFT_FEATURES | NETIF_F_SOFT_FEATURES_OFF);
11400: dev->features |= NETIF_F_SOFT_FEATURES;
11401:
11402: if (dev->udp_tunnel_nic_info) {
11403: dev->features |= NETIF_F_RX_UDP_TUNNEL_PORT;
11404: dev->hw_features |= NETIF_F_RX_UDP_TUNNEL_PORT;
11405: }
11406:
11407: dev->wanted_features = dev->features & dev->hw_features;
11408:
11409: if (!(dev->flags & IFF_LOOPBACK))
11410: dev->hw_features |= NETIF_F_NOCACHE_COPY;
11411:
11412: /* If IPv4 TCP segmentation offload is supported we should also
11413: * allow the device to enable segmenting the frame with the option
11414: * of ignoring a static IP ID value. This doesn't enable the
11415: * feature itself but allows the user to enable it later.
11416: */
11417: if (dev->hw_features & NETIF_F_TSO)
11418: dev->hw_features |= NETIF_F_TSO_MANGLEID;
11419: if (dev->vlan_features & NETIF_F_TSO)
11420: dev->vlan_features |= NETIF_F_TSO_MANGLEID;
11421: if (dev->mpls_features & NETIF_F_TSO)
11422: dev->mpls_features |= NETIF_F_TSO_MANGLEID;
11423: if (dev->hw_enc_features & NETIF_F_TSO)
11424: dev->hw_enc_features |= NETIF_F_TSO_MANGLEID;
11425:
11426: /* TSO_MANGLEID belongs in mangleid_features by definition */
11427: dev->mangleid_features |= NETIF_F_TSO_MANGLEID;
11428:
11429: /* Make NETIF_F_HIGHDMA inheritable to VLAN devices.
11430: */
11431: dev->vlan_features |= NETIF_F_HIGHDMA;
11432:
11433: /* Make NETIF_F_SG inheritable to tunnel devices.
11434: */
11435: dev->hw_enc_features |= NETIF_F_SG | NETIF_F_GSO_PARTIAL;
11436:
11437: /* Make NETIF_F_SG inheritable to MPLS.
11438: */
11439: dev->mpls_features |= NETIF_F_SG;
11440:
11441: ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev);
11442: ret = notifier_to_errno(ret);
11443: if (ret)
11444: goto err_ifindex_release;
11445:
11446: ret = netdev_register_kobject(dev);
11447:
11448: netdev_lock(dev);
11449: WRITE_ONCE(dev->reg_state, ret ? NETREG_UNREGISTERED : NETREG_REGISTERED);
11450: netdev_unlock(dev);
11451:
11452: if (ret)
11453: goto err_uninit_notify;
11454:
11455: netdev_lock_ops(dev);
11456: __netdev_update_features(dev);
11457: netdev_unlock_ops(dev);
11458:
11459: /*
11460: * Default initial state at registry is that the
11461: * device is present.
11462: */
11463:
11464: set_bit(__LINK_STATE_PRESENT, &dev->state);
11465:
11466: linkwatch_init_dev(dev);
11467:
11468: dev_init_scheduler(dev);
11469:
11470: netdev_hold(dev, &dev->dev_registered_tracker, GFP_KERNEL);
11471: list_netdevice(dev);
11472:
11473: add_device_randomness(dev->dev_addr, dev->addr_len);
11474:
11475: /* If the device has permanent device address, driver should
11476: * set dev_addr and also addr_assign_type should be set to
11477: * NET_ADDR_PERM (default value).
11478: */
11479: if (dev->addr_assign_type == NET_ADDR_PERM)
11480: memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len);
11481:
11482: /* Notify protocols, that a new device appeared. */
11483: netdev_lock_ops(dev);
11484: ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);
11485: netdev_unlock_ops(dev);
11486: ret = notifier_to_errno(ret);
11487: if (ret) {
11488: /* Expect explicit free_netdev() on failure */
11489: dev->needs_free_netdev = false;
11490: unregister_netdevice_queue(dev, NULL);
11491: goto out;
11492: }
11493: /*
11494: * Prevent userspace races by waiting until the network
11495: * device is fully setup before sending notifications.
11496: */
11497: if (!(dev->rtnl_link_ops && dev->rtnl_link_initializing))
11498: rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U, GFP_KERNEL, 0, NULL);
11499:
11500: out:
11501: return ret;
11502:
11503: err_uninit_notify:
11504: call_netdevice_notifiers(NETDEV_PRE_UNINIT, dev);
11505: err_ifindex_release:
11506: dev_index_release(net, dev->ifindex);
11507: err_free_pcpu:
11508: netdev_do_free_pcpu_stats(dev);
11509: err_uninit:
11510: if (dev->netdev_ops->ndo_uninit)
11511: dev->netdev_ops->ndo_uninit(dev);
11512: if (dev->priv_destructor)
11513: dev->priv_destructor(dev);
11514: err_free_name:
11515: netdev_name_node_free(dev->name_node);
11516: goto out;
11517: }
SourceFile:net/core/dev.c]
|
| 53/2 |
2026/07/30 09:14 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 42807
output: 46
thoughts: 683
Thoughts: **Analyzing a Kernel BUG: Network Device Registration**
Okay, let's break this down. My initial thought is that a failure in `call_netdevice_notifiers(NETDEV_REGISTER, dev)` leads to a chain of events that exposes a bug. The failure causes the code to `unregister_netdevice_queue(dev, NULL)` and return an error code. This is where it gets interesting, because `unregister_netdevice_queue` sets `dev->reg_state` to `NETREG_UNREGISTERING`. Then, `register_netdevice` returns the error, and `gphonet_register_netdev` correctly sees the non-zero status and calls `free_netdev(net)`.
Inside `free_netdev`, there's this critical check: `if (dev->reg_state == NETREG_UNREGISTERING)`. If that condition is met, which it is in this case, the function only sets `dev->needs_free_netdev = true` and *returns*. It doesn't actually free the memory!
So, the error propagates up, and the user attempts to bind again. `pn_bind` is called again. Crucially, `phonet_opts->bound` is false, so it goes back to `gphonet_register_netdev`, *using the same `net` pointer!*
Now the problem becomes clear. `dev->reg_state` is now `NETREG_UNREGISTERING` (or possibly `NETREG_UNREGISTERED` if the unregister queue processed fast enough). The key is this: the kernel then asserts: `BUG_ON(dev->reg_state != NETREG_UNINITIALIZED)`. And since `dev->reg_state` is NOT `NETREG_UNINITIALIZED`, the BUG is hit! That perfectly explains the root cause!
However, it's worth revisiting why `call_netdevice_notifiers` might fail in the first place. This could happen if a notifier function returns an error. But `register_netdevice` could fail earlier.
If it fails earlier, let's say due to `dev_get_valid_name` or `ndo_init` failing, then `dev->reg_state` would *still* be `NETREG_UNINITIALIZED`. In that case, `free_netdev` *would* call `kvfree(dev)`. That's important!
If `free_netdev` *did* free the memory, a subsequent access to `dev->reg_state` in a later `register_netdevice` would trigger a KASAN use-after-free, not the `BUG` that's reported. This confirms that memory wasn't freed in the scenario described in the bug report.
So, when is `dev` *not* freed? It's when `register_netdevice` fails, *after* it has allocated the device but before it has fully initialized it, and leaves `dev` allocated. Then `free_netdev` doesn't `kvfree`.
But wait! `phonet_opts->net` is allocated by `gphonet_setup_default()`. Time to check that function. I must re-examine the allocation strategy there.
|
| 54/2 |
2026/07/30 09:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_phonet.c Name:gphonet_setup_default]
Results: map[SourceCode: 700: struct net_device *gphonet_setup_default(void)
701: {
702: struct net_device *dev;
703: struct phonet_port *port;
704:
705: /* Create net device */
706: dev = alloc_netdev(sizeof(*port), "upnlink%d", NET_NAME_UNKNOWN,
707: pn_net_setup);
708: if (!dev)
709: return ERR_PTR(-ENOMEM);
710:
711: port = netdev_priv(dev);
712: spin_lock_init(&port->lock);
713: netif_carrier_off(dev);
714:
715: return dev;
716: }
SourceFile:drivers/usb/gadget/function/f_phonet.c]
|
| 55/2 |
2026/07/30 09:14 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 44502
output: 35
thoughts: 230
Thoughts: **My Analysis of Potential Use-After-Free in Network Device Registration**
Okay, so I'm digging into this network device registration process, and here's what's rattling around in my head. The device itself is allocated using `alloc_netdev`, that much is clear. The crux of the issue revolves around `register_netdevice` and what happens if it fails. If `register_netdevice` bails out, `gphonet_register_netdev` will call `free_netdev`. The immediate concern is: if `free_netdev` *immediately* frees the device, then any subsequent access would be a classic use-after-free scenario.
However, it's not quite that simple. `free_netdev` might defer the free operation if `dev->reg_state == NETREG_UNREGISTERING`. So, itβs *not* instantly freed in that scenario. But here's where it gets interestingβ¦ `unregister_netdevice_queue` schedules the unregistration, that much is fine. Crucially, when that unregistration *completes*, `netdev_run_todo` gets called. And, aha, it appears that `netdev_run_todo` will call `free_netdev` again because `dev->needs_free_netdev` is true! Itβs like a double-tap on the free!
Hold on⦠if `netdev_run_todo` *does* call `free_netdev` a second time, then the device *will* be freed, eventually. The key is in understanding *when* `netdev_run_todo` is called. It's called when the RTNL lock is released. Now, the potential problem is that `configfs_composite_bind` doesn't hold the RTNL lock. So, the question now is, what's holding the lock? Oh! And then the key moment I see is that `register_netdev` does take the RTNL lock. So `free_netdev` is likely always called when the RTNL is released. This means `netdev_run_todo` should always be safe.
I need to look deeper into `configfs_composite_bind` to see how it interacts with all of this but it seems that `register_netdev` is RTNL locked.
|
| 56/2 |
2026/07/30 09:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:register_netdev]
Results: map[SourceCode:11541: /**
11542: * register_netdev - register a network device
11543: * @dev: device to register
11544: *
11545: * Take a completed network device structure and add it to the kernel
11546: * interfaces. A %NETDEV_REGISTER message is sent to the netdev notifier
11547: * chain. 0 is returned on success. A negative errno code is returned
11548: * on a failure to set up the device, or if the name is a duplicate.
11549: *
11550: * This is a wrapper around register_netdevice that takes the rtnl semaphore
11551: * and expands the device name if you passed a format string to
11552: * alloc_netdev.
11553: */
11554: int register_netdev(struct net_device *dev)
11555: {
11556: struct net *net = dev_net(dev);
11557: int err;
11558:
11559: if (rtnl_net_lock_killable(net))
11560: return -EINTR;
11561:
11562: err = register_netdevice(dev);
11563:
11564: rtnl_net_unlock(net);
11565:
11566: return err;
11567: }
SourceFile:net/core/dev.c]
|
| 57/2 |
2026/07/30 09:14 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 45709
output: 43
thoughts: 266
Thoughts: **Debugging a Tricky Netdev Issue**
Okay, so `register_netdev` definitely grabs that RTNL lock, calls `register_netdevice`, and then lets go of the RTNL lock. And when it releases the RTNL lock with `rtnl_net_unlock`, that's when `netdev_run_todo` gets called, right? And `netdev_run_todo` is the one responsible for handling the unregistration and actually *freeing* the netdev. So, the netdev *is* being freed, no question.
But wait a minute... why isn't KASAN screaming about a use-after-free? This is perplexing. Let's look at the crash trace again. Huh.
```
RIP: 0010:register_netdevice+0x1233/0x25a0 net/core/dev.c:11340
```
Okay, the crash is happening *inside* `register_netdevice`. If it's freed, then `dev->reg_state` is accessed which seems correct. If that memory was reallocated, KASAN wouldn't catch it!
But `alloc_netdev` allocates the memory in the first place, and if it's subsequently freed and reallocated by another thread, then KASAN wouldn't complain about subsequent accesses. Okay. But, what if the `register_netdevice` didn't fail at all, but some later process, like a `pn_bind` fails? Let me go back and re-examine the `pn_bind` function logic one more time.
|
| 58/2 |
2026/07/30 09:14 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:510 LineCount:30]
Results: map[Contents: 510: gphonet_set_gadget(phonet_opts->net, gadget);
511: status = gphonet_register_netdev(phonet_opts->net);
512: if (status)
513: return status;
514: phonet_opts->bound = true;
515: }
516:
517: /* Reserve interface IDs */
518: status = usb_interface_id(c, f);
519: if (status < 0)
520: goto err;
521: pn_control_intf_desc.bInterfaceNumber = status;
522: pn_union_desc.bMasterInterface0 = status;
523:
524: status = usb_interface_id(c, f);
525: if (status < 0)
526: goto err;
527: pn_data_nop_intf_desc.bInterfaceNumber = status;
528: pn_data_intf_desc.bInterfaceNumber = status;
529: pn_union_desc.bSlaveInterface0 = status;
530:
531: /* Reserve endpoints */
532: status = -ENODEV;
533: ep = usb_ep_autoconfig(gadget, &pn_fs_sink_desc);
534: if (!ep)
535: goto err;
536: fp->out_ep = ep;
537:
538: ep = usb_ep_autoconfig(gadget, &pn_fs_source_desc);
539: if (!ep)
]
|
| 59/2 |
2026/07/30 09:14 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 46839
output: 40
thoughts: 297
Thoughts: **Debugging a Phonet Binding Issue**
Okay, so if `pn_bind` fails initially, it returns a status code, but the `phonet_opts->bound` flag remains set to true. Then, `usb_add_function` and `configfs_composite_bind` subsequently fail. The user then tries to bind again. This causes `pn_bind` to be called a second time. Since that `phonet_opts->bound` flag is already true, it *skips* the `gphonet_register_netdev` call. This is the crucial point. From there, it proceeds to `usb_interface_id`. The critical problem is that if `gphonet_register_netdev` is skipped, it *doesn't* call `register_netdevice` a second time! So, how can `register_netdevice` be called *at all* in the second attempt if it's skipped the first?
Wait⦠what if `phonet_opts->bound` isn't true? But that's not how it should happen, since it is set on the first failure. The code is written assuming that the state from the previous call to `pn_bind` is kept.
Aha! What if there are *two* `phonet` functions in the same configuration? Or even two different configurations, each with a `phonet` function? No, `phonet_opts` is function instance specific. If there are two function instances, they will have different `phonet_opts` and, critically, different network devices.
Wait⦠what if you link the *same* function instance to *two* configurations? *That's* the key! In configfs, you *can* link the same function instance to multiple configurations. That's a definite possibility. I'd better check `configfs_composite_bind` to see if this is allowed. This may be the core of the problem.
|
| 60/2 |
2026/07/30 09:14 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/configfs.c FirstLine:1790 LineCount:30]
Results: map[Contents:1790: }
1791: cfg->gstrings[i] = NULL;
1792: s = usb_gstrings_attach(&gi->cdev, cfg->gstrings, 1);
1793: if (IS_ERR(s)) {
1794: ret = PTR_ERR(s);
1795: goto err_comp_cleanup;
1796: }
1797: c->iConfiguration = s[0].id;
1798: }
1799:
1800: list_for_each_entry_safe(f, tmp, &cfg->func_list, list) {
1801: list_del(&f->list);
1802: ret = usb_add_function(c, f);
1803: if (ret) {
1804: list_add(&f->list, &cfg->func_list);
1805: goto err_purge_funcs;
1806: }
1807: }
1808: ret = usb_gadget_check_config(cdev->gadget);
1809: if (ret)
1810: goto err_purge_funcs;
1811:
1812: usb_ep_autoconfig_reset(cdev->gadget);
1813: }
1814: if (cdev->use_os_string) {
1815: ret = composite_os_desc_req_prepare(cdev, gadget->ep0);
1816: if (ret)
1817: goto err_purge_funcs;
1818: }
1819:
]
|
| 61/2 |
2026/07/30 09:14 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 48045
output: 40
thoughts: 104
Thoughts: **My Analysis of USB Function Configuration**
Okay, so I'm digging into this `usb_add_function` stuff and a potential design issue has just hit me. If I understand this correctly, when I tie the same function *instance* to several different configurations, `usb_add_function` gets called multiple times, once for each configuration. That seems reasonable on the surface.
But hold on a second. `usb_add_function` takes a `struct usb_function *f` as input. A `usb_function`, as I recall, can only be part of *one* configuration at a time, right? Because it has that `list` node. It's designed to be in a single linked list, representing a single configuration.
So, here's the problem. *Does* linking a function instance to a configuration result in the creation of a *new* `struct usb_function` for *each* configuration? That would make sense, but I need to confirm that. Otherwise, we're violating the core design. If it's *not* creating a new `struct usb_function` each time, we're clearly going to have problems with how it's linked into configurations.
Time to look at how configfs handles these functions. Need to confirm or disprove my hypothesis about how that's being managed. Let's trace through the configuration process.
|
| 62/2 |
2026/07/30 09:14 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/configfs.c FirstLine:1250 LineCount:30]
Results: map[Contents:1250: out:
1251: mutex_unlock(&gi->lock);
1252: return ret;
1253: }
1254:
1255: static void os_desc_unlink(struct config_item *os_desc_ci,
1256: struct config_item *usb_cfg_ci)
1257: {
1258: struct gadget_info *gi = os_desc_item_to_gadget_info(os_desc_ci);
1259: struct usb_composite_dev *cdev = &gi->cdev;
1260:
1261: mutex_lock(&gi->lock);
1262: if (gi->composite.gadget_driver.udc_name)
1263: unregister_gadget(gi);
1264: cdev->os_desc_config = NULL;
1265: WARN_ON(gi->composite.gadget_driver.udc_name);
1266: mutex_unlock(&gi->lock);
1267: }
1268:
1269: static const struct configfs_item_operations os_desc_ops = {
1270: .allow_link = os_desc_link,
1271: .drop_link = os_desc_unlink,
1272: };
1273:
1274: static const struct config_item_type os_desc_type = {
1275: .ct_item_ops = &os_desc_ops,
1276: .ct_attrs = os_desc_attrs,
1277: .ct_owner = THIS_MODULE,
1278: };
1279:
]
|
| 63/2 |
2026/07/30 09:14 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 48956
output: 38
thoughts: 13
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience and presented in the first person:
**Navigating the USB Configuration Labyrinth**
Alright, time to get to it. Let's start with a crucial check β I need to dive into `config_usb_cfg_link`. That's the logical starting point, isn't it? It's the central configuration link, so that's where I need to start. From there I can determine what I need, and start the iterative process of determining where everything is linked, and then figure out the dependencies and possible conflicts. No use jumping to conclusions without a solid foundation, especially with USB. There are so many potential gotchas, so I will start there, and work my way outward.
|
| 64/2 |
2026/07/30 09:14 |
tool |
grepper |
0m
Args: map[Expression:config_usb_cfg_link PathPrefix:drivers/usb/gadget/configfs.c]
Results: map[Output:drivers/usb/gadget/configfs.c=416=static void gadget_config_attr_release(struct config_item *item)
--
drivers/usb/gadget/configfs.c-425-
drivers/usb/gadget/configfs.c:426:static int config_usb_cfg_link(
drivers/usb/gadget/configfs.c-427- struct config_item *usb_cfg_ci,
--
drivers/usb/gadget/configfs.c=517=static const struct configfs_item_operations gadget_config_item_ops = {
drivers/usb/gadget/configfs.c-518- .release = gadget_config_attr_release,
drivers/usb/gadget/configfs.c:519: .allow_link = config_usb_cfg_link,
drivers/usb/gadget/configfs.c-520- .drop_link = config_usb_cfg_unlink,
]
|
| 65/2 |
2026/07/30 09:14 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 49421
output: 39
thoughts: 13
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in this field, presented as a first-person summary:
**Analyzing `config_usb_cfg_link`**
Alright, let's dive into this `config_usb_cfg_link` file. I'm assuming this is a configuration file, likely related to USB device configuration and linking within a system or driver. Given the naming convention, I'm expecting to find parameters or settings that dictate how a USB device behaves, perhaps its connection type, its endpoints, maybe some power management aspects, or even the process of linking the driver to the device itself. My focus right now is to understand the scope and purpose of this configuration file and how it fits into the broader USB architecture that I'm working with. Let's see what we can find. I anticipate having to trace its usage in the source code or related config files as well as the immediate need to parse the contents.
|
| 66/2 |
2026/07/30 09:14 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/configfs.c FirstLine:426 LineCount:40]
Results: map[Contents: 426: static int config_usb_cfg_link(
427: struct config_item *usb_cfg_ci,
428: struct config_item *usb_func_ci)
429: {
430: struct config_usb_cfg *cfg = to_config_usb_cfg(usb_cfg_ci);
431: struct gadget_info *gi = cfg_to_gadget_info(cfg);
432:
433: struct usb_function_instance *fi =
434: to_usb_function_instance(usb_func_ci);
435: struct usb_function_instance *a_fi = NULL, *iter;
436: struct usb_function *f;
437: int ret;
438:
439: mutex_lock(&gi->lock);
440: /*
441: * Make sure this function is from within our _this_ gadget and not
442: * from another gadget or a random directory.
443: * Also a function instance can only be linked once.
444: */
445:
446: if (gi->composite.gadget_driver.udc_name) {
447: ret = -EINVAL;
448: goto out;
449: }
450:
451: list_for_each_entry(iter, &gi->available_func, cfs_list) {
452: if (iter != fi)
453: continue;
454: a_fi = iter;
455: break;
456: }
457: if (!a_fi) {
458: ret = -EINVAL;
459: goto out;
460: }
461:
462: list_for_each_entry(f, &cfg->func_list, list) {
463: if (f->fi == fi) {
464: ret = -EEXIST;
465: goto out;
]
|
| 67/2 |
2026/07/30 09:14 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 50259
output: 39
thoughts: 33
Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience:
**Function Instance Linking - One or Many?**
Right, so I'm thinking about how a function instance behaves with respect to its configuration. The documentation is pretty clear: it can only be *directly* linked to a single configuration. That's straightforward, and I've seen that behavior confirmed in practice. But the key question that's rattling around in my head is: can the same function instance be indirectly associated with multiple configurations somehow? Are there alternative linking mechanisms, perhaps through intermediate layers or shared resources, that allow for a broader scope of applicability? I should dive a bit deeper into the architecture to see if there are any subtle workarounds or design patterns that support more flexible linkage models.
|
| 68/2 |
2026/07/30 09:14 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/configfs.c FirstLine:466 LineCount:40]
Results: map[Contents: 466: }
467: }
468:
469: f = usb_get_function(fi);
470: if (IS_ERR(f)) {
471: ret = PTR_ERR(f);
472: goto out;
473: }
474:
475: /* stash the function until we bind it to the gadget */
476: list_add_tail(&f->list, &cfg->func_list);
477: ret = 0;
478: out:
479: mutex_unlock(&gi->lock);
480: return ret;
481: }
482:
483: static void config_usb_cfg_unlink(
484: struct config_item *usb_cfg_ci,
485: struct config_item *usb_func_ci)
486: {
487: struct config_usb_cfg *cfg = to_config_usb_cfg(usb_cfg_ci);
488: struct gadget_info *gi = cfg_to_gadget_info(cfg);
489:
490: struct usb_function_instance *fi =
491: to_usb_function_instance(usb_func_ci);
492: struct usb_function *f;
493:
494: /*
495: * ideally I would like to forbid to unlink functions while a gadget is
496: * bound to an UDC. Since this isn't possible at the moment, we simply
497: * force an unbind, the function is available here and then we can
498: * remove the function.
499: */
500: mutex_lock(&gi->lock);
501: if (gi->composite.gadget_driver.udc_name)
502: unregister_gadget(gi);
503: WARN_ON(gi->composite.gadget_driver.udc_name);
504:
505: list_for_each_entry(f, &cfg->func_list, list) {
]
|
| 69/2 |
2026/07/30 09:14 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 51095
output: 43
thoughts: 74
Thoughts: **Analyzing Function Instance Configuration**
Okay, so I've got this function instance, and the important thing here is that *it* can be connected to several different configurations. That's a key point. When the linking happens, `usb_get_function(fi)` gets called, and that in turn calls `fi->alloc_func(fi)`. Right. So, `fi->alloc_func` - which is a crucial part - is like a pointer to the right memory allocation function based on the configuration.
Ah, and specifically in the case of `phonet`, `fi->alloc_func` is set to `phonet_alloc`. Okay, now I want to delve into what `phonet_alloc` does exactly, as it's the core of how the memory management works under this configuration. Let me get a look at that function.
|
| 70/2 |
2026/07/30 09:14 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:676 LineCount:20]
Results: map[Contents: 676: static struct usb_function *phonet_alloc(struct usb_function_instance *fi)
677: {
678: struct f_phonet *fp;
679: struct f_phonet_opts *opts;
680:
681: fp = kzalloc_flex(*fp, out_reqv, phonet_rxq_size);
682: if (!fp)
683: return ERR_PTR(-ENOMEM);
684:
685: opts = container_of(fi, struct f_phonet_opts, func_inst);
686:
687: fp->dev = opts->net;
688: fp->function.name = "phonet";
689: fp->function.bind = pn_bind;
690: fp->function.unbind = pn_unbind;
691: fp->function.set_alt = pn_set_alt;
692: fp->function.get_alt = pn_get_alt;
693: fp->function.disable = pn_disconnect;
694: fp->function.free_func = phonet_free;
695: spin_lock_init(&fp->rx.lock);
]
|
| 71/2 |
2026/07/30 09:14 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 51741
output: 43
thoughts: 1201
Thoughts: **Analyzing the phonet Gadget Driver's Binding Logic**
Okay, let's break this down. My initial thought was that `phonet_alloc` is creating a *new* `struct f_phonet` for each link, which is expected. However, they're all sharing the *same* `opts` and `opts->net`. That's the crux of the problem, I suspect. Now, let's step through the binding process with two configurations.
1. `configfs_composite_bind` gets called.
2. It loops through the configurations.
3. *Configuration 1*: `usb_add_function` is called and `f1->bind` (which calls `pn_bind`) is executed. Here, `phonet_opts->bound` starts at `false`, so `gphonet_register_netdev` *should* be called, and it *should* succeed, setting `phonet_opts->bound` to `true`.
4. *Configuration 2*: Again, `usb_add_function` is executed and `f2->bind` (which calls `pn_bind`) is called. But, `phonet_opts->bound` is now `true`. This means `gphonet_register_netdev` is *skipped*. This raises a red flag. What if `f2->bind` fails?
If `f2->bind` (or more specifically, `usb_interface_id`) fails, the error bubbles up, and we end up in `err_purge_funcs` and then `purge_configs_funcs`. `f1->unbind` (which calls `pn_unbind`) is called. The surprising thing here is that `pn_unbind` *doesn't* unregister the netdev, nor does it reset `phonet_opts->bound`! It also does nothing with `opts->net`. That's a major oversight.
The composite bind then returns an error, the gadget *isn't* bound. Then the user tries to bind it *again*. In this second attempt, configuration 1 executes first and the call to `pn_bind` finds that `phonet_opts->bound` is *still* `true`. This means `gphonet_register_netdev` is *still skipped*, and `register_netdevice` will not be called again. This doesn't explain the bug I am seeing in `register_netdevice`.
Okay, next thought: what if `gphonet_register_netdev` fails *during* the first configuration? If that fails, `free_netdev` is called, and `pn_bind` returns an error, leading to a failed bind. The user tries again. `pn_bind` is again called, `phonet_opts->bound` is false. `gphonet_register_netdev` attempts to register the freed `net`! But if `register_netdevice` fails, `free_netdev` *should* have freed the memory. If it was freed, KASAN *should* be reporting a use-after-free error, which I'm not seeing. Why?
I went back and checked the crash trace. It pointed at `net/core/dev.c:11340`, inside `register_netdevice`, specifically: `BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);`. This means `dev` wasn't freed! If it *was* freed, accessing `dev->reg_state` would have triggered a KASAN use-after-free.
So the question is: *why* wasn't `dev` freed? `gphonet_register_netdev` *should* call `free_netdev` if `register_netdevice` fails. Then I had a new thought: what if `register_netdev` succeeds but then `pn_bind` fails LATER? Then an error is returned. But `pn_bind` *doesn't* call `free_netdev` or unregister the netdev. That means `phonet_opts->bound` will be set to `true`. In this situation, if the user then *removes* the `phonet` function directory, `phonet_free_inst` will be called.
|
| 72/2 |
2026/07/30 09:15 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:619 LineCount:20]
Results: map[Contents: 619: static void phonet_free_inst(struct usb_function_instance *f)
620: {
621: struct f_phonet_opts *opts;
622:
623: opts = container_of(f, struct f_phonet_opts, func_inst);
624: if (opts->bound)
625: gphonet_cleanup(opts->net);
626: else
627: free_netdev(opts->net);
628: kfree(opts);
629: }
630:
631: static struct usb_function_instance *phonet_alloc_inst(void)
632: {
633: struct f_phonet_opts *opts;
634:
635: opts = kzalloc_obj(*opts);
636: if (!opts)
637: return ERR_PTR(-ENOMEM);
638:
]
|
| 73/2 |
2026/07/30 09:15 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 54198
output: 35
thoughts: 1144
Thoughts: **The Mystery of the Persistent Netdev**
Okay, let me think this through. I see the code calls `gphonet_cleanup`, which should lead to `unregister_netdev`, and then `free_netdev`. So, logically, the netdev *should* be freed, and `phonet_free_inst` should clean up everything, including the options struct. I was initially looking for a bug there, but the sequence seems correct.
But then I thought, what if `opts->bound` is true, but the device *wasn't* registered? That's impossible... `bound` is only set after a *successful* registration in `gphonet_register_netdev`. But then, wait. What if registration *succeeds*, binding fails, then the user tries again? If `bound` is true, we *skip* registration. Then it works. Then unbinding happens which *doesn't* unregister. Then binding is called *again* and we still skip registration. That seems fine, the netdev is registered once and never touched unless the function instance is removed.
So, where is the problem? The crash trace shows `register_netdev` is called, but it *shouldn't* be! So `opts->bound` must be false. But...if `opts->bound` is false, we *should* call `gphonet_register_netdev`, and that's where the problem is! The `register_netdevice` function is triggering a crash. This means `dev->reg_state` is not in the expected initial state! That state implies the device has been registered, is being unregistered, or is unregistered! So... how is `dev->reg_state` messed up, but `bound` still false?
I checked, `bound` is initialized to false and is never set to false again in this code. So, the bug must be in `gphonet_register_netdev`. When it fails, does it somehow leave the `dev` in a bad state? On failure, it calls `free_netdev`. But `free_netdev` *should* free the netdev, and if KASAN was on, a use-after-free would have been flagged. But it's not.
Ah-ha! `free_netdev` calls `put_device`. That *decrements* the refcount. If the refcount isn't zero, `netdev_release` *isn't* called, and therefore the netdev *isn't* freed! So, someone else is holding a reference! `netdev_release` would `kvfree` the device. Because this is not happening, `reg_state` will persist! When we call `pn_bind` again and call `register_netdev`, the existing `dev` is still not initialized and boom, the BUG!
So, the issue is that something is leaking a reference to `dev->dev` during a *failed* registration. We need to check the error paths in `register_netdevice` to confirm.
|
| 74/2 |
2026/07/30 09:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:register_netdevice]
Results: map[SourceCode:11318: /**
11319: * register_netdevice() - register a network device
11320: * @dev: device to register
11321: *
11322: * Take a prepared network device structure and make it externally accessible.
11323: * A %NETDEV_REGISTER message is sent to the netdev notifier chain.
11324: * Callers must hold the rtnl lock - you may want register_netdev()
11325: * instead of this.
11326: */
11327: int register_netdevice(struct net_device *dev)
11328: {
11329: int ret;
11330: struct net *net = dev_net(dev);
11331:
11332: BUILD_BUG_ON(sizeof(netdev_features_t) * BITS_PER_BYTE <
11333: NETDEV_FEATURE_COUNT);
11334: BUG_ON(dev_boot_phase);
11335: ASSERT_RTNL();
11336:
11337: might_sleep();
11338:
11339: /* When net_device's are persistent, this will be fatal. */
11340: BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
11341: BUG_ON(!net);
11342:
11343: ret = ethtool_check_ops(dev->ethtool_ops);
11344: if (ret)
11345: return ret;
11346:
11347: /* rss ctx ID 0 is reserved for the default context, start from 1 */
11348: xa_init_flags(&dev->ethtool->rss_ctx, XA_FLAGS_ALLOC1);
11349: mutex_init(&dev->ethtool->rss_lock);
11350:
11351: spin_lock_init(&dev->addr_list_lock);
11352: netdev_set_addr_lockdep_class(dev);
11353:
11354: ret = dev_get_valid_name(net, dev, dev->name);
11355: if (ret < 0)
11356: goto out;
11357:
11358: ret = -ENOMEM;
11359: dev->name_node = netdev_name_node_head_alloc(dev);
11360: if (!dev->name_node)
11361: goto out;
11362:
11363: /* Init, if this function is available */
11364: if (dev->netdev_ops->ndo_init) {
11365: ret = dev->netdev_ops->ndo_init(dev);
11366: if (ret) {
11367: if (ret > 0)
11368: ret = -EIO;
11369: goto err_free_name;
11370: }
11371: }
11372:
11373: if (((dev->hw_features | dev->features) &
11374: NETIF_F_HW_VLAN_CTAG_FILTER) &&
11375: (!dev->netdev_ops->ndo_vlan_rx_add_vid ||
11376: !dev->netdev_ops->ndo_vlan_rx_kill_vid)) {
11377: netdev_WARN(dev, "Buggy VLAN acceleration in driver!\n");
11378: ret = -EINVAL;
11379: goto err_uninit;
11380: }
11381:
11382: if (netdev_need_ops_lock(dev) &&
11383: dev->netdev_ops->ndo_set_rx_mode &&
11384: !dev->netdev_ops->ndo_set_rx_mode_async)
11385: netdev_WARN(dev, "ops-locked drivers should use ndo_set_rx_mode_async\n");
11386:
11387: ret = netdev_do_alloc_pcpu_stats(dev);
11388: if (ret)
11389: goto err_uninit;
11390:
11391: ret = dev_index_reserve(net, dev->ifindex);
11392: if (ret < 0)
11393: goto err_free_pcpu;
11394: dev->ifindex = ret;
11395:
11396: /* Transfer changeable features to wanted_features and enable
11397: * software offloads (GSO and GRO).
11398: */
11399: dev->hw_features |= (NETIF_F_SOFT_FEATURES | NETIF_F_SOFT_FEATURES_OFF);
11400: dev->features |= NETIF_F_SOFT_FEATURES;
11401:
11402: if (dev->udp_tunnel_nic_info) {
11403: dev->features |= NETIF_F_RX_UDP_TUNNEL_PORT;
11404: dev->hw_features |= NETIF_F_RX_UDP_TUNNEL_PORT;
11405: }
11406:
11407: dev->wanted_features = dev->features & dev->hw_features;
11408:
11409: if (!(dev->flags & IFF_LOOPBACK))
11410: dev->hw_features |= NETIF_F_NOCACHE_COPY;
11411:
11412: /* If IPv4 TCP segmentation offload is supported we should also
11413: * allow the device to enable segmenting the frame with the option
11414: * of ignoring a static IP ID value. This doesn't enable the
11415: * feature itself but allows the user to enable it later.
11416: */
11417: if (dev->hw_features & NETIF_F_TSO)
11418: dev->hw_features |= NETIF_F_TSO_MANGLEID;
11419: if (dev->vlan_features & NETIF_F_TSO)
11420: dev->vlan_features |= NETIF_F_TSO_MANGLEID;
11421: if (dev->mpls_features & NETIF_F_TSO)
11422: dev->mpls_features |= NETIF_F_TSO_MANGLEID;
11423: if (dev->hw_enc_features & NETIF_F_TSO)
11424: dev->hw_enc_features |= NETIF_F_TSO_MANGLEID;
11425:
11426: /* TSO_MANGLEID belongs in mangleid_features by definition */
11427: dev->mangleid_features |= NETIF_F_TSO_MANGLEID;
11428:
11429: /* Make NETIF_F_HIGHDMA inheritable to VLAN devices.
11430: */
11431: dev->vlan_features |= NETIF_F_HIGHDMA;
11432:
11433: /* Make NETIF_F_SG inheritable to tunnel devices.
11434: */
11435: dev->hw_enc_features |= NETIF_F_SG | NETIF_F_GSO_PARTIAL;
11436:
11437: /* Make NETIF_F_SG inheritable to MPLS.
11438: */
11439: dev->mpls_features |= NETIF_F_SG;
11440:
11441: ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev);
11442: ret = notifier_to_errno(ret);
11443: if (ret)
11444: goto err_ifindex_release;
11445:
11446: ret = netdev_register_kobject(dev);
11447:
11448: netdev_lock(dev);
11449: WRITE_ONCE(dev->reg_state, ret ? NETREG_UNREGISTERED : NETREG_REGISTERED);
11450: netdev_unlock(dev);
11451:
11452: if (ret)
11453: goto err_uninit_notify;
11454:
11455: netdev_lock_ops(dev);
11456: __netdev_update_features(dev);
11457: netdev_unlock_ops(dev);
11458:
11459: /*
11460: * Default initial state at registry is that the
11461: * device is present.
11462: */
11463:
11464: set_bit(__LINK_STATE_PRESENT, &dev->state);
11465:
11466: linkwatch_init_dev(dev);
11467:
11468: dev_init_scheduler(dev);
11469:
11470: netdev_hold(dev, &dev->dev_registered_tracker, GFP_KERNEL);
11471: list_netdevice(dev);
11472:
11473: add_device_randomness(dev->dev_addr, dev->addr_len);
11474:
11475: /* If the device has permanent device address, driver should
11476: * set dev_addr and also addr_assign_type should be set to
11477: * NET_ADDR_PERM (default value).
11478: */
11479: if (dev->addr_assign_type == NET_ADDR_PERM)
11480: memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len);
11481:
11482: /* Notify protocols, that a new device appeared. */
11483: netdev_lock_ops(dev);
11484: ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);
11485: netdev_unlock_ops(dev);
11486: ret = notifier_to_errno(ret);
11487: if (ret) {
11488: /* Expect explicit free_netdev() on failure */
11489: dev->needs_free_netdev = false;
11490: unregister_netdevice_queue(dev, NULL);
11491: goto out;
11492: }
11493: /*
11494: * Prevent userspace races by waiting until the network
11495: * device is fully setup before sending notifications.
11496: */
11497: if (!(dev->rtnl_link_ops && dev->rtnl_link_initializing))
11498: rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U, GFP_KERNEL, 0, NULL);
11499:
11500: out:
11501: return ret;
11502:
11503: err_uninit_notify:
11504: call_netdevice_notifiers(NETDEV_PRE_UNINIT, dev);
11505: err_ifindex_release:
11506: dev_index_release(net, dev->ifindex);
11507: err_free_pcpu:
11508: netdev_do_free_pcpu_stats(dev);
11509: err_uninit:
11510: if (dev->netdev_ops->ndo_uninit)
11511: dev->netdev_ops->ndo_uninit(dev);
11512: if (dev->priv_destructor)
11513: dev->priv_destructor(dev);
11514: err_free_name:
11515: netdev_name_node_free(dev->name_node);
11516: goto out;
11517: }
SourceFile:net/core/dev.c]
|
| 75/2 |
2026/07/30 09:15 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 59452
output: 47
thoughts: 165
Thoughts: **Debugging a Potential Memory Leak**
Okay, so I'm chasing a potential use-after-free scenario here, and KASAN isn't screaming at me, which is odd. My initial thought process is this: If `dev_get_valid_name` fails, it jumps out to `out` and just returns `ret`. I know `dev->reg_state` is set to `NETREG_UNINITIALIZED` at this point. The immediate follow-up should be `free_netdev`, which in turn calls `kvfree(dev)`. Then, *boom*, `dev` should be free'd. If that's the case, and somewhere further down the line `dev` is accessed, KASAN should be yelling about a use-after-free. But, nothing. Silence. So, `dev` *wasn't* freed. Why?
Wait a minute, I'm getting ahead of myself. Let me back up and retrace my steps. The logic clearly states that `free_netdev` *should* be called, and it's responsible for the `kvfree(dev)` call. Is it possible that `free_netdev` isn't being called? I'm missing a critical link in the chain here. Let me check the caller of `dev_get_valid_name`, namely `gphonet_register_netdev`. I'll start there and see where the actual execution path goes.
|
| 76/2 |
2026/07/30 09:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_phonet.c Name:gphonet_register_netdev]
Results: map[SourceCode: 723: int gphonet_register_netdev(struct net_device *net)
724: {
725: int status;
726:
727: status = register_netdev(net);
728: if (status)
729: free_netdev(net);
730:
731: return status;
732: }
SourceFile:drivers/usb/gadget/function/f_phonet.c]
|
| 77/2 |
2026/07/30 09:15 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 60130
output: 38
thoughts: 992
Thoughts: **Analyzing a Potential Use-After-Free in Network Device Registration**
Okay, let's break this down. We have `free_netdev(net)` being called. The immediate assumption is that this frees the device memory, but we need to verify this assumption. Is there a scenario where `free_netdev` doesn't actually release the memory immediately?
The code checks if `dev->reg_state` is `NETREG_UNREGISTERING`. If so, freeing is deferred via `dev->needs_free_netdev`. So, the memory *is* eventually freed through `netdev_run_todo`, but not immediately in that case.
Now, what about error handling in `register_netdev`? If `register_netdev` fails, and `dev->reg_state` has been set to `NETREG_UNREGISTERED` within `netdev_register_kobject`, the execution flow goes through `err_uninit_notify` and returns. Then `free_netdev` gets called.
Here is another crucial check! If `dev->reg_state` is `NETREG_UNREGISTERED`, it calls `put_device(&dev->dev)`. This path leads to `netdev_release`, which ultimately calls `kvfree(dev)`. Therefore, the device *is* freed in this scenario too. If it *were* freed, KASAN (Kernel Address Sanitizer) would scream about a use-after-free!
Why didn't KASAN report the use-after-free then? Could syzbot have incorrectly labeled the crash as a "kernel BUG" because the `BUG` happened *before* the KASAN finding? That's unlikely. KASAN always gives its own, specific report if it finds a use-after-free.
So, here's the core question: if `dev` *is* freed, then accessing `dev->reg_state` after the fact triggers a KASAN report, right? But the code calls `dev_net(dev)` *before* the problematic `BUG_ON`! If `dev` had been freed, `dev_net` would have triggered KASAN immediately due to access to `dev->nd_net`. The crash *is* at `BUG_ON(dev->reg_state != NETREG_UNINITIALIZED)`, which implies that `dev_net(dev)` did *not* trigger KASAN. This is the critical clue: `dev` is *not* freed.
How could `dev` *not* be freed? If `free_netdev` was *never* called, of course! But `gphonet_register_netdev` calls `free_netdev` if `register_netdev` fails. So, the question becomes: how else is `register_netdevice` called?
Then I think: What if `gphonet_register_netdev` *succeeds* the first time around? If it succeeds, `phonet_opts->bound` is set to `true`. Then, if `pn_bind` fails later, then `usb_add_function` fails and `configfs_composite_bind` fails, then the user unbinds the gadget. Wait, if the gadget bind failed, the gadget is *not* actually bound! Then the user could try to bind the gadget *again*. Then `pn_bind` is called again. However, since `phonet_opts->bound` is `true`, it *skips* `gphonet_register_netdev`! Consequently, `register_netdevice` is *not* called again. So, did `phonet_opts->bound` get set to `false` somewhere? Now I need to check how `phonet_opts->bound` is set to false. This is the crucial point!
|
| 78/2 |
2026/07/30 09:15 |
tool |
grepper |
0m
Error:
Args: map[Expression:bound = false PathPrefix:drivers/usb/gadget/function/f_phonet.c]
Results:
|
| 79/2 |
2026/07/30 09:15 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 62002
output: 45
thoughts: 145
Thoughts: Okay, here's my take on those thoughts, as if they were running through my head:
**Analyzing a Potential Kernel Bug**
Hmm, that initial assumption about a bug... nah, that's not quite right. Let's think this through carefully. The core issue is the potential for `phonet_opts` to be reallocated after it's been freed. If that happens, and if `phonet_opts` gets freed, then `phonet_opts->net` is *also* freed, right? That makes sense.
So, if that's the case, a *new* `phonet_opts` structure would be allocated, and crucially, it would contain a *new* `net` pointer as well. Now, the critical line is the call to `register_netdevice` with the new `net`. If we are using the new `net` then the `dev->reg_state` is going to be `NETREG_UNINITIALIZED`. That means, the original potential bug is a non-issue. We're in the clear.
But, wait a minute, is it *guaranteed* that `phonet_opts->net` is freed along with the rest of the structure? I have to consider that this might not be the case. Let's check `phonet_free_inst` just to be absolutely sure. Gotta be thorough here and make sure this doesn't bite us down the road.
|
| 80/2 |
2026/07/30 09:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_phonet.c Name:phonet_free_inst]
Results: map[SourceCode: 619: static void phonet_free_inst(struct usb_function_instance *f)
620: {
621: struct f_phonet_opts *opts;
622:
623: opts = container_of(f, struct f_phonet_opts, func_inst);
624: if (opts->bound)
625: gphonet_cleanup(opts->net);
626: else
627: free_netdev(opts->net);
628: kfree(opts);
629: }
SourceFile:drivers/usb/gadget/function/f_phonet.c]
|
| 81/2 |
2026/07/30 09:16 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 62696
output: 42
thoughts: 273
Thoughts: **My Reasoning on Netdev Cleanup**
Okay, so I'm looking at this cleanup process. It looks like it either calls `gphonet_cleanup(opts->net)` or `free_netdev(opts->net)`. My initial thought is, "Yep, it's freed, all good." `gphonet_cleanup` then calls `unregister_netdev(dev)`. That should be the definitive point where the device is de-registered, and therefore freed. But then I have to think, *does* `unregister_netdev` actually guarantee the memory is freed?
I should check the function's implementation. Alright, it calls `unregister_netdevice_queue` and then `netdev_run_todo`. A quick dive into `netdev_run_todo` reveals that it does, in fact, call `free_netdev`. So yes, the device *is* freed eventually, that's good.
But now a new edge case pops up. What if `phonet_opts->bound` is set to true, but `opts->net` was *never* actually registered in the first place? How could that possibly happen? That's impossible, as it means the setup function for the device was somehow skipped.
Wait, what if `gphonet_register_netdev` is called concurrently?
I need to look for any race conditions. What if two threads are calling `pn_bind` concurrently? Does the driver protect against concurrent use? The code says `configfs_composite_bind` holds `gi->lock`, so `pn_bind` isn't called concurrently for the same gadget. Okay, that's one check. But what if two different gadgets *are* bound concurrently, and they happen to share the *same* function instance? That could create a concurrency issue, I need to check. Can two gadgets use the same function instance? This is a point to look at in greater detail. I need to look at `config_usb_cfg_link`.
|
| 82/2 |
2026/07/30 09:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/configfs.c Name:config_usb_cfg_link]
Results: map[SourceCode: 426: static int config_usb_cfg_link(
427: struct config_item *usb_cfg_ci,
428: struct config_item *usb_func_ci)
429: {
430: struct config_usb_cfg *cfg = to_config_usb_cfg(usb_cfg_ci);
431: struct gadget_info *gi = cfg_to_gadget_info(cfg);
432:
433: struct usb_function_instance *fi =
434: to_usb_function_instance(usb_func_ci);
435: struct usb_function_instance *a_fi = NULL, *iter;
436: struct usb_function *f;
437: int ret;
438:
439: mutex_lock(&gi->lock);
440: /*
441: * Make sure this function is from within our _this_ gadget and not
442: * from another gadget or a random directory.
443: * Also a function instance can only be linked once.
444: */
445:
446: if (gi->composite.gadget_driver.udc_name) {
447: ret = -EINVAL;
448: goto out;
449: }
450:
451: list_for_each_entry(iter, &gi->available_func, cfs_list) {
452: if (iter != fi)
453: continue;
454: a_fi = iter;
455: break;
456: }
457: if (!a_fi) {
458: ret = -EINVAL;
459: goto out;
460: }
461:
462: list_for_each_entry(f, &cfg->func_list, list) {
463: if (f->fi == fi) {
464: ret = -EEXIST;
465: goto out;
466: }
467: }
468:
469: f = usb_get_function(fi);
470: if (IS_ERR(f)) {
471: ret = PTR_ERR(f);
472: goto out;
473: }
474:
475: /* stash the function until we bind it to the gadget */
476: list_add_tail(&f->list, &cfg->func_list);
477: ret = 0;
478: out:
479: mutex_unlock(&gi->lock);
480: return ret;
481: }
SourceFile:drivers/usb/gadget/configfs.c]
|
| 83/2 |
2026/07/30 09:16 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 64236
output: 43
thoughts: 685
Thoughts: **Analyzing the Phonet Gadget Binding Logic**
Okay, so I'm looking at this `pn_bind` function and trying to wrap my head around potential race conditions and edge cases. First, it's pretty clear that the code uses `list_for_each_entry` on `gi->available_func` to ensure a function instance belongs specifically to this gadget. That's good; it means we're not dealing with shared instances between gadgets, which simplifies concurrency issues. So, no simultaneous `pn_bind` calls using the same function instance, right?
But then I started to think about the netdev lifecycle. Specifically, what happens if `gphonet_register_netdev` succeeds, sets `phonet_opts->bound` to true, then the gadget is unbound, but the function instance isn't explicitly freed because the directory isn't removed. If we bind the gadget again, `pn_bind` sees `phonet_opts->bound` is true and skips `gphonet_register_netdev`. That *seems* fine since the device should already be registered.
But here's where it gets interesting: what if `phonet_opts->bound` is *still* true, but the netdev itself gets *unregistered*? How could that even happen? Well, it's a `phonet` device, so it's not like a user can manually unregister it directly. It would require the module unloading or gadget destruction. Then it hit me: network namespaces! We can move a `phonet` device to another namespace using `ip link` and then what if *that* namespace gets destroyed? It calls `default_device_exit_batch` which unregisters all devices in the namespace! Bingo.
If the netdev is unregistered, `dev->reg_state` transitions to `NETREG_UNREGISTERED` and eventually `NETREG_RELEASED` and the netdev is freed! But `phonet_opts->bound` is STILL TRUE! That's a problem, until I recalled the control flow: `pn_bind` sees `phonet_opts->bound` is true, it *skips* the `gphonet_register_netdev` call, and therefore it doesn't try to call `register_netdevice`! So, no bug!
But what if `phonet_opts->bound` is FALSE? If false, `pn_bind` WILL call `gphonet_register_netdev`. But how could it be false if the netdev was registered? Oh, maybe if `gphonet_register_netdev` *fails* after the netdev is registered! How does `register_netdev` *fail* but leave the netdev registered? Wait, it doesn't! If `register_netdev` fails, it returns an error and the netdev isn't registered!
|
| 84/2 |
2026/07/30 09:16 |
tool |
read-file |
0m
Args: map[File:drivers/usb/gadget/function/f_phonet.c FirstLine:510 LineCount:30]
Results: map[Contents: 510: gphonet_set_gadget(phonet_opts->net, gadget);
511: status = gphonet_register_netdev(phonet_opts->net);
512: if (status)
513: return status;
514: phonet_opts->bound = true;
515: }
516:
517: /* Reserve interface IDs */
518: status = usb_interface_id(c, f);
519: if (status < 0)
520: goto err;
521: pn_control_intf_desc.bInterfaceNumber = status;
522: pn_union_desc.bMasterInterface0 = status;
523:
524: status = usb_interface_id(c, f);
525: if (status < 0)
526: goto err;
527: pn_data_nop_intf_desc.bInterfaceNumber = status;
528: pn_data_intf_desc.bInterfaceNumber = status;
529: pn_union_desc.bSlaveInterface0 = status;
530:
531: /* Reserve endpoints */
532: status = -ENODEV;
533: ep = usb_ep_autoconfig(gadget, &pn_fs_sink_desc);
534: if (!ep)
535: goto err;
536: fp->out_ep = ep;
537:
538: ep = usb_ep_autoconfig(gadget, &pn_fs_source_desc);
539: if (!ep)
]
|
| 85/2 |
2026/07/30 09:16 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 66048
output: 44
thoughts: 814
Thoughts: **Debugging a Potential Use-After-Free in the `phonet` Driver**
Okay, so I'm looking at this issue where `gphonet_register_netdev` might fail, and I need to figure out why KASAN isn't flagging a use-after-free scenario. The initial thought is that if `gphonet_register_netdev` fails, it returns a status, but what if it's called concurrently? I already know `gi->lock` is held during `configfs_composite_bind`, which calls it, so concurrency shouldn't be the direct cause.
Digging deeper, I see `gphonet_register_netdev` is called from `pn_bind`. Now, is `pn_bind` called from anywhere else? Nope, only `usb_add_function`, which is itself called from `configfs_composite_bind` (under the protection of `gi->lock`) or `usb_add_config` for legacy stuff. So, for configfs gadgets, it's *only* through `configfs_composite_bind`.
Here's the problem: what if `gphonet_register_netdev` fails, and calls `free_netdev`? That should free the netdev, `pn_bind` returns an error. Then, the user tries to bind again; it calls `pn_bind` again. `phonet_opts->bound` is false at this point, so it calls `gphonet_register_netdev` again with the *freed* netdev. KASAN *should* be yelling about a use-after-free, but it isn't. Why?
Maybe `free_netdev` doesn't *actually* free the netdev? Let's check `free_netdev`. Ah, if `dev->reg_state` is `NETREG_UNINITIALIZED` or `NETREG_DUMMY`, it calls `kvfree(dev)`. `kvfree` *does* free the memory, so KASAN should be poisoning it. Any access to `dev->reg_state` after that should trigger a report. Unless⦠unless the memory has been reallocated!
Could `dev` be reallocated? Possibly, if something else allocates memory of the same size. But syzbot reproduces this reliably. It's not likely the exact same memory is always allocated *and* that `reg_state` isn't zero, or the KASAN report should have triggered.
If it *is* reallocated, `reg_state` could be anything, which could trigger a BUG if it's not zero. The KASAN report wouldn't show up. Thatβs a potential explanation.
So, let's write a reproducer: create a gadget, add a `phonet` function, link it to a config, and bind. Then, force `gphonet_register_netdev` to fail.
How to make `gphonet_register_netdev` fail? `register_netdev` can fail if the name (`upnlink%d`) is taken. But itβs auto-assigned, so unless we have 4 billion devices, that's not it. What else can make it fail? Fault injection. Syzkaller can do that, but the report doesn't mention faults.
Wait, it can fail if `dev_get_valid_name` or `ndo_init` fails. Does `phonet` have `ndo_init`? I need to check!
|
| 86/2 |
2026/07/30 09:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_phonet.c Name:pn_net_setup]
Results: map[SourceCode: 268: static void pn_net_setup(struct net_device *dev)
269: {
270: const u8 addr = PN_MEDIA_USB;
271:
272: dev->features = 0;
273: dev->type = ARPHRD_PHONET;
274: dev->flags = IFF_POINTOPOINT | IFF_NOARP;
275: dev->mtu = PHONET_DEV_MTU;
276: dev->min_mtu = PHONET_MIN_MTU;
277: dev->max_mtu = PHONET_MAX_MTU;
278: dev->hard_header_len = 1;
279: dev->addr_len = 1;
280: dev_addr_set(dev, &addr);
281:
282: dev->tx_queue_len = 1;
283:
284: dev->netdev_ops = &pn_netdev_ops;
285: dev->needs_free_netdev = true;
286: dev->header_ops = &phonet_header_ops;
287: }
SourceFile:drivers/usb/gadget/function/f_phonet.c]
|
| 87/2 |
2026/07/30 09:16 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 68050
output: 35
thoughts: 54
Thoughts: **Analyzing `free_netdev` in Depth**
Okay, hold on a second! I just saw that assignment: `dev->needs_free_netdev = true;`. That's a flag that's going to influence the network device cleanup process. The key question now is, what *exactly* happens within the `free_netdev` function when this flag is set? I need to understand the implications of this.
Right, time to re-examine the `free_netdev` implementation. I need a clear picture of how it handles a scenario where this `needs_free_netdev` flag is true. Are there specific resources deallocated? Does it trigger any potentially problematic sequences, particularly around memory management or device teardown? I need to make sure I don't miss anything that could lead to a resource leak or, even worse, a kernel panic. Let's dig in and make sure that this particular code path is sound.
|
| 88/2 |
2026/07/30 09:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:free_netdev]
Results: map[SourceCode:12183: /**
12184: * free_netdev - free network device
12185: * @dev: device
12186: *
12187: * This function does the last stage of destroying an allocated device
12188: * interface. The reference to the device object is released. If this
12189: * is the last reference then it will be freed.Must be called in process
12190: * context.
12191: */
12192: void free_netdev(struct net_device *dev)
12193: {
12194: might_sleep();
12195:
12196: /* When called immediately after register_netdevice() failed the unwind
12197: * handling may still be dismantling the device. Handle that case by
12198: * deferring the free.
12199: */
12200: if (dev->reg_state == NETREG_UNREGISTERING) {
12201: ASSERT_RTNL();
12202: dev->needs_free_netdev = true;
12203: return;
12204: }
12205:
12206: WARN_ON(dev->cfg != dev->cfg_pending);
12207: kfree(dev->cfg);
12208: kfree(dev->ethtool);
12209: netif_free_tx_queues(dev);
12210: netif_free_rx_queues(dev);
12211:
12212: kfree(rcu_dereference_protected(dev->ingress_queue, 1));
12213:
12214: __hw_addr_flush(&dev->rx_mode_addr_cache);
12215:
12216: /* Flush device addresses */
12217: dev_addr_flush(dev);
12218:
12219: netdev_napi_exit(dev);
12220:
12221: netif_del_cpu_rmap(dev);
12222:
12223: ref_tracker_dir_exit(&dev->refcnt_tracker);
12224: #ifdef CONFIG_PCPU_DEV_REFCNT
12225: free_percpu(dev->pcpu_refcnt);
12226: dev->pcpu_refcnt = NULL;
12227: #endif
12228: free_percpu(dev->core_stats);
12229: dev->core_stats = NULL;
12230: free_percpu(dev->xdp_bulkq);
12231: dev->xdp_bulkq = NULL;
12232:
12233: netdev_free_phy_link_topology(dev);
12234:
12235: mutex_destroy(&dev->lock);
12236:
12237: /* Compatibility with error handling in drivers */
12238: if (dev->reg_state == NETREG_UNINITIALIZED ||
12239: dev->reg_state == NETREG_DUMMY) {
12240: kvfree(dev);
12241: return;
12242: }
12243:
12244: BUG_ON(dev->reg_state != NETREG_UNREGISTERED);
12245: WRITE_ONCE(dev->reg_state, NETREG_RELEASED);
12246:
12247: /* will free via device release */
12248: put_device(&dev->dev);
12249: }
SourceFile:net/core/dev.c]
|
| 89/2 |
2026/07/30 09:16 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 69389
output: 45
thoughts: 1554
Thoughts: **Analyzing the Network Device Registration Logic**
Okay, let's break this down. My initial thought process was: `netdev_run_todo` uses `dev->needs_free_netdev`. If it's true, `free_netdev` gets called. `pn_net_setup` sets `dev->needs_free_netdev = true`, and then `register_netdevice` is invoked. Initially, if `register_netdevice` fails, `dev->needs_free_netdev` gets set to `false`. But, it isn't set to false if `register_netdevice` succeeds! Instead `dev->needs_free_netdev` *stays* true! So, when the netdev is unregistered, `netdev_run_todo` *will* call `free_netdev`.
But then, `phonet_free_inst` *also* calls `free_netdev` under certain conditions. If `opts->bound` is false it calls `free_netdev`. If `opts->bound` is true, then `gphonet_cleanup` is called, which calls `unregister_netdev`, and since `dev->needs_free_netdev` is true, `netdev_run_todo` calls `free_netdev`. So, the netdev is freed by either method. This seemed correct.
Now, if `pn_bind` fails LATER, after `register_netdev` succeeds, but before the gadget is truly bound, what happens? If it fails after `register_netdev` succeeds, but before `usb_interface_id` works, the gadget isn't bound, but the netdev is registered. So, when `pn_bind` is called again, it skips `gphonet_register_netdev` and everything is fine. This seemed correct.
Even if the gadget is unbound, `phonet_free_inst` would be called. `opts->bound` would be true. It calls `gphonet_cleanup`, then `unregister_netdev`, and the netdev is unregistered and freed. So far, so good.
So where's the bug? Looking again at `gphonet_register_netdev`, it calls `register_netdev`, and if *that* fails, `free_netdev` is immediately called. So if `register_netdev` failed, that code is correct.
But, wait...if `register_netdev` FAILED, then `pn_bind` RETURNS with an error, the netdev is freed, and *then* the user tries to bind the gadget AGAIN. This means `gphonet_register_netdev` would be called again! And, if it fails, then the netdev is freed. But then if `register_netdev` DID NOT fail, how can `register_netdevice` be called again? If `gphonet_register_netdev` succeeds, `phonet_opts->bound` is *always* set to true. There's no way it's not.
Then, I thought, what if `phonet_opts->bound` is true, but `pn_bind` is called for a different function instance? No, the crash trace shows `pn_bind` calling `gphonet_register_netdev`. So, `phonet_opts->bound` *must* be false. That means it is attempting to register something *new*. If it's false, and `gphonet_register_netdev` is called, *that's* where we're bugging in `register_netdevice` because `dev->reg_state != NETREG_UNINITIALIZED`. The net device is already registered! But how is this possible if `phonet_opts->bound` is false? Could it be that `phonet_opts->net` is somehow SHARED between multiple function instances, even though it *shouldn't* be? Let's check `phonet_alloc_inst`. This seems to be the critical area to investigate!
|
| 90/2 |
2026/07/30 09:17 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_phonet.c Name:phonet_alloc_inst]
Results: map[SourceCode: 631: static struct usb_function_instance *phonet_alloc_inst(void)
632: {
633: struct f_phonet_opts *opts;
634:
635: opts = kzalloc_obj(*opts);
636: if (!opts)
637: return ERR_PTR(-ENOMEM);
638:
639: opts->func_inst.free_func_inst = phonet_free_inst;
640: opts->net = gphonet_setup_default();
641: if (IS_ERR(opts->net)) {
642: struct net_device *net = opts->net;
643: kfree(opts);
644: return ERR_CAST(net);
645: }
646:
647: config_group_init_type_name(&opts->func_inst.group, "",
648: &phonet_func_type);
649:
650: return &opts->func_inst;
651: }
SourceFile:drivers/usb/gadget/function/f_phonet.c]
|
| 91/2 |
2026/07/30 09:17 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 72195
output: 47
thoughts: 1111
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability in Network Device Registration**
Okay, so I'm diving into this `gphonet_setup_default` function, and it seems like it's allocating a new netdev for each instance. That's fine, but what happens if `register_netdev` fails? My immediate concern is the `free_netdev` function. Initially, I thought that `free_netdev` wasn't freeing the netdev in some cases. Why would it *not* free it?
I quickly realized that `free_netdev` has a check for the `reg_state`, and it *does* free the memory if the state is `NETREG_UNINITIALIZED` or `NETREG_DUMMY`. My concern was a potential use-after-free, since if `register_netdev` failed, the `dev->reg_state` should be `NETREG_UNINITIALIZED`, `kvfree(dev)` would get called, and then the netdev *would* be freed.
I would expect a KASAN report if this memory were accessed after being freed. But wait, syzbot reported a "kernel BUG at net/core/dev.c:11340!". That means KASAN *didn't* detect a use-after-free! That's odd. Why wouldn't it?
The only way this makes sense is if the memory *wasn't* actually freed. So I dug deeper and I now see that the `register_netdev` function, if the `call_netdevice_notifiers` fails, it calls `unregister_netdevice_queue`. This sets `dev->reg_state` to `NETREG_UNREGISTERING`, which is NOT `NETREG_UNINITIALIZED`. The subsequent `free_netdev` then checks for `NETREG_UNREGISTERING`, and sets `dev->needs_free_netdev = true`, but *returns* without freeing the memory! So, *that's* why!
Then, after releasing the RTNL lock, the `netdev_run_todo` function processes the unregister queue and transitions `dev->reg_state` to `NETREG_UNREGISTERED` and finally `NETREG_RELEASED`. Because `dev->needs_free_netdev` is true, it calls `free_netdev` *again*! But this time, `free_netdev` is called with `dev->reg_state == NETREG_RELEASED` (or `NETREG_UNREGISTERED`), which calls `put_device`, which eventually calls `netdev_release`, which finally calls `kvfree(dev)`. Okay, so the memory *is* freed eventually!
If that's the case, KASAN *should* have flagged a use-after-free when `dev->reg_state` was accessed. Why didn't it? Perhaps there's a race condition. Let's trace all uses of `dev->dev`. Aha! I found it! `gphonet_set_gadget` also accesses the netdev. This could be where the issue lies.
|
| 92/2 |
2026/07/30 09:17 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_phonet.c Name:gphonet_set_gadget]
Results: map[SourceCode: 718: void gphonet_set_gadget(struct net_device *net, struct usb_gadget *g)
719: {
720: SET_NETDEV_DEV(net, &g->dev);
721: }
SourceFile:drivers/usb/gadget/function/f_phonet.c]
|
| 93/2 |
2026/07/30 09:17 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 74139
output: 35
thoughts: 5014
Thoughts: **Analyzing the Register/Unregister Netdevice Vulnerability**
Okay, so I've been digging into this netdevice registration issue, and it's a real head-scratcher. The core of the problem boils down to a use-after-free scenario, but it's tricky to trigger and even trickier to understand why KASAN isn't screaming about it.
It starts with `SET_NETDEV_DEV` and the potential for a race with `netdev_hold` and `netdev_put`, but that seems like a red herring. Then, I considered if sysfs entries were involved and how `device_del` could cause problems, but that's not quite right. The key is how the `net_device` struct is *handled* when a registration fails.
If `register_netdevice` fails *after* `netdev_register_kobject`, we get a weird state. The code goes to `err_uninit_notify`, which leads to the `gphonet_register_netdev` function, and that is where the trouble begins.
`gphonet_register_netdev` calls `free_netdev` in case of failure. Inside `free_netdev`, `kvfree(dev)` is called on failure of the `NETREG_UNREGISTERED` state. If `dev` is freed, and the device goes through the `put_device`, the device is freed with the `netdev_release` call and then `kvfree(dev)`. This means it is freed. But what if it isn't `NETREG_UNREGISTERED`?
The initial thought was that a notifier could be the culprit, but the problem is not in the notifier. If the `register_netdevice` call fails on `call_netdevice_notifiers`, then `unregister_netdevice_queue` is called and `netdev_run_todo` is called. However, this is not the case because `dev->needs_free_netdev = false;` is set. Then it will go to `free_netdev`.
The problem is that the bug happens inside `register_netdevice`, meaning that `register_netdevice` is called on the *same* device *twice*.
Now, the crucial insight: a failure *before* `netdev_register_kobject` leads to immediate freeing and no crash. But, if a failure occurs *after* the `netdev_register_kobject` call and `register_netdev` fails, but the code still calls `free_netdev`, the memory gets freed, and then `register_netdevice` is called on that freed memory.
Here's the scenario: First, `pn_bind` calls `register_netdevice` with a fresh netdevice. If `register_netdevice` fails (and I need to figure out how to make it fail, but I will get to that), `free_netdev` gets called and frees the device. Now, the key! The user tries again. Another call to `pn_bind` happens. The next `register_netdevice` is called on the same device. This is where it crashes because the device is *already freed*. The KASAN check is skipped because `dev` is still valid. The crash is triggered on the `BUG_ON` call.
The remaining question is: How do we make `register_netdev` fail? What part of `register_netdev` could fail? Creating a device with the same name will not work.
I then realized that, after all, the key to the vulnerability lies in that call to `free_netdev(opts->net)`. If this is *not* called when the device fails, then the call to `register_netdevice` will have a *double free* problem. That will cause it to trigger. So, I will focus on making `register_netdev` fail such that the failure causes a double free. Fault injection is a possibility.
A double free is *not* the problem, because the function instance is NOT freed until the USB gadget is removed. So that is not the cause.
The solution: We need a scenario where `register_netdev` fails, `free_netdev` frees the device, and then *later* `register_netdevice` is called on the freed device. The answer is fault injection. We use a fault injector to make the first time call to `register_netdev` fail and then, at a later time, when the gadget is retried, the function calls register on freed memory.
|
| 94/2 |
2026/07/30 09:18 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:register_netdevice]
Results: map[SourceCode:11318: /**
11319: * register_netdevice() - register a network device
11320: * @dev: device to register
11321: *
11322: * Take a prepared network device structure and make it externally accessible.
11323: * A %NETDEV_REGISTER message is sent to the netdev notifier chain.
11324: * Callers must hold the rtnl lock - you may want register_netdev()
11325: * instead of this.
11326: */
11327: int register_netdevice(struct net_device *dev)
11328: {
11329: int ret;
11330: struct net *net = dev_net(dev);
11331:
11332: BUILD_BUG_ON(sizeof(netdev_features_t) * BITS_PER_BYTE <
11333: NETDEV_FEATURE_COUNT);
11334: BUG_ON(dev_boot_phase);
11335: ASSERT_RTNL();
11336:
11337: might_sleep();
11338:
11339: /* When net_device's are persistent, this will be fatal. */
11340: BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
11341: BUG_ON(!net);
11342:
11343: ret = ethtool_check_ops(dev->ethtool_ops);
11344: if (ret)
11345: return ret;
11346:
11347: /* rss ctx ID 0 is reserved for the default context, start from 1 */
11348: xa_init_flags(&dev->ethtool->rss_ctx, XA_FLAGS_ALLOC1);
11349: mutex_init(&dev->ethtool->rss_lock);
11350:
11351: spin_lock_init(&dev->addr_list_lock);
11352: netdev_set_addr_lockdep_class(dev);
11353:
11354: ret = dev_get_valid_name(net, dev, dev->name);
11355: if (ret < 0)
11356: goto out;
11357:
11358: ret = -ENOMEM;
11359: dev->name_node = netdev_name_node_head_alloc(dev);
11360: if (!dev->name_node)
11361: goto out;
11362:
11363: /* Init, if this function is available */
11364: if (dev->netdev_ops->ndo_init) {
11365: ret = dev->netdev_ops->ndo_init(dev);
11366: if (ret) {
11367: if (ret > 0)
11368: ret = -EIO;
11369: goto err_free_name;
11370: }
11371: }
11372:
11373: if (((dev->hw_features | dev->features) &
11374: NETIF_F_HW_VLAN_CTAG_FILTER) &&
11375: (!dev->netdev_ops->ndo_vlan_rx_add_vid ||
11376: !dev->netdev_ops->ndo_vlan_rx_kill_vid)) {
11377: netdev_WARN(dev, "Buggy VLAN acceleration in driver!\n");
11378: ret = -EINVAL;
11379: goto err_uninit;
11380: }
11381:
11382: if (netdev_need_ops_lock(dev) &&
11383: dev->netdev_ops->ndo_set_rx_mode &&
11384: !dev->netdev_ops->ndo_set_rx_mode_async)
11385: netdev_WARN(dev, "ops-locked drivers should use ndo_set_rx_mode_async\n");
11386:
11387: ret = netdev_do_alloc_pcpu_stats(dev);
11388: if (ret)
11389: goto err_uninit;
11390:
11391: ret = dev_index_reserve(net, dev->ifindex);
11392: if (ret < 0)
11393: goto err_free_pcpu;
11394: dev->ifindex = ret;
11395:
11396: /* Transfer changeable features to wanted_features and enable
11397: * software offloads (GSO and GRO).
11398: */
11399: dev->hw_features |= (NETIF_F_SOFT_FEATURES | NETIF_F_SOFT_FEATURES_OFF);
11400: dev->features |= NETIF_F_SOFT_FEATURES;
11401:
11402: if (dev->udp_tunnel_nic_info) {
11403: dev->features |= NETIF_F_RX_UDP_TUNNEL_PORT;
11404: dev->hw_features |= NETIF_F_RX_UDP_TUNNEL_PORT;
11405: }
11406:
11407: dev->wanted_features = dev->features & dev->hw_features;
11408:
11409: if (!(dev->flags & IFF_LOOPBACK))
11410: dev->hw_features |= NETIF_F_NOCACHE_COPY;
11411:
11412: /* If IPv4 TCP segmentation offload is supported we should also
11413: * allow the device to enable segmenting the frame with the option
11414: * of ignoring a static IP ID value. This doesn't enable the
11415: * feature itself but allows the user to enable it later.
11416: */
11417: if (dev->hw_features & NETIF_F_TSO)
11418: dev->hw_features |= NETIF_F_TSO_MANGLEID;
11419: if (dev->vlan_features & NETIF_F_TSO)
11420: dev->vlan_features |= NETIF_F_TSO_MANGLEID;
11421: if (dev->mpls_features & NETIF_F_TSO)
11422: dev->mpls_features |= NETIF_F_TSO_MANGLEID;
11423: if (dev->hw_enc_features & NETIF_F_TSO)
11424: dev->hw_enc_features |= NETIF_F_TSO_MANGLEID;
11425:
11426: /* TSO_MANGLEID belongs in mangleid_features by definition */
11427: dev->mangleid_features |= NETIF_F_TSO_MANGLEID;
11428:
11429: /* Make NETIF_F_HIGHDMA inheritable to VLAN devices.
11430: */
11431: dev->vlan_features |= NETIF_F_HIGHDMA;
11432:
11433: /* Make NETIF_F_SG inheritable to tunnel devices.
11434: */
11435: dev->hw_enc_features |= NETIF_F_SG | NETIF_F_GSO_PARTIAL;
11436:
11437: /* Make NETIF_F_SG inheritable to MPLS.
11438: */
11439: dev->mpls_features |= NETIF_F_SG;
11440:
11441: ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev);
11442: ret = notifier_to_errno(ret);
11443: if (ret)
11444: goto err_ifindex_release;
11445:
11446: ret = netdev_register_kobject(dev);
11447:
11448: netdev_lock(dev);
11449: WRITE_ONCE(dev->reg_state, ret ? NETREG_UNREGISTERED : NETREG_REGISTERED);
11450: netdev_unlock(dev);
11451:
11452: if (ret)
11453: goto err_uninit_notify;
11454:
11455: netdev_lock_ops(dev);
11456: __netdev_update_features(dev);
11457: netdev_unlock_ops(dev);
11458:
11459: /*
11460: * Default initial state at registry is that the
11461: * device is present.
11462: */
11463:
11464: set_bit(__LINK_STATE_PRESENT, &dev->state);
11465:
11466: linkwatch_init_dev(dev);
11467:
11468: dev_init_scheduler(dev);
11469:
11470: netdev_hold(dev, &dev->dev_registered_tracker, GFP_KERNEL);
11471: list_netdevice(dev);
11472:
11473: add_device_randomness(dev->dev_addr, dev->addr_len);
11474:
11475: /* If the device has permanent device address, driver should
11476: * set dev_addr and also addr_assign_type should be set to
11477: * NET_ADDR_PERM (default value).
11478: */
11479: if (dev->addr_assign_type == NET_ADDR_PERM)
11480: memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len);
11481:
11482: /* Notify protocols, that a new device appeared. */
11483: netdev_lock_ops(dev);
11484: ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);
11485: netdev_unlock_ops(dev);
11486: ret = notifier_to_errno(ret);
11487: if (ret) {
11488: /* Expect explicit free_netdev() on failure */
11489: dev->needs_free_netdev = false;
11490: unregister_netdevice_queue(dev, NULL);
11491: goto out;
11492: }
11493: /*
11494: * Prevent userspace races by waiting until the network
11495: * device is fully setup before sending notifications.
11496: */
11497: if (!(dev->rtnl_link_ops && dev->rtnl_link_initializing))
11498: rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U, GFP_KERNEL, 0, NULL);
11499:
11500: out:
11501: return ret;
11502:
11503: err_uninit_notify:
11504: call_netdevice_notifiers(NETDEV_PRE_UNINIT, dev);
11505: err_ifindex_release:
11506: dev_index_release(net, dev->ifindex);
11507: err_free_pcpu:
11508: netdev_do_free_pcpu_stats(dev);
11509: err_uninit:
11510: if (dev->netdev_ops->ndo_uninit)
11511: dev->netdev_ops->ndo_uninit(dev);
11512: if (dev->priv_destructor)
11513: dev->priv_destructor(dev);
11514: err_free_name:
11515: netdev_name_node_free(dev->name_node);
11516: goto out;
11517: }
SourceFile:net/core/dev.c]
|
| 95/2 |
2026/07/30 09:18 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 83580
output: 38
thoughts: 4251
Thoughts: **Analyzing the Bug in Netdevice Registration**
Okay, here's the breakdown of my thinking on this issue. My initial observation is that if `register_netdevice` fails, it returns a negative error code, but `dev_get_valid_name` returning an error code (-EEXIST or similar) also triggers an issue where the `dev`'s `reg_state` is `NETREG_UNINITIALIZED`. The subsequent `free_netdev` and `pn_bind` sequence, followed by another call to `gphonet_register_netdev` with a freed and potentially reallocated `dev`, accessing `dev->reg_state`, leads to a `BUG!`. The straightforward way to trigger this is by creating a device with the same name, thus making `dev_get_valid_name` fail. Creating `upnlink0` via `ip link add upnlink0 type dummy` should be enough. However, `%d` in the name template means it will find the next available number like `upnlink1`, `upnlink32`, etc. so I will not get an easy failure.
I move onto other possible failures. `netdev_do_alloc_pcpu_stats(dev)` might fail due to OOM, but syzbot rarely hits OOM reliably. `dev_index_reserve` could fail if `ifindex` is already taken, but since ifindex = 0 at the start this is impossible. Then I considered the failure of `call_netdevice_notifiers(NETDEV_POST_INIT, dev)`. If this fails, then `notifier_to_errno(ret)` converts the notifier return to an error. If this happens, it goes to `err_ifindex_release` and then, the familiar sequence of `free_netdev`, re-binding, and the `BUG`. Crucially, if this *does* fail, `dev->needs_free_netdev` is set to `false`, and this is why the problem is not a simple use-after-free detectable by KASAN.
I then tried to understand why KASAN wasn't detecting the use-after-free, if `dev` was actually freed. If it *wasn't* freed, and the bug was a simple case, I'd expect KASAN to catch the `dev->nd_net` access in `dev_net(dev)` or later. The fact that the report is a BUG and not a KASAN use-after-free implies that the memory *isn't* being freed, and thus is being re-used.
The sequence of the unregister queue and `netdev_run_todo` gets me to a key observation: if a notifier fails, `free_netdev` *will* be called via `netdev_run_todo`, and the netdev *will* be freed *unless* a userland process has a reference open to the netdev's sysfs entry. In this case `put_device(&dev->dev)` will not bring the reference to zero, so `dev` will not be freed.
I then explored how a userspace process could interfere with this sequence and make `register_netdevice` BUG. I thought about how syzkaller can quickly open sysfs files, but that seems like a race condition. Then I went to the idea that the netdev *isn't* freed, and thus *is* re-used after the call to `pn_bind`. Now I can see that `dev->reg_state` is `NETREG_RELEASED` (4) due to the failed notifier, so the `BUG_ON` fires.
So this all makes sense: No KASAN use-after-free, a sysfs entry remains, a call is made again and now we hit the condition in `BUG_ON`. But how to make sure that a call *can* succeed but then fails such that this scenario can happen? What if we have a gadget that binds, but *then* has the bind fail? I then thought about how unbinding would cause netdev unregistration. But a failed gadget bind causes the sysfs entries to be *purged* such that no external process is holding a reference. This can not be the case. I would still have the memory being freed in the correct circumstances. So I start to think that maybe unbinding is happening without being triggered. So I begin thinking of how the networking namespace would interfere with the reference count such that the reference count *is* 0 and the memory is freed. But even still, the function call to `register_netdevice` could *not* trigger the `BUG_ON` condition. The memory would be freed completely.
Now I am back to how can `register_netdevice`
|
| 96/2 |
2026/07/30 09:18 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:alloc_netdev_mqs]
Results: map[SourceCode:12018: /**
12019: * alloc_netdev_mqs - allocate network device
12020: * @sizeof_priv: size of private data to allocate space for
12021: * @name: device name format string
12022: * @name_assign_type: origin of device name
12023: * @setup: callback to initialize device
12024: * @txqs: the number of TX subqueues to allocate
12025: * @rxqs: the number of RX subqueues to allocate
12026: *
12027: * Allocates a struct net_device with private data area for driver use
12028: * and performs basic initialization. Also allocates subqueue structs
12029: * for each queue on the device.
12030: */
12031: struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
12032: unsigned char name_assign_type,
12033: void (*setup)(struct net_device *),
12034: unsigned int txqs, unsigned int rxqs)
12035: {
12036: struct net_device *dev;
12037: size_t napi_config_sz;
12038: unsigned int maxqs;
12039:
12040: BUG_ON(strlen(name) >= sizeof(dev->name));
12041:
12042: if (txqs < 1) {
12043: pr_err("alloc_netdev: Unable to allocate device with zero queues\n");
12044: return NULL;
12045: }
12046:
12047: if (rxqs < 1) {
12048: pr_err("alloc_netdev: Unable to allocate device with zero RX queues\n");
12049: return NULL;
12050: }
12051:
12052: maxqs = max(txqs, rxqs);
12053:
12054: dev = kvzalloc_flex(*dev, priv, sizeof_priv,
12055: GFP_KERNEL_ACCOUNT | __GFP_RETRY_MAYFAIL);
12056: if (!dev)
12057: return NULL;
12058:
12059: dev->priv_len = sizeof_priv;
12060:
12061: ref_tracker_dir_init(&dev->refcnt_tracker, 128, "netdev");
12062: #ifdef CONFIG_PCPU_DEV_REFCNT
12063: dev->pcpu_refcnt = alloc_percpu(int);
12064: if (!dev->pcpu_refcnt)
12065: goto free_dev;
12066: __dev_hold(dev);
12067: #else
12068: refcount_set(&dev->dev_refcnt, 1);
12069: #endif
12070:
12071: if (dev_addr_init(dev))
12072: goto free_pcpu;
12073:
12074: dev_mc_init(dev);
12075: dev_uc_init(dev);
12076:
12077: dev_net_set(dev, &init_net);
12078:
12079: dev->gso_max_size = GSO_LEGACY_MAX_SIZE;
12080: dev->xdp_zc_max_segs = 1;
12081: dev->gso_max_segs = GSO_MAX_SEGS;
12082: dev->gro_max_size = GRO_LEGACY_MAX_SIZE;
12083: dev->gso_ipv4_max_size = GSO_LEGACY_MAX_SIZE;
12084: dev->gro_ipv4_max_size = GRO_LEGACY_MAX_SIZE;
12085: dev->tso_max_size = TSO_LEGACY_MAX_SIZE;
12086: dev->tso_max_segs = TSO_MAX_SEGS;
12087: dev->upper_level = 1;
12088: dev->lower_level = 1;
12089: #ifdef CONFIG_LOCKDEP
12090: dev->nested_level = 0;
12091: INIT_LIST_HEAD(&dev->unlink_list);
12092: #endif
12093:
12094: INIT_LIST_HEAD(&dev->napi_list);
12095: INIT_LIST_HEAD(&dev->unreg_list);
12096: INIT_LIST_HEAD(&dev->close_list);
12097: INIT_LIST_HEAD(&dev->link_watch_list);
12098: INIT_LIST_HEAD(&dev->adj_list.upper);
12099: INIT_LIST_HEAD(&dev->adj_list.lower);
12100: INIT_LIST_HEAD(&dev->ptype_all);
12101: INIT_LIST_HEAD(&dev->ptype_specific);
12102: INIT_LIST_HEAD(&dev->net_notifier_list);
12103: INIT_LIST_HEAD(&dev->work_node);
12104: #ifdef CONFIG_NET_SCHED
12105: hash_init(dev->qdisc_hash);
12106: #endif
12107:
12108: mutex_init(&dev->lock);
12109: netif_rx_mode_init(dev);
12110:
12111: dev->priv_flags = IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM;
12112: setup(dev);
12113:
12114: if (!dev->tx_queue_len) {
12115: dev->priv_flags |= IFF_NO_QUEUE;
12116: dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;
12117: }
12118:
12119: dev->num_tx_queues = txqs;
12120: dev->real_num_tx_queues = txqs;
12121: if (netif_alloc_netdev_queues(dev))
12122: goto free_all;
12123:
12124: dev->num_rx_queues = rxqs;
12125: dev->real_num_rx_queues = rxqs;
12126: if (netif_alloc_rx_queues(dev))
12127: goto free_all;
12128: dev->ethtool = kzalloc_obj(*dev->ethtool, GFP_KERNEL_ACCOUNT);
12129: if (!dev->ethtool)
12130: goto free_all;
12131:
12132: dev->cfg = kzalloc_obj(*dev->cfg, GFP_KERNEL_ACCOUNT);
12133: if (!dev->cfg)
12134: goto free_all;
12135: dev->cfg_pending = dev->cfg;
12136:
12137: dev->num_napi_configs = maxqs;
12138: napi_config_sz = array_size(maxqs, sizeof(*dev->napi_config));
12139: dev->napi_config = kvzalloc(napi_config_sz, GFP_KERNEL_ACCOUNT);
12140: if (!dev->napi_config)
12141: goto free_all;
12142:
12143: strscpy(dev->name, name);
12144: dev->name_assign_type = name_assign_type;
12145: dev->group = INIT_NETDEV_GROUP;
12146: if (!dev->ethtool_ops)
12147: dev->ethtool_ops = &default_ethtool_ops;
12148:
12149: nf_hook_netdev_init(dev);
12150:
12151: return dev;
12152:
12153: free_all:
12154: free_netdev(dev);
12155: return NULL;
12156:
12157: free_pcpu:
12158: #ifdef CONFIG_PCPU_DEV_REFCNT
12159: free_percpu(dev->pcpu_refcnt);
12160: free_dev:
12161: #endif
12162: kvfree(dev);
12163: return NULL;
12164: }
SourceFile:net/core/dev.c]
|
| 97/2 |
2026/07/30 09:18 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 91384
output: 37
thoughts: 602
Thoughts: **Analyzing Network Device Registration and Unregistration**
Okay, so I'm trying to figure out the possible states and transitions of a network device's registration, specifically how `dev->reg_state` gets changed. Right now, `kvzalloc_flex` initialized the `dev` struct, so `dev->reg_state` starts at 0, which is `NETREG_UNINITIALIZED`. I know for sure that `dev->reg_state` *won't* be 0 if `register_netdevice` was called, failed *after* `netdev_register_kobject`, or if it succeeded and was then unregistered, or if the memory was freed and reallocated. I'm focusing on the edge cases.
I was originally concerned that `gphonet_register_netdev` might return an error *after* a successful `register_netdevice` call, which would cause an inconsistent state, but that's not possible. The return value `status` from `gphonet_register_netdev` is the return of `register_netdev`. `register_netdev` returns 0 on success, so `gphonet_register_netdev` cannot return an error if `register_netdevice` succeeds. I can rule that out.
Now, I'm thinking about the scenario where `register_netdev` fails and `free_netdev` is called. The original assumption was that the `dev` would not be freed. But, wait. Looking at the code for `register_netdevice` again, if `call_netdevice_notifiers` fails, it sets `dev->needs_free_netdev = false`. `register_netdevice` then returns an error. It appears that `gphonet_register_netdev` will then call `free_netdev(dev)`. And, the code of `free_netdev` will see `dev->reg_state == NETREG_UNREGISTERING`. It sets `dev->needs_free_netdev = true`, and *then* returns. Okay, so, `netdev_run_todo` gets called later, and it *will* see `dev->needs_free_netdev == true`! So it *will* call `free_netdev(dev)` and `dev` *will* be freed! This is the most complex path, but it's now clear.
Hold on! There's one more situation: if `call_netdevice_notifiers` fails, and `unregister_netdevice_queue` is called. That call, `unregister_netdevice_queue`, does *not* set `dev->reg_state = NETREG_UNREGISTERING`? That's suspicious. I need to check the code for `unregister_netdevice_queue` to confirm this.
|
| 98/2 |
2026/07/30 09:19 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:unregister_netdevice_queue]
Results: map[SourceCode:12309: void unregister_netdevice_queue(struct net_device *dev, struct list_head *head)
12310: {
12311: ASSERT_RTNL();
12312:
12313: if (head) {
12314: list_move_tail(&dev->unreg_list, head);
12315: } else {
12316: LIST_HEAD(single);
12317:
12318: list_add(&dev->unreg_list, &single);
12319: unregister_netdevice_many(&single);
12320: }
12321: }
SourceFile:net/core/dev.c]
|
| 99/2 |
2026/07/30 09:19 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 92844
output: 37
thoughts: 10
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Unregistering Many Network Devices: The Low-Level Details**
So, the code's calling `unregister_netdevice_many`. Right, that's the function for handling the removal of *multiple* network devices in one go. That immediately tells me this isn't a simple "take down one interface" operation. This points towards something more complex, perhaps:
* **Bulk Removal:** It could be a shutdown sequence for a device with multiple virtual interfaces, or maybe during a module unload where a whole group of related devices needs to be unregistered atomically. The atomicity is important, particularly in a multithreaded environment.
* **Error Handling:** It implies a need for robust error handling. If one device fails to unregister, you have to decide whether to abort the entire operation and leave things in a consistent (or least-inconsistent) state, or to continue and attempt to clean up as much as possible, potentially logging errors for each failed interface. It's likely we need to trace the return codes and determine what to do with the failures, if any.
* **Concurrency Issues:** The function likely has to deal with concurrency considerations. There are race conditions to be aware of: other threads accessing the net devices while they are being unregistered; and the need for locking around data structures or interrupt disabling during this process.
* **Dependency Management:** The function has to handle dependencies between the network devices being unregistered. You can't unregister device A if device B depends on it, for example. The function probably has a mechanism to avoid dependency loops or deadlock situations.
It's going to be essential to examine the call sites of `unregister_netdevice_many` and the surrounding code, and to read the function documentation very closely to fully understand the context. This likely involves managing internal data structures, perhaps lists of devices, reference counts, and ensuring that no other parts of the kernel are still holding references to the devices. Gotta keep an eye out for potential memory leaks, too. The fact it's `many` immediately flags all these considerations in my mind.
|
| 100/2 |
2026/07/30 09:19 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:unregister_netdevice_many]
Results: map[SourceCode:12498: /**
12499: * unregister_netdevice_many - unregister many devices
12500: * @head: list of devices
12501: *
12502: * Note: As most callers use a stack allocated list_head,
12503: * we force a list_del() to make sure stack won't be corrupted later.
12504: */
12505: void unregister_netdevice_many(struct list_head *head)
12506: {
12507: unregister_netdevice_many_notify(head, 0, NULL);
12508: }
SourceFile:net/core/dev.c]
|
| 101/2 |
2026/07/30 09:19 |
llm |
crash-repro-finder |
0m
Model:
Tokens:
input: 93563
output: 39
thoughts: 14
Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own:
**Unpacking `unregister_netdevice_many_notify`**
Alright, time to get my head around `unregister_netdevice_many_notify`. I need to understand what's happening under the hood here. I know the general function of network device unregistration, and I know the `notify` aspect suggests a notification mechanism. But `many` is key, isn't it? It implies that it handles the unregistration of *multiple* network devices simultaneously. So, I need to understand the implications of that parallelism or batch processing approach. Are there any concurrency concerns? Does it introduce any new complexities compared to unregistering devices individually? Is there a performance benefit? I need to look closely at the implementation for any potential race conditions, locking mechanisms, or optimizations it's employing. My focus will be on the notification side and ensuring proper synchronization if multiple devices are unregistering concurrently. Gotta ensure network connectivity is handled gracefully during this process. Let's see how they've handled potential error conditions within a batch operation too.
|
| 102/2 |
2026/07/30 09:19 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/dev.c Name:unregister_netdevice_many_notify]
Results: map[SourceCode:12370: void unregister_netdevice_many_notify(struct list_head *head,
12371: u32 portid, const struct nlmsghdr *nlh)
12372: {
12373: struct net_device *dev, *tmp;
12374: LIST_HEAD(close_head);
12375: int cnt = 0;
12376:
12377: BUG_ON(dev_boot_phase);
12378: ASSERT_RTNL();
12379:
12380: if (list_empty(head))
12381: return;
12382:
12383: list_for_each_entry_safe(dev, tmp, head, unreg_list) {
12384: /* Some devices call without registering
12385: * for initialization unwind. Remove those
12386: * devices and proceed with the remaining.
12387: */
12388: if (dev->reg_state == NETREG_UNINITIALIZED) {
12389: pr_debug("unregister_netdevice: device %s/%p never was registered\n",
12390: dev->name, dev);
12391:
12392: WARN_ON(1);
12393: list_del(&dev->unreg_list);
12394: continue;
12395: }
12396: dev->dismantle = true;
12397: BUG_ON(dev->reg_state != NETREG_REGISTERED);
12398: }
12399:
12400: /* If device is running, close it first. Start with ops locked... */
12401: list_for_each_entry(dev, head, unreg_list) {
12402: if (!(dev->flags & IFF_UP))
12403: continue;
12404: if (netdev_need_ops_lock(dev)) {
12405: list_add_tail(&dev->close_list, &close_head);
12406: netdev_lock(dev);
12407: }
12408: netif_close_many_and_unlock_cond(&close_head);
12409: }
12410: netif_close_many_and_unlock(&close_head);
12411: /* ... now go over the rest. */
12412: list_for_each_entry(dev, head, unreg_list) {
12413: if (!netdev_need_ops_lock(dev))
12414: list_add_tail(&dev->close_list, &close_head);
12415: }
12416: netif_close_many(&close_head, true);
12417:
12418: list_for_each_entry(dev, head, unreg_list) {
12419: /* And unlink it from device chain. */
12420: unlist_netdevice(dev);
12421: netdev_lock(dev);
12422: WRITE_ONCE(dev->reg_state, NETREG_UNREGISTERING);
12423: netdev_unlock(dev);
12424: }
12425: flush_all_backlogs();
12426:
12427: synchronize_net();
12428:
12429: list_for_each_entry(dev, head, unreg_list) {
12430: struct sk_buff *skb = NULL;
12431:
12432: /* Shutdown queueing discipline. */
12433: netdev_lock_ops(dev);
12434: dev_shutdown(dev);
12435: dev_tcx_uninstall(dev);
12436: dev_xdp_uninstall(dev);
12437: dev_memory_provider_uninstall(dev);
12438: netdev_unlock_ops(dev);
12439: bpf_dev_bound_netdev_unregister(dev);
12440:
12441: netdev_offload_xstats_disable_all(dev);
12442:
12443: /* Notify protocols, that we are about to destroy
12444: * this device. They should clean all the things.
12445: */
12446: call_netdevice_notifiers(NETDEV_UNREGISTER, dev);
12447:
12448: if (!(dev->rtnl_link_ops && dev->rtnl_link_initializing))
12449: skb = rtmsg_ifinfo_build_skb(RTM_DELLINK, dev, ~0U, 0,
12450: GFP_KERNEL, NULL, 0,
12451: portid, nlh);
12452:
12453: /*
12454: * Flush the unicast and multicast chains
12455: */
12456: dev_uc_flush(dev);
12457: dev_mc_flush(dev);
12458:
12459:
12460: netdev_rss_contexts_free(dev);
12461:
12462: call_netdevice_notifiers(NETDEV_PRE_UNINIT, dev);
12463:
12464: if (dev->netdev_ops->ndo_uninit)
12465: dev->netdev_ops->ndo_uninit(dev);
12466:
12467: mutex_destroy(&dev->ethtool->rss_lock);
12468:
12469: net_shaper_flush_netdev(dev);
12470:
12471: if (skb)
12472: rtmsg_ifinfo_send(skb, dev, GFP_KERNEL, portid, nlh);
12473:
12474: /* Notifier chain MUST detach us all upper devices. */
12475: WARN_ON(netdev_has_any_upper_dev(dev));
12476: WARN_ON(netdev_has_any_lower_dev(dev));
12477:
12478: /* Remove entries from kobject tree */
12479: netdev_unregister_kobject(dev);
12480: #ifdef CONFIG_XPS
12481: /* Remove XPS queueing entries */
12482: netif_reset_xps_queues_gt(dev, 0);
12483: #endif
12484: }
12485:
12486: synchronize_net();
12487:
12488: list_for_each_entry(dev, head, unreg_list) {
12489: netdev_put(dev, &dev->dev_registered_tracker);
12490: net_set_todo(dev);
12491: cnt++;
12492: }
12493: atomic_add(cnt, &dev_unreg_count);
12494:
12495: list_del(head);
12496: }
SourceFile:net/core/dev.c]
|
| 103/2 |
2026/07/30 09:19 |
llm |
crash-repro-finder |
6m
Model:
Tokens:
input: 95917
output: 73
thoughts: 62913
|
| 104/2 |
2026/07/30 09:25 |
tool |
set-results |
0m
Args: map[ReproSyz:#syz test
r0 = syz_usb_connect(0, 0, 0, 0)
r1 = syz_usb_control_io(r0, 0, 0)
Sandbox:none]
Results: map[ReproSyz:r0 = syz_usb_connect(0x0, 0x0, 0x0, 0x0)
syz_usb_control_io(r0, 0x0, 0x0)
Sandbox:none]
|
| 105/1 |
2026/07/30 09:25 |
action |
generate-repro-opts |
0m
Results: map[ReproOpts:{"threaded":true,"repeat":true,"procs":6,"slowdown":1,"sandbox":"none","sandbox_arg":0,"tun":true,"netdev":true,"resetnet":true,"cgroups":true,"binfmt_misc":true,"close_fds":true,"devlinkpci":true,"nicvf":true,"usb":true,"vhci":true,"wifi":true,"ieee802154":true,"sysctl":true,"swap":true,"tmpdir":true,"segv":true,"callcomments":true}]
|
| 106/1 |
2026/07/30 09:25 |
action |
crash-reproducer |
7m
Error:
Results: map[OtherCrashReports:<nil> ReproducedBugTitle: ReproducedCrashReport: ReproducedFaultInjection:]
|