Allow secure-netboot

When doing file verification, tftp needs to be able to handle multiple
open files concurrently.
We also need tftp_stat() to provide useful values for st_dev and st_ino.

Allow an architecture to define NETPROTO_DEFAULT.
The default is NET_NFS for backwards compatability.

In net_parse_rootpath() fix parsing of
<scheme>://<ip>[:<port]/<path>
and ensure we return INADDR_NONE unless we successfully
parsed an addr, so we don't end up clobbering rootip obtained
from bootp().

Sponsored by:   Juniper Networks, Inc.
Differential Revision:	https://reviews.freebsd.org/D51187
This commit is contained in:
Simon J. Gerraty 2025-08-20 15:49:53 -07:00
parent b44cc1b479
commit 5bfb3045d2
7 changed files with 140 additions and 55 deletions

View file

@ -66,6 +66,10 @@
#include "dev_net.h"
#include "bootstrap.h"
#ifndef NETPROTO_DEFAULT
# define NETPROTO_DEFAULT NET_NFS
#endif
static char *netdev_name;
static int netdev_sock = -1;
static int netdev_opens;
@ -304,7 +308,7 @@ net_getparams(int sock)
return (EIO);
}
exit:
if ((rootaddr = net_parse_rootpath()) != INADDR_NONE)
if ((rootaddr = net_parse_rootpath()) != htonl(INADDR_NONE))
rootip.s_addr = rootaddr;
DEBUG_PRINTF(1,("%s: proto: %d\n", __func__, netproto));
@ -355,7 +359,7 @@ is_tftp(void)
* Parses the rootpath if present
*
* The rootpath format can be in the form
* <scheme>://ip/path
* <scheme>://ip[:port]/path
* <scheme>:/path
*
* For compatibility with previous behaviour it also accepts as an NFS scheme
@ -370,10 +374,10 @@ is_tftp(void)
uint32_t
net_parse_rootpath(void)
{
n_long addr = htonl(INADDR_NONE);
n_long addr = 0;
size_t i;
char ip[FNAME_SIZE];
char *ptr, *val;
char *ptr, *portp, *val;
netproto = NET_NONE;
@ -388,7 +392,7 @@ net_parse_rootpath(void)
ptr = rootpath;
/* Fallback for compatibility mode */
if (netproto == NET_NONE) {
netproto = NET_NFS;
netproto = NETPROTO_DEFAULT;
(void)strsep(&ptr, ":");
if (ptr != NULL) {
addr = inet_addr(rootpath);
@ -401,16 +405,21 @@ net_parse_rootpath(void)
if (*ptr == '/') {
/* we are in the form <scheme>://, we do expect an ip */
ptr++;
/*
* XXX when http will be there we will need to check for
* a port, but right now we do not need it yet
*/
portp = val = strchr(ptr, ':');
if (val != NULL) {
val++;
rootport = strtol(val, NULL, 10);
}
val = strchr(ptr, '/');
if (val != NULL) {
if (portp == NULL)
portp = val;
snprintf(ip, sizeof(ip), "%.*s",
(int)((uintptr_t)val - (uintptr_t)ptr),
(int)(portp - ptr),
ptr);
addr = inet_addr(ip);
DEBUG_PRINTF(1,("ip=%s addr=%#x\n",
ip, addr));
bcopy(val, rootpath, strlen(val) + 1);
}
} else {
@ -418,6 +427,7 @@ net_parse_rootpath(void)
bcopy(ptr, rootpath, strlen(ptr) + 1);
}
}
if (addr == 0)
addr = htonl(INADDR_NONE);
return (addr);
}

View file

@ -207,6 +207,8 @@ LOADER_INTERP?=${LOADER_DEFAULT_INTERP}
# Make sure we use the machine link we're about to create
CFLAGS+=-I.
.include "${BOOTSRC}/veriexec.mk"
all: ${PROG}
CLEANFILES+= teken_state.h

View file

@ -17,6 +17,7 @@
u_char bcea[6] = BA; /* broadcast ethernet address */
char rootpath[FNAME_SIZE] = "/"; /* root mount path */
int rootport; /* port for rootpath server */
char bootfile[FNAME_SIZE]; /* bootp says to boot this */
char hostname[FNAME_SIZE]; /* our hostname */
int hostnamelen;

View file

@ -781,6 +781,10 @@ The same as
but for
.Xr bzip2 1 Ns -compressed
files.
.It Va pkgfs_fsops
File access from a tar file typically streamed via TFTP.
The order of files in the tar file must match the order they are
to be consumed as rewind is not practical.
.El
.Pp
The array of

View file

@ -75,6 +75,7 @@ enum net_proto {
extern u_char bcea[6];
extern char rootpath[FNAME_SIZE];
extern int rootport;
extern char bootfile[FNAME_SIZE];
extern char hostname[FNAME_SIZE];
extern int hostnamelen;

View file

@ -50,6 +50,10 @@
#include <netinet/in_systm.h>
#include <arpa/tftp.h>
#ifdef LOADER_VERIEXEC
#include <verify_file.h>
#endif
#include <string.h>
#include "stand.h"
@ -85,7 +89,6 @@ struct fs_ops tftp_fsops = {
};
static int tftpport = 2000;
static int is_open = 0;
/*
* The legacy TFTP_BLKSIZE value was SEGSIZE(512).
@ -99,10 +102,14 @@ static int is_open = 0;
* Jumbo frames in the future.
*/
#define TFTP_MAX_BLKSIZE 9008
#define TFTP_TRIES 2
#define TFTP_TRIES 3
struct tftp_handle {
struct iodesc *iodesc;
struct iodesc io;
int id;
ino_t ino;
int port;
int currblock; /* contents of lastdata */
unsigned int islastblock:1; /* flag */
unsigned int tries:4; /* number of read attempts */
@ -178,6 +185,9 @@ tftp_sendack(struct tftp_handle *h, u_short block)
wbuf.t.th_block = htons(block);
wtail += 2;
DEBUG_PRINTF(5,("%s: myport=%hu xid=%lu, block=%hu\n",
__func__, h->iodesc->myport, h->iodesc->xid, block));
sendudp(h->iodesc, &wbuf.t, wtail - (char *)&wbuf.t);
}
@ -191,6 +201,7 @@ recvtftp(struct iodesc *d, void **pkt, void **payload, time_t tleft,
void *ptr = NULL;
ssize_t len;
int tftp_error;
unsigned short block;
errno = 0;
extra = recv_extra;
@ -204,19 +215,22 @@ recvtftp(struct iodesc *d, void **pkt, void **payload, time_t tleft,
}
extra->rtype = ntohs(t->th_opcode);
switch (ntohs(t->th_opcode)) {
block = ntohs(t->th_block);
DEBUG_PRINTF(6,("%s: myport=%hu xid=%lu, block=%hu, opcode=%hu\n",
__func__, d->myport, d->xid, block, extra->rtype));
switch (extra->rtype) {
case DATA: {
int got;
if (htons(t->th_block) < (u_short)d->xid) {
if (block < (u_short)d->xid) {
/*
* Apparently our ACK was missed, re-send.
*/
tftp_sendack(h, htons(t->th_block));
tftp_sendack(h, block);
free(ptr);
return (-1);
}
if (htons(t->th_block) != (u_short)d->xid) {
if (block != (u_short)d->xid) {
/*
* Packet from the future, drop this.
*/
@ -242,9 +256,7 @@ recvtftp(struct iodesc *d, void **pkt, void **payload, time_t tleft,
printf("illegal tftp error %d\n", tftp_error);
errno = EIO;
} else {
#ifdef TFTP_DEBUG
printf("tftp-error %d\n", tftp_error);
#endif
DEBUG_PRINTF(0, ("tftp-error %d\n", tftp_error));
errno = tftperrors[tftp_error];
}
free(ptr);
@ -285,9 +297,7 @@ recvtftp(struct iodesc *d, void **pkt, void **payload, time_t tleft,
return (0);
}
default:
#ifdef TFTP_DEBUG
printf("tftp type %d not handled\n", ntohs(t->th_opcode));
#endif
DEBUG_PRINTF(0, ("tftp type %hu not handled\n", extra->rtype));
free(ptr);
return (-1);
}
@ -344,7 +354,7 @@ tftp_makereq(struct tftp_handle *h)
bcopy("0", wtail, 2);
wtail += 2;
h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff));
h->iodesc->myport = htons(h->port + (getsecs() & 0x3ff));
h->iodesc->destport = htons(IPPORT_TFTP);
h->iodesc->xid = 1; /* expected block */
@ -352,11 +362,15 @@ tftp_makereq(struct tftp_handle *h)
h->islastblock = 0;
h->validsize = 0;
DEBUG_PRINTF(5,("%s: %s: id=%d port=%d myport=%hu xid=1\n",
__func__, h->path, h->id, h->port, ntohs(h->iodesc->myport)));
pkt = NULL;
recv_extra.tftp_handle = h;
res = sendrecv(h->iodesc, &sendudp, &wbuf.t, wtail - (char *)&wbuf.t,
&recvtftp, &pkt, (void **)&t, &recv_extra);
if (res == -1) {
DEBUG_PRINTF(3,("%s: %s: id=%d errno=%d\n",
__func__, h->path, h->id, errno));
free(pkt);
return (errno);
}
@ -411,12 +425,18 @@ tftp_getnextblock(struct tftp_handle *h)
h->iodesc->xid = h->currblock + 1; /* expected block */
DEBUG_PRINTF(5,("%s: %s: id=%d port=%d myport=%hu xid=%lu\n",
__func__, h->path, h->id, h->port,
ntohs(h->iodesc->myport), h->iodesc->xid));
pkt = NULL;
recv_extra.tftp_handle = h;
res = sendrecv(h->iodesc, &sendudp, &wbuf.t, wtail - (char *)&wbuf.t,
&recvtftp, &pkt, (void **)&t, &recv_extra);
if (res == -1) { /* 0 is OK! */
DEBUG_PRINTF(3,("%s: %s: id=%d errno=%d\n",
__func__, h->path, h->id, errno));
free(pkt);
return (errno);
}
@ -429,21 +449,32 @@ tftp_getnextblock(struct tftp_handle *h)
if (res < h->tftp_blksize)
h->islastblock = 1; /* EOF */
if (h->islastblock == 1) {
DEBUG_PRINTF(5,("%s: %s: id=%d res=%d blksz=%d last=%d\n",
__func__, h->path, h->id, res, h->tftp_blksize, h->islastblock));
if (h->islastblock) {
/* Send an ACK for the last block */
wbuf.t.th_block = htons((u_short)h->currblock);
sendudp(h->iodesc, &wbuf.t, wtail - (char *)&wbuf.t);
tftp_sendack(h, h->currblock);
}
return (0);
}
/*
* If doing verification we need to handle multiple
* files at the same time.
*/
#define TOPEN_MAX 8
static struct tftp_handle *handles[TOPEN_MAX];
static int
tftp_open(const char *path, struct open_file *f)
{
struct devdesc *dev;
struct tftp_handle *tftpfile;
struct iodesc *io;
static int lx = 0;
int i, x;
int res;
size_t pathsize;
const char *extraslash;
@ -451,24 +482,39 @@ tftp_open(const char *path, struct open_file *f)
if (netproto != NET_TFTP)
return (EINVAL);
if (f->f_dev->dv_type != DEVT_NET)
if (f->f_dev == NULL || f->f_dev->dv_type != DEVT_NET)
return (EINVAL);
if (is_open)
tftpfile = NULL;
for (x = lx + 1, i = 0; i < TOPEN_MAX; i++, x++) {
x %= TOPEN_MAX;
if (handles[x] == NULL) {
handles[x] = tftpfile = calloc(1, sizeof(*tftpfile));
if (tftpfile == NULL)
return (ENOMEM);
/* id allows us to clear the slot on close */
tftpfile->id = lx = x;
/* port ensures a different session with server */
tftpfile->port = (tftpport + (x * tftpport)) & 0xffff;
DEBUG_PRINTF(1, ("%s(%s) id=%d port=%d\n",
__func__, path, tftpfile->id, tftpfile->port));
break;
}
}
if (tftpfile == NULL) {
DEBUG_PRINTF(1, ("%s: EBUSY\n", __func__));
return (EBUSY);
tftpfile = calloc(1, sizeof(*tftpfile));
if (!tftpfile)
return (ENOMEM);
}
tftpfile->tftp_blksize = TFTP_REQUESTED_BLKSIZE;
dev = f->f_devdata;
tftpfile->iodesc = io = socktodesc(*(int *)(dev->d_opendata));
io = socktodesc(*(int *)(dev->d_opendata));
if (io == NULL) {
free(tftpfile);
return (EINVAL);
}
memcpy(&tftpfile->io, io, sizeof(tftpfile->io));
io = tftpfile->iodesc = &tftpfile->io;
io->destip = rootip;
tftpfile->off = 0;
pathsize = (strlen(rootpath) + 1 + strlen(path) + 1) * sizeof(char);
@ -481,8 +527,11 @@ tftp_open(const char *path, struct open_file *f)
extraslash = "";
else
extraslash = "/";
res = snprintf(tftpfile->path, pathsize, "%s%s%s",
rootpath, extraslash, path);
if (rootpath[0] == '/' && rootpath[1] == '\0' && path[0] == '/')
res = strlcpy(tftpfile->path, path, pathsize);
else
res = snprintf(tftpfile->path, pathsize, "%s%s%s",
rootpath, extraslash, path);
if (res < 0 || res > pathsize) {
free(tftpfile->path);
free(tftpfile);
@ -492,13 +541,13 @@ tftp_open(const char *path, struct open_file *f)
res = tftp_makereq(tftpfile);
if (res) {
handles[tftpfile->id] = NULL;
free(tftpfile->path);
free(tftpfile->pkt);
free(tftpfile);
return (res);
}
f->f_fsdata = tftpfile;
is_open = 1;
return (0);
}
@ -548,9 +597,7 @@ tftp_read(struct open_file *f, void *addr, size_t size,
rc = tftp_getnextblock(tftpfile);
if (rc) { /* no answer */
#ifdef TFTP_DEBUG
printf("tftp: read error\n");
#endif
DEBUG_PRINTF(0, ("tftp: read error\n"));
if (tftpfile->tries > TFTP_TRIES) {
return (rc);
} else {
@ -569,10 +616,8 @@ tftp_read(struct open_file *f, void *addr, size_t size,
inbuffer = tftpfile->validsize - offinblock;
if (inbuffer < 0) {
#ifdef TFTP_DEBUG
printf("tftp: invalid offset %d\n",
tftpfile->off);
#endif
DEBUG_PRINTF(0, ("tftp: invalid offset %d\n",
tftpfile->off));
return (EINVAL);
}
count = (size < inbuffer ? size : inbuffer);
@ -587,15 +632,15 @@ tftp_read(struct open_file *f, void *addr, size_t size,
if ((tftpfile->islastblock) && (count == inbuffer))
break; /* EOF */
} else {
#ifdef TFTP_DEBUG
printf("tftp: block %d not found\n", needblock);
#endif
DEBUG_PRINTF(0, ("tftp: block %d not found\n", needblock));
return (EINVAL);
}
}
out:
DEBUG_PRINTF(4, ("%s(%s) res=%ld\n", __func__, tftpfile->path,
(tftpfile->tftp_tsize - tftpfile->off)));
if (resid != NULL)
*resid = res;
return (rc);
@ -611,15 +656,18 @@ tftp_close(struct open_file *f)
tftp_senderr(tftpfile, 0, "No error: file closed");
if (tftpfile) {
DEBUG_PRINTF(1, ("%s(%d): %s\n", __func__,
tftpfile->id, tftpfile->path));
handles[tftpfile->id] = NULL;
free(tftpfile->path);
free(tftpfile->pkt);
free(tftpfile->tftp_cache);
free(tftpfile);
}
is_open = 0;
return (0);
}
static int
tftp_stat(struct open_file *f, struct stat *sb)
{
@ -631,6 +679,29 @@ tftp_stat(struct open_file *f, struct stat *sb)
sb->st_uid = 0;
sb->st_gid = 0;
sb->st_size = tftpfile->tftp_tsize;
sb->st_mtime = 0;
#ifdef LOADER_VERIEXEC
/* libsecureboot needs st_dev and st_ino at minimum;
* we need to fake something that will be close enough to
* unique.
*/
sb->st_dev = (dev_t)tftpfile->iodesc->destip.s_addr;
/* we don't want to compute this more than once */
if (tftpfile->ino == 0) {
union {
unsigned char digest[SHA_DIGEST_LENGTH];
ino_t ino;
} u;
hash_string(tftpfile->path, 0, u.digest, sizeof(u.digest));
tftpfile->ino = u.ino & 0x7fffffff;
DEBUG_PRINTF(2,("%s(%s) dev=%lu ino=%lu\n", __func__,
tftpfile->path, (unsigned long)sb->st_dev,
(unsigned long)tftpfile->ino));
}
sb->st_ino = tftpfile->ino;
#endif
return (0);
}
@ -828,9 +899,7 @@ tftp_parse_oack(struct tftp_handle *h, char *buf, size_t len)
return (-1);
}
#ifdef TFTP_DEBUG
printf("tftp_blksize: %u\n", h->tftp_blksize);
printf("tftp_tsize: %lu\n", h->tftp_tsize);
#endif
DEBUG_PRINTF(2, ("tftp_blksize: %u\n", h->tftp_blksize));
DEBUG_PRINTF(2, ("tftp_tsize: %lu\n", h->tftp_tsize));
return (0);
}

View file

@ -101,8 +101,6 @@ SRCS+= interp_simple.c
.error Unknown interpreter ${LOADER_INTERP}
.endif
.include "${BOOTSRC}/veriexec.mk"
.if defined(BOOT_PROMPT_123)
CFLAGS+= -DBOOT_PROMPT_123
.endif