Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MediaTime: fix negative value into Duration bug #400

Merged
merged 7 commits into from
Nov 27, 2023

Conversation

david-flok
Copy link
Contributor

as u64 of i64 is dangerous. Duration is non-negative but MediaTime is not necessarily. This fixes what is effectively an integer underflow.

@david-flok
Copy link
Contributor Author

Actually, I'm not sure that MediaTime should have From<MediaTime> for Duration at all -- this conversion is not lossless. I think I am in favor of an explicit conversion instead or TryFrom or something.

@lolgesten
Copy link

I don't feel strongly one way or the other.

Have you experienced a bug due to this conversion?

@david-flok
Copy link
Contributor Author

Yes. I had two MediaTime values that I expected to almost always be ordered tsA < tsB and so I used Duration::max(Duration::ZERO, (tsB - tsA).into()) but this results in underflow. Instead, I must use MediaTime::max(MediaTime::ZERO, tsB - tsA).into() so the timestamp doesn't get corrupted. I would expect _.into() and max to commute.

I will prepare a proposed MediaTime patch to catch this issue statically.

@algesten
Copy link
Owner

Sounds good!

@david-flok david-flok force-pushed the mediatime-negative-duration branch from 3aaf33b to 1495560 Compare November 20, 2023 14:30
@david-flok
Copy link
Contributor Author

@algesten I've revised the patch. While I was there, I also fixed some defects regarding handling of negative and zero denominators. Those cases are now statically disallowed. This should eliminate a possible DoS vulnerability resulting from SDP parsing of streams with 0 frequency resulting in division by zero panics.

You can, of course, cherry-pick the commit(s) you want or ask me to prepare a reduced changeset but I would very much like this basic data type to be as safe as possible.

@lolgesten
Copy link

Thanks!

I'm in two minds about this Frequency newtype. On the one hand, it provides some really nice guarantees in the SDP parsing, on the other, it is Another Thing a user of the library need to get their head around. It doesn't look like Frequency is visible in that many places in the public API though. Could you maybe try making it pub(crate) and see what compilation errors you get?

For now we could potentially keep it internal and decide later if we want to have it public.

@david-flok david-flok force-pushed the mediatime-negative-duration branch from 56bc26c to 0659282 Compare November 21, 2023 08:31
@david-flok
Copy link
Contributor Author

Unfortunately, it is indeed rather heavyweight compared to just plugging in literals but I have tried to make it as straightforward as possible to use with frequency-specific constructors and constants.

As far as I can see, the design space for this division by zero restriction is:

  1. Do nothing and expect the user to ensure non-zero frequency.
  2. Check at construction time and panic.
  3. Check at construction time and return a Result/Option.
  4. Check at construction time and map 0 to 1 silently.
  5. Use NonZeroU32 directly in interfaces.
  6. Wrap NonZeroU32 with a newtype (Frequency).

Perhaps you'd be happier with 4?

I have added AddAssign and SubAssign implementations because doing t = t + x instead of t += x was bothering me. I've also rebased onto main as there were some conflicts.

@david-flok
Copy link
Contributor Author

Another option would be to do both the Frequency newtype and the 0 to 1 mapping with the larger type on input and the smaller type on output (Postel style).

The Frequency type only shows up in signatures for things like clock_rate and the denominator of these MediaTime terms -- it's there entirely to guarantee that there are only 2^32 - 1 values.

@algesten
Copy link
Owner

@david-flok I'm coming around to this being a newtype. Having it in Format changes things a bit for me.

@algesten
Copy link
Owner

algesten commented Nov 21, 2023

@xnorpx you had thoughts on MediaTime (passing it to write_rtp, rather than just the RTP u32 value).

My idea for API ergonomics here is that

  1. Get the Format for whatever you're sending (Rtc -> CodecConfig -> PayloadParam -> CodecSpec -> Format)
  2. Grab the Frequency from that Format.
  3. Make a MediaTime using the Frequency (this could potentially be expanded with more helpers)

Since you need to know the negotiated PT, you already need to dig out the PayloadParam.

@xnorpx
Copy link
Collaborator

xnorpx commented Nov 21, 2023

It feels a little clunky to need to create a MediaTime everytime I call write. Pass in Pt and RtpTimestamp and MediaTime could be created inside. But it could just be the way I use it.

We probably need to add 8000 and 16000 at some point to the frequency struct, but I like having the new type.

Copy link
Owner

@algesten algesten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's land this. I want some documentation is all.

@@ -1,36 +1,127 @@
#![allow(missing_docs)]

use std::cmp::Ordering;
use std::ops::{Add, Sub};
use std::fmt::Display;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not your problem, really, but it's expanding with the increased amount of code in this file.

Could you remove the #![allow(missing_docs)] at the top and document the new and old code?

Also, In the Frequency doc on struct, and MediaTime struct, I'd like to see some code examples how to get the Frequency for a certain Format from the chain Rtc -> CodecConfig -> PayloadParam -> CodecSpec -> Format -> Frequency and how to subsequenty use that to construct a MediaTime. You can duplicate the code example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you have in mind for this example so I've not guessed, sorry.

@algesten
Copy link
Owner

@david-flok also, note #338 which I want to land before this, which will clean up in MediaTime a bit.

@algesten
Copy link
Owner

Hi @david-flok, I know I've created some more work in this PR.

Would you prefer if I took the PR to completion, or do you want to finish it?

These each indicate a defect of some severity.
@david-flok
Copy link
Contributor Author

david-flok commented Nov 27, 2023

Hi @algesten, no worries. I'll polish this now, sorry for the delay.

This enforces the signedness invariant in the type so that negative
denominators are unrepresentable. This saves complexity in managing
improper ratios with cancelling signs.
MediaTime supports negative durations but Duration does not. Expose
the partiality of the conversion in the signature.
Ensure that we can never have a delayed divide by zero panic due to
carrying a zero denominator around. This removes a denial of service
vulnerability from SDP parsing and makes the MediaTime type safer for
API users. The change from 64-bit to 32-bit is justified by the
observation that it is unlikely applications will need to represent
times with precision smaller than quarters of nanoseconds.
@david-flok david-flok force-pushed the mediatime-negative-duration branch from c09474e to 342a384 Compare November 27, 2023 14:42
@david-flok
Copy link
Contributor Author

Ok, I've rebased and resolved the conflicts with #338. I also did some documentation but I haven't provided the example(s) you wanted, sorry.

Copy link

@lolgesten lolgesten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's ship it!

Thanks!

Copy link
Owner

@algesten algesten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my private user

@algesten algesten merged commit 7565523 into algesten:main Nov 27, 2023
22 checks passed
@algesten
Copy link
Owner

@david-flok I added some code example: a319da9

@david-flok david-flok deleted the mediatime-negative-duration branch November 27, 2023 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants