Writing MP4 tag sometimes causes segmentation fault.
TagLib::MP4::Tag#[]= sometimes fails even if the same actions often success.
irb(main):001:0> require 'taglib'
=> true
irb(main):002:0> f = TagLib::MP4::File.new('test.m4a')
=> #<TagLib::MP4::File:0x0000000104ef0dd0 @__swigtype__="_p_TagLib__MP4__File">
irb(main):003:0> f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
=> #<TagLib::MP4::Item:0x0000000104f3b308 @__swigtype__="_p_TagLib__MP4__Item">
irb(main):004:0> f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
=> #<TagLib::MP4::Item:0x0000000104f71980 @__swigtype__="_p_TagLib__MP4__Item">
irb(main):005:0> f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
=> #<TagLib::MP4::Item:0x0000000104f76638 @__swigtype__="_p_TagLib__MP4__Item">
irb(main):006:0> f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
=> #<TagLib::MP4::Item:0x0000000104f7b368 @__swigtype__="_p_TagLib__MP4__Item">
irb(main):007:0> f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
=> #<TagLib::MP4::Item:0x0000000104f7adf0 @__swigtype__="_p_TagLib__MP4__Item">
irb(main):008:0> f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
=> #<TagLib::MP4::Item:0x0000000104f79338 @__swigtype__="_p_TagLib__MP4__Item">
irb(main):009:0> f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
=> #<TagLib::MP4::Item:0x0000000104f77240 @__swigtype__="_p_TagLib__MP4__Item">
irb(main):010:0> f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
=> #<TagLib::MP4::Item:0x0000000104f75b70 @__swigtype__="_p_TagLib__MP4__Item">
irb(main):011:0> f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
(irb):11: [BUG] Segmentation fault at 0x5f6d6f72662e6d65
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
-- Crash Report log information --------------------------------------------
See Crash Report log file in one of the following locations:
* ~/Library/Logs/DiagnosticReports
* /Library/Logs/DiagnosticReports
for more details.
Don't forget to include the above Crash Report log file in bug reports.
-- Control frame information -----------------------------------------------
c:0022 p:---- s:0116 e:000115 CFUNC :[]=
c:0021 p:0018 s:0110 e:000108 EVAL (irb):11 [FINISH]
c:0020 p:---- s:0106 e:000105 CFUNC :eval
c:0019 p:0020 s:0098 e:000097 METHOD /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/workspace.rb:113
c:0018 p:0152 s:0091 e:000089 METHOD /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/context.rb:497
c:0017 p:0106 s:0078 e:000077 BLOCK /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:581
c:0016 p:0024 s:0072 e:000071 METHOD /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:770
c:0015 p:0007 s:0066 e:000065 BLOCK /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:561
c:0014 p:0123 s:0061 e:000060 BLOCK /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/ruby-lex.rb:253 [FINISH]
c:0013 p:---- s:0057 e:000056 CFUNC :loop
c:0012 p:0005 s:0053 e:000052 BLOCK /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/ruby-lex.rb:235 [FINISH]
c:0011 p:---- s:0050 e:000049 CFUNC :catch
c:0010 p:0010 s:0045 e:000044 METHOD /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/ruby-lex.rb:234
c:0009 p:0034 s:0041 E:000638 METHOD /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:560
c:0008 p:0003 s:0036 e:000035 BLOCK /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:494 [FINISH]
c:0007 p:---- s:0033 e:000032 CFUNC :catch
c:0006 p:0050 s:0028 E:0005e0 METHOD /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:493
c:0005 p:0069 s:0022 e:000021 METHOD /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:416
c:0004 p:0012 s:0016 e:000015 TOP /Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/exe/irb:9 [FINISH]
c:0003 p:---- s:0013 e:000012 CFUNC :load
c:0002 p:0078 s:0008 E:000d50 EVAL /Users/ishotihadus/.rbenv/versions/3.2.2/bin/irb:25 [FINISH]
c:0001 p:0000 s:0003 E:001400 DUMMY [FINISH]
-- Ruby level backtrace information ----------------------------------------
/Users/ishotihadus/.rbenv/versions/3.2.2/bin/irb:25:in `<main>'
/Users/ishotihadus/.rbenv/versions/3.2.2/bin/irb:25:in `load'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/exe/irb:9:in `<top (required)>'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:416:in `start'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:493:in `run'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:493:in `catch'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:494:in `block in run'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:560:in `eval_input'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/ruby-lex.rb:234:in `each_top_level_statement'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/ruby-lex.rb:234:in `catch'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/ruby-lex.rb:235:in `block in each_top_level_statement'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/ruby-lex.rb:235:in `loop'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/ruby-lex.rb:253:in `block (2 levels) in each_top_level_statement'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:561:in `block in eval_input'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:770:in `signal_status'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb.rb:581:in `block (2 levels) in eval_input'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/context.rb:497:in `evaluate'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/workspace.rb:113:in `evaluate'
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.6.4/lib/irb/workspace.rb:113:in `eval'
(irb):11:in `<top (required)>'
(irb):11:in `[]='
-- Machine register context ------------------------------------------------
x0: 0x5f6d6f72662e6d65 x1: 0x000000000000009b x2: 0x000000016eee1f90
x3: 0x0000000000000ca1 x4: 0x0000000000000001 x5: 0x0000000000000000
x6: 0x000060000309be70 x7: 0x00000000000008e0 x18: 0x0000000000000000
x19: 0x000000000000009b x20: 0x00000001060bb650 x21: 0x0000000000000000
x22: 0x000000016eee1ff8 x23: 0x5f6d6f72662e6d65 x24: 0x00000001016ed000
x25: 0x000000013e004740 x26: 0x00000001060bb650 x27: 0x0000000000000000
x28: 0x0000000000000000 lr: 0x00000001015e3a7c fp: 0x000000016eee1fd0
sp: 0x000000016eee1f80
-- C level backtrace information -------------------------------------------
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_vm_bugreport+0x9a0) [0x1016044a8]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_bug_for_fatal_signal+0x160) [0x1014405b4]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(sig_do_nothing+0x0) [0x10156dcc8]
/usr/lib/system/libsystem_platform.dylib(_sigtramp+0x38) [0x18ebc6a84]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(callable_method_entry_or_negative+0x6c) [0x1015e3a7c]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_respond_to+0x50) [0x1015e5c98]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_check_funcall_default_kw+0xec) [0x1015e843c]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(convert_type_with_id+0x3c) [0x1014e4990]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_convert_type_with_id+0xc0) [0x1014e48a4]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_string_value_ptr+0x4c) [0x101581a50]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/taglib-ruby-1.1.3/lib/taglib_mp4.bundle(_ZL19SWIG_Ruby_MangleStrm+0x44) [0x10641efb0]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/taglib-ruby-1.1.3/lib/taglib_mp4.bundle(_ZL26SWIG_Ruby_ConvertPtrAndOwnmPPvP14swig_type_infoiP17swig_ruby_owntype) [0x106414a58]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/taglib-ruby-1.1.3/lib/taglib_mp4.bundle(_ZL21_wrap_Tag___setitem__iPmm) [0x106419998]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_call_cfunc_with_frame+0xe8) [0x1015f884c]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_exec_core+0x1fc4) [0x1015de1c4]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_vm_exec+0x82c) [0x1015efdec]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_f_eval+0x1f0) [0x1015ea758]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_call_cfunc_with_frame+0xe8) [0x1015f884c]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_exec_core+0x1fc4) [0x1015de1c4]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_vm_exec+0x82c) [0x1015efdec]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(invoke_block_from_c_bh+0x3a4) [0x1015ff188]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(loop_i+0x70) [0x1015fe908]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_vrescue2+0x170) [0x10144c660]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_rescue2+0x2c) [0x10144c4c8]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_f_loop+0x48) [0x1015ec4e8]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_call_cfunc_with_frame+0xe8) [0x1015f884c]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_sendish+0x488) [0x1015fabbc]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_exec_core+0x1ec4) [0x1015de0c4]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_vm_exec+0x82c) [0x1015efdec]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(invoke_block_from_c_bh+0x3a4) [0x1015ff188]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(catch_i+0x7c) [0x1015fe850]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_catch_protect+0x15c) [0x1015ebb3c]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_f_catch+0x7c) [0x1015ec408]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_call_cfunc_with_frame+0xe8) [0x1015f884c]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_sendish+0x488) [0x1015fabbc]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_exec_core+0x1ec4) [0x1015de0c4]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_vm_exec+0x82c) [0x1015efdec]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(invoke_block_from_c_bh+0x3a4) [0x1015ff188]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(catch_i+0x7c) [0x1015fe850]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_catch_protect+0x15c) [0x1015ebb3c]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_f_catch+0x7c) [0x1015ec408]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_call_cfunc_with_frame+0xe8) [0x1015f884c]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_sendish+0x488) [0x1015fabbc]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_exec_core+0x1ec4) [0x1015de0c4]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_vm_exec+0x82c) [0x1015efdec]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(load_iseq_eval+0xf8) [0x1014ac514]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_load_internal+0x84) [0x1014a97a8]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_f_load+0xb8) [0x1014aacbc]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_call_cfunc_with_frame+0xe8) [0x1015f884c]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(vm_exec_core+0x1fc4) [0x1015de1c4]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_vm_exec+0x82c) [0x1015efdec]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(rb_ec_exec_node+0x12c) [0x10144bb38]
/Users/ishotihadus/.rbenv/versions/3.2.2/lib/libruby.3.2.dylib(ruby_run_node+0x60) [0x10144b9a4]
/Users/ishotihadus/.rbenv/versions/3.2.2/bin/ruby(main+0x68) [0x100f1bf34]
I found that this phenomenon occurs when f.tag.item_map is accessed after some tags are added.
(Of course, tag[key] = item is counted as a tag addition.)
As a workaround, write item_map = f.tag.item_map at first, and then use item_map.insert or item_map.fetch.
Calling f.tag.item_map after inserting tags can cause a segmentation fault.
I suspect this is a problem in the implementation of __setItem__.
I am clueless about C++ so I don't understand why, but in this code I had to take a pointer to the item list map before modifying it.
Edit: take this with a grain of salt, I have not reproduced the problem yet.
@Ishotihadus I am not able to reproduce the segfault. I tried the following, on 189fa6b.
bundle exec rake
ruby -Ilib -rtaglib test.rb
With test.rb being:
f = TagLib::MP4::File.new('test/data/mp4.m4a')
100.times do
f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
end
puts f.tag.item_map.to_h
This prints a Ruby hash, as expected, without segfaulting.
Could you try this code? This always fails in my environment even without irb.
# frozen_string_literal: true
require 'net/http'
require 'json'
100.times do
f = TagLib::MP4::File.new('test.m4a')
f.tag['©nam'] = TagLib::MP4::Item.from_string_list(['name'])
f.tag['©ART'] = TagLib::MP4::Item.from_string_list(['artist'])
request = Net::HTTP::Post.new(URI.parse('https://example.com'))
request.content_type = 'application/x-www-form-urlencoded;charset=UTF-8'
request.set_form_data('name' => f.tag['©nam'].to_string_list[0], 'artist' => f.tag['©ART'].to_string_list[0])
Net::HTTP.start('example.com', 443, use_ssl: true) { |http| http.request(request) }
f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
end
I have a feeling that something in the memory handling around strings is causing this problem, but I am not sure.
- taglib-ruby: 1.1.3 / 189fa6b
- taglib: 1.13 (via Homebrew)
@Ishotihadus that reproducer still does nothing on my end. I'm also using a mac and taglib 1.13.
- What ruby version are you using?
- Does your reproducer work if you use the test file from taglib-ruby?
- I use Ruby 3.2.2 from rbenv without customization.
- Yes. I can even reproduce the problem with the test file. I have MacBook Pro and Mac Studio, but the problem occurs on both hardwares.
My environment has
- macOS 13.3.1 (a)
- Ruby 3.2.2 from rbenv (2023-03-30 revision e51014f9c0) [arm64-darwin22]
My Gemflie.lock is: Gemfile.lock.
I also attach logs of bundle exec rake: rake.log; but I think there is no valuable information in this log.
Thanks for your supporting.
I found this problem appears on Ruby >= 3.2.0 and does not appear on Ruby <= 3.1.4! I use Ruby 3.1.4 for taglib-ruby for now!
Thanks. Using ruby 3.2.2 I also get the segfault.
I can't get rid of the HTTP request in the reproducer yet; writing the tag values to /dev/null breaks the reproducer.
The segfault also happens if I replace f.tag['©lyr'] = ... with f.tag.empty?. Looks like f.tag is a Ruby object pointing to a garbage memory address?
I've narrowed down the reproducer a bit.
require 'net/http'
def do_stuff
Net::HTTP.get('localhost', '/', 8080)
end
$stdout.sync=true
1000.times do |i|
printf("%d ", i)
f = TagLib::MP4::File.new('test/data/mp4.m4a')
f.tag['©ART'] = TagLib::MP4::Item.from_string_list(['artist'])
do_stuff
f.tag.empty?
f.close
end
puts
I still don't understand why I can't get rid of net/http here.
The number of iterations before a segfault seems to be kind of stable. It varies if you make small changes to the code, such as adding another f.tag[xxx] =.
Interesting. What might help narrowing it down is to see if any of the specific TagLib functionality triggers it. E.g. if you remove from_string_list and use a different tag, can you still reproduce? If not, that would suggest the problem is somewhere in our handling of string lists (just an example).
Also, reading through the Ruby 3.2 release notes, it looks like YJIT is now enabled by default? Does the problem also occur if you disable it on 3.2 (not sure if that's possible?) or if you enable it on < 3.2?
Good idea, but YJIT does not seem to be active when I reproduce the bug. I checked by adding puts "YJIT enabled: #{RubyVM::YJIT.enabled?}" which reports false.
Replacing from_string_list with e.g. from_int does not make the crash go away. Replacing the f.tag[xxx] = with f.tag.empty? still triggers the crash.
require 'net/http'
def do_stuff
Net::HTTP.get('localhost', '/', 8080)
end
$stdout.sync=true
puts "YJIT enabled: #{RubyVM::YJIT.enabled?}"
1000.times do
f = TagLib::MP4::File.new('test/data/mp4.m4a')
f.tag.empty?
do_stuff
f.tag.empty? # <- second call to #empty? triggers segfault
f.close
end
puts
To be clear, it's not about #empty?, that is just a conveniently succise method call. It also crashes if you call #album or something else.
It seems as if the HTTP request eventually causes the tag to get deallocated somewhere (in C++?).