When performing /bsd.bupgrade automated snapshots, the upgrade kernel is removed as soon as the root filesystem can be mounted. That way, you don't get stuck in a upgrade reboot loop. But this removal happens pretty far into the upgrade script..
So consider the circumstances. What if the bsd.upgrade kernel is corrupt, or incompatible in some way. One example would be a large boot-block ABI break. In that case, if you crash you want the old /bsd kernel to run. There is also a shell-script timer running in bsd.upgrade to cause a reboot. I've suggested we need a kernel-timer also. Then upon returning to your old system, and scratching your head about it looks un-upgraded, you could fix some things and then retry the upgrade. So Florian asked me. Is it possible to disable the bsd.upgrade earlier... immediately I was praying the bootblock block-IO code could perform writes... well, at least the BIOS code can! If we remember the inode of bsd.upgrade, chmod a-x is a fairly safe operation on the filesystem. Upon next boot, the bsd.upgrade kernel is not executable, and thus not considered a candidate, so voila you go back to your regular kernel. I have only converted the amd64 bootblocks so far, but the MD changes are fairly mechanical, as long as the IO path has working write operations. I haven't tested EFI or PXE, and there may be other corner cases. (I haven't seen any filesystem damage....) Index: lib/libsa/fchmod.c =================================================================== RCS file: lib/libsa/fchmod.c diff -N lib/libsa/fchmod.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lib/libsa/fchmod.c 24 Jun 2019 14:51:49 -0000 @@ -0,0 +1,59 @@ +/* $OpenBSD: fstat.c,v 1.4 2003/08/11 06:23:09 deraadt Exp $ */ +/* $NetBSD: stat.c,v 1.3 1994/10/26 05:45:07 cgd Exp $ */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)stat.c 8.1 (Berkeley) 6/11/93 + */ + +#include "stand.h" + +int +fchmod(int fd, mode_t m) +{ + struct open_file *f = &files[fd]; + + if (f->f_ops->fchmod == NULL) { + errno = EOPNOTSUPP; + return (-1); + } + if ((unsigned)fd >= SOPEN_MAX || f->f_flags == 0) { + errno = EBADF; + return (-1); + } + + /* operation not defined on raw devices */ + if (f->f_flags & F_RAW) { + errno = EOPNOTSUPP; + return (-1); + } + + errno = (f->f_ops->fchmod)(f, m); + return (0); +} Index: lib/libsa/stand.h =================================================================== RCS file: /cvs/src/sys/lib/libsa/stand.h,v retrieving revision 1.66 diff -u -p -u -r1.66 stand.h --- lib/libsa/stand.h 20 Apr 2019 22:59:04 -0000 1.66 +++ lib/libsa/stand.h 23 Jun 2019 04:26:11 -0000 @@ -67,6 +67,7 @@ struct fs_ops { off_t (*seek)(struct open_file *f, off_t offset, int where); int (*stat)(struct open_file *f, struct stat *sb); int (*readdir)(struct open_file *f, char *); + int (*fchmod)(struct open_file *f, mode_t); }; extern struct fs_ops file_system[]; Index: lib/libsa/ufs.c =================================================================== RCS file: /cvs/src/sys/lib/libsa/ufs.c,v retrieving revision 1.26 diff -u -p -u -r1.26 ufs.c --- lib/libsa/ufs.c 25 Nov 2016 17:00:33 -0000 1.26 +++ lib/libsa/ufs.c 25 Jun 2019 03:00:34 -0000 @@ -81,6 +81,7 @@ struct file { off_t f_seekp; /* seek pointer */ struct fs *f_fs; /* pointer to super-block */ struct ufs1_dinode f_di; /* copy of on-disk inode */ + ufsino_t f_ino; /* our inode number */ int f_nindir[NIADDR]; /* number of blocks mapped by indirect block at level i */ @@ -95,6 +96,7 @@ struct file { }; static int read_inode(ufsino_t, struct open_file *); +static int chmod_inode(ufsino_t, struct open_file *, mode_t); static int block_map(struct open_file *, daddr32_t, daddr32_t *); static int buf_read_file(struct open_file *, char **, size_t *); static int search_directory(char *, struct open_file *, ufsino_t *); @@ -154,6 +156,50 @@ out: } /* + * Read a new inode into a file structure. + */ +static int +chmod_inode(ufsino_t inumber, struct open_file *f, mode_t mode) +{ + struct file *fp = (struct file *)f->f_fsdata; + struct fs *fs = fp->f_fs; + char *buf; + size_t rsize; + int rc; + + /* + * Read inode and save it. + */ + buf = alloc(fs->fs_bsize); + twiddle(); + rc = (f->f_dev->dv_strategy)(f->f_devdata, F_READ, + fsbtodb(fs, (daddr32_t)ino_to_fsba(fs, inumber)), fs->fs_bsize, + buf, &rsize); + if (rc) + goto out; + if (rsize != (size_t)fs->fs_bsize) { + rc = EIO; + goto out; + } + + { + struct ufs1_dinode *dp; + + dp = &((struct ufs1_dinode *)buf)[ino_to_fsbo(fs, inumber)]; + dp->di_mode = mode; + } + + twiddle(); + rc = (f->f_dev->dv_strategy)(f->f_devdata, F_WRITE, + fsbtodb(fs, (daddr32_t)ino_to_fsba(fs, inumber)), fs->fs_bsize, + buf, NULL); + +out: + free(buf, fs->fs_bsize); + return (rc); +} + +/* * Given an offset in a file, find the disk block number that * contains that block. */ @@ -522,6 +568,7 @@ ufs_open(char *path, struct open_file *f /* * Found terminal component. */ + fp->f_ino = inumber; rc = 0; out: if (buf) @@ -637,6 +684,14 @@ ufs_stat(struct open_file *f, struct sta sb->st_gid = fp->f_di.di_gid; sb->st_size = fp->f_di.di_size; return (0); +} + +int +ufs_fchmod(struct open_file *f, mode_t mode) +{ + struct file *fp = (struct file *)f->f_fsdata; + + return chmod_inode(fp->f_ino, f, mode); } #ifndef NO_READDIR Index: lib/libsa/ufs.h =================================================================== RCS file: /cvs/src/sys/lib/libsa/ufs.h,v retrieving revision 1.6 diff -u -p -u -r1.6 ufs.h --- lib/libsa/ufs.h 2 Jun 2003 23:28:10 -0000 1.6 +++ lib/libsa/ufs.h 23 Jun 2019 04:37:03 -0000 @@ -41,4 +41,5 @@ int ufs_write(struct open_file *f, void off_t ufs_seek(struct open_file *f, off_t offset, int where); int ufs_stat(struct open_file *f, struct stat *sb); int ufs_readdir(struct open_file *f, char *name); +int ufs_fchmod(struct open_file *f, mode_t mode); Index: lib/libsa/ufs2.c =================================================================== RCS file: /cvs/src/sys/lib/libsa/ufs2.c,v retrieving revision 1.7 diff -u -p -u -r1.7 ufs2.c --- lib/libsa/ufs2.c 27 Nov 2016 13:57:32 -0000 1.7 +++ lib/libsa/ufs2.c 25 Jun 2019 03:01:20 -0000 @@ -80,6 +80,7 @@ struct file { off_t f_seekp; /* seek pointer */ struct fs *f_fs; /* pointer to super-block */ struct ufs2_dinode f_di; /* copy of on-disk inode */ + ufsino_t f_ino; /* our inode number */ int f_nindir[NIADDR]; /* number of blocks mapped by indirect block at level i */ @@ -94,6 +95,7 @@ struct file { }; static int read_inode(ufsino_t, struct open_file *); +static int chmod_inode(ufsino_t, struct open_file *, mode_t); static int block_map(struct open_file *, daddr_t, daddr_t *); static int buf_read_file(struct open_file *, char **, size_t *); static int search_directory(char *, struct open_file *, ufsino_t *); @@ -152,6 +154,48 @@ out: } /* + * Read a new inode into a file structure. + */ +static int +chmod_inode(ufsino_t inumber, struct open_file *f, mode_t mode) +{ + struct file *fp = (struct file *)f->f_fsdata; + struct fs *fs = fp->f_fs; + char *buf; + size_t rsize; + int rc; + + /* + * Read inode and save it. + */ + buf = alloc(fs->fs_bsize); + twiddle(); + rc = (f->f_dev->dv_strategy)(f->f_devdata, F_READ, + fsbtodb(fs, ino_to_fsba(fs, inumber)), fs->fs_bsize, buf, &rsize); + if (rc) + goto out; + if (rsize != (size_t)fs->fs_bsize) { + rc = EIO; + goto out; + } + + { + struct ufs2_dinode *dp; + + dp = &((struct ufs2_dinode *)buf)[ino_to_fsbo(fs, inumber)]; + dp->di_mode = mode; + } + + twiddle(); + rc = (f->f_dev->dv_strategy)(f->f_devdata, F_WRITE, + fsbtodb(fs, ino_to_fsba(fs, inumber)), fs->fs_bsize, buf, NULL); + +out: + free(buf, fs->fs_bsize); + return (rc); +} + +/* * Given an offset in a file, find the disk block number that * contains that block. */ @@ -520,6 +564,7 @@ ufs2_open(char *path, struct open_file * /* * Found terminal component. */ + fp->f_ino = inumber; rc = 0; out: if (buf) @@ -635,6 +680,14 @@ ufs2_stat(struct open_file *f, struct st sb->st_gid = fp->f_di.di_gid; sb->st_size = fp->f_di.di_size; return (0); +} + +int +ufs2_fchmod(struct open_file *f, mode_t mode) +{ + struct file *fp = (struct file *)f->f_fsdata; + + return chmod_inode(fp->f_ino, f, mode); } #ifndef NO_READDIR Index: stand/boot/boot.c =================================================================== RCS file: /cvs/src/sys/stand/boot/boot.c,v retrieving revision 1.48 diff -u -p -u -r1.48 boot.c --- stand/boot/boot.c 10 Apr 2019 19:41:03 -0000 1.48 +++ stand/boot/boot.c 25 Jun 2019 02:57:02 -0000 @@ -59,7 +59,7 @@ char rnddata[BOOTRANDOM_MAX]; void boot(dev_t bootdev) { - int fd; + int fd, isupgrade = 0; int try = 0, st; uint64_t marks[MARK_MAX]; @@ -79,6 +79,7 @@ boot(dev_t bootdev) if (upgrade()) { strlcpy(cmd.image, "/bsd.upgrade", sizeof(cmd.image)); printf("upgrade detected: switching to %s\n", cmd.image); + isupgrade = 1; } st = read_conf(); @@ -118,6 +119,18 @@ boot(dev_t bootdev) printf("booting %s: ", cmd.path); marks[MARK_START] = (u_long)cmd.addr; if ((fd = loadfile(cmd.path, marks, LOAD_ALL)) != -1) { + + /* Prevent re-upgrade: chmod a-x bsd.upgrade */ + if (isupgrade) { + struct stat st; + + if (fstat(fd, &st) == 0) { + st.st_mode &= ~(S_IXUSR|S_IXGRP|S_IXOTH); + if (fchmod(fd, st.st_mode) == -1) + printf("fchmod a-x %s: failed\n", + cmd.path); + } + } close(fd); break; } Index: stand/boot/cmd.c =================================================================== RCS file: /cvs/src/sys/stand/boot/cmd.c,v retrieving revision 1.64 diff -u -p -u -r1.64 cmd.c --- stand/boot/cmd.c 8 Apr 2019 13:55:46 -0000 1.64 +++ stand/boot/cmd.c 25 Jun 2019 02:48:41 -0000 @@ -530,5 +530,9 @@ upgrade(void) if (stat(qualify(("/bsd.upgrade")), &sb) < 0) return 0; + if ((sb.st_mode & S_IXUSR) == 0) { + printf("/bsd.upgrade is not not u+x\n"); + return 0; + } return 1; } Index: arch/amd64/stand/boot/Makefile =================================================================== RCS file: /cvs/src/sys/arch/amd64/stand/boot/Makefile,v retrieving revision 1.41 diff -u -p -u -r1.41 Makefile --- arch/amd64/stand/boot/Makefile 15 May 2019 06:52:33 -0000 1.41 +++ arch/amd64/stand/boot/Makefile 23 Jun 2019 04:25:44 -0000 @@ -35,7 +35,7 @@ SRCS+= softraid_amd64.c SRCS+= alloc.c ctime.c exit.c getchar.c memcmp.c memcpy.c memmove.c memset.c printf.c \ putchar.c snprintf.c strcmp.c strerror.c strlen.c strncmp.c strncpy.c \ strtol.c strtoll.c -SRCS+= close.c closeall.c cons.c cread.c dev.c disklabel.c dkcksum.c fstat.c \ +SRCS+= close.c closeall.c cons.c cread.c dev.c disklabel.c dkcksum.c fchmod.c fstat.c \ lseek.c open.c read.c readdir.c stat.c SRCS+= elf32.c elf64.c loadfile.c SRCS+= ufs.c Index: arch/amd64/stand/boot/conf.c =================================================================== RCS file: /cvs/src/sys/arch/amd64/stand/boot/conf.c,v retrieving revision 1.47 diff -u -p -u -r1.47 conf.c --- arch/amd64/stand/boot/conf.c 8 Jun 2019 02:52:20 -0000 1.47 +++ arch/amd64/stand/boot/conf.c 23 Jun 2019 04:28:57 -0000 @@ -63,7 +63,7 @@ int nibprobes = nitems(probe_list); struct fs_ops file_system[] = { { ufs_open, ufs_close, ufs_read, ufs_write, ufs_seek, - ufs_stat, ufs_readdir }, + ufs_stat, ufs_readdir, ufs_fchmod }, #ifdef notdef { fat_open, fat_close, fat_read, fat_write, fat_seek, fat_stat, fat_readdir }, Index: arch/amd64/stand/cdboot/Makefile =================================================================== RCS file: /cvs/src/sys/arch/amd64/stand/cdboot/Makefile,v retrieving revision 1.36 diff -u -p -u -r1.36 Makefile --- arch/amd64/stand/cdboot/Makefile 15 May 2019 06:52:33 -0000 1.36 +++ arch/amd64/stand/cdboot/Makefile 24 Jun 2019 14:47:24 -0000 @@ -28,7 +28,7 @@ SRCS+= cmd.c vars.c bootarg.c SRCS+= alloc.c exit.c getchar.c putchar.c strcmp.c strlen.c \ strncmp.c memcmp.c memcpy.c memmove.c memset.c printf.c snprintf.c \ strerror.c strncpy.c strtol.c strtoll.c ctime.c strlcpy.c -SRCS+= close.c closeall.c dev.c disklabel.c dkcksum.c fstat.c lseek.c \ +SRCS+= close.c closeall.c dev.c disklabel.c dkcksum.c fchmod.c fstat.c lseek.c \ open.c read.c stat.c cread.c readdir.c cons.c loadfile.c \ elf32.c elf64.c SRCS+= ufs.c cd9660.c Index: arch/amd64/stand/cdboot/conf.c =================================================================== RCS file: /cvs/src/sys/arch/amd64/stand/cdboot/conf.c,v retrieving revision 1.40 diff -u -p -u -r1.40 conf.c --- arch/amd64/stand/cdboot/conf.c 15 May 2019 06:52:33 -0000 1.40 +++ arch/amd64/stand/cdboot/conf.c 24 Jun 2019 14:47:10 -0000 @@ -65,7 +65,7 @@ struct fs_ops file_system[] = { { cd9660_open, cd9660_close, cd9660_read, cd9660_write, cd9660_seek, cd9660_stat, cd9660_readdir }, { ufs_open, ufs_close, ufs_read, ufs_write, ufs_seek, - ufs_stat, ufs_readdir }, + ufs_stat, ufs_readdir, ufs_fchmod }, #ifdef notdef { tftp_open, tftp_close, tftp_read, tftp_write, tftp_seek, tftp_stat, tftp_readdir }, Index: arch/amd64/stand/efi32/conf.c =================================================================== RCS file: /cvs/src/sys/arch/amd64/stand/efi32/conf.c,v retrieving revision 1.1 diff -u -p -u -r1.1 conf.c --- arch/amd64/stand/efi32/conf.c 11 May 2019 02:33:34 -0000 1.1 +++ arch/amd64/stand/efi32/conf.c 24 Jun 2019 14:48:49 -0000 @@ -66,7 +66,7 @@ struct fs_ops file_system[] = { { tftp_open, tftp_close, tftp_read, tftp_write, tftp_seek, tftp_stat, tftp_readdir }, { ufs_open, ufs_close, ufs_read, ufs_write, ufs_seek, - ufs_stat, ufs_readdir }, + ufs_stat, ufs_readdir ufs_fchmod }, { cd9660_open, cd9660_close, cd9660_read, cd9660_write, cd9660_seek, cd9660_stat, cd9660_readdir }, #ifdef notdef Index: arch/amd64/stand/efi64/conf.c =================================================================== RCS file: /cvs/src/sys/arch/amd64/stand/efi64/conf.c,v retrieving revision 1.1 diff -u -p -u -r1.1 conf.c --- arch/amd64/stand/efi64/conf.c 11 May 2019 02:36:10 -0000 1.1 +++ arch/amd64/stand/efi64/conf.c 24 Jun 2019 14:54:59 -0000 @@ -66,7 +66,7 @@ struct fs_ops file_system[] = { { tftp_open, tftp_close, tftp_read, tftp_write, tftp_seek, tftp_stat, tftp_readdir }, { ufs_open, ufs_close, ufs_read, ufs_write, ufs_seek, - ufs_stat, ufs_readdir }, + ufs_stat, ufs_readdir, ufs_fchmod }, { cd9660_open, cd9660_close, cd9660_read, cd9660_write, cd9660_seek, cd9660_stat, cd9660_readdir }, #ifdef notdef Index: arch/amd64/stand/efiboot/Makefile.common =================================================================== RCS file: /cvs/src/sys/arch/amd64/stand/efiboot/Makefile.common,v retrieving revision 1.16 diff -u -p -u -r1.16 Makefile.common --- arch/amd64/stand/efiboot/Makefile.common 10 May 2019 21:20:42 -0000 1.16 +++ arch/amd64/stand/efiboot/Makefile.common 24 Jun 2019 14:55:56 -0000 @@ -35,7 +35,8 @@ SRCS+= boot.c bootarg.c cmd.c vars.c SRCS+= alloc.c ctime.c exit.c getchar.c memcmp.c memcpy.c memmove.c memset.c printf.c \ putchar.c snprintf.c strcmp.c strerror.c strlen.c strncmp.c strncpy.c \ strtol.c strtoll.c -SRCS+= close.c closeall.c cons.c cread.c dev.c disklabel.c dkcksum.c fstat.c \ +SRCS+= close.c closeall.c cons.c cread.c dev.c disklabel.c dkcksum.c \ + fchmod.c fstat.c \ lseek.c open.c read.c readdir.c stat.c SRCS+= ufs.c cd9660.c .if ${SOFTRAID:L} == "yes" Index: arch/amd64/stand/efiboot/conf.c =================================================================== RCS file: /cvs/src/sys/arch/amd64/stand/efiboot/conf.c,v retrieving revision 1.20 diff -u -p -u -r1.20 conf.c --- arch/amd64/stand/efiboot/conf.c 5 May 2019 19:17:03 -0000 1.20 +++ arch/amd64/stand/efiboot/conf.c 24 Jun 2019 14:55:07 -0000 @@ -66,7 +66,7 @@ struct fs_ops file_system[] = { { tftp_open, tftp_close, tftp_read, tftp_write, tftp_seek, tftp_stat, tftp_readdir }, { ufs_open, ufs_close, ufs_read, ufs_write, ufs_seek, - ufs_stat, ufs_readdir }, + ufs_stat, ufs_readdir, ufs_fchmod }, { cd9660_open, cd9660_close, cd9660_read, cd9660_write, cd9660_seek, cd9660_stat, cd9660_readdir }, #ifdef notdef Index: arch/amd64/stand/pxeboot/Makefile =================================================================== RCS file: /cvs/src/sys/arch/amd64/stand/pxeboot/Makefile,v retrieving revision 1.34 diff -u -p -u -r1.34 Makefile --- arch/amd64/stand/pxeboot/Makefile 15 May 2019 06:52:33 -0000 1.34 +++ arch/amd64/stand/pxeboot/Makefile 24 Jun 2019 14:55:34 -0000 @@ -34,7 +34,8 @@ SRCS+= alloc.c exit.c getchar.c getfile. SRCS+= aes_xts.c bcrypt_pbkdf.c blowfish.c explicit_bzero.c hmac_sha1.c \ pkcs5_pbkdf2.c rijndael.c sha1.c sha2.c softraid.c -SRCS+= close.c closeall.c dev.c disklabel.c dkcksum.c fstat.c ioctl.c lseek.c \ +SRCS+= close.c closeall.c dev.c disklabel.c dkcksum.c fstat.c \ + fchmod.c ioctl.c lseek.c \ read.c stat.c write.c cread.c readdir.c cons.c loadfile.c \ elf32.c elf64.c SRCS+= ether.c net.c netif.c rpc.c Index: arch/amd64/stand/pxeboot/conf.c =================================================================== RCS file: /cvs/src/sys/arch/amd64/stand/pxeboot/conf.c,v retrieving revision 1.44 diff -u -p -u -r1.44 conf.c --- arch/amd64/stand/pxeboot/conf.c 15 May 2019 06:52:33 -0000 1.44 +++ arch/amd64/stand/pxeboot/conf.c 24 Jun 2019 14:49:54 -0000 @@ -75,7 +75,7 @@ int nfsname = nitems(fs_name); struct fs_ops file_system[] = { { ufs_open, ufs_close, ufs_read, ufs_write, ufs_seek, - ufs_stat, ufs_readdir }, + ufs_stat, ufs_readdir, ufs_fchmod }, { tftp_open, tftp_close, tftp_read, tftp_write, tftp_seek, tftp_stat, tftp_readdir }, { nfs_open, nfs_close, nfs_read, nfs_write, nfs_seek,