From: Mark J. <ma...@fr...> - 2024-04-12 13:44:42
|
I'm quite new to net-snmp, so apologies in advance if this is the wrong place to discuss patches. Currently, when one runs snmpd on FreeBSD, it keeps /dev/mem and /dev/kmem open. This is a result of using kvm_openfiles() to get a kvm descriptor, which is used to implement various MIBs (via kvm_getprocs(), kvm_getswapinfo(), etc.). However, the libkvm interfaces used by net-snmp don't actually require /dev/mem or /dev/kmem - they are all implemented using sysctls. I would like to have snmpd not hold /dev/(k)mem open. I can of course compile using --without-kmem-usage, but that disables the use of libkvm, so all of that code (used to implement the hostres MIB, for instance) would need to be rewritten. However, it turns out that one can ask kvm_openfiles() to not open /dev/mem and /dev/kmem, by passing "/dev/null" as the path. In fact, snmpd already contains code to do this, but it's only done in a fallback path in init_kmem(). I would like to introduce the patch below, which gets compiled when --without-kmem-usage is specified. In this case, snmpd will still use libkvm, but won't open /dev/(k)mem. In my testing so far, this works perfectly. Does anyone have any thoughts on this patch/approach? Would the net-snmp project be willing to accept the patch? Thank you in advance for any feedback or guidance. commit 1c7881da5963508bcbcbd4ac44a60192fec4f7d8 Author: Mark Johnston <ma...@Fr...> Date: Thu Apr 4 16:34:26 2024 -0400 snmpd: Enable the use of libkvm without kmem access on FreeBSD diff --git a/agent/kernel.c b/agent/kernel.c index 285a603a77..164a6a8ca2 100644 --- a/agent/kernel.c +++ b/agent/kernel.c @@ -259,7 +259,37 @@ free_kmem(void) kmem = -1; } } +#elif defined(__FreeBSD__) +kvm_t *kd; + +/** + * Initialize the libkvm descriptor. On FreeBSD we can use most of libkvm + * without requiring /dev/kmem access. Only kvm_nlist() and kvm_read() need + * that, and we don't use them. + * + * @return TRUE upon success; FALSE upon failure. + */ +int +init_kmem(const char *file) +{ + char err[4096]; + kd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, err); + if (!kd) { + snmp_log(LOG_CRIT, "init_kmem: kvm_openfiles failed: %s\n", err); + return FALSE; + } + return TRUE; +} + +void +free_kmem(void) +{ + if (kd != NULL) { + (void)kvm_close(kd); + kd = NULL; + } +} #else int init_kmem(const char *file) |
From: Bart V. A. <bva...@ac...> - 2024-04-12 14:13:57
|
On 4/12/24 06:44, Mark Johnston wrote: > I would like to introduce the patch below, which gets compiled when > --without-kmem-usage is specified. In this case, snmpd will still use > libkvm, but won't open /dev/(k)mem. In my testing so far, this works > perfectly. Does anyone have any thoughts on this patch/approach? Would > the net-snmp project be willing to accept the patch? Thank you in > advance for any feedback or guidance. What privileges are required to call kvm_open() on FreeBSD? Are the same privileges required as for opening /dev/kmem or not? If not, has it been considered to make init_kmem() call kvm_open() on FreeBSD independent of whether or not --without-kmem-usage has been specified? Thanks, Bart. |
From: Mark J. <ma...@fr...> - 2024-04-12 14:29:55
|
On Fri, Apr 12, 2024 at 07:13:41AM -0700, Bart Van Assche wrote: > On 4/12/24 06:44, Mark Johnston wrote: > > I would like to introduce the patch below, which gets compiled when > > --without-kmem-usage is specified. In this case, snmpd will still use > > libkvm, but won't open /dev/(k)mem. In my testing so far, this works > > perfectly. Does anyone have any thoughts on this patch/approach? Would > > the net-snmp project be willing to accept the patch? Thank you in > > advance for any feedback or guidance. > > What privileges are required to call kvm_open() on FreeBSD? Are > the same privileges required as for opening /dev/kmem or not? The only privileges required are those needed to open the file. So, kvm_openfiles(NULL, NULL, NULL, O_RDONLY, err) requires root privileges, because it tries to open /dev/mem. If I change the second parameter to "/dev/null", then any user can call it successfully, and most of the kvm_* functions will still work as expected. > If not, > has it been considered to make init_kmem() call kvm_open() on FreeBSD > independent of whether or not --without-kmem-usage has been specified? Assuming I don't misunderstand, that's effectively what my patch does - it just does so in a separate implementation of init_kmem(). Do you mean that on FreeBSD we should always perform an unprivileged kvm_openfile() call, no matter whether --without-kmem-usage is specified? |
From: Bart V. A. <bva...@ac...> - 2024-04-12 22:52:04
|
On 4/12/24 7:29 AM, Mark Johnston wrote: > Do you mean that on FreeBSD we should always perform an unprivileged > kvm_openfile() call, no matter whether --without-kmem-usage is > specified? Yes, that's what I'm proposing. If someone disagrees, please share your opinion now. Thanks, Bart. |
From: Mark J. <ma...@fr...> - 2024-04-13 14:45:01
|
On Fri, Apr 12, 2024 at 03:51:51PM -0700, Bart Van Assche wrote: > On 4/12/24 7:29 AM, Mark Johnston wrote: > > Do you mean that on FreeBSD we should always perform an unprivileged > > kvm_openfile() call, no matter whether --without-kmem-usage is > > specified? > > Yes, that's what I'm proposing. If someone disagrees, please share your > opinion now. I can't see any reason not to go ahead with that, for what it's worth. I'm confident that none of the existing code in net-snmp needs /dev/kmem access on FreeBSD, and it shouldn't be required by new code. |
From: Mark J. <ma...@fr...> - 2024-04-30 17:54:08
|
On Fri, Apr 12, 2024 at 03:51:51PM -0700, Bart Van Assche wrote: > On 4/12/24 7:29 AM, Mark Johnston wrote: > > Do you mean that on FreeBSD we should always perform an unprivileged > > kvm_openfile() call, no matter whether --without-kmem-usage is > > specified? > > Yes, that's what I'm proposing. If someone disagrees, please share your > opinion now. I spent some more time testing this and hit a small problem with the IPv6-TCP MIB. The implementation doesn't compile on FreeBSD (it references a non-existent field in struct inpcb) and so is disabled in the FreeBSD ports tree, so I didn't catch it earlier when testing a patch to avoid using /dev/kmem. The implementation of var_tcp6() and var_udp6() used on FreeBSD isn't quite right; it uses the wrong structures to access the output of the net.inet.{tcp,udp}.pcblist sysctls, and var_tcp6() tries to use kmem access to get the TCP state of the matched connection, which at least on FreeBSD isn't necessary (net.inet.tcp.pcblist exports that field directly via struct xtcpcb). I wrote the patch below to fix this and manually verified the MIB values on a test system. commit f131c5b7dd599cf6c3a15b372075d299262cbfee Author: Mark Johnston <ma...@Fr...> Date: Tue Apr 30 10:23:25 2024 -0400 Fix var_udp6 and var_tcp6 on FreeBSD diff --git a/agent/mibgroup/mibII/ipv6.c b/agent/mibgroup/mibII/ipv6.c index f44568c9fa..1ec05efce2 100644 --- a/agent/mibgroup/mibII/ipv6.c +++ b/agent/mibgroup/mibII/ipv6.c @@ -5,9 +5,6 @@ #include <net-snmp/net-snmp-config.h> #include <net-snmp/net-snmp-features.h> -/* For FreeBSD */ -#define _WANT_INPCB 1 -#define _WANT_TCPCB 1 #include <sys/types.h> #include <sys/socket.h> #ifdef HAVE_SYS_IOCTL_H @@ -1513,8 +1510,10 @@ var_udp6(register struct variable * vp, int result; int i, j; caddr_t p; -#if defined(openbsd4) || defined(freebsd3) +#if defined(openbsd4) static struct inpcb in6pcb, savpcb; +#elif defined(freebsd3) + static struct xinpcb in6pcb, savpcb; #else static struct in6pcb in6pcb, savpcb; #endif @@ -1614,8 +1613,7 @@ var_udp6(register struct variable * vp, DEBUGMSGTL(("mibII/ipv6", "looping: p=%p\n", p)); #if defined(freebsd3) - /* To do: fill in in6pcb properly. */ - memset(&in6pcb, 0, sizeof(in6pcb)); + in6pcb = *(struct xinpcb *) xig; #elif defined(darwin) in6pcb = ((struct xinpcb *) xig)->xi_inp; #else @@ -2108,12 +2106,18 @@ var_tcp6(register struct variable * vp, int result; int i, j; caddr_t p; -#if defined(openbsd4) || defined(freebsd3) +#if defined(openbsd4) static struct inpcb in6pcb, savpcb; +#elif defined(freebsd3) + static struct xinpcb in6pcb; + static int savstate; #else static struct in6pcb in6pcb, savpcb; #endif +#if !defined(freebsd3) struct tcpcb tcpcb; +#endif + int state; int found, savnameLen; #if defined(__NetBSD__) && __NetBSD_Version__ >= 106250000 || defined(openbsd4) /*1.6Y*/ struct inpcbtable tcbtable; @@ -2208,8 +2212,7 @@ var_tcp6(register struct variable * vp, DEBUGMSGTL(("mibII/ipv6", "looping: p=%p\n", p)); #if defined(freebsd3) - /* To do: fill in in6pcb properly. */ - memset(&in6pcb, 0, sizeof(in6pcb)); + in6pcb = ((struct xtcpcb *) xig)->xt_inp; #elif defined(dragonfly) in6pcb = xtp->xt_inp; #elif defined(darwin) @@ -2294,7 +2297,11 @@ var_tcp6(register struct variable * vp, #endif result = snmp_oid_compare(name, *length, newname, j); if (exact && (result == 0)) { +#if defined(freebsd3) + savstate = ((struct xtcpcb *) xig)->t_state; +#else memcpy(&savpcb, &in6pcb, sizeof(savpcb)); +#endif savnameLen = j; memcpy(savname, newname, j * sizeof(oid)); found++; @@ -2305,7 +2312,11 @@ var_tcp6(register struct variable * vp, */ if ((savnameLen == 0) || (snmp_oid_compare(savname, savnameLen, newname, j) > 0)) { +#if defined(freebsd3) + savstate = ((struct xtcpcb *) xig)->t_state; +#else memcpy(&savpcb, &in6pcb, sizeof(savpcb)); +#endif savnameLen = j; memcpy(savname, newname, j * sizeof(oid)); found++; @@ -2344,19 +2355,27 @@ var_tcp6(register struct variable * vp, return NULL; *length = savnameLen; memcpy((char *) name, (char *) savname, *length * sizeof(oid)); +#if defined(freebsd3) + state = savstate; +#elif defined(__NetBSD__) && __NetBSD_Version__ >= 999010400 memcpy(&in6pcb, &savpcb, sizeof(savpcb)); -#if defined(__NetBSD__) && __NetBSD_Version__ >= 999010400 if (!NETSNMP_KLOOKUP(in6pcb.in6p_pcb.inp_ppcb, (char *) &tcpcb, sizeof(tcpcb))) { DEBUGMSGTL(("mibII/ipv6", "klookup fail for tcb6.tcpcb at %p\n", in6pcb.in6p_pcb.inp_ppcb)); + found = 0; + return NULL; + } + state = (int)tcpcb.t_state; #else + memcpy(&in6pcb, &savpcb, sizeof(savpcb)); if (!NETSNMP_KLOOKUP(in6pcb.inp_ppcb, (char *) &tcpcb, sizeof(tcpcb))) { DEBUGMSGTL(("mibII/ipv6", "klookup fail for tcb6.tcpcb at %p\n", in6pcb.inp_ppcb)); -#endif found = 0; return NULL; } + state = (int)tcpcb.t_state; +#endif *write_method = 0; *var_len = sizeof(long); /* default to 'long' results */ @@ -2368,7 +2387,7 @@ var_tcp6(register struct variable * vp, DEBUGMSGTL(("mibII/ipv6", "magic=%d\n", vp->magic)); switch (vp->magic) { case IPV6TCPCONNSTATE: - long_return = mapTcpState((int)tcpcb.t_state); + long_return = mapTcpState(state); return (u_char *) & long_return; default: break; |
From: Bart V. A. <bva...@ac...> - 2024-05-06 17:34:34
|
On 4/30/24 10:53 AM, Mark Johnston wrote: > Fix var_udp6 and var_tcp6 on FreeBSD > [ ... ] This patch has been applied on the V5-9-patches and master branches. Thanks for the patch! Bart. |
From: Mark J. <ma...@fr...> - 2024-06-10 20:27:38
|
On Fri, Apr 12, 2024 at 03:51:51PM -0700, Bart Van Assche wrote: > On 4/12/24 7:29 AM, Mark Johnston wrote: > > Do you mean that on FreeBSD we should always perform an unprivileged > > kvm_openfile() call, no matter whether --without-kmem-usage is > > specified? > > Yes, that's what I'm proposing. If someone disagrees, please share your > opinion now. Hi Bart, Were you planning to make a change along these lines? Would it be helpful for me to submit a patch? A few of us have been testing snmpd with my original patch (to tell libkvm not to open /dev/kmem etc.) for a while now with no issues. Thanks, -Mark |
From: Bart V. A. <bva...@ac...> - 2024-06-10 22:51:41
|
On 6/10/24 13:27, Mark Johnston wrote: > Would it be helpful for me to submit a patch? A few of us have been > testing snmpd with my original patch (to tell libkvm not to open > /dev/kmem etc.) for a while now with no issues. A patch definitely would be welcome. Thanks, Bart. |
From: Mark J. <ma...@fr...> - 2024-06-11 14:04:16
|
On Mon, Jun 10, 2024 at 03:51:33PM -0700, Bart Van Assche wrote: > > On 6/10/24 13:27, Mark Johnston wrote: > > Would it be helpful for me to submit a patch? A few of us have been > > testing snmpd with my original patch (to tell libkvm not to open > > /dev/kmem etc.) for a while now with no issues. > A patch definitely would be welcome. The patch below implements your suggestion. That is, init_kmem() gets a libkvm handle without opening /dev/kmem, no matter whether --with-kmem-usage or --without-kmem-usage was specified at compile time. Thinking about this more, users might still want --with-kmem-usage to cause snmpd to open /dev/kmem, in the case where they have custom MIBs implementations which require the use of klookup(). With this patch, it's impossible to use klookup() on FreeBSD. I believe this is fine for the code shipped with net-snmp, but it might break some custom 3rd-party MIBs. I'm not too concerned about this, as the official FreeBSD net-snmp package will be compiled with --without-kmem-usage, but perhaps it still makes sense to leave an escape hatch. commit 304f8cf7f176920cb689d237f612c9a25cd14e84 Author: Mark Johnston <ma...@Fr...> Date: Thu Apr 4 16:34:26 2024 -0400 snmpd: Always open libkvm in "safe mode" on FreeBSD By specifying /dev/null as the path to kvm_openfiles(), we can get a libkvm descriptor which does not hold /dev/kmem open. None of the code shipped with net-snmp needs a /dev/kmem handle. Make this change for both the NETSNMP_NO_KMEM_USAGE and !NETSNMP_NO_KMEM_USAGE cases, per a suggestion from Bart Van Assche <bva...@ac...>. diff --git a/agent/kernel.c b/agent/kernel.c index 9a6d22592c..671e5244fc 100644 --- a/agent/kernel.c +++ b/agent/kernel.c @@ -44,7 +44,7 @@ #include "kernel.h" #include <net-snmp/agent/ds_agent.h> -#if defined(HAVE_KVM_H) && !defined(NETSNMP_NO_KMEM_USAGE) +#if defined(HAVE_KVM_H) && !defined(NETSNMP_NO_KMEM_USAGE) && !defined(__FreeBSD__) kvm_t *kd; /** @@ -130,7 +130,7 @@ free_kmem(void) } } -#elif defined(HAVE_NLIST_H) && !defined(__linux__) && \ +#elif defined(HAVE_NLIST_H) && !defined(__linux__) && !defined(__FreeBSD__) && \ !defined(NETSNMP_NO_KMEM_USAGE) static off_t klseek(off_t); @@ -252,7 +252,48 @@ free_kmem(void) kmem = -1; } } +#elif defined(__FreeBSD__) +kvm_t *kd; + +/** + * Initialize the libkvm descriptor. On FreeBSD we can use most of libkvm + * without requiring /dev/kmem access. Only kvm_nlist() and kvm_read() need + * that, and we don't use them. + * + * @return TRUE upon success; FALSE upon failure. + */ +int +init_kmem(const char *file) +{ + char err[4096]; + + kd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, err); + if (!kd) { + snmp_log(LOG_CRIT, "init_kmem: kvm_openfiles failed: %s\n", err); + return FALSE; + } + return TRUE; +} +/** + * A stub to return failure to any attempt to read kernel memory. Our + * libkvm handle doesn't enable /dev/kmem access. MIB implementations should + * use unprivileged to fetch information about the system. + */ +int +klookup(unsigned long off, void *target, size_t siz) +{ + return 0; +} + +void +free_kmem(void) +{ + if (kd != NULL) { + (void)kvm_close(kd); + kd = NULL; + } +} #else int init_kmem(const char *file) |
From: Bart V. A. <bva...@ac...> - 2024-06-18 20:08:24
|
On 6/11/24 7:04 AM, Mark Johnston wrote: > commit 304f8cf7f176920cb689d237f612c9a25cd14e84 > Author: Mark Johnston <ma...@Fr...> > Date: Thu Apr 4 16:34:26 2024 -0400 > > snmpd: Always open libkvm in "safe mode" on FreeBSD This patch has been applied on the V5-9-patches and master branches. Thanks for the patch! Bart. |