The GRUB is failing to build with GCC-12 in many places like this:
In function 'init_cbfsdisk',
inlined from 'grub_mod_init' at ../../grub-core/fs/cbfs.c:391:3:
../../grub-core/fs/cbfs.c:345:7: error: array subscript 0 is outside array bounds of 'grub_uint32_t[0]' {aka 'unsigned int[]'} [-Werror=array-bounds]
345 | ptr = *(grub_uint32_t *) 0xfffffffc;
| ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is caused by GCC regression in 11/12 [1]. In a nut shell, the
warning is about detected invalid accesses at non-zero offsets to NULL
pointers. Since hardwired constant address is treated as NULL plus an
offset in the same underlying code, the warning is therefore triggered.
Instead of inserting #pragma all over the places where literal pointers
are accessed to avoid diagnosing array-bounds, we can try to borrow the
idea from Linux kernel that the absolute_pointer() macro [2][3] is used
to disconnect a pointer using literal address from it's original object,
hence GCC won't be able to make assumptions on the boundary while doing
pointer arithmetic. With that we can greatly reduce the code we have to
cover up by making initial literal pointer assignment to use the new
wrapper but not having to track everywhere literal pointers are
accessed. This also makes code looks cleaner.
Please note the grub_absolute_pointer() macro requires to be invoked in
a function as long as it is compound expression. Some global variables
with literal pointers has been changed to local ones in order to use
grub_absolute_pointer() to initialize it. The shuffling is basically done
in a selective and careful way that the variable's scope doesn't matter
being local or global, for example, the global variable must not get
modified at run time throughout. For the record, here's the list of
global variables got shuffled in this patch:
grub-core/commands/i386/pc/drivemap.c:int13slot
grub-core/term/i386/pc/console.c:bios_data_area
grub-core/term/ns8250.c:serial_hw_io_addr
[1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99578
[2] https://elixir.bootlin.com/linux/v5.16.14/source/include/linux/compiler.h#L180
[3] https://elixir.bootlin.com/linux/v5.16.14/source/include/linux/compiler-gcc.h#L31
Signed-off-by: Michael Chang <mchang@suse.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
420 lines
10 KiB
C
420 lines
10 KiB
C
/* pxe.c - Driver to provide access to the pxe filesystem */
|
|
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2008,2009,2011 Free Software Foundation, Inc.
|
|
*
|
|
* GRUB is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GRUB is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <grub/dl.h>
|
|
#include <grub/net.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/file.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/env.h>
|
|
#include <grub/i18n.h>
|
|
#include <grub/loader.h>
|
|
|
|
#include <grub/machine/pxe.h>
|
|
#include <grub/machine/int.h>
|
|
#include <grub/machine/memory.h>
|
|
#include <grub/machine/kernel.h>
|
|
|
|
GRUB_MOD_LICENSE ("GPLv3+");
|
|
|
|
#define SEGMENT(x) ((x) >> 4)
|
|
#define OFFSET(x) ((x) & 0xF)
|
|
#define SEGOFS(x) ((SEGMENT(x) << 16) + OFFSET(x))
|
|
#define LINEAR(x) (void *) ((((x) >> 16) << 4) + ((x) & 0xFFFF))
|
|
|
|
struct grub_pxe_undi_open
|
|
{
|
|
grub_uint16_t status;
|
|
grub_uint16_t open_flag;
|
|
grub_uint16_t pkt_filter;
|
|
grub_uint16_t mcast_count;
|
|
grub_uint8_t mcast[8][6];
|
|
} GRUB_PACKED;
|
|
|
|
struct grub_pxe_undi_info
|
|
{
|
|
grub_uint16_t status;
|
|
grub_uint16_t base_io;
|
|
grub_uint16_t int_number;
|
|
grub_uint16_t mtu;
|
|
grub_uint16_t hwtype;
|
|
grub_uint16_t hwaddrlen;
|
|
grub_uint8_t current_addr[16];
|
|
grub_uint8_t permanent_addr[16];
|
|
grub_uint32_t romaddr;
|
|
grub_uint16_t rxbufct;
|
|
grub_uint16_t txbufct;
|
|
} GRUB_PACKED;
|
|
|
|
|
|
struct grub_pxe_undi_isr
|
|
{
|
|
grub_uint16_t status;
|
|
grub_uint16_t func_flag;
|
|
grub_uint16_t buffer_len;
|
|
grub_uint16_t frame_len;
|
|
grub_uint16_t frame_hdr_len;
|
|
grub_uint32_t buffer;
|
|
grub_uint8_t prot_type;
|
|
grub_uint8_t pkt_type;
|
|
} GRUB_PACKED;
|
|
|
|
enum
|
|
{
|
|
GRUB_PXE_ISR_IN_START = 1,
|
|
GRUB_PXE_ISR_IN_PROCESS,
|
|
GRUB_PXE_ISR_IN_GET_NEXT
|
|
};
|
|
|
|
enum
|
|
{
|
|
GRUB_PXE_ISR_OUT_OURS = 0,
|
|
GRUB_PXE_ISR_OUT_NOT_OURS = 1
|
|
};
|
|
|
|
enum
|
|
{
|
|
GRUB_PXE_ISR_OUT_DONE = 0,
|
|
GRUB_PXE_ISR_OUT_TRANSMIT = 2,
|
|
GRUB_PXE_ISR_OUT_RECEIVE = 3,
|
|
GRUB_PXE_ISR_OUT_BUSY = 4,
|
|
};
|
|
|
|
struct grub_pxe_undi_transmit
|
|
{
|
|
grub_uint16_t status;
|
|
grub_uint8_t protocol;
|
|
grub_uint8_t xmitflag;
|
|
grub_uint32_t dest;
|
|
grub_uint32_t tbd;
|
|
grub_uint32_t reserved[2];
|
|
} GRUB_PACKED;
|
|
|
|
struct grub_pxe_undi_tbd
|
|
{
|
|
grub_uint16_t len;
|
|
grub_uint32_t buf;
|
|
grub_uint16_t blk_count;
|
|
struct
|
|
{
|
|
grub_uint8_t ptr_type;
|
|
grub_uint8_t reserved;
|
|
grub_uint16_t len;
|
|
grub_uint32_t ptr;
|
|
} blocks[8];
|
|
} GRUB_PACKED;
|
|
|
|
struct grub_pxe_bangpxe *grub_pxe_pxenv;
|
|
static grub_uint32_t pxe_rm_entry = 0;
|
|
|
|
static struct grub_pxe_bangpxe *
|
|
grub_pxe_scan (void)
|
|
{
|
|
struct grub_bios_int_registers regs;
|
|
struct grub_pxenv *pxenv;
|
|
struct grub_pxe_bangpxe *bangpxe;
|
|
|
|
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
|
|
|
|
regs.ebx = 0;
|
|
regs.ecx = 0;
|
|
regs.eax = 0x5650;
|
|
regs.es = 0;
|
|
|
|
grub_bios_interrupt (0x1a, ®s);
|
|
|
|
if ((regs.eax & 0xffff) != 0x564e)
|
|
return NULL;
|
|
|
|
pxenv = (struct grub_pxenv *) ((regs.es << 4) + (regs.ebx & 0xffff));
|
|
if (grub_memcmp (pxenv->signature, GRUB_PXE_SIGNATURE,
|
|
sizeof (pxenv->signature))
|
|
!= 0)
|
|
return NULL;
|
|
|
|
if (pxenv->version < 0x201)
|
|
return NULL;
|
|
|
|
bangpxe = (void *) ((((pxenv->pxe_ptr & 0xffff0000) >> 16) << 4)
|
|
+ (pxenv->pxe_ptr & 0xffff));
|
|
|
|
if (!bangpxe)
|
|
return NULL;
|
|
|
|
if (grub_memcmp (bangpxe->signature, GRUB_PXE_BANGPXE_SIGNATURE,
|
|
sizeof (bangpxe->signature)) != 0)
|
|
return NULL;
|
|
|
|
pxe_rm_entry = bangpxe->rm_entry;
|
|
|
|
return bangpxe;
|
|
}
|
|
|
|
static struct grub_net_buff *
|
|
grub_pxe_recv (struct grub_net_card *dev __attribute__ ((unused)))
|
|
{
|
|
struct grub_pxe_undi_isr *isr;
|
|
static int in_progress = 0;
|
|
grub_uint8_t *ptr, *end;
|
|
struct grub_net_buff *buf;
|
|
|
|
isr = (void *) grub_absolute_pointer (GRUB_MEMORY_MACHINE_SCRATCH_ADDR);
|
|
|
|
if (!in_progress)
|
|
{
|
|
grub_memset (isr, 0, sizeof (*isr));
|
|
isr->func_flag = GRUB_PXE_ISR_IN_START;
|
|
grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
|
|
/* Normally according to the specification we should also check
|
|
that isr->func_flag != GRUB_PXE_ISR_OUT_OURS but unfortunately it
|
|
breaks on intel cards.
|
|
*/
|
|
if (isr->status)
|
|
{
|
|
in_progress = 0;
|
|
return NULL;
|
|
}
|
|
grub_memset (isr, 0, sizeof (*isr));
|
|
isr->func_flag = GRUB_PXE_ISR_IN_PROCESS;
|
|
grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
|
|
}
|
|
else
|
|
{
|
|
grub_memset (isr, 0, sizeof (*isr));
|
|
isr->func_flag = GRUB_PXE_ISR_IN_GET_NEXT;
|
|
grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
|
|
}
|
|
|
|
while (isr->func_flag != GRUB_PXE_ISR_OUT_RECEIVE)
|
|
{
|
|
if (isr->status || isr->func_flag == GRUB_PXE_ISR_OUT_DONE)
|
|
{
|
|
in_progress = 0;
|
|
return NULL;
|
|
}
|
|
grub_memset (isr, 0, sizeof (*isr));
|
|
isr->func_flag = GRUB_PXE_ISR_IN_GET_NEXT;
|
|
grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
|
|
}
|
|
|
|
buf = grub_netbuff_alloc (isr->frame_len + 2);
|
|
if (!buf)
|
|
return NULL;
|
|
/* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible
|
|
by 4. So that IP header is aligned on 4 bytes. */
|
|
if (grub_netbuff_reserve (buf, 2))
|
|
{
|
|
grub_netbuff_free (buf);
|
|
return NULL;
|
|
}
|
|
ptr = buf->data;
|
|
end = ptr + isr->frame_len;
|
|
grub_netbuff_put (buf, isr->frame_len);
|
|
grub_memcpy (ptr, LINEAR (isr->buffer), isr->buffer_len);
|
|
ptr += isr->buffer_len;
|
|
while (ptr < end)
|
|
{
|
|
grub_memset (isr, 0, sizeof (*isr));
|
|
isr->func_flag = GRUB_PXE_ISR_IN_GET_NEXT;
|
|
grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
|
|
if (isr->status || isr->func_flag != GRUB_PXE_ISR_OUT_RECEIVE)
|
|
{
|
|
in_progress = 1;
|
|
grub_netbuff_free (buf);
|
|
return NULL;
|
|
}
|
|
|
|
grub_memcpy (ptr, LINEAR (isr->buffer), isr->buffer_len);
|
|
ptr += isr->buffer_len;
|
|
}
|
|
in_progress = 1;
|
|
|
|
return buf;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_pxe_send (struct grub_net_card *dev __attribute__ ((unused)),
|
|
struct grub_net_buff *pack)
|
|
{
|
|
struct grub_pxe_undi_transmit *trans;
|
|
struct grub_pxe_undi_tbd *tbd;
|
|
char *buf;
|
|
|
|
trans = (void *) grub_absolute_pointer (GRUB_MEMORY_MACHINE_SCRATCH_ADDR);
|
|
grub_memset (trans, 0, sizeof (*trans));
|
|
tbd = (void *) grub_absolute_pointer (GRUB_MEMORY_MACHINE_SCRATCH_ADDR + 128);
|
|
grub_memset (tbd, 0, sizeof (*tbd));
|
|
buf = (void *) grub_absolute_pointer (GRUB_MEMORY_MACHINE_SCRATCH_ADDR + 256);
|
|
grub_memcpy (buf, pack->data, pack->tail - pack->data);
|
|
|
|
trans->tbd = SEGOFS ((grub_addr_t) tbd);
|
|
trans->protocol = 0;
|
|
tbd->len = pack->tail - pack->data;
|
|
tbd->buf = SEGOFS ((grub_addr_t) buf);
|
|
|
|
grub_pxe_call (GRUB_PXENV_UNDI_TRANSMIT, trans, pxe_rm_entry);
|
|
if (trans->status)
|
|
return grub_error (GRUB_ERR_IO, N_("couldn't send network packet"));
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
grub_pxe_close (struct grub_net_card *dev __attribute__ ((unused)))
|
|
{
|
|
if (pxe_rm_entry)
|
|
grub_pxe_call (GRUB_PXENV_UNDI_CLOSE,
|
|
(void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
|
|
pxe_rm_entry);
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_pxe_open (struct grub_net_card *dev __attribute__ ((unused)))
|
|
{
|
|
struct grub_pxe_undi_open *ou;
|
|
ou = (void *) grub_absolute_pointer (GRUB_MEMORY_MACHINE_SCRATCH_ADDR);
|
|
grub_memset (ou, 0, sizeof (*ou));
|
|
ou->pkt_filter = 4;
|
|
grub_pxe_call (GRUB_PXENV_UNDI_OPEN, ou, pxe_rm_entry);
|
|
|
|
if (ou->status)
|
|
return grub_error (GRUB_ERR_IO, "can't open UNDI");
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
struct grub_net_card_driver grub_pxe_card_driver =
|
|
{
|
|
.open = grub_pxe_open,
|
|
.close = grub_pxe_close,
|
|
.send = grub_pxe_send,
|
|
.recv = grub_pxe_recv
|
|
};
|
|
|
|
struct grub_net_card grub_pxe_card =
|
|
{
|
|
.driver = &grub_pxe_card_driver,
|
|
.name = "pxe"
|
|
};
|
|
|
|
static grub_err_t
|
|
grub_pxe_shutdown (int flags)
|
|
{
|
|
if (flags & GRUB_LOADER_FLAG_PXE_NOT_UNLOAD)
|
|
return GRUB_ERR_NONE;
|
|
if (!pxe_rm_entry)
|
|
return GRUB_ERR_NONE;
|
|
|
|
grub_pxe_call (GRUB_PXENV_UNDI_CLOSE,
|
|
(void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
|
|
pxe_rm_entry);
|
|
grub_pxe_call (GRUB_PXENV_UNDI_SHUTDOWN,
|
|
(void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
|
|
pxe_rm_entry);
|
|
grub_pxe_call (GRUB_PXENV_UNLOAD_STACK,
|
|
(void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
|
|
pxe_rm_entry);
|
|
pxe_rm_entry = 0;
|
|
grub_net_card_unregister (&grub_pxe_card);
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
/* Nothing we can do. */
|
|
static grub_err_t
|
|
grub_pxe_restore (void)
|
|
{
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
void *
|
|
grub_pxe_get_cached (grub_uint16_t type)
|
|
{
|
|
struct grub_pxenv_get_cached_info ci;
|
|
ci.packet_type = type;
|
|
ci.buffer = 0;
|
|
ci.buffer_size = 0;
|
|
grub_pxe_call (GRUB_PXENV_GET_CACHED_INFO, &ci, pxe_rm_entry);
|
|
if (ci.status)
|
|
return 0;
|
|
|
|
return LINEAR (ci.buffer);
|
|
}
|
|
|
|
static void
|
|
grub_pc_net_config_real (char **device, char **path)
|
|
{
|
|
struct grub_net_bootp_packet *bp;
|
|
|
|
bp = grub_pxe_get_cached (GRUB_PXENV_PACKET_TYPE_DHCP_ACK);
|
|
|
|
if (!bp)
|
|
return;
|
|
grub_net_configure_by_dhcp_ack ("pxe", &grub_pxe_card, 0,
|
|
bp, GRUB_PXE_BOOTP_SIZE,
|
|
1, device, path);
|
|
|
|
}
|
|
|
|
static struct grub_preboot *fini_hnd;
|
|
|
|
GRUB_MOD_INIT(pxe)
|
|
{
|
|
struct grub_pxe_bangpxe *pxenv;
|
|
struct grub_pxe_undi_info *ui;
|
|
unsigned i;
|
|
|
|
pxenv = grub_pxe_scan ();
|
|
if (! pxenv)
|
|
return;
|
|
|
|
ui = (void *) grub_absolute_pointer (GRUB_MEMORY_MACHINE_SCRATCH_ADDR);
|
|
grub_memset (ui, 0, sizeof (*ui));
|
|
grub_pxe_call (GRUB_PXENV_UNDI_GET_INFORMATION, ui, pxe_rm_entry);
|
|
|
|
grub_memcpy (grub_pxe_card.default_address.mac, ui->current_addr,
|
|
sizeof (grub_pxe_card.default_address.mac));
|
|
for (i = 0; i < sizeof (grub_pxe_card.default_address.mac); i++)
|
|
if (grub_pxe_card.default_address.mac[i] != 0)
|
|
break;
|
|
if (i != sizeof (grub_pxe_card.default_address.mac))
|
|
{
|
|
for (i = 0; i < sizeof (grub_pxe_card.default_address.mac); i++)
|
|
if (grub_pxe_card.default_address.mac[i] != 0xff)
|
|
break;
|
|
}
|
|
if (i == sizeof (grub_pxe_card.default_address.mac))
|
|
grub_memcpy (grub_pxe_card.default_address.mac, ui->permanent_addr,
|
|
sizeof (grub_pxe_card.default_address.mac));
|
|
grub_pxe_card.mtu = ui->mtu;
|
|
|
|
grub_pxe_card.default_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
|
|
|
|
grub_net_card_register (&grub_pxe_card);
|
|
grub_pc_net_config = grub_pc_net_config_real;
|
|
fini_hnd = grub_loader_register_preboot_hook (grub_pxe_shutdown,
|
|
grub_pxe_restore,
|
|
GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
|
|
}
|
|
|
|
GRUB_MOD_FINI(pxe)
|
|
{
|
|
grub_pc_net_config = 0;
|
|
grub_pxe_shutdown (0);
|
|
grub_loader_unregister_preboot_hook (fini_hnd);
|
|
}
|