ENOMEM when calling sysctl_handle_int from custom handler
Austin Shafer
amshafer64 at gmail.com
Mon Dec 17 22:34:07 UTC 2018
Hi freebsd-hackers,
I've had an issue with creating a custom sysctl handler that I can't
seem to find an answer for. Using sysctl_handle_int as a handler in
SYSCTL_ADD_PROC works fine, but calling sysctl_handle_int from a custom
sysctl handler results in an error.
(sorry for the long message, I tried to be verbose)
Creating the nodes works fine, but SYSCTL_OUT or sysctl_handle_int
return ENOMEM despite there being plenty of available memory. I based my
approach on other areas of the source where those sysctls work without
error. It seems to only happen to custom sysctls that I add. This
happens on multiple machines, but the output in this email is from the
bhyve VM I test in. Any insight as to where my implementation went wrong
would be extremely helpful.
Full Source: ("make" should compile on a bsd system)
https://github.com/ashaferian/sysctls
echo_modevent ()
{
...
/* working sysctl using default handler sysctl_handle_int */
SYSCTL_ADD_PROC(&ctx, SYSCTL_CHILDREN(poid), OID_AUTO, "default", CTLTYPE_INT|CTLFLAG_RW, &ret, -1, sysctl_handle_int, "I", "working sysctl");
/* broken sysctl using my handler */
SYSCTL_ADD_PROC(&ctx, SYSCTL_CHILDREN(poid), OID_AUTO, "custom",
CTLTYPE_INT|CTLFLAG_RW, &ret, -1, sysctl_handle, "I", "working
sysctl");
}
static int
sysctl_handle(SYSCTL_HANDLER_ARGS)
{
int error;
/* returns ENOMEM */
error = sysctl_handle_int(oidp, arg1, arg2, req);
...
In the code above I use the "sysctl_handle" function to handle requests
to the echo.* sysctls I have created. I create two nodes echo.default
which uses the default handler "sysctl_handle_int", and echo.custom
which uses my "sysctl_handle" function. What confuses me so greatly is
that sysctl_handle is just a wrapper that calls sysctl_handle_int and
passes its arguments without changing anything. There should be no
difference because all I do is call the default handler. echo.default works,
while echo.custom does not. Both functions operate on the same
variables and have pretty much the same SYSCLT_ADD_PROC declaration.
My best guess:
I've tried to dig around in the debugger (using kgdb) but haven't been
able to find anything. The only difference I noticed while tracing was
that the "oldlenp" field in the sysctl_args struct passed in from
userland was 0 when calling my handler and 8 when calling the default
handler. I am not very familiar with the sysctl implementation, but this
affected the valid length in the sysctl_req which ended up returning
ENOMEM from sysctl_old_user. You can watch/confirm this change in sysctl_req
using the following dtrace one-liner. Its unclear why changing the
sysctl handler would affect the arguments passed to it.
---------------------------
dtrace -n '*::sysctl_handle_int:entry /execname == "sysctl" / {
print(*args[3]); }'
// sysctl echo.default
0 27761 sysctl_handle_int:entry struct sysctl_req {
struct thread *td = 0xfffff8000392f580
int lock = 0x1
void *oldptr = 0x7fffffffd69c
size_t oldlen = 0x4
size_t oldidx = 0
int (*)() oldfunc = kernel`sysctl_old_user
void *newptr = 0
size_t newlen = 0
size_t newidx = 0
int (*)() newfunc = kernel`sysctl_new_user
size_t validlen = 0x4
int flags = 0
}
0 27761 sysctl_handle_int:entry struct sysctl_req {
struct thread *td = 0xfffff8000392f580
int lock = 0x1
void *oldptr = 0
size_t oldlen = 0
size_t oldidx = 0
int (*)() oldfunc = kernel`sysctl_old_user
void *newptr = 0
size_t newlen = 0
size_t newidx = 0
int (*)() newfunc = kernel`sysctl_new_user
size_t validlen = 0
int flags = 0
}
0 27761 sysctl_handle_int:entry struct sysctl_req {
struct thread *td = 0xfffff8000392f580
int lock = 0x1
void *oldptr = 0x800668000
size_t oldlen = 0x8
size_t oldidx = 0
int (*)() oldfunc = kernel`sysctl_old_user
void *newptr = 0
size_t newlen = 0
size_t newidx = 0
int (*)() newfunc = kernel`sysctl_new_user
size_t validlen = 0x8
int flags = 0
}
// sysctl echo.custom
Custom Sysctl: Error 0
Custom Sysctl: Error 12
CPU ID FUNCTION:NAME
1 27761 sysctl_handle_int:entry struct sysctl_req {
struct thread *td = 0xfffff8000392f580
int lock = 0x1
void *oldptr = 0x7fffffffd69c
size_t oldlen = 0x4
size_t oldidx = 0
int (*)() oldfunc = kernel`sysctl_old_user
void *newptr = 0
size_t newlen = 0
size_t newidx = 0
int (*)() newfunc = kernel`sysctl_new_user
size_t validlen = 0x4
int flags = 0
}
1 27761 sysctl_handle_int:entry struct sysctl_req {
struct thread *td = 0xfffff8000392f580
int lock = 0x1
void *oldptr = 0
size_t oldlen = 0
size_t oldidx = 0
int (*)() oldfunc = kernel`sysctl_old_user
void *newptr = 0
size_t newlen = 0
size_t newidx = 0
int (*)() newfunc = kernel`sysctl_new_user
size_t validlen = 0
int flags = 0
}
1 27761 sysctl_handle_int:entry struct sysctl_req {
struct thread *td = 0xfffff8000392f580
int lock = 0x1
void *oldptr = 0x800667008
size_t oldlen = 0
size_t oldidx = 0
int (*)() oldfunc = kernel`sysctl_old_user
void *newptr = 0
size_t newlen = 0
size_t newidx = 0
int (*)() newfunc = kernel`sysctl_new_user
size_t validlen = 0
int flags = 0
}
---------------------------
I'm really not sure what I've done wrong so any help is greatly
appreciated. Most of the online resources/books that show sysctl
creation seem to do the same thing I did. If there is tricky or
non-obvious behavior that I have missed it would be helpful to note it
for others trying to learn.
If there is anything else I can provide or do please let me know.
Thank you so much for your time!
Austin Shafer
_________________________________________________________________________
uname -a:
FreeBSD punk-vm 13.0-CURRENT FreeBSD 13.0-CURRENT GENERIC-NODEBUG amd64
* uname doesn't list revision but it should be r342141
top memory usage:
...
Mem: 20M Active, 2672K Inact, 143M Wired, 98M Buf, 1789M Free
More information about the freebsd-hackers
mailing list