Skip to content

Improve ethernet handling, add PCAP as a network implementation#545

Draft
nbriggs wants to merge 45 commits into
masterfrom
nhb-pcap-ethernet
Draft

Improve ethernet handling, add PCAP as a network implementation#545
nbriggs wants to merge 45 commits into
masterfrom
nhb-pcap-ethernet

Conversation

@nbriggs

@nbriggs nbriggs commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Quite a bit of cleanup in the ethernet, and adds a raw ethernet implementation via libpcap. Fixing the interrupt handling for ethernet also involved the regular I/O interrupts, and display subsystem I/O signals. There is still a fair bit to be done with regard to multiple instances running on the same machine -- scripts to build pairs of virtual ethernet interfaces.

Adds names for individual interrupt-in-progress bits in the
struct interrupt_state to match the structure defined in Lisp.

Adjusts the base datatype to DLword to make clear the size and
alignment of interrupt bits in the structure.
…vents()

Cleans up include of xwinmandefs.h and sdldefs.h to depend on which display
subsystem is being compiled in to create ldex and ldesdl.

Replaces process_SDLevents and process_Xevents with process_Display_events to
avoid conditional compilation around which process_..._events to call.

Updates commentary in various places to be clearer.

Unrelated change included in xc.c:
   Cleans up usage of booleans extended_frame and need_irq to use
   TRUE/FALSE since they are declared integer not LispPTR and therefore
   should not be using T/NIL.

 Changes to be committed:
	modified:   inc/sdldefs.h
	modified:   inc/xwinmandefs.h
	modified:   src/sdl.c
	modified:   src/xc.c
	modified:   src/xwinman.c
       (see xc.c in 6c80962)
       Cleans up usage of booleans extended_frame and need_irq to use
       TRUE/FALSE since they are declared integer not LispPTR and therefore
       should not be using T/NIL.
	Update description of interrupt routines to include int_io_service()
	Upgrade to POSIX SA_SIGINFO style signature for int_io_service()
	remove unnecessary include malloc.h
	ldeether should be a dummy program if neither USE_DLPI nor USE_NIT are defined
	adjust conditional compilation around device opening so that it does not confuse formatters
…a number of cleanups

