codesign --sign --force breaks disk images
| Originator: | mark | ||
| Number: | rdar://27402026 | Date Originated: | 2016-07-18 |
| Status: | Open | Resolved: | |
| Product: | OS X | Product Version: | 10.11.5 15F34 |
| Classification: | Serious Bug | Reproducible: | Sometimes |
Disk image signing is available in 10.11.4 and later. https://developer.apple.com/library/prerelease/content/technotes/tn2206/_index.html#//apple_ref/doc/uid/DTS40007919-CH1-TNTAG18
When re-signing an already-signed disk image with codesign --sign --force, the disk image may be broken, rendered unreadable. This happens on approximately half of all re-sign attempts.
UDIF disk image is normally laid out with its UDIF header occupying the final 512 bytes of the file. The UDIF header is identified by its magic number (“koly”) and version number (currently 4). When codesign --sign adds a signature to an unsigned disk image, the signature is placed at the same file offset where the UDIF header had been located, and the UDIF header is re-written after the signature so that it still occupies the final 512 bytes of the file.
When codesign --sign --force re-signs an already-signed disk image, the signature is placed at the same file offset as the existing signature, replacing it, and the UDIF header is re-written after the signature. If the new signature is the same length as the existing signature, or if the new signature is longer, the disk image file remains the same size or grows, and the disk image remains usable. However, if the new signature is shorter than the old signature, the UDIF header as re-written is not in the correct location, because the disk image file is not truncated immediately following the UDIF header. The disk image file remains the same size, but the UDIF header is more than 512 bytes from the end of the file.
Steps to Reproduce:
Create a disk image file, sign it with codesign --sign, and then re-sign it with codesign --sign --force repeatedly until the disk image is rendered unusable (or until you’ve done it enough times that you’ve seen the disk image file size shrink and can ascertain that it’s behaving correctly):
$ mkdir empty
$ hdiutil create -srcdir empty -fs HFS+ -format UDZO -imagekey zlib-level=9 -o empty.dmg
.
created: …/empty.dmg
$ codesign --sign='Developer ID Application: Me' empty.dmg
Now repeat these next two commands:
$ codesign --sign='Developer ID Application: Me' --force empty.dmg
empty.dmg: replacing existing signature
$ hdiutil imageinfo empty.dmg > /dev/null
Expected Results:
$ mkdir empty
$ hdiutil create -srcdir empty -fs HFS+ -format UDZO -imagekey zlib-level=9 -o empty.dmg
.
created: …/empty.dmg
$ codesign --sign='Developer ID Application: Me' empty.dmg
$ codesign --sign='Developer ID Application: Me' --force empty.dmg
empty.dmg: replacing existing signature
$ hdiutil imageinfo empty.dmg > /dev/null
$ codesign --sign='Developer ID Application: Me' --force empty.dmg
empty.dmg: replacing existing signature
$ hdiutil imageinfo empty.dmg > /dev/null
$ codesign --sign='Developer ID Application: Me' --force empty.dmg
empty.dmg: replacing existing signature
$ hdiutil imageinfo empty.dmg > /dev/null
(No failures detected, and at some point, you observe empty.dmg shrinking in size and can stop testing.)
$ codesign --verify --verbose empty.dmg
empty.dmg: valid on disk
empty.dmg: satisfies its Designated Requirement
Actual Results:
$ mkdir empty
$ hdiutil create -srcdir empty -fs HFS+ -format UDZO -imagekey zlib-level=9 -o empty.dmg
.
created: …/empty.dmg
$ codesign --sign='Developer ID Application: Me' empty.dmg
$ codesign --sign='Developer ID Application: Me' --force empty.dmg
empty.dmg: replacing existing signature
$ hdiutil imageinfo empty.dmg > /dev/null
$ codesign --sign='Developer ID Application: Me' --force empty.dmg
empty.dmg: replacing existing signature
$ hdiutil imageinfo empty.dmg > /dev/null
hdiutil: imageinfo failed - image not recognized
$ codesign --verify --verbose empty.dmg
empty.dmg: code object is not signed at all
At this point, you can see that codesign has broken the disk image. Neither hdiutil nor codesign can make any sense of it as a disk image any longer. You can examine what’s supposed to be the UDIF header in the final 512 bytes of the file:
$ tail -c 512 empty.dmg | hexdump -C
00000000 6f 6c 79 00 00 00 04 00 00 02 00 00 00 00 01 00 |oly.............|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000020 00 00 00 00 00 08 23 00 00 00 00 00 00 00 00 00 |......#.........|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 08 23 00 |..............#.|
000000e0 00 00 00 00 00 1f 99 00 00 00 00 00 00 00 00 00 |................|
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 27 bc 00 |.............'..|
00000130 00 00 00 00 00 24 98 00 00 00 00 00 00 00 00 00 |.....$..........|
00000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000160 00 00 02 00 00 00 20 7b a8 58 9d 00 00 00 00 00 |...... {.X......|
00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001e0 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 |................|
000001f0 00 07 c8 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000200
Here, you can see because of the incomplete magic number (should be “koly”) that the file should have been truncated to be one byte shorter than it currently is. If the file is truncated to this length, it begins working again:
$ stat -f %z empty.dmg
20053
$ python
[…]
>>> import os
>>> os.ftruncate(os.open("empty.dmg", os.O_WRONLY), 20052)
>>> ^D
$ hdiutil imageinfo empty.dmg > /dev/null
$ codesign --verify --verbose empty.dmg
empty.dmg: valid on disk
empty.dmg: satisfies its Designated Requirement
Version:
10.11.5 15F34
10.12dp2 16A239j
Comments
Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!
TN2206 now contains this note acknowledging the bug:
Note: A disk image signed on OS X 10.11.5 or 10.11.6 may not be able to be re-signed. In this situation, the operation will appear to succeed, but the signature will be invalid. If you encounter this condition, sign a new (unsigned) copy of the image on macOS Sierra or later.
https://developer.apple.com/library/content/technotes/tn2206/
04-Aug-2016 02:12 PM
This appears to have been fixed in 10.12db4 16A270f.