Overflow in calculating audio timestamp

Jaroslav Kysela perex at perex.cz
Fri Feb 3 19:02:08 CET 2023


On 03. 02. 23 17:11, Alan Young wrote:
> 
> On 03/02/2023 00:34, Takashi Sakamoto wrote:
>> Hi,
>>
>> Thank you for the report.
>>
>> On Thu, Feb 02, 2023 at 01:55:24PM +0000, Alan Young wrote:
>>> sound/core/pcm_lib.c:update_audio_tstamp() contains the following
>>> calculation:
>>>
>>>           audio_nsecs = div_u64(audio_frames * 1000000000LL,
>>>                   runtime->rate);
>>>
>>> This will result in a 64-bit overflow after 4.4 days at 48000 Hz, or 1.1
>>> days at 192000.
>>>
>>> Are you interested in a patch to improve this?
>>>
>>> The same calculation occurs in a couple of other places.
>> I'm interested in your patch. Would you please post it C.C.ed to the
>> list and me?  As you noted, we can see the issue in ALSA PCM core and
>> Intel HDA stuffs at least.
>>
>>    * sound/core/pcm_lib.c
>>    * sound/pci/hda/hda_controller.c
>>    * sound/soc/intel/skylake/skl-pcm.c
>>
>> I note that 'NSEC_PER_SEC' macro is available once including
>> 'linux/time.h'. It is better to use instead of the literal.
>> The macro is defined in 'include/vdso/time64.h'.
>>
>>
>> As another issue, the value of 'audio_frames' comes from the value of
>> 'struct snd_pcm_runtime.hw_ptr_wrap'. In ALSA PCM core, the value is
>> increased by the size of PCM buffer every time hw_ptr cross the boundary
>> of PCM buffer, thus multiples of the size is expected. Nevertheless,
>> there is no check for overflow within 64 bit storage. In my opinion, the
>> committer had less care of it since user does not practically playback or
>> capture PCM substream so long. But the additional check is preferable as
>> long as it does not break the fallback implementation of audio time stamp.
> 
> 
> I have not yet finished testing various alternatives. I want to extend
> the overflow by "enough" and also am conscious of the need to keep the
> overhead down.
> 
> I actually think, on reflection, that the only case that matters is the
> call from update_audio_tstamp(). The others only deal with codec delays
> which will be small (unless I misunderstand those drivers).
> 
> This is what I have so far but I'll submit a proper patch when I have it
> refined.
> 
> static u64 snd_pcm_lib_frames_to_nsecs(u64 frames, unsigned int rate)
> {
>       /*
>        *  Avoid 64-bit calculation overflow after:
>        *  - 4.8 days @ 44100
>        *  - 0.56 days @ 384000
>        *  extending these intervals by a factor of 100.
>        */
>       if (frames < 0xffffffffffffffffLLU / NSEC_PER_SEC)
>           return div_u64(frames * NSEC_PER_SEC, rate);
> 
>       if (rate % 100 == 0)
>           return div_u64(frames * (NSEC_PER_SEC/100), (rate/100));
> 
>       /* Fallback: reduce precision to approximately deci-micro-seconds: 1.28e-7 */
>       return div_u64(frames * (NSEC_PER_SEC >> 7), rate) << 7;
> }

Thank you for your suggestion, but I think that the *whole* code for 
!get_time_info in update_audio_tstamp() should be recoded. The calling of 
ns_to_timespec64() is not enough to handle the boundary wraps in a decent 
range (tenths years for 24x7 operation) and the bellow code is dangerous for 
32-bit apps / system:

      if (crossed_boundary) {
                 snd_BUG_ON(crossed_boundary != 1);
                 runtime->hw_ptr_wrap += runtime->boundary;
      }

I would probably propose to have just hw_ptr_wrap +1 counter (we can 
reconstruct the frame position back by multiplication and do range check 
later), remove snd_BUG_ON and improve the timespec64 calculation.

The calculation should be split to two parts (tv_sec / tv_nsec):

1) calculate seconds: (frames / rate)
2) calculate the remainder (ns): ((frames % rate) * NSEC_PER_SEC) / rate

With 64-bit integer range, we should go up to (for 384000Hz rate):

2**64 / 384000 / 3600 / 24 / 365 = ~1523287 years

Maybe I did a mistake somewhere. I'm open for comments.

					Jaroslav

-- 
Jaroslav Kysela <perex at perex.cz>
Linux Sound Maintainer; ALSA Project; Red Hat, Inc.



More information about the Alsa-devel mailing list