which are explained with the individual files modified below:

	* modified:   CMakeLists.txt
	  Adds the new ether_pcap.c file
	  Adds PCAP as an option for the network implementation to use
	  Adjusts the use of MAIKO_ENABLE_ETHERNET so that it is present for all
	      network implementations, and USE_xxx is present for an implementation xxx.
	  Adjust targets to only create ldeether if using SUN_DLPI or SUN_NIT for network

	* modified:   bin/compile-flags
	  Updates noes on MAIKO_ENABLE_ETHERNET usage

	* modified:   bin/linux-x.mk
	* modified:   bin/makefile-darwin.386-x
	* modified:   bin/makefile-darwin.aarch64-x
	* modified:   bin/makefile-darwin.ppc-x
	* modified:   bin/makefile-darwin.x86_64-x
	* modified:   bin/makefile-emscripten.wasm-wasm
	* modified:   bin/makefile-emscripten.wasm_nl-wasm_nl
	* modified:   bin/makefile-freebsd.386-x
	* modified:   bin/makefile-freebsd.aarch64-x
	* modified:   bin/makefile-freebsd.x86_64-x
	* modified:   bin/makefile-init-darwin.386
	* modified:   bin/makefile-init-darwin.aarch64
	* modified:   bin/makefile-init-darwin.x86_64
	* modified:   bin/makefile-init-freebsd.386
	* modified:   bin/makefile-init-freebsd.aarch64
	* modified:   bin/makefile-init-freebsd.x86_64
	* modified:   bin/makefile-init-openbsd.x86_64
	* modified:   bin/makefile-init-sunos5.sparc
	* modified:   bin/makefile-openbsd.x86_64-x
	* modified:   bin/makefile-sunos5.386-x
	* modified:   bin/makefile-sunos5.i386-x
	* modified:   bin/makefile-sunos5.sparc-x
	* modified:   bin/makefile-sunos5.x86_64-x
	  Moves dspif.o from XFILES to DEVICES since it will be used by SDL as well as X11

	* modified:   bin/makefile-header
	* modified:   bin/makefile-tail
	  Adds infrastructure to allow selecting network implementation by calling
	  "makeright ..." with additional NETWORK=xxx
	  Moves dspif.o from XFILES to DEVICES since it will be used by SDL as well as X11

	* modified:   inc/etherdefs.h
	  Moves ether_addr_equal to common code and so into etherdefs.h

	modified:   src/ether_common.c
	  Moves ether_addr_equal to common code
	  Removes ether event counting
	  Adds global for storing ethernet interface name (when necessary)
	  Cleans up use of MAIKO_ENABLE_ETHERNET and USE_xxx defines
	  Adds init_ether() stub for case of no ethernet compiled in, to avoid
	    having conditional compilation in main code.
	  Adds ether_enabled flag to indicate whether it should be enable

	* modified:   src/ether_nethub.c
	  Cleans up use of MAIKO_ENABLE_ETHERNET and USE_xxx defines
	  Fix typo for blen vs. bLen
	  Adds init_ether() so that initialization call is shared between implementations

        * modified:   src/ether_sunos.c
	  Cleans up use of MAIKO_ENABLE_ETHERNET and USE_xxx defines
	  Fix typo in BYTESPER_DLWORD
	  Adjusts return value from get_packet to indicate whether a packet was available (ATOM_T vs NIL)

	* modified:   src/initsout.c
	  Cleans up use of MAIKO_ENABLE_ETHERNET and USE_xxx defines

	* modified:   src/keyevent.c
	  Cleans up use of MAIKO_ENABLE_ETHERNET and USE_xxx defines
	  Changes interrupt handling as not all network implementations have a file descriptor that
	    works with select() calls. Use implementation's check_ether() instead.

	* modified:   src/main.c
	  Cleans up use of MAIKO_ENABLE_ETHERNET and USE_xxx defines
	  Adds processing of -E address%interfacename option for PCAP network implementation
	  Improves error reporting for -E option parsing failures
	  Handles setting of async mode for X display connection to avoid premature interrupts

	* modified:   src/xc.c
	  Updates comments (only) in periodic interrupt handling interval calculation

	* modified:   src/xinit.c
	  Handles setting of async mode for X display connection to avoid premature interrupts -
	    Can't fcntl() to turn on ASYNC mode until SIGIO handler is installed.  See main.c.

	* modified:   src/xrdopt.c
	  Adds processing of -E address%interfacename option for PCAP network implementation as
	    X resource ldex.EtherNet
@nbriggs nbriggs marked this pull request as draft June 1, 2026 17:11

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new raw ethernet implementation using the libpcap library (src/ether_pcap.c) and refactors the build system and event loop to support multiple networking backends. The code review identified several critical and high-severity issues in the new implementation, including stack buffer overflows in ether_send and read_Xoption, improper handling of pcap_next_ex return values, potential NULL pointer dereferences, and undefined behavior when calling FD_SET with an invalid file descriptor. Additionally, improvements were suggested for error handling in pcap_create and removing a redundant pre-activation call to pcap_setfilter.

Comment thread src/ether_pcap.c
Comment on lines +250 to +269
LispPTR ether_send(LispPTR args[])
{
DLword wordCount;
DLword *bufferAddr; /* buffer address pointer(in native address) */
#ifdef BYTESWAP
DLword networkOrderBuffer[750];
#endif
if (ether_fd < 0) return (NIL);
wordCount = LispIntToCInt(args[0]);
bufferAddr = NativeAligned2FromLAddr(args[1]);
#ifdef BYTESWAP
for (int i = 0; i < wordCount; i++) {
networkOrderBuffer[i] = htons(GETBASEWORD(bufferAddr, i));
}
bufferAddr = networkOrderBuffer;
#endif
pcap_inject(pcap_handle, bufferAddr, 2 * wordCount);
ether_out++;
return (ATOM_T);
} /* ether_send */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

There is a critical stack buffer overflow vulnerability in ether_send.

The networkOrderBuffer array is declared with a fixed size of 750 words (1500 bytes):
DLword networkOrderBuffer[750];

However, wordCount is retrieved from Lisp via LispIntToCInt(args[0]) and is not validated before copying up to wordCount elements into networkOrderBuffer:

for (int i = 0; i < wordCount; i++) {
  networkOrderBuffer[i] = htons(GETBASEWORD(bufferAddr, i));
}

