--- a/src/modules/actions/file.py Thu Dec 08 03:40:53 2016 +0530
+++ b/src/modules/actions/file.py Thu Dec 08 03:40:55 2016 +0530
@@ -415,7 +415,7 @@
else:
get_sha256 = False
get_sha1 = True
- elfhash = elf.get_dynamic(path,
+ elfhash = elf.get_hashes(path,
sha1=get_sha1,
sha256=get_sha256)[ehash_attr]
except RuntimeError, e:
--- a/src/modules/elf.c Thu Dec 08 03:40:53 2016 +0530
+++ b/src/modules/elf.c Thu Dec 08 03:40:55 2016 +0530
@@ -20,7 +20,7 @@
*/
/*
- * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
*/
#include <sys/stat.h>
@@ -52,7 +52,7 @@
int fd;
int sha1;
int sha256;
-} dargs_t;
+} hargs_t;
static int
pythonify_ver_liblist_cb(libnode_t *n, void *info, void *info2)
@@ -130,40 +130,41 @@
return (fd);
}
-static dargs_t
-py_get_dyn_args(PyObject *args, PyObject *kwargs)
+static hargs_t
+py_get_hash_args(PyObject *args, PyObject *kwargs)
{
int fd = -1;
char *f;
int get_sha1 = 1;
int get_sha256 = 0;
- dargs_t dargs;
- dargs.fd = -1;
+ hargs_t hargs;
+ hargs.fd = -1;
+
/*
* By default, we always get an SHA-1 hash, and never get an SHA-2
* hash.
*/
- dargs.sha1 = 1;
- dargs.sha256 = 0;
+ hargs.sha1 = 1;
+ hargs.sha256 = 0;
static char *kwlist[] = {"fd", "sha1", "sha256", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ii", kwlist, &f,
&get_sha1, &get_sha256)) {
PyErr_SetString(PyExc_ValueError, "could not parse argument");
- return (dargs);
+ return (hargs);
}
if ((fd = open(f, O_RDONLY)) < 0) {
PyErr_SetFromErrnoWithFilename(PyExc_OSError, f);
- return (dargs);
+ return (hargs);
}
- dargs.fd = fd;
- dargs.sha1 = get_sha1;
- dargs.sha256 = get_sha256;
- return (dargs);
+ hargs.fd = fd;
+ hargs.sha1 = get_sha1;
+ hargs.sha256 = get_sha256;
+ return (hargs);
}
/*
@@ -236,11 +237,81 @@
}
/*
- * Returns a dictionary with the relevant information. No longer
- * accurately titled "get_dynamic," as can return hashes as well.
+ * Returns a dictionary with the requested hash(es).
+ *
+ * Dictionary format:
+ *
+ * {
+ * elfhash: "sha1hash",
+ * pkg.content-type.sha256: "sha2hash"
+ * }
+ *
+ * If a hash was not requested, it is omitted from the dictionary.
*
- * The hash is currently of the following sections (when present):
- * .text .data .data1 .rodata .rodata1
+ */
+/*ARGSUSED*/
+static PyObject *
+get_hashes(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ hargs_t hargs;
+ hashinfo_t *h = NULL;
+ PyObject *pdict = NULL;
+ char hexchars[17] = "0123456789abcdef";
+
+ hargs = py_get_hash_args(args, keywords);
+ if (hargs.fd < 0)
+ return (NULL);
+
+ if ((h = gethashes(hargs.fd, hargs.sha1, hargs.sha256)) == NULL)
+ goto out;
+
+ if ((pdict = PyDict_New()) == NULL)
+ goto out;
+
+ if (hargs.sha1 > 0) {
+ int i;
+ char hexhash[41];
+
+ for (i = 0; i < 20; i++) {
+ hexhash[2 * i] = hexchars[(h->hash[i] & 0xf0) >> 4];
+ hexhash[2 * i + 1] = hexchars[h->hash[i] & 0x0f];
+ }
+ hexhash[40] = '\0';
+ if (PyDict_SetItemString(pdict, "elfhash",
+ Py_BuildValue("s", hexhash)) != 0)
+ goto pyerror;
+ }
+
+ if (hargs.sha256 > 0) {
+ int i;
+ char hexhash[65];
+
+ for (i = 0; i < 32; i++) {
+ hexhash[2 * i] = hexchars[(h->hash256[i] & 0xf0) >> 4];
+ hexhash[2 * i + 1] = hexchars[h->hash256[i] & 0x0f];
+ }
+ hexhash[64] = '\0';
+ if (PyDict_SetItemString(pdict, "pkg.content-type.sha256",
+ Py_BuildValue("s", hexhash)) != 0)
+ goto pyerror;
+ }
+
+ goto out;
+
+pyerror:
+ Py_CLEAR(pdict);
+
+out:
+ (void) close(hargs.fd);
+
+ if (h != NULL)
+ free(h);
+
+ return (pdict);
+}
+
+/*
+ * Returns a dictionary with the relevant information.
*
* Dictionary format:
*
@@ -248,17 +319,11 @@
* runpath: "/path:/entries",
* defs: ["version", ... ],
* deps: [["file", ["versionlist"]], ...],
- * elfhash: "sha1hash"
- * pkg.elf.sha256: "sha2hash"
* }
*
* If any item is empty or has no value, it is omitted from the
* dictionary.
*
- * The keyword arguments "sha1" and "sha256" are allowed, which
- * take Python booleans, declaring which hashes should be
- * computed on the input file.
- *
* XXX: Currently, defs contains some duplicate entries. There
* may be meaning attached to this, or it may just be something
* worth trimming out at this stage or above.
@@ -266,47 +331,52 @@
*/
/*ARGSUSED*/
static PyObject *
-get_dynamic(PyObject *self, PyObject *args, PyObject *keywords)
+get_dynamic(PyObject *self, PyObject *args)
{
- int i;
- dargs_t dargs;
+ int i;
+ int fd;
dyninfo_t *dyn = NULL;
PyObject *pdep = NULL;
PyObject *pdef = NULL;
PyObject *pdict = NULL;
- char hexhash[41];
- char hexsha256[65];
- char hexchars[17] = "0123456789abcdef";
- dargs = py_get_dyn_args(args, keywords);
- if (dargs.fd < 0)
+ fd = py_get_fd(args);
+ if (fd < 0)
return (NULL);
- if ((dyn = getdynamic(dargs.fd, dargs.sha1, dargs.sha256)) == NULL)
+ if ((dyn = getdynamic(fd)) == NULL)
goto out;
- pdict = PyDict_New();
+ if ((pdict = PyDict_New()) == NULL)
+ goto out;
+
if (dyn->deps->head) {
- pdep = PyList_New(0);
+ if ((pdep = PyList_New(0)) == NULL)
+ goto err;
if (liblist_foreach(
dyn->deps, pythonify_2dliblist_cb, pdep, dyn) == -1)
goto err;
- PyDict_SetItemString(pdict, "deps", pdep);
+ if (PyDict_SetItemString(pdict, "deps", pdep) != 0)
+ goto err;
}
if (dyn->def) {
char *str;
- pdef = PyList_New(0);
+ if ((pdef = PyList_New(0)) == NULL)
+ goto err;
if (liblist_foreach(
dyn->vers, pythonify_1dliblist_cb, pdef, dyn) == -1)
goto err;
- PyDict_SetItemString(pdict, "vers", pdef);
+ if (PyDict_SetItemString(pdict, "vers", pdef) != 0)
+ goto err;
if ((str = elf_strptr(
dyn->elf, dyn->dynstr, dyn->def)) == NULL) {
PyErr_SetString(ElfError, elf_errmsg(-1));
goto err;
}
- PyDict_SetItemString(pdict, "def", Py_BuildValue("s", str));
+ if (PyDict_SetItemString(pdict, "def",
+ Py_BuildValue("s", str)) != 0)
+ goto err;
}
if (dyn->runpath) {
char *str;
@@ -316,49 +386,30 @@
PyErr_SetString(ElfError, elf_errmsg(-1));
goto err;
}
- PyDict_SetItemString(pdict, "runpath", Py_BuildValue("s", str));
+ if (PyDict_SetItemString(pdict, "runpath",
+ Py_BuildValue("s", str)) != 0)
+ goto err;
}
- if (dargs.sha1 > 0) {
- for (i = 0; i < 20; i++) {
- hexhash[2 * i] = hexchars[(dyn->hash[i] & 0xf0) >> 4];
- hexhash[2 * i + 1] = hexchars[dyn->hash[i] & 0x0f];
- }
- hexhash[40] = '\0';
- PyDict_SetItemString(pdict, "elfhash",
- Py_BuildValue("s", hexhash));
- }
-
- if (dargs.sha256 > 0) {
- for (i = 0; i < 32; i++) {
- hexsha256[2 * i] = \
- hexchars[(dyn->hash256[i] & 0xf0) >> 4];
- hexsha256[2 * i + 1] = hexchars[dyn->hash256[i] & 0x0f];
- }
- hexsha256[64] = '\0';
- PyDict_SetItemString(pdict, "pkg.content-type.sha256",
- Py_BuildValue("s", hexsha256));
- }
goto out;
err:
- PyDict_Clear(pdict);
- Py_DECREF(pdict);
- pdict = NULL;
+ Py_CLEAR(pdict);
out:
if (dyn != NULL)
dyninfo_free(dyn);
- (void) close(dargs.fd);
+ (void) close(fd);
return (pdict);
}
static PyMethodDef methods[] = {
{ "is_elf_object", elf_is_elf_object, METH_VARARGS },
{ "get_info", get_info, METH_VARARGS },
- { "get_dynamic", (PyCFunction)get_dynamic,
- METH_VARARGS | METH_KEYWORDS},
+ { "get_dynamic", (PyCFunction)get_dynamic, METH_VARARGS },
+ { "get_hashes", (PyCFunction)get_hashes,
+ METH_VARARGS | METH_KEYWORDS },
{ NULL, NULL }
};
--- a/src/modules/elfextract.c Thu Dec 08 03:40:53 2016 +0530
+++ b/src/modules/elfextract.c Thu Dec 08 03:40:55 2016 +0530
@@ -20,12 +20,13 @@
*/
/*
- * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
*/
#include <libelf.h>
#include <gelf.h>
+#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
@@ -255,75 +256,32 @@
return (hi);
}
-/*
- * For ELF nontriviality: Need to turn an ELF object into a unique hash.
- *
- * From Eric Saxe's investigations, we see that the following sections can
- * generally be ignored:
- *
- * .SUNW_signature, .comment, .SUNW_dof, .debug, .plt, .rela.bss,
- * .rela.plt, .line, .note
- *
- * Conversely, the following sections are generally significant:
- *
- * .rodata.str1.8, .rodata.str1.1, .rodata, .data1, .data, .text
- *
- * Accordingly, we will hash on the latter group of sections to determine our
- * ELF hash.
- */
-static int
-hashsection(char *name)
-{
- if (strcmp(name, ".SUNW_signature") == 0 ||
- strcmp(name, ".comment") == 0 ||
- strcmp(name, ".SUNW_dof") == 0 ||
- strcmp(name, ".debug") == 0 ||
- strcmp(name, ".plt") == 0 ||
- strcmp(name, ".rela.bss") == 0 ||
- strcmp(name, ".rela.plt") == 0 ||
- strcmp(name, ".line") == 0 ||
- strcmp(name, ".note") == 0 ||
- strcmp(name, ".compcom") == 0)
- return (0);
-
- return (1);
-}
+typedef struct hashcb_data {
+ char *base;
+ SHA1_CTX *s1c;
+ SHA256_CTX *s2c;
+} hashcb_data_t;
/*
- * Reads a section in 64k increments, adding it to the hash.
+ * Hashes a range from an mmap'd elf file
*/
-static int
-readhash(int fd, SHA1_CTX *shc, SHA256_CTX *shc2, off_t offset, off_t size,
- int sha1, int sha256)
+static void
+elfhash_cb(size_t offset, size_t size, void *udata)
{
- off_t n;
- char hashbuf[64 * 1024];
- ssize_t rbytes;
+ hashcb_data_t *h = (hashcb_data_t *)udata;
if (!size)
- return (0);
+ return;
- if (lseek(fd, offset, SEEK_SET) == -1) {
- PyErr_SetFromErrno(PyExc_IOError);
- return (-1);
+ if (h->s1c != NULL) {
+ SHA1Update(h->s1c, h->base + offset, size);
}
- do {
- n = MIN(size, sizeof (hashbuf));
- if ((rbytes = read(fd, hashbuf, n)) == -1) {
- PyErr_SetFromErrno(PyExc_IOError);
- return (-1);
- }
- if (sha1 > 0) {
- SHA1Update(shc, hashbuf, rbytes);
- }
- if (sha256 > 0) {
- SHA256Update(shc2, hashbuf, rbytes);
- }
- size -= rbytes;
- } while (size != 0);
+ if (h->s2c != NULL) {
+ SHA256Update(h->s2c, h->base + offset, size);
+ }
- return (0);
+ return;
}
/*
@@ -331,11 +289,9 @@
* information we want from an ELF file. Returns NULL
* if it can't find everything (eg. not ELF file, wrong
* class of ELF file).
- * If sha1 is > 0, we produce an SHA1 hash as part of the returned dictionary.
- * If sha256 is > 0, we include an SHA2 256 hash in the returned dictionary.
*/
dyninfo_t *
-getdynamic(int fd, int sha1, int sha256)
+getdynamic(int fd)
{
Elf *elf = NULL;
Elf_Scn *scn = NULL;
@@ -383,12 +339,6 @@
}
/* get useful sections */
- if (sha1 > 0) {
- SHA1Init(&shc);
- }
- if (sha256 > 0) {
- SHA256Init(&shc2);
- }
while ((scn = elf_nextscn(elf, scn))) {
if (gelf_getshdr(scn, &shdr) != &shdr) {
PyErr_SetString(ElfError, elf_errmsg(-1));
@@ -400,36 +350,6 @@
goto bad;
}
- if (hashsection(name) && (sha1 > 0 || sha256 > 0)) {
- if (shdr.sh_type == SHT_NOBITS) {
- /*
- * We can't just push shdr.sh_size into
- * SHA1Update(), as its raw bytes will be
- * different on x86 than they are on sparc.
- * Convert to network byte-order first.
- */
- uint64_t n = shdr.sh_size;
- uint64_t mask = 0xffffffff00000000ULL;
- uint32_t top = htonl((uint32_t)((n & mask) >> 32));
- uint32_t bot = htonl((uint32_t)n);
- if (sha1 > 0) {
- SHA1Update(&shc, &top, sizeof (top));
- SHA1Update(&shc, &bot, sizeof (bot));
- }
- if (sha256 > 0) {
- SHA256Update(&shc2, &top, sizeof (top));
- SHA256Update(&shc2, &bot, sizeof (bot));
- }
- } else {
- int hash;
- hash = readhash(fd, &shc, &shc2, shdr.sh_offset,
- shdr.sh_size, sha1, sha256);
-
- if (hash == -1)
- goto bad;
- }
- }
-
switch (shdr.sh_type) {
case SHT_DYNAMIC:
if (!(data_dyn = elf_getdata(scn, NULL))) {
@@ -604,12 +524,6 @@
dyn->deps = deps;
dyn->def = def;
dyn->vers = verdef;
- if (sha1 > 0) {
- SHA1Final(dyn->hash, &shc);
- }
- if (sha256 > 0) {
- SHA256Final(dyn->hash256, &shc2);
- }
return (dyn);
bad:
@@ -632,3 +546,80 @@
(void) elf_end(dyn->elf);
free(dyn);
}
+
+/*
+ * gethashes - returns a struct filled with the hashes computed from an ELF
+ * file. Returns NULL if it encounters a problem (eg. not ELF file, offset out
+ * of range).
+ */
+hashinfo_t *
+gethashes(int fd, int dosha1, int dosha2)
+{
+ hashinfo_t *hashes = NULL;
+ hashcb_data_t hdata = { NULL, NULL, NULL };
+ Elf *elf;
+ struct stat status;
+
+ if ((elf_version(EV_CURRENT) == EV_NONE) ||
+ ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)) {
+ PyErr_SetString(ElfError, elf_errmsg(-1));
+ return (NULL);
+ }
+
+ if ((fstat(fd, &status) == -1) ||
+ ((hdata.base = mmap(NULL, status.st_size, PROT_READ, MAP_PRIVATE,
+ fd, 0)) == MAP_FAILED)) {
+ PyErr_SetString(ElfError, strerror(errno));
+ goto hash_out;
+ }
+
+ if (dosha1 > 0) {
+ hdata.s1c = (SHA1_CTX *)malloc(sizeof (SHA1_CTX));
+ if (hdata.s1c == NULL) {
+ (void) PyErr_NoMemory();
+ goto hash_out;
+ }
+ SHA1Init(hdata.s1c);
+ }
+
+ if (dosha2 > 0) {
+ hdata.s2c = (SHA256_CTX *)malloc(sizeof (SHA256_CTX));
+ if (hdata.s2c == NULL) {
+ (void) PyErr_NoMemory();
+ goto hash_out;
+ }
+ SHA256Init(hdata.s2c);
+ }
+
+ if (!gelf_sign_range(elf, elfhash_cb, ELF_SR_INTERPRET, &hdata)) {
+ PyErr_SetString(ElfError, elf_errmsg(-1));
+ goto hash_out;
+ }
+
+ if ((hashes = malloc(sizeof (hashinfo_t))) == NULL) {
+ (void) PyErr_NoMemory();
+ goto hash_out;
+ }
+
+ if (dosha1 > 0) {
+ SHA1Final(hashes->hash, hdata.s1c);
+ }
+
+ if (dosha2 > 0) {
+ SHA256Final(hashes->hash256, hdata.s2c);
+ }
+
+hash_out:
+ (void) elf_end(elf);
+
+ if (hdata.base)
+ munmap(hdata.base, status.st_size);
+
+ if (hdata.s1c)
+ free(hdata.s1c);
+
+ if (hdata.s2c)
+ free(hdata.s2c);
+
+ return (hashes);
+}
--- a/src/modules/elfextract.h Thu Dec 08 03:40:53 2016 +0530
+++ b/src/modules/elfextract.h Thu Dec 08 03:40:55 2016 +0530
@@ -20,7 +20,7 @@
*/
/*
- * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
*/
#ifndef _ELFEXTRACT_H
@@ -46,11 +46,13 @@
/* offsets) */
liblist_t *vers; /* version provided list (also */
/* contains offsets) */
+ Elf *elf; /* elf data -- must be freed */
+} dyninfo_t;
+
+typedef struct hashinfo {
unsigned char hash[20]; /* SHA1 Hash of significant segs. */
unsigned char hash256[32]; /* SHA2 Hash of significant segs. */
-
- Elf *elf; /* elf data -- must be freed */
-} dyninfo_t;
+} hashinfo_t;
typedef struct hdrinfo {
int type; /* e_type */
@@ -62,7 +64,8 @@
extern int iself(int fd);
extern int iself32(int fd);
-extern dyninfo_t *getdynamic(int fd, int sha1, int sha256);
+extern dyninfo_t *getdynamic(int fd);
+extern hashinfo_t *gethashes(int fd, int sha1, int sha256);
extern void dyninfo_free(dyninfo_t *dyn);
extern hdrinfo_t *getheaderinfo(int fd);
--- a/src/modules/flavor/elf.py Thu Dec 08 03:40:53 2016 +0530
+++ b/src/modules/flavor/elf.py Thu Dec 08 03:40:55 2016 +0530
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
#
import os
@@ -176,7 +176,7 @@
try:
ei = elf.get_info(proto_file)
- ed = elf.get_dynamic(proto_file, sha1=False, sha256=False)
+ ed = elf.get_dynamic(proto_file)
except elf.ElfError, e:
raise BadElfFile(proto_file, e)
deps = [
--- a/src/modules/server/transaction.py Thu Dec 08 03:40:53 2016 +0530
+++ b/src/modules/server/transaction.py Thu Dec 08 03:40:55 2016 +0530
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
#
import calendar
@@ -517,16 +517,15 @@
else:
get_sha1 = False
- dyn = elf.get_dynamic(
- elf_name, sha1=get_sha1,
- sha256=get_sha256)
+ hashes = elf.get_hashes(elf_name,
+ sha1=get_sha1, sha256=get_sha256)
if get_sha1:
- action.attrs[elf1] = dyn[elf1]
+ action.attrs[elf1] = hashes[elf1]
if get_sha256:
action.attrs[elf256] = \
- dyn[elf256]
+ hashes[elf256]
except elf.ElfError:
pass
--- a/src/tests/api/t_elf.py Thu Dec 08 03:40:53 2016 +0530
+++ b/src/tests/api/t_elf.py Thu Dec 08 03:40:55 2016 +0530
@@ -20,7 +20,7 @@
# CDDL HEADER END
#
-# Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
import testutils
if __name__ == "__main__":
@@ -41,7 +41,6 @@
elf_paths = [
"/usr/bin/mdb",
"/usr/bin/__ARCH__/mdb",
- "/dev/ksyms",
"/usr/lib/libc.so",
"/usr/lib/__ARCH__/libc.so",
"/usr/lib/crti.o",
@@ -59,6 +58,7 @@
os.chdir(self.test_root)
self.assertEqual(elf.is_elf_object(p), False)
self.assertRaises(elf.ElfError, elf.get_dynamic, p)
+ self.assertRaises(elf.ElfError, elf.get_hashes, p)
self.assertRaises(elf.ElfError, elf.get_info, p)
def test_non_existent(self):
@@ -68,6 +68,7 @@
p = "does/not/exist"
self.assertRaises(OSError, elf.is_elf_object, p)
self.assertRaises(OSError, elf.get_dynamic, p)
+ self.assertRaises(OSError, elf.get_hashes, p)
self.assertRaises(OSError, elf.get_info, p)
def test_valid_elf(self):
@@ -79,10 +80,11 @@
self.assert_(os.path.exists(p), "%s does not exist" % p)
self.assertEqual(elf.is_elf_object(p), True)
elf.get_dynamic(p)
+ elf.get_hashes(p)
elf.get_info(p)
- def test_get_dynamic_params(self):
- """Test that get_dynamic(..) returns checksums according to the
+ def test_get_hashes_params(self):
+ """Test that get_hashes(..) returns checksums according to the
parameters passed to the method."""
# Check that the hashes generated have the correct length
@@ -91,19 +93,19 @@
sha256_len = 64
# the default is to return an SHA-1 elfhash only
- d = elf.get_dynamic(self.elf_paths[0])
+ d = elf.get_hashes(self.elf_paths[0])
self.assert_(len(d["elfhash"]) == sha1_len)
self.assert_("pkg.content-type.sha256" not in d)
- d = elf.get_dynamic(self.elf_paths[0], sha256=True)
+ d = elf.get_hashes(self.elf_paths[0], sha256=True)
self.assert_(len(d["elfhash"]) == sha1_len)
self.assert_(len(d["pkg.content-type.sha256"]) == sha256_len)
- d = elf.get_dynamic(self.elf_paths[0], sha1=False, sha256=True)
+ d = elf.get_hashes(self.elf_paths[0], sha1=False, sha256=True)
self.assert_("elfhash" not in d)
self.assert_(len(d["pkg.content-type.sha256"]) == sha256_len)
- d = elf.get_dynamic(self.elf_paths[0], sha1=False, sha256=False)
+ d = elf.get_hashes(self.elf_paths[0], sha1=False, sha256=False)
self.assert_("elfhash" not in d)
self.assert_("pkg.content-type.sha256" not in d)