diff --git a/src/borg/_hashindex.c b/src/borg/_hashindex.c index 289457fe5..b41d57b70 100644 --- a/src/borg/_hashindex.c +++ b/src/borg/_hashindex.c @@ -291,6 +291,20 @@ hashindex_read(PyObject *file_py) goto fail_decref_header; } + /* + * Hash the header + * If the header is corrupted this bails before doing something stupid (like allocating 3.8 TB of memory) + */ + Py_XDECREF(PyObject_CallMethod(file_py, "hash_part", "s", "HashHeader")); + if(PyErr_Occurred()) { + if(PyErr_ExceptionMatches(PyExc_AttributeError)) { + /* Be able to work with regular file objects which do not have a hash_part method. */ + PyErr_Clear(); + } else { + goto fail_decref_header; + } + } + /* Find length of file */ length_object = PyObject_CallMethod(file_py, "seek", "ni", (Py_ssize_t)0, SEEK_END); if(PyErr_Occurred()) { @@ -473,6 +487,19 @@ hashindex_write(HashIndex *index, PyObject *file_py) return; } + /* + * Hash the header + */ + Py_XDECREF(PyObject_CallMethod(file_py, "hash_part", "s", "HashHeader")); + if(PyErr_Occurred()) { + if(PyErr_ExceptionMatches(PyExc_AttributeError)) { + /* Be able to work with regular file objects which do not have a hash_part method. */ + PyErr_Clear(); + } else { + return; + } + } + /* Note: explicitly construct view; BuildValue can convert (pointer, length) to Python objects, but copies them for doing so */ buckets_view = PyMemoryView_FromMemory((char*)index->buckets, buckets_length, PyBUF_READ); if(!buckets_view) { diff --git a/src/borg/testsuite/hashindex.py b/src/borg/testsuite/hashindex.py index 116399071..120c01b44 100644 --- a/src/borg/testsuite/hashindex.py +++ b/src/borg/testsuite/hashindex.py @@ -6,6 +6,7 @@ import zlib from ..hashindex import NSIndex, ChunkIndex from .. import hashindex +from ..crypto.file_integrity import IntegrityCheckedFile, FileIntegrityError from . import BaseTestCase # Note: these tests are part of the self test, do not use or import py.test functionality here. @@ -319,6 +320,27 @@ class HashIndexDataTestCase(BaseTestCase): assert idx1[H(3)] == (ChunkIndex.MAX_VALUE, 6, 7) +class HashIndexIntegrityTestCase(HashIndexDataTestCase): + def write_integrity_checked_index(self, tempdir): + idx = self._deserialize_hashindex(self.HASHINDEX) + file = os.path.join(tempdir, 'idx') + with IntegrityCheckedFile(path=file, write=True) as fd: + idx.write(fd) + integrity_data = fd.integrity_data + assert 'final' in integrity_data + assert 'HashHeader' in integrity_data + return file, integrity_data + + def test_integrity_checked_file(self): + with tempfile.TemporaryDirectory() as tempdir: + file, integrity_data = self.write_integrity_checked_index(tempdir) + with open(file, 'r+b') as fd: + fd.write(b'Foo') + with self.assert_raises(FileIntegrityError): + with IntegrityCheckedFile(path=file, write=False, integrity_data=integrity_data) as fd: + ChunkIndex.read(fd) + + class NSIndexTestCase(BaseTestCase): def test_nsindex_segment_limit(self): idx = NSIndex()