If wordCount exceeds 750, this will overflow the stack buffer, leading to memory corruption or arbitrary code execution. We must add a bounds check to ensure wordCount does not exceed 750.

LispPTR ether_send(LispPTR args[])
{
  DLword wordCount;
  DLword *bufferAddr; /* buffer address pointer(in native address) */
#ifdef BYTESWAP
  DLword networkOrderBuffer[750];
#endif
  if (ether_fd < 0) return (NIL);
  wordCount = LispIntToCInt(args[0]);
  if (wordCount > 750) return (NIL);
  bufferAddr = NativeAligned2FromLAddr(args[1]);
#ifdef BYTESWAP
  for (int i = 0; i < wordCount; i++) {
    networkOrderBuffer[i] = htons(GETBASEWORD(bufferAddr, i));
  }
  bufferAddr = networkOrderBuffer;
#endif
  pcap_inject(pcap_handle, bufferAddr, 2 * wordCount);
  ether_out++;
  return (ATOM_T);
} /* ether_send */

Comment thread src/ether_pcap.c Outdated
Comment on lines +121 to +148
static int recvPacket() {
int pcap_rval = 0;
bpf_u_int32 hlen;
const uint16_t *packet = NULL;
struct pcap_pkthdr *header = NULL;

switch (pcap_next_ex(pcap_handle, &header, (void *)&packet)) {
case 0:
return (0);
case PCAP_ERROR:
pcap_perror(pcap_handle, "recvPacket");
return (0);
}
hlen = header->len;
if (hlen > ether_bsize || hlen == 0) return (0);

#ifdef BYTESWAP
for (int i = 0; i < (hlen + 1) / 2; i++) {
GETBASEWORD((DLword *)ether_buf, i) = ntohs(packet[i]);
}
#else
memcpy(ether_buf, packet, hlen);
#endif

ether_bsize = 0;
((DLETHERCSB *)IOPage->dlethernet)->DLFIRSTICB = hlen;
return (hlen);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There are several critical issues in the recvPacket implementation:

  1. Incorrect pcap_next_ex Return Value Handling: pcap_next_ex can return PCAP_ERROR_BREAK (-2) or other non-1 values. The current switch statement only handles 0 and PCAP_ERROR. If it returns -2, the code falls through and attempts to dereference header and packet, which are not initialized/set in those cases, leading to a NULL pointer dereference and crash.
  2. Unaligned Memory Access: Casting packet to const uint16_t * and dereferencing it as packet[i] can cause unaligned memory access (and a SIGBUS crash) on architectures that do not support unaligned accesses (e.g., SPARC, ARM).
  3. Out-of-bounds Read: If hlen is odd, (hlen + 1) / 2 reads one byte past the allocated packet buffer.
  4. Type Safety: Changing packet to const u_char * avoids the need for a (void *)&packet cast in pcap_next_ex.

We should explicitly check if the return value is 1 before proceeding, use const u_char * for the packet pointer, and copy bytes safely.

static int recvPacket() {
  bpf_u_int32 hlen;
  const u_char *packet = NULL;
  struct pcap_pkthdr *header = NULL;

  int res = pcap_next_ex(pcap_handle, &header, &packet);
  if (res != 1) {
    if (res == PCAP_ERROR) {
      pcap_perror(pcap_handle, "recvPacket");
    }
    return (0);
  }
  hlen = header->len;
  if (hlen > ether_bsize || hlen == 0) return (0);

#ifdef BYTESWAP
  for (int i = 0; i < hlen / 2; i++) {
    GETBASEWORD((DLword *)ether_buf, i) = (packet[2 * i] << 8) | packet[2 * i + 1];
  }
  if (hlen % 2) {
    GETBASEWORD((DLword *)ether_buf, hlen / 2) = packet[hlen - 1] << 8;
  }
#else
  memcpy(ether_buf, packet, hlen);
#endif

  ether_bsize = 0;
  ((DLETHERCSB *)IOPage->dlethernet)->DLFIRSTICB = hlen;
  return (hlen);
}

Comment thread src/ether_pcap.c Outdated
Comment on lines +343 to +379
if (strlen(ether_ifname) == 0) {
pcap_if_t *alldevs = NULL;
pcap_if_t *dev = NULL;
pcap_rval = pcap_findalldevs(&alldevs, errbuf);
for (pcap_if_t *d = alldevs; d; d = d->next) {
if ((d->flags & PCAP_IF_UP) && (d->flags & PCAP_IF_RUNNING) && !(d->flags & PCAP_IF_LOOPBACK)) {
dev = d;
break;
}
}
/* did we find one */
if (dev) {
strlcpy(ether_ifname, dev->name, sizeof(ether_ifname));
for (pcap_addr_t *a = dev->addresses; a; a = a->next) {
#if defined(MAIKO_OS_SOLARIS)
/* Solaris defines both AF_LINK and AF_PACKET but neither give us
* the desired result
*/
#elif defined(AF_LINK)
/* this is BSD-like, macOS? */
if (a->addr->sa_family == AF_LINK) {
memcpy(ether_host, LLADDR(((struct sockaddr_dl *)(a->addr))), sizeof(ether_host));
break;
}
#elif defined(AF_PACKET)
/* this is Linux-like */
if (a->addr->sa_family == AF_PACKET) {
memcpy(ether_host, ((struct sockaddr_ll *)(a->addr))->sll_addr, sizeof(ether_host));
break;
}
#else
#warning Neither AF_LINK nor AF_PACKET address families defined
#endif
}
}
pcap_freealldevs(alldevs);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There are two major issues in this block:

  1. Unchecked pcap_findalldevs Return Value: pcap_findalldevs can fail and return -1. If it fails, alldevs is not guaranteed to be valid or NULL. We should check if pcap_rval == 0 before iterating over alldevs and calling pcap_freealldevs.
  2. NULL Pointer Dereference: In the loop iterating over dev->addresses, the code dereferences a->addr without checking if it is NULL (e.g., a->addr->sa_family). According to libpcap documentation, a->addr can be NULL for some interfaces or address families. Dereferencing a NULL pointer here will cause a crash. We must check if (a->addr != NULL) before accessing a->addr->sa_family.
  /* select an ethernet interface if one was not provided */
  if (strlen(ether_ifname) == 0) {
    pcap_if_t *alldevs = NULL;
    pcap_if_t *dev = NULL;
    pcap_rval = pcap_findalldevs(&alldevs, errbuf);
    if (pcap_rval == 0) {
      for (pcap_if_t *d = alldevs; d; d = d->next) {
        if ((d->flags & PCAP_IF_UP) && (d->flags & PCAP_IF_RUNNING) && !(d->flags & PCAP_IF_LOOPBACK)) {
          dev = d;
          break;
        }
      }
      /* did we find one */
      if (dev) {
        strlcpy(ether_ifname, dev->name, sizeof(ether_ifname));
        for (pcap_addr_t *a = dev->addresses; a; a = a->next) {
          if (a->addr == NULL) continue;
#if defined(MAIKO_OS_SOLARIS)        
          /* Solaris defines both AF_LINK and AF_PACKET but neither give us
           * the desired result
           */
#elif defined(AF_LINK)
          /* this is BSD-like, macOS? */
          if (a->addr->sa_family == AF_LINK) {
            memcpy(ether_host, LLADDR(((struct sockaddr_dl *)(a->addr))), sizeof(ether_host));
            break;
          }
#elif defined(AF_PACKET)
          /* this is Linux-like */
          if (a->addr->sa_family == AF_PACKET) {
            memcpy(ether_host, ((struct sockaddr_ll *)(a->addr))->sll_addr, sizeof(ether_host));
            break;
          }
#else
#warning Neither AF_LINK nor AF_PACKET address families defined
#endif
        }
      }
      pcap_freealldevs(alldevs);
    } else {
      fprintf(stderr, "pcap_findalldevs failed: %s\n", errbuf);
    }
  }

Comment thread src/ether_pcap.c
Comment on lines +408 to +410
pcap_rval = pcap_setnonblock(pcap_handle, 1, errbuf);
ether_fd = pcap_get_selectable_fd(pcap_handle);
FD_SET(ether_fd, &LispReadFds);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If pcap_get_selectable_fd fails or is not supported on the platform, it returns -1. Calling FD_SET(-1, &LispReadFds) results in undefined behavior (out-of-bounds write to the fd_set bitmask), which can corrupt memory and cause a crash. We must check if ether_fd is non-negative before calling FD_SET.

Suggested change
pcap_rval = pcap_setnonblock(pcap_handle, 1, errbuf);
ether_fd = pcap_get_selectable_fd(pcap_handle);
FD_SET(ether_fd, &LispReadFds);
pcap_rval = pcap_setnonblock(pcap_handle, 1, errbuf);
ether_fd = pcap_get_selectable_fd(pcap_handle);
if (ether_fd < 0) {
fprintf(stderr, "pcap_get_selectable_fd failed\n");
pcap_close(pcap_handle);
return;
}
FD_SET(ether_fd, &LispReadFds);

Comment thread src/xrdopt.c Outdated
Comment on lines +355 to +360
#if defined(MAIKO_ENABLE_ETHERNET) && defined(USE_PCAP)
if (XrmGetResource(rDB, "ldex.EtherNet", "Ldex.EtherNet", str_type, &value) == True) {
int b0, b1, b2, b3, b4, b5, fields;
char ifname[32];
(void)strncpy(tmp, value.addr, value.size);
fields = sscanf(tmp, "%x:%x:%x:%x:%x:%x%%%s", &b0, &b1, &b2, &b3, &b4, &b5, ifname);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There is a stack buffer overflow vulnerability in read_Xoption when parsing the ldex.EtherNet resource. The ifname buffer is declared with a size of 32 bytes: char ifname[32];. However, sscanf uses %s without any width limit: fields = sscanf(tmp, "%x:%x:%x:%x:%x:%x%%%s", ...). If the input string contains more than 31 characters after the % character, it will overflow ifname on the stack. We must limit the string width to %31s to prevent stack buffer overflow.

#if defined(MAIKO_ENABLE_ETHERNET) && defined(USE_PCAP)
  if (XrmGetResource(rDB, "ldex.EtherNet", "Ldex.EtherNet", str_type, &value) == True) {
    int b0, b1, b2, b3, b4, b5, fields;
    char ifname[32];
    (void)strncpy(tmp, value.addr, value.size);
    fields = sscanf(tmp, "%x:%x:%x:%x:%x:%x%%%31s",  &b0, &b1, &b2, &b3, &b4, &b5, ifname);

Comment thread src/ether_pcap.c
Comment on lines +380 to +384
pcap_handle = pcap_create(ether_ifname, errbuf);
if (strlen(errbuf) > 0) {
fprintf(stderr, "%s\n", errbuf);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

pcap_create returns NULL on failure and fills errbuf. On success, it returns a valid handle, but errbuf might still contain non-empty warning messages. Checking strlen(errbuf) > 0 to detect failure is incorrect because it will abort initialization even if pcap_create succeeded with a warning. We should check if (pcap_handle == NULL) to detect failure, and only print warnings if pcap_handle is non-NULL but errbuf is not empty.

Suggested change
pcap_handle = pcap_create(ether_ifname, errbuf);
if (strlen(errbuf) > 0) {
fprintf(stderr, "%s\n", errbuf);
return;
}
pcap_handle = pcap_create(ether_ifname, errbuf);
if (pcap_handle == NULL) {
fprintf(stderr, "pcap_create failed: %s\n", errbuf);
return;
}
if (strlen(errbuf) > 0) {
fprintf(stderr, "pcap_create warning: %s\n", errbuf);
}

Comment thread src/ether_pcap.c
Comment on lines +386 to +390
pcap_rval = pcap_set_immediate_mode(pcap_handle, 1);
pcap_rval = pcap_set_buffer_size(pcap_handle, 65536);
pcap_rval = pcap_set_snaplen(pcap_handle, 1518);
pcap_rval = pcap_setfilter(pcap_handle, &filter_match_none);
pcap_rval = pcap_activate(pcap_handle);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

pcap_setfilter must only be called on an activated pcap_t handle. Calling it before pcap_activate (line 389) will fail and return PCAP_ERROR_NOT_ACTIVATED. Since pcap_setfilter is called again with the correct filter after activation (line 406), the pre-activation call is both invalid and redundant and should be removed.

Suggested change
pcap_rval = pcap_set_immediate_mode(pcap_handle, 1);
pcap_rval = pcap_set_buffer_size(pcap_handle, 65536);
pcap_rval = pcap_set_snaplen(pcap_handle, 1518);
pcap_rval = pcap_setfilter(pcap_handle, &filter_match_none);
pcap_rval = pcap_activate(pcap_handle);
/* set up properties on the pcap_handle and then activate it */
pcap_rval = pcap_set_immediate_mode(pcap_handle, 1);
pcap_rval = pcap_set_buffer_size(pcap_handle, 65536);
pcap_rval = pcap_set_snaplen(pcap_handle, 1518);
pcap_rval = pcap_activate(pcap_handle);

@pamoroso

pamoroso commented Jun 1, 2026

Copy link
Copy Markdown
Member

On Linux Mint 22.1 Cinnamon it builds and runs fine when networking is not active.

nbriggs added 4 commits June 1, 2026 12:27
	  * pcap_next_ex() does not guarantee even byte alignment on returned packets
	  * pcap_next_ex() does not guarantee even byte length of packet
	  * handle errors from pcap_findalldevs()
	  * avoid NULL pointer dereference if device has no associated address
	  * handle potential errors from pcap_setfilter, pcap_setnonblock, pcap_get_selectable_fd
	  * report errors and warnings from pcap_create()
	  * reformat error messages slightly
	  * move initial packet filter setting to after pcap activation, as required
	    by the pcap API.
@nbriggs

nbriggs commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new raw Ethernet networking implementation using the libpcap library, refactors display event processing, and updates interrupt state and signal handling. The code review identified several critical issues that need to be addressed, including potential pointer corruption in src/xc.c when setting PENDINGINTERRUPT68k, packet truncation on little-endian systems due to a low MAXETHERPACKETWORDS limit, and an out-of-bounds read vulnerability in src/xrdopt.c from unsafe strncpy usage. Additionally, there is a type mismatch for ether_ifname, fragile handling of pcap_next_ex returns, a potential crash if no suitable network interface is found, and a buffer overflow risk if the selectable file descriptor exceeds FD_SETSIZE.

Comment thread src/xc.c Outdated
*PENDINGINTERRUPT68k |= extended_frame;
Irq_Stk_Check = (UNSIGNED)EndSTKP - STK_MIN(FuncObj);
Irq_Stk_End = (UNSIGNED)EndSTKP;
*PENDINGINTERRUPT68k |= extended_frame ? ATOM_T : 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

PENDINGINTERRUPT68k is a pointer to a LispPTR (which represents a Lisp object reference). Bitwise ORing ATOM_T into it (*PENDINGINTERRUPT68k |= extended_frame ? ATOM_T : 0;) will corrupt the pointer if it was already non-zero, leading to memory corruption or crashes. It should be set directly using an if statement.

      if (extended_frame) {
        *PENDINGINTERRUPT68k = ATOM_T;
      }

Comment thread src/ether_pcap.c
**********************************************************************/

/* normally an XNS packet would be no bigger than 576 bytes */
#define MAXETHERPACKETWORDS 300

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

MAXETHERPACKETWORDS is defined as 300 (600 bytes). On little-endian (BYTESWAP) systems, any Ethernet packet larger than 600 bytes will be silently truncated to 600 bytes because of the cap if (wordCount > MAXETHERPACKETWORDS) wordCount = MAXETHERPACKETWORDS;. Since standard Ethernet MTU is 1500 bytes (750 words), this will break standard-sized IP or large XNS packets. We should increase MAXETHERPACKETWORDS to at least 1000 to accommodate full-sized Ethernet frames.

Suggested change
#define MAXETHERPACKETWORDS 300
#define MAXETHERPACKETWORDS 1000

Comment thread src/xrdopt.c Outdated
Comment on lines +359 to +360
(void)strncpy(tmp, value.addr, value.size);
fields = sscanf(tmp, "%x:%x:%x:%x:%x:%x%%%31s", &b0, &b1, &b2, &b3, &b4, &b5, ifname);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

strncpy does not guarantee null-termination if value.size is greater than or equal to sizeof(tmp). If value.addr is not null-terminated or value.size is large, sscanf(tmp, ...) will read out of bounds. We should safely copy and explicitly null-terminate tmp.

    size_t len = (value.size < sizeof(tmp)) ? value.size : (sizeof(tmp) - 1);
    memcpy(tmp, value.addr, len);
    tmp[len] = '\0';
    fields = sscanf(tmp, "%x:%x:%x:%x:%x:%x%%%31s",  &b0, &b1, &b2, &b3, &b4, &b5, ifname);

Comment thread src/ether_common.c Outdated
extern int ether_enabled;
extern int ether_fd;
extern u_char ether_host[6];
extern u_char ether_ifname[32];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a type mismatch for ether_ifname. It is declared as extern u_char ether_ifname[32] here, but declared as extern char ether_ifname[32] in other files (e.g., main.c, xrdopt.c, and ether_pcap.c). Since it holds an interface name (a standard C string), it should be declared as char instead of u_char to avoid compiler warnings and potential undefined behavior.

Suggested change
extern u_char ether_ifname[32];
extern char ether_ifname[32];

Comment thread src/ether_pcap.c Outdated
Comment on lines +124 to +134
const uint8_t *packet = NULL;
struct pcap_pkthdr *header = NULL;

switch (pcap_next_ex(pcap_handle, &header, (void *)&packet)) {
case 0:
return (0);
case PCAP_ERROR:
case PCAP_ERROR_BREAK:
pcap_perror(pcap_handle, "recvPacket");
return (0);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The switch statement on pcap_next_ex is fragile. If pcap_next_ex returns any other value than 0, PCAP_ERROR, or PCAP_ERROR_BREAK (for example, if it returns a different negative value or warning), it will fall through to the packet processing logic, which expects a valid packet. It is much safer and cleaner to only proceed if pcap_next_ex returns exactly 1 (success). Additionally, declaring packet as const u_char * avoids the need for a (void *) cast.

  const u_char *packet = NULL;
  struct pcap_pkthdr *header = NULL;

  if (pcap_next_ex(pcap_handle, &header, &packet) != 1) {
    return (0);
  }

Comment thread src/ether_pcap.c
Comment on lines +391 to +392
pcap_freealldevs(alldevs);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If pcap_findalldevs succeeds but no suitable interface is found (i.e., dev remains NULL), ether_ifname remains empty (""). The code then proceeds to call pcap_create("", errbuf), which will fail. It is better to check if ether_ifname is empty after the pcap_findalldevs block and return early with an error message.

    pcap_freealldevs(alldevs);
    if (strlen(ether_ifname) == 0) {
      fprintf(stderr, "No suitable network interface found\n");
      return;
    }
  }

Comment thread src/ether_pcap.c
Comment on lines +437 to +443
ether_fd = pcap_get_selectable_fd(pcap_handle);
if (ether_fd == -1) {
fprintf(stderr, "Couldn't get selectable fd for pcap\n");
pcap_close(pcap_handle);
return;
}
FD_SET(ether_fd, &LispReadFds);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If pcap_get_selectable_fd returns a file descriptor >= FD_SETSIZE (1024), calling FD_SET will cause a buffer overflow on LispReadFds. We should check if (ether_fd >= FD_SETSIZE) and handle it gracefully.

Suggested change
ether_fd = pcap_get_selectable_fd(pcap_handle);
if (ether_fd == -1) {
fprintf(stderr, "Couldn't get selectable fd for pcap\n");
pcap_close(pcap_handle);
return;
}
FD_SET(ether_fd, &LispReadFds);
ether_fd = pcap_get_selectable_fd(pcap_handle);
if (ether_fd == -1 || ether_fd >= FD_SETSIZE) {
fprintf(stderr, "Couldn't get valid selectable fd for pcap\n");
pcap_close(pcap_handle);
ether_fd = -1;
return;
}
FD_SET(ether_fd, &LispReadFds);

@nbriggs

nbriggs commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new PCAP-based raw ethernet implementation for XNS and PUP networking, refactors build configurations across CMake and Makefiles to support multiple network backends, and cleans up interrupt and event processing. Feedback on these changes highlights several critical and moderate issues: an uninitialized fd_set in src/ether_sunos.c that could cause undefined behavior; a hardcoded PCAP filter in src/ether_pcap.c that incorrectly blocks PUP packets; ignored return values for pcap_inject and an unused variable devname in the same file; and the use of uint8_t in inc/etherdefs.h and src/ether_common.c without including <stdint.h>, which could lead to compilation errors.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/ether_sunos.c
Comment on lines +494 to 496
if (ether_fd < 0) return (NIL);

FD_SET(ether_fd, &rfds);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The local fd_set rfds is declared on the stack but never initialized with FD_ZERO(&rfds) before calling FD_SET(ether_fd, &rfds). This leaves rfds containing random stack garbage. When select is subsequently called with rfds, it will monitor arbitrary file descriptors, leading to undefined behavior, potential crashes, or blocking on unrelated descriptors.

  if (ether_fd < 0) return (NIL);

  FD_ZERO(&rfds);
  FD_SET(ether_fd, &rfds);

Comment thread src/ether_pcap.c
Comment thread src/ether_pcap.c Outdated
Comment thread src/ether_pcap.c Outdated

int pcap_rval = 0;
// char *etherdev = getenv("LDEETHERDEV"); /* name of interface we want */
char devname[32] = {0};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The variable devname is declared but never used in init_ether. It should be removed to clean up the code.

Comment thread inc/etherdefs.h Outdated
Comment thread src/ether_common.c Outdated
Comment on lines +111 to +113
int ether_addr_equal(const uint8_t addr1[6], const uint8_t addr2[6])
{
return (0 == memcmp(addr1, addr2, 6));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The definition of ether_addr_equal uses uint8_t, but <stdint.h> is not included in src/ether_common.c. To prevent compilation errors on platforms where uint8_t is not defined, use u_char to match the prototype in inc/etherdefs.h and the rest of the ethernet codebase.

Suggested change
int ether_addr_equal(const uint8_t addr1[6], const uint8_t addr2[6])
{
return (0 == memcmp(addr1, addr2, 6));
int ether_addr_equal(const u_char addr1[6], const u_char addr2[6])
{
return (0 == memcmp(addr1, addr2, 6));

nbriggs added 3 commits June 2, 2026 14:38
  On systems where PCAP can determine all the interface names,
  specifying -E interface-name will search for that interface and use
  the ethernet MAC address from the interface, instead of the user having
  to extract it from ifconfig/ipadm/... to pass it in.

  Minor cleanup to parsing of -E option when giving a full MAC address
@pamoroso

pamoroso commented Jun 3, 2026

Copy link
Copy Markdown
Member

Still nothing to report at commit b50f976.

@nbriggs

nbriggs commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new raw Ethernet networking implementation using the libpcap library (PCAP), refactoring existing network configurations to unify them under a common MAIKO_ENABLE_ETHERNET flag. It also cleans up event processing by introducing process_Display_events and improves string copying safety in src/xrdopt.c. The review feedback recommends removing an unused variable xfd in src/xinit.c, using const u_char * for the packet pointer in src/ether_pcap.c to avoid unnecessary casts, and adding length checks on AF_LINK and AF_PACKET address structures before copying MAC addresses to prevent potential out-of-bounds reads.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/xinit.c Outdated
Comment thread src/ether_pcap.c Outdated
Comment thread src/ether_pcap.c
Comment thread src/ether_pcap.c
@pamoroso

Copy link
Copy Markdown
Member

Still looking good at commit f7284f2.

@nbriggs

nbriggs commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces raw Ethernet access using the libpcap library (PCAP network implementation) alongside refactoring of the build system, interrupt handling, and I/O event processing. It also improves X resource option parsing safety with a new XrmValueCopy helper. The review feedback suggests robustifying the automatic network interface selection in the PCAP implementation, correcting a potential integer division/overflow calculation in the timer interrupt frequency, and clarifying an error message in the X resource parsing code to refer to the correct resource name.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/ether_pcap.c
Comment thread src/xc.c Outdated
Comment thread src/xrdopt.c Outdated
@pamoroso

Copy link
Copy Markdown
Member

Nothing unusual to report at commit c579084.

@pamoroso

Copy link
Copy Markdown
Member

Still nothing unusual at commit b0706d4.

@pamoroso

Copy link
Copy Markdown
Member

Still looking good at commit 9725309.

nbriggs added 2 commits June 23, 2026 11:29
Where the target is a Lisp string, not null terminated, use memcpy()
to better reflect the intent.
Where the target is null terminated, but the source is not, use
snprintf() with calculated string length.
@nbriggs nbriggs force-pushed the nhb-pcap-ethernet branch from 9725309 to 374a41e Compare June 23, 2026 21:30
@pamoroso

Copy link
Copy Markdown
Member

Still looking good at commit f8f4ddf.

@pamoroso

Copy link
Copy Markdown
Member

Nothing unusual to report at 74cea17.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants