elfsignlib.c revision 9b009fc1b553084f6003dcd46b171890049de0ff
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
*/
#define ELF_TARGET_ALL /* get definitions of all section flags */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <stddef.h>
#include <stdlib.h>
#include <libintl.h>
#include <dirent.h>
#include <errno.h>
#include <libelf.h>
#include <gelf.h>
#include <cryptoutil.h>
#include <sha1.h>
#include <sys/crypto/elfsign.h>
#include <libelfsign.h>
#ifndef SHA1_DIGEST_LENGTH
#define SHA1_DIGEST_LENGTH 20
#endif /* SHA1_DIGEST_LENGTH */
const char SUNW_ELF_SIGNATURE_ID[] = ELF_SIGNATURE_SECTION;
const char OID_sha1WithRSAEncryption[] = "1.2.840.113549.1.1.5";
static ELFsign_status_t elfsign_adjustoffsets(ELFsign_t ess,
Elf_Scn *scn, uint64_t new_size);
static uint32_t elfsign_switch_uint32(uint32_t i);
static ELFsign_status_t elfsign_switch(ELFsign_t ess,
struct filesignatures *fssp, enum ES_ACTION action);
struct filesig_extraction {
filesig_vers_t fsx_version;
char *fsx_format;
char fsx_signer_DN[ELFCERT_MAX_DN_LEN];
size_t fsx_signer_DN_len;
uchar_t fsx_signature[SIG_MAX_LENGTH];
size_t fsx_sig_len;
char fsx_sig_oid[100];
size_t fsx_sig_oid_len;
time_t fsx_time;
};
static char *
version_to_str(filesig_vers_t v)
{
char *ret;
switch (v) {
case FILESIG_VERSION1:
ret = "VERSION1";
break;
case FILESIG_VERSION2:
ret = "VERSION2";
break;
case FILESIG_VERSION3:
ret = "VERSION3";
break;
case FILESIG_VERSION4:
ret = "VERSION4";
break;
default:
ret = "UNKNOWN";
break;
}
return (ret);
}
/*
* Update filesignatures to include the v1/v2 filesig,
* composed of signer DN, signature, and OID.
*/
static struct filesignatures *
filesig_insert_dso(struct filesignatures *fssp,
filesig_vers_t version,
const char *dn,
int dn_len,
const uchar_t *sig,
int sig_len,
const char *oid,
int oid_len)
{
struct filesig *fsgp;
char *fsdatap;
if (oid == NULL) {
/*
* This OID is used for the rsa_md5_sha1 format signature also.
* This use is historical, and is hence continued,
* despite its lack of technical accuracy.
*/
oid = OID_sha1WithRSAEncryption;
oid_len = strlen(oid);
}
/*
* for now, always insert a single-signature signature block
*/
if (fssp != NULL)
free(fssp);
fssp = (struct filesignatures *)
malloc(filesig_ALIGN(sizeof (struct filesignatures) +
dn_len + sig_len + oid_len));
if (fssp == NULL)
return (fssp);
fssp->filesig_cnt = 1;
fssp->filesig_pad = 0; /* reserve for future use */
fsgp = &fssp->filesig_sig;
fsgp->filesig_size = sizeof (struct filesig) +
dn_len + sig_len + oid_len;
fsgp->filesig_version = version;
switch (version) {
case FILESIG_VERSION1:
case FILESIG_VERSION2:
fsgp->filesig_size -= sizeof (struct filesig) -
offsetof(struct filesig, filesig_v1_data[0]);
fsgp->filesig_v1_dnsize = dn_len;
fsgp->filesig_v1_sigsize = sig_len;
fsgp->filesig_v1_oidsize = oid_len;
fsdatap = &fsgp->filesig_v1_data[0];
break;
case FILESIG_VERSION3:
case FILESIG_VERSION4:
fsgp->filesig_size -= sizeof (struct filesig) -
offsetof(struct filesig, filesig_v3_data[0]);
fsgp->filesig_v3_time = time(NULL);
fsgp->filesig_v3_dnsize = dn_len;
fsgp->filesig_v3_sigsize = sig_len;
fsgp->filesig_v3_oidsize = oid_len;
fsdatap = &fsgp->filesig_v3_data[0];
break;
default:
cryptodebug("filesig_insert_dso: unknown version: %d",
version);
free(fssp);
return (NULL);
}
(void) memcpy(fsdatap, dn, dn_len);
fsdatap += dn_len;
(void) memcpy(fsdatap, (char *)sig, sig_len);
fsdatap += sig_len;
(void) memcpy(fsdatap, oid, oid_len);
fsdatap += oid_len;
fsgp = filesig_next(fsgp);
(void) memset(fsdatap, 0, (char *)(fsgp) - fsdatap);
return (fssp);
}
/*
* filesig_extract - extract filesig structure to internal form
*/
static filesig_vers_t
filesig_extract(struct filesig *fsgp, struct filesig_extraction *fsxp)
{
char *fsdp;
#define filesig_extract_common(cp, field, data_var, len_var, len_limit) { \
len_var = len_limit; \
if (len_var > fsgp->field) \
len_var = fsgp->field; \
(void) memcpy(data_var, cp, len_var); \
cp += fsgp->field; }
#define filesig_extract_str(cp, field, data_var, len_var) \
filesig_extract_common(cp, field, data_var, len_var, \
sizeof (data_var) - 1); \
data_var[len_var] = '\0';
#define filesig_extract_opaque(cp, field, data_var, len_var) \
filesig_extract_common(cp, field, data_var, len_var, sizeof (data_var))
fsxp->fsx_version = fsgp->filesig_version;
cryptodebug("filesig_extract: version=%s",
version_to_str(fsxp->fsx_version));
switch (fsxp->fsx_version) {
case FILESIG_VERSION1:
case FILESIG_VERSION2:
/*
* extract VERSION1 DN, signature, and OID
*/
fsdp = fsgp->filesig_v1_data;
fsxp->fsx_format = ES_FMT_RSA_MD5_SHA1;
fsxp->fsx_time = 0;
filesig_extract_str(fsdp, filesig_v1_dnsize,
fsxp->fsx_signer_DN, fsxp->fsx_signer_DN_len);
filesig_extract_opaque(fsdp, filesig_v1_sigsize,
fsxp->fsx_signature, fsxp->fsx_sig_len);
filesig_extract_str(fsdp, filesig_v1_oidsize,
fsxp->fsx_sig_oid, fsxp->fsx_sig_oid_len);
break;
case FILESIG_VERSION3:
case FILESIG_VERSION4:
fsdp = fsgp->filesig_v3_data;
fsxp->fsx_format = ES_FMT_RSA_SHA1;
fsxp->fsx_time = fsgp->filesig_v3_time;
filesig_extract_str(fsdp, filesig_v3_dnsize,
fsxp->fsx_signer_DN, fsxp->fsx_signer_DN_len);
filesig_extract_opaque(fsdp, filesig_v3_sigsize,
fsxp->fsx_signature, fsxp->fsx_sig_len);
filesig_extract_str(fsdp, filesig_v3_oidsize,
fsxp->fsx_sig_oid, fsxp->fsx_sig_oid_len);
break;
default:
break;
}
return (fsxp->fsx_version);
}
ELFsign_status_t
elfsign_begin(const char *filename, enum ES_ACTION action, ELFsign_t *essp)
{
Elf_Cmd elfcmd;
int oflags = 0;
short l_type;
ELFsign_t ess;
struct stat stb;
union {
char c[2];
short s;
} uorder;
GElf_Ehdr elfehdr;
char *ident;
switch (action) {
case ES_GET:
case ES_GET_CRYPTO:
case ES_GET_FIPS140:
cryptodebug("elfsign_begin for get");
elfcmd = ELF_C_READ;
oflags = O_RDONLY | O_NOCTTY | O_NDELAY;
l_type = F_RDLCK;
break;
case ES_UPDATE_RSA_MD5_SHA1:
case ES_UPDATE_RSA_SHA1:
cryptodebug("elfsign_begin for update");
elfcmd = ELF_C_RDWR;
oflags = O_RDWR | O_NOCTTY | O_NDELAY;
l_type = F_WRLCK;
break;
default:
return (ELFSIGN_UNKNOWN);
}
if ((ess = malloc(sizeof (struct ELFsign_s))) == NULL) {
return (ELFSIGN_UNKNOWN);
}
(void) memset((void *)ess, 0, sizeof (struct ELFsign_s));
if (!elfcertlib_init(ess)) {
cryptodebug("elfsign_begin: failed initialization");
return (ELFSIGN_UNKNOWN);
}
ess->es_elf = NULL;
ess->es_action = action;
ess->es_version = FILESIG_UNKNOWN;
ess->es_pathname = NULL;
ess->es_certpath = NULL;
if (filename == NULL) {
*essp = ess;
return (ELFSIGN_SUCCESS);
}
if ((ess->es_fd = open(filename, oflags)) == -1) {
elfsign_end(ess);
return (ELFSIGN_INVALID_ELFOBJ);
}
if ((fstat(ess->es_fd, &stb) == -1) || !S_ISREG(stb.st_mode)) {
elfsign_end(ess);
return (ELFSIGN_INVALID_ELFOBJ);
}
if ((ess->es_pathname = strdup(filename)) == NULL) {
elfsign_end(ess);
return (ELFSIGN_UNKNOWN);
}
/*
* The following lock is released in elfsign_end() when we close(2)
* the es_fd. This ensures that we aren't trying verify a file
* we are currently updating.
*/
ess->es_flock.l_type = l_type;
ess->es_flock.l_whence = SEEK_CUR;
ess->es_flock.l_start = 0;
ess->es_flock.l_len = 0;
if (fcntl(ess->es_fd, F_SETLK, &ess->es_flock) == -1) {
cryptodebug("fcntl(F_SETLK) of %s failed with: %s",
ess->es_pathname, strerror(errno));
elfsign_end(ess);
return (ELFSIGN_UNKNOWN);
}
if (elf_version(EV_CURRENT) == EV_NONE) {
elfsign_end(ess);
return (ELFSIGN_UNKNOWN);
}
if ((ess->es_elf = elf_begin(ess->es_fd, elfcmd,
(Elf *)NULL)) == NULL) {
cryptodebug("elf_begin() failed: %s", elf_errmsg(-1));
elfsign_end(ess);
return (ELFSIGN_INVALID_ELFOBJ);
}
if (gelf_getehdr(ess->es_elf, &elfehdr) == NULL) {
cryptodebug("elf_getehdr() failed: %s", elf_errmsg(-1));
elfsign_end(ess);
return (ELFSIGN_INVALID_ELFOBJ);
}
ess->es_has_phdr = (elfehdr.e_phnum != 0);
uorder.s = ELFDATA2MSB << 8 | ELFDATA2LSB;
ident = elf_getident(ess->es_elf, NULL);
if (ident == NULL) {
cryptodebug("elf_getident() failed: %s", elf_errmsg(-1));
elfsign_end(ess);
return (ELFSIGN_INVALID_ELFOBJ);
}
ess->es_same_endian = (ident[EI_DATA] == uorder.c[0]);
ess->es_ei_class = ident[EI_CLASS];
/*
* Call elf_getshstrndx to be sure we have a real ELF object
* this is required because elf_begin doesn't check that.
*/
if (elf_getshstrndx(ess->es_elf, &ess->es_shstrndx) == 0) {
elfsign_end(ess);
cryptodebug("elfsign_begin: elf_getshstrndx failed");
return (ELFSIGN_INVALID_ELFOBJ);
}
/*
* Make sure libelf doesn't rearrange section ordering / offsets.
*/
(void) elf_flagelf(ess->es_elf, ELF_C_SET, ELF_F_LAYOUT);
*essp = ess;
return (ELFSIGN_SUCCESS);
}
/*
* elfsign_end - cleanup the ELFsign_t
*
* IN/OUT: ess
*/
void
elfsign_end(ELFsign_t ess)
{
if (ess == NULL)
return;
if (ess->es_elf != NULL && ES_ACTISUPDATE(ess->es_action)) {
if (elf_update(ess->es_elf, ELF_C_WRITE) == -1) {
cryptodebug("elf_update() failed: %s",
elf_errmsg(-1));
return;
}
}
if (ess->es_fd != -1) {
(void) close(ess->es_fd);
ess->es_fd = -1;
}
if (ess->es_pathname != NULL) {
free(ess->es_pathname);
ess->es_pathname = NULL;
}
if (ess->es_certpath != NULL) {
free(ess->es_certpath);
ess->es_certpath = NULL;
}
if (ess->es_elf != NULL) {
(void) elf_end(ess->es_elf);
ess->es_elf = NULL;
}
elfcertlib_fini(ess);
free(ess);
}
/*
* set the certificate path
*/
ELFsign_status_t
elfsign_setcertpath(ELFsign_t ess, const char *certpath)
{
/*
* Normally use of access(2) is insecure, here we are only
* doing it to help provide early failure and better error
* checking, so there is no race condition.
*/
if (access(certpath, R_OK) != 0)
return (ELFSIGN_INVALID_CERTPATH);
if ((ess->es_certpath = strdup(certpath)) == NULL)
return (ELFSIGN_FAILED);
if (ES_ACTISUPDATE(ess->es_action)) {
ELFCert_t cert = NULL;
char *subject;
/* set the version based on the certificate */
if (elfcertlib_getcert(ess, ess->es_certpath, NULL,
&cert, ess->es_action)) {
if ((subject = elfcertlib_getdn(cert)) != NULL) {
if (strstr(subject, ELFSIGN_CRYPTO))
ess->es_version = (ess->es_action ==
ES_UPDATE_RSA_MD5_SHA1) ?
FILESIG_VERSION1 : FILESIG_VERSION3;
else
ess->es_version = (ess->es_action ==
ES_UPDATE_RSA_MD5_SHA1) ?
FILESIG_VERSION2 : FILESIG_VERSION4;
}
elfcertlib_releasecert(ess, cert);
}
if (ess->es_version == FILESIG_UNKNOWN)
return (ELFSIGN_FAILED);
}
return (ELFSIGN_SUCCESS);
}
/*
* set the callback context
*/
void
elfsign_setcallbackctx(ELFsign_t ess, void *ctx)
{
ess->es_callbackctx = ctx;
}
/*
* set the signature extraction callback
*/
void
elfsign_setsigvercallback(ELFsign_t ess,
void (*cb)(void *, void *, size_t, ELFCert_t))
{
ess->es_sigvercallback = cb;
}
/*
* elfsign_signatures
*
* IN: ess, fsspp, action
* OUT: fsspp
*/
ELFsign_status_t
elfsign_signatures(ELFsign_t ess,
struct filesignatures **fsspp,
size_t *fslen,
enum ES_ACTION action)
{
Elf_Scn *scn = NULL, *sig_scn = NULL;
GElf_Shdr shdr;
Elf_Data *data = NULL;
const char *elf_section = SUNW_ELF_SIGNATURE_ID;
int fscnt, fssize;
struct filesig *fsgp, *fsgpnext;
uint64_t sig_offset = 0;
cryptodebug("elfsign_signature");
if ((ess == NULL) || (fsspp == NULL)) {
cryptodebug("invalid arguments");
return (ELFSIGN_UNKNOWN);
}
cryptodebug("elfsign_signature %s for %s",
ES_ACTISUPDATE(action) ? "ES_UPDATE" : "ES_GET", elf_section);
(void) elf_errno();
while ((scn = elf_nextscn(ess->es_elf, scn)) != NULL) {
const char *sh_name;
/*
* Do a string compare to examine each section header
* to see if this is the section that needs to be updated.
*/
if (gelf_getshdr(scn, &shdr) == NULL) {
cryptodebug("gelf_getshdr() failed: %s",
elf_errmsg(-1));
return (ELFSIGN_FAILED);
}
sh_name = elf_strptr(ess->es_elf, ess->es_shstrndx,
(size_t)shdr.sh_name);
if (strcmp(sh_name, elf_section) == 0) {
cryptodebug("elfsign_signature: found %s", elf_section);
sig_scn = scn;
break;
}
if (shdr.sh_type != SHT_NOBITS &&
sig_offset < shdr.sh_offset + shdr.sh_size) {
sig_offset = shdr.sh_offset + shdr.sh_size;
}
}
if (elf_errmsg(0) != NULL) {
cryptodebug("unexpected error: %s", elf_section,
elf_errmsg(-1));
return (ELFSIGN_FAILED);
}
if (ES_ACTISUPDATE(action) && (sig_scn == NULL)) {
size_t old_size, new_size;
char *new_d_buf;
cryptodebug("elfsign_signature: %s not found - creating",
elf_section);
/*
* insert section name in .shstrtab
*/
if ((scn = elf_getscn(ess->es_elf, ess->es_shstrndx)) == 0) {
cryptodebug("elf_getscn() failed: %s",
elf_errmsg(-1));
return (ELFSIGN_FAILED);
}
if (gelf_getshdr(scn, &shdr) == NULL) {
cryptodebug("gelf_getshdr() failed: %s",
elf_errmsg(-1));
return (ELFSIGN_FAILED);
}
if ((data = elf_getdata(scn, data)) == NULL) {
cryptodebug("elf_getdata() failed: %s",
elf_errmsg(-1));
return (ELFSIGN_FAILED);
}
old_size = data->d_size;
if (old_size != shdr.sh_size) {
cryptodebug("mismatch between data size %d "
"and section size %lld", old_size, shdr.sh_size);
return (ELFSIGN_FAILED);
}
new_size = old_size + strlen(elf_section) + 1;
if ((new_d_buf = malloc(new_size)) == NULL)
return (ELFSIGN_FAILED);
(void) memcpy(new_d_buf, data->d_buf, old_size);
(void) strlcpy(new_d_buf + old_size, elf_section,
new_size - old_size);
data->d_buf = new_d_buf;
data->d_size = new_size;
data->d_align = 1;
/*
* Add the section name passed in to the end of the file.
* Initialize the fields in the Section Header that
* libelf will not fill in.
*/
if ((sig_scn = elf_newscn(ess->es_elf)) == 0) {
cryptodebug("elf_newscn() failed: %s",
elf_errmsg(-1));
return (ELFSIGN_FAILED);
}
if (gelf_getshdr(sig_scn, &shdr) == 0) {
cryptodebug("gelf_getshdr() failed: %s",
elf_errmsg(-1));
return (ELFSIGN_FAILED);
}
shdr.sh_name = old_size;
shdr.sh_type = SHT_SUNW_SIGNATURE;
shdr.sh_flags = SHF_EXCLUDE;
shdr.sh_addr = 0;
shdr.sh_link = 0;
shdr.sh_info = 0;
shdr.sh_size = 0;
shdr.sh_offset = sig_offset;
shdr.sh_addralign = 1;
/*
* Flush the changes to the underlying elf32 or elf64
* section header.
*/
if (gelf_update_shdr(sig_scn, &shdr) == 0) {
cryptodebug("gelf_update_shdr failed");
return (ELFSIGN_FAILED);
}
if ((data = elf_newdata(sig_scn)) == NULL) {
cryptodebug("can't add elf data area for %s: %s",
elf_section, elf_errmsg(-1));
return (ELFSIGN_FAILED);
}
if (elfsign_adjustoffsets(ess, scn,
old_size + strlen(elf_section) + 1) != ELFSIGN_SUCCESS) {
cryptodebug("can't adjust for new section name %s",
elf_section);
return (ELFSIGN_FAILED);
}
} else {
if (sig_scn == NULL) {
cryptodebug("can't find signature section");
*fsspp = NULL;
return (ELFSIGN_NOTSIGNED);
}
if ((data = elf_getdata(sig_scn, NULL)) == 0) {
cryptodebug("can't get section data for %s",
elf_section);
return (ELFSIGN_FAILED);
}
}
if (ES_ACTISUPDATE(action)) {
fssize = offsetof(struct filesignatures, _u1);
if (*fsspp != NULL) {
fsgp = &(*fsspp)->filesig_sig;
for (fscnt = 0; fscnt < (*fsspp)->filesig_cnt;
fscnt++) {
fsgpnext = filesig_next(fsgp);
fssize += (char *)(fsgpnext) - (char *)(fsgp);
fsgp = fsgpnext;
}
}
if (shdr.sh_addr != 0) {
cryptodebug("section %s is part of a loadable segment, "
"it cannot be changed.\n", elf_section);
return (ELFSIGN_FAILED);
}
if ((data->d_buf = malloc(fssize)) == NULL)
return (ELFSIGN_FAILED);
if (*fsspp != NULL) {
(void) memcpy(data->d_buf, *fsspp, fssize);
(void) elfsign_switch(ess,
(struct filesignatures *)data->d_buf, action);
}
data->d_size = fssize;
data->d_align = 1;
data->d_type = ELF_T_BYTE;
cryptodebug("elfsign_signature: data->d_size = %d",
data->d_size);
if (elfsign_adjustoffsets(ess, sig_scn, fssize) !=
ELFSIGN_SUCCESS) {
cryptodebug("can't adjust for revised signature "
"section contents");
return (ELFSIGN_FAILED);
}
} else {
*fsspp = malloc(data->d_size);
if (*fsspp == NULL)
return (ELFSIGN_FAILED);
(void) memcpy(*fsspp, data->d_buf, data->d_size);
if (elfsign_switch(ess, *fsspp, ES_GET) != ELFSIGN_SUCCESS) {
free(*fsspp);
*fsspp = NULL;
return (ELFSIGN_FAILED);
}
*fslen = data->d_size;
}
return (ELFSIGN_SUCCESS);
}
static ELFsign_status_t
elfsign_adjustoffsets(ELFsign_t ess, Elf_Scn *scn, uint64_t new_size)
{
GElf_Ehdr elfehdr;
GElf_Shdr shdr;
uint64_t prev_end, scn_offset;
char *name;
Elf_Scn *scnp;
Elf_Data *data;
ELFsign_status_t retval = ELFSIGN_FAILED;
struct scninfo {
struct scninfo *scni_next;
Elf_Scn *scni_scn;
uint64_t scni_offset;
} *scnip = NULL, *tmpscnip, **scnipp;
/* get the size of the current section */
if (gelf_getshdr(scn, &shdr) == NULL)
return (ELFSIGN_FAILED);
if (shdr.sh_size == new_size)
return (ELFSIGN_SUCCESS);
scn_offset = shdr.sh_offset;
name = elf_strptr(ess->es_elf, ess->es_shstrndx,
(size_t)shdr.sh_name);
if (shdr.sh_flags & SHF_ALLOC && ess->es_has_phdr) {
cryptodebug("elfsign_adjustoffsets: "
"can't move allocated section %s", name ? name : "NULL");
return (ELFSIGN_FAILED);
}
/* resize the desired section */
cryptodebug("elfsign_adjustoffsets: "
"resizing %s at 0x%llx from 0x%llx to 0x%llx",
name ? name : "NULL", shdr.sh_offset, shdr.sh_size, new_size);
shdr.sh_size = new_size;
if (gelf_update_shdr(scn, &shdr) == 0) {
cryptodebug("gelf_update_shdr failed");
goto bad;
}
prev_end = shdr.sh_offset + shdr.sh_size;
/*
* find sections whose data follows the changed section
* must scan all sections since section data may not
* be in same order as section headers
*/
scnp = elf_getscn(ess->es_elf, 0); /* "seek" to start */
while ((scnp = elf_nextscn(ess->es_elf, scnp)) != NULL) {
if (gelf_getshdr(scnp, &shdr) == NULL)
goto bad;
if (shdr.sh_offset <= scn_offset)
continue;
name = elf_strptr(ess->es_elf, ess->es_shstrndx,
(size_t)shdr.sh_name);
if (shdr.sh_flags & SHF_ALLOC && ess->es_has_phdr) {
if (shdr.sh_type == SHT_NOBITS) {
/* .bss can occasionally overlap .shrtab */
continue;
}
cryptodebug("elfsign_adjustoffsets: "
"can't move allocated section %s",
name ? name : "NULL");
goto bad;
}
/*
* force reading of data to memory image
*/
data = NULL;
while ((data = elf_rawdata(scnp, data)) != NULL)
;
/*
* capture section information
* insert into list in order of sh_offset
*/
cryptodebug("elfsign_adjustoffsets: "
"may have to adjust section %s, offset 0x%llx",
name ? name : "NULL", shdr.sh_offset);
tmpscnip = (struct scninfo *)malloc(sizeof (struct scninfo));
if (tmpscnip == NULL) {
cryptodebug("elfsign_adjustoffsets: "
"memory allocation failure");
goto bad;
}
tmpscnip->scni_scn = scnp;
tmpscnip->scni_offset = shdr.sh_offset;
for (scnipp = &scnip; *scnipp != NULL;
scnipp = &(*scnipp)->scni_next) {
if ((*scnipp)->scni_offset > tmpscnip->scni_offset)
break;
}
tmpscnip->scni_next = *scnipp;
*scnipp = tmpscnip;
}
/* move following sections as necessary */
for (tmpscnip = scnip; tmpscnip != NULL;
tmpscnip = tmpscnip->scni_next) {
scnp = tmpscnip->scni_scn;
if (gelf_getshdr(scnp, &shdr) == NULL) {
cryptodebug("elfsign_adjustoffsets: "
"elf_getshdr for section %d failed",
elf_ndxscn(scnp));
goto bad;
}
if (shdr.sh_offset >= prev_end)
break;
prev_end = (prev_end + shdr.sh_addralign - 1) &
(-shdr.sh_addralign);
name = elf_strptr(ess->es_elf, ess->es_shstrndx,
(size_t)shdr.sh_name);
cryptodebug("elfsign_adjustoffsets: "
"moving %s size 0x%llx from 0x%llx to 0x%llx",
name ? name : "NULL", shdr.sh_size,
shdr.sh_offset, prev_end);
shdr.sh_offset = prev_end;
if (gelf_update_shdr(scnp, &shdr) == 0) {
cryptodebug("gelf_update_shdr failed");
goto bad;
}
prev_end = shdr.sh_offset + shdr.sh_size;
}
/*
* adjust section header offset in elf header
*/
if (gelf_getehdr(ess->es_elf, &elfehdr) == NULL) {
cryptodebug("elf_getehdr() failed: %s", elf_errmsg(-1));
goto bad;
}
if (elfehdr.e_shoff < prev_end) {
if (ess->es_ei_class == ELFCLASS32)
prev_end = (prev_end + ELF32_FSZ_OFF - 1) &
(-ELF32_FSZ_OFF);
else if (ess->es_ei_class == ELFCLASS64)
prev_end = (prev_end + ELF64_FSZ_OFF - 1) &
(-ELF64_FSZ_OFF);
cryptodebug("elfsign_adjustoffsets: "
"move sh_off from 0x%llx to 0x%llx",
elfehdr.e_shoff, prev_end);
elfehdr.e_shoff = prev_end;
if (gelf_update_ehdr(ess->es_elf, &elfehdr) == 0) {
cryptodebug("elf_update_ehdr() failed: %s",
elf_errmsg(-1));
goto bad;
}
}
retval = ELFSIGN_SUCCESS;
bad:
while (scnip != NULL) {
tmpscnip = scnip->scni_next;
free(scnip);
scnip = tmpscnip;
}
return (retval);
}
struct filesignatures *
elfsign_insert_dso(ELFsign_t ess,
struct filesignatures *fssp,
const char *dn,
int dn_len,
const uchar_t *sig,
int sig_len,
const char *oid,
int oid_len)
{
return (filesig_insert_dso(fssp, ess->es_version, dn, dn_len,
sig, sig_len, oid, oid_len));
}
/*ARGSUSED*/
filesig_vers_t
elfsign_extract_sig(ELFsign_t ess,
struct filesignatures *fssp,
uchar_t *sig,
size_t *sig_len)
{
struct filesig_extraction fsx;
filesig_vers_t version;
if (fssp == NULL)
return (FILESIG_UNKNOWN);
if (fssp->filesig_cnt != 1)
return (FILESIG_UNKNOWN);
version = filesig_extract(&fssp->filesig_sig, &fsx);
switch (version) {
case FILESIG_VERSION1:
case FILESIG_VERSION2:
case FILESIG_VERSION3:
case FILESIG_VERSION4:
if (*sig_len >= fsx.fsx_sig_len) {
(void) memcpy((char *)sig, (char *)fsx.fsx_signature,
*sig_len);
*sig_len = fsx.fsx_sig_len;
} else
version = FILESIG_UNKNOWN;
break;
default:
version = FILESIG_UNKNOWN;
break;
}
if (ess->es_version == FILESIG_UNKNOWN) {
ess->es_version = version;
}
return (version);
}
static ELFsign_status_t
elfsign_hash_common(ELFsign_t ess, uchar_t *hash, size_t *hash_len,
boolean_t hash_mem_resident)
{
Elf_Scn *scn = NULL;
ELFsign_status_t elfstat;
GElf_Shdr shdr;
SHA1_CTX ctx;
/* The buffer must be large enough to hold the hash */
if (*hash_len < SHA1_DIGEST_LENGTH)
return (ELFSIGN_FAILED);
bzero(hash, *hash_len);
/* Initialize the digest session */
SHA1Init(&ctx);
scn = elf_getscn(ess->es_elf, 0); /* "seek" to start */
(void) elf_errno();
while ((scn = elf_nextscn(ess->es_elf, scn)) != 0) {
char *name = NULL;
Elf_Data *data = NULL;
if (gelf_getshdr(scn, &shdr) == NULL) {
elfstat = ELFSIGN_FAILED;
goto done;
}
name = elf_strptr(ess->es_elf, ess->es_shstrndx,
(size_t)shdr.sh_name);
if (name == NULL)
name = "NULL";
if (!hash_mem_resident &&
(ess->es_version == FILESIG_VERSION1 ||
ess->es_version == FILESIG_VERSION3)) {
/*
* skip the signature section only
*/
if (shdr.sh_type == SHT_SUNW_SIGNATURE) {
cryptodebug("elfsign_hash: skipping %s", name);
continue;
}
} else if (!(shdr.sh_flags & SHF_ALLOC)) {
/*
* select only memory resident sections
*/
cryptodebug("elfsign_hash: skipping %s", name);
continue;
}
/*
* throw this section into the hash
* use elf_rawdata for endian-independence
* use elf_getdata to get update of .shstrtab
*/
while ((data = (shdr.sh_type == SHT_STRTAB ?
elf_getdata(scn, data) : elf_rawdata(scn, data))) != NULL) {
if (data->d_buf == NULL) {
cryptodebug("elfsign_hash: %s has NULL data",
name);
continue;
}
cryptodebug("elfsign_hash: updating hash "
"with %s data size=%d", name, data->d_size);
SHA1Update(&ctx, data->d_buf, data->d_size);
}
}
if (elf_errmsg(0) != NULL) {
cryptodebug("elfsign_hash: %s", elf_errmsg(-1));
elfstat = ELFSIGN_FAILED;
goto done;
}
SHA1Final(hash, &ctx);
*hash_len = SHA1_DIGEST_LENGTH;
{ /* DEBUG START */
const int hashstr_len = (*hash_len) * 2 + 1;
char *hashstr = malloc(hashstr_len);
if (hashstr != NULL) {
tohexstr(hash, *hash_len, hashstr, hashstr_len);
cryptodebug("hash value is: %s", hashstr);
free(hashstr);
}
} /* DEBUG END */
elfstat = ELFSIGN_SUCCESS;
done:
return (elfstat);
}
/*
* elfsign_hash - return the hash of the ELF sections affecting execution.
*
* IN: ess, hash_len
* OUT: hash, hash_len
*/
ELFsign_status_t
elfsign_hash(ELFsign_t ess, uchar_t *hash, size_t *hash_len)
{
return (elfsign_hash_common(ess, hash, hash_len, B_FALSE));
}
/*
* elfsign_hash_mem_resident - return the hash of the ELF sections
* with only memory resident sections.
*
* IN: ess, hash_len
* OUT: hash, hash_len
*/
ELFsign_status_t
elfsign_hash_mem_resident(ELFsign_t ess, uchar_t *hash, size_t *hash_len)
{
return (elfsign_hash_common(ess, hash, hash_len, B_TRUE));
}
/*
* elfsign_verify_signature - Verify the signature of the ELF object.
*
* IN: ess
* OUT: esipp
* RETURNS:
* ELFsign_status_t
*/
ELFsign_status_t
elfsign_verify_signature(ELFsign_t ess, struct ELFsign_sig_info **esipp)
{
ELFsign_status_t ret = ELFSIGN_FAILED;
struct filesignatures *fssp;
struct filesig *fsgp;
size_t fslen;
struct filesig_extraction fsx;
uchar_t hash[SIG_MAX_LENGTH];
size_t hash_len;
ELFCert_t cert = NULL;
int sigcnt;
int nocert = 0;
struct ELFsign_sig_info *esip = NULL;
if (esipp != NULL) {
esip = (struct ELFsign_sig_info *)
calloc(1, sizeof (struct ELFsign_sig_info));
*esipp = esip;
}
/*
* Find out which cert we need, based on who signed the ELF object
*/
if (elfsign_signatures(ess, &fssp, &fslen, ES_GET) != ELFSIGN_SUCCESS) {
return (ELFSIGN_NOTSIGNED);
}
if (fssp->filesig_cnt < 1) {
ret = ELFSIGN_FAILED;
goto cleanup;
}
fsgp = &fssp->filesig_sig;
/*
* Scan the signature block, looking for a verifiable signature
*/
for (sigcnt = 0; sigcnt < fssp->filesig_cnt;
sigcnt++, fsgp = filesig_next(fsgp)) {
ess->es_version = filesig_extract(fsgp, &fsx);
cryptodebug("elfsign_verify_signature: version=%s",
version_to_str(ess->es_version));
switch (ess->es_version) {
case FILESIG_VERSION1:
case FILESIG_VERSION2:
case FILESIG_VERSION3:
case FILESIG_VERSION4:
break;
default:
ret = ELFSIGN_FAILED;
goto cleanup;
}
cryptodebug("elfsign_verify_signature: signer_DN=\"%s\"",
fsx.fsx_signer_DN);
cryptodebug("elfsign_verify_signature: algorithmOID=\"%s\"",
fsx.fsx_sig_oid);
/* return signer DN if requested */
if (esipp != NULL) {
esip->esi_format = fsx.fsx_format;
if (esip->esi_signer != NULL)
free(esip->esi_signer);
esip->esi_signer = strdup(fsx.fsx_signer_DN);
esip->esi_time = fsx.fsx_time;
}
/*
* look for certificate
*/
if (cert != NULL)
elfcertlib_releasecert(ess, cert);
/*
* skip unfound certificates
*/
if (!elfcertlib_getcert(ess, ess->es_certpath,
fsx.fsx_signer_DN, &cert, ess->es_action)) {
cryptodebug("unable to find certificate "
"with DN=\"%s\" for %s",
fsx.fsx_signer_DN, ess->es_pathname);
nocert++;
continue;
}
/*
* skip unverified certificates
* force verification of crypto certs
*/
if ((ess->es_action == ES_GET_CRYPTO ||
ess->es_action == ES_GET_FIPS140 ||
strstr(fsx.fsx_signer_DN, ELFSIGN_CRYPTO)) &&
!elfcertlib_verifycert(ess, cert)) {
cryptodebug("elfsign_verify_signature: invalid cert");
nocert++;
continue;
}
/*
* At this time the only sha1WithRSAEncryption is supported,
* so check that is what we have and skip with anything else.
*/
if (strcmp(fsx.fsx_sig_oid, OID_sha1WithRSAEncryption) != 0) {
continue;
}
nocert = 0;
/*
* compute file hash
*/
hash_len = sizeof (hash);
if (elfsign_hash(ess, hash, &hash_len) != ELFSIGN_SUCCESS) {
cryptodebug("elfsign_verify_signature:"
" elfsign_hash failed");
ret = ELFSIGN_FAILED;
break;
}
{ /* DEBUG START */
const int sigstr_len = fsx.fsx_sig_len * 2 + 1;
char *sigstr = malloc(sigstr_len);
if (sigstr != NULL) {
tohexstr(fsx.fsx_signature, fsx.fsx_sig_len,
sigstr, sigstr_len);
cryptodebug("signature value is: %s", sigstr);
free(sigstr);
}
} /* DEBUG END */
if (elfcertlib_verifysig(ess, cert,
fsx.fsx_signature, fsx.fsx_sig_len, hash, hash_len)) {
if (ess->es_sigvercallback)
(ess->es_sigvercallback)
(ess->es_callbackctx, fssp, fslen, cert);
/*
* The signature is verified!
*/
ret = ELFSIGN_SUCCESS;
}
cryptodebug("elfsign_verify_signature: invalid signature");
}
cleanup:
if (cert != NULL)
elfcertlib_releasecert(ess, cert);
free(fssp);
if (ret == ELFSIGN_FAILED && nocert)
ret = ELFSIGN_INVALID_CERTPATH;
return (ret);
}
static uint32_t
elfsign_switch_uint32(uint32_t i)
{
return (((i & 0xff) << 24) | ((i & 0xff00) << 8) |
((i >> 8) & 0xff00) | ((i >> 24) & 0xff));
}
static uint64_t
elfsign_switch_uint64(uint64_t i)
{
return (((uint64_t)elfsign_switch_uint32(i) << 32) |
(elfsign_switch_uint32(i >> 32)));
}
/*
* If appropriate, switch the endianness of the filesignatures structure
* Examine the structure only when it is in native endianness
*/
static ELFsign_status_t
elfsign_switch(ELFsign_t ess, struct filesignatures *fssp,
enum ES_ACTION action)
{
int fscnt;
filesig_vers_t version;
struct filesig *fsgp, *fsgpnext;
if (ess->es_same_endian)
return (ELFSIGN_SUCCESS);
if (ES_ACTISUPDATE(action))
fscnt = fssp->filesig_cnt;
fssp->filesig_cnt = elfsign_switch_uint32(fssp->filesig_cnt);
if (!ES_ACTISUPDATE(action))
fscnt = fssp->filesig_cnt;
fsgp = &(fssp)->filesig_sig;
for (; fscnt > 0; fscnt--, fsgp = fsgpnext) {
if (ES_ACTISUPDATE(action)) {
version = fsgp->filesig_version;
fsgpnext = filesig_next(fsgp);
}
fsgp->filesig_size =
elfsign_switch_uint32(fsgp->filesig_size);
fsgp->filesig_version =
elfsign_switch_uint32(fsgp->filesig_version);
if (!ES_ACTISUPDATE(action)) {
version = fsgp->filesig_version;
fsgpnext = filesig_next(fsgp);
}
switch (version) {
case FILESIG_VERSION1:
case FILESIG_VERSION2:
fsgp->filesig_v1_dnsize =
elfsign_switch_uint32(fsgp->filesig_v1_dnsize);
fsgp->filesig_v1_sigsize =
elfsign_switch_uint32(fsgp->filesig_v1_sigsize);
fsgp->filesig_v1_oidsize =
elfsign_switch_uint32(fsgp->filesig_v1_oidsize);
break;
case FILESIG_VERSION3:
case FILESIG_VERSION4:
fsgp->filesig_v3_time =
elfsign_switch_uint64(fsgp->filesig_v3_time);
fsgp->filesig_v3_dnsize =
elfsign_switch_uint32(fsgp->filesig_v3_dnsize);
fsgp->filesig_v3_sigsize =
elfsign_switch_uint32(fsgp->filesig_v3_sigsize);
fsgp->filesig_v3_oidsize =
elfsign_switch_uint32(fsgp->filesig_v3_oidsize);
break;
default:
cryptodebug("elfsign_switch: failed");
return (ELFSIGN_FAILED);
}
}
return (ELFSIGN_SUCCESS);
}
/*
* get/put an integer value from/to a buffer, possibly of opposite endianness
*/
void
elfsign_buffer_len(ELFsign_t ess, size_t *ip, uchar_t *cp,
enum ES_ACTION action)
{
uint32_t tmp;
if (!ES_ACTISUPDATE(action)) {
/* fetch integer from buffer */
(void) memcpy(&tmp, cp, sizeof (tmp));
if (!ess->es_same_endian) {
tmp = elfsign_switch_uint32(tmp);
}
*ip = tmp;
} else {
/* put integer into buffer */
tmp = *ip;
if (!ess->es_same_endian) {
tmp = elfsign_switch_uint32(tmp);
}
(void) memcpy(cp, &tmp, sizeof (tmp));
}
}
char const *
elfsign_strerror(ELFsign_status_t elferror)
{
char const *msg = NULL;
switch (elferror) {
case ELFSIGN_SUCCESS:
msg = gettext("sign or verify of ELF object succeeded");
break;
case ELFSIGN_FAILED:
msg = gettext("sign or verify of ELF object failed");
break;
case ELFSIGN_NOTSIGNED:
msg = gettext("ELF object not signed");
break;
case ELFSIGN_INVALID_CERTPATH:
msg = gettext("cannot access certificate");
break;
case ELFSIGN_INVALID_ELFOBJ:
msg = gettext("unable to open as an ELF object");
break;
case ELFSIGN_UNKNOWN:
default:
msg = gettext("Unknown error");
break;
}
return (msg);
}
boolean_t
elfsign_sig_info(struct filesignatures *fssp, struct ELFsign_sig_info **esipp)
{
struct filesig_extraction fsx;
struct ELFsign_sig_info *esip;
esip = (struct ELFsign_sig_info *)
calloc(1, sizeof (struct ELFsign_sig_info));
*esipp = esip;
if (esip == NULL)
return (B_FALSE);
switch (filesig_extract(&fssp->filesig_sig, &fsx)) {
case FILESIG_VERSION1:
case FILESIG_VERSION2:
case FILESIG_VERSION3:
case FILESIG_VERSION4:
esip->esi_format = fsx.fsx_format;
esip->esi_signer = strdup(fsx.fsx_signer_DN);
esip->esi_time = fsx.fsx_time;
break;
default:
free(esip);
*esipp = NULL;
}
return (*esipp != NULL);
}
void
elfsign_sig_info_free(struct ELFsign_sig_info *esip)
{
if (esip != NULL) {
free(esip->esi_signer);
free(esip);
}
}