diff --git a/Library/Homebrew/extend/os/mac/keg.rb b/Library/Homebrew/extend/os/mac/keg.rb index a6ad435634..6bd927012d 100644 --- a/Library/Homebrew/extend/os/mac/keg.rb +++ b/Library/Homebrew/extend/os/mac/keg.rb @@ -120,20 +120,22 @@ module OS "--preserve-metadata=entitlements,requirements,flags,runtime", file) - # If the codesigning fails, it may be a bug in Apple's codesign utility. - # A known workaround is to copy the file to another inode, then move it back - # erasing the previous file. Then sign again. + # If codesigning fails, retry on a new inode at a different path. + # Sign before moving it back, as macOS may restore com.apple.provenance + # at the original path. Dir::Tmpname.create("workaround") do |tmppath| FileUtils.cp file, tmppath - FileUtils.mv tmppath, file, force: true - end - odebug "Codesigning (2nd try) #{file}" - result = system_command("codesign", args: [ - "--sign", "-", "--force", - "--preserve-metadata=entitlements,requirements,flags,runtime", - file - ], print_stderr: false) + odebug "Codesigning (2nd try) #{file}" + result = system_command("codesign", args: [ + "--sign", "-", "--force", + "--preserve-metadata=entitlements,requirements,flags,runtime", + tmppath + ], print_stderr: false) + FileUtils.mv tmppath, file if result.success? + ensure + FileUtils.rm_f tmppath + end return if result.success? onoe <<~EOS diff --git a/Library/Homebrew/test/os/mac/keg_spec.rb b/Library/Homebrew/test/os/mac/keg_spec.rb index f03106ba8b..e4f8ecc069 100644 --- a/Library/Homebrew/test/os/mac/keg_spec.rb +++ b/Library/Homebrew/test/os/mac/keg_spec.rb @@ -96,6 +96,41 @@ RSpec.describe Keg do keg.codesign_patched_binary(file) end + + it "signs a temporary copy when the original path rejects signing on Intel" do + Pathname(file).dirname.mkpath + File.write(file, "unsigned") + allow(Hardware::CPU).to receive(:arm?).and_return(false) + allow(SystemCommand).to receive(:quiet_system).and_return(false) + allow(keg).to receive(:system_command) do |_, args:, **| + if args.first == "--verify" + instance_double(SystemCommand::Result, stderr: "#{file}: invalid signature") + elsif args.last == file + instance_double(SystemCommand::Result, success?: false, stderr: "#{file}: detritus not allowed") + else + File.write(args.last, "signed") + instance_double(SystemCommand::Result, success?: true) + end + end + + keg.codesign_patched_binary(file) + + expect(File.read(file)).to eq("signed") + end + + it "raises when replacing the binary after a successful signing retry fails on Intel" do + Pathname(file).dirname.mkpath + touch file + allow(Hardware::CPU).to receive(:arm?).and_return(false) + allow(SystemCommand).to receive(:quiet_system).and_return(false) + allow(keg).to receive(:system_command).and_return( + instance_double(SystemCommand::Result, stderr: "#{file}: invalid signature"), + instance_double(SystemCommand::Result, success?: true), + ) + allow(File).to receive(:rename).with(anything, file).and_raise(Errno::EACCES) + + expect { keg.codesign_patched_binary(file) }.to raise_error(Errno::EACCES) + end end describe "#relocate_dynamic_linkage" do