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

[BUG] FieldBitLength use can cause hang and consume all memory #180

Open
knutsr opened this issue Dec 21, 2021 · 13 comments
Open

[BUG] FieldBitLength use can cause hang and consume all memory #180

knutsr opened this issue Dec 21, 2021 · 13 comments
Assignees
Labels

Comments

@knutsr
Copy link

knutsr commented Dec 21, 2021

Hi,

We ran into an issue using FieldBitLength. We have defined a class with some byte-wise fields and some bit-wise fields. Trying to deserialize an array of such class instance will fail if the input array size is not an exact multiple of the size of a single instance. The Deserialize call never returns and consumes all available memory.

Trying to deserialize by calling ser.Deserialize<BitFieldsAlone>(serialized); with this definition succeeds with any input array length:

    public class BitFieldsAlone
    {
        [FieldOrder(0)]
        [FieldBitLength(7)]
        public byte SevenBits;
        [FieldOrder(1)]
        [FieldBitLength(1)]
        public byte OneBit;
    }

Trying to deserialize by calling ser.Deserialize<BitFieldsPrecededByByte[]>(serialized); with this definition fails with input array lengths that are not multiples of 2:

    public class BitFieldsPrecededByByte
    {
        [FieldOrder(0)] 
        public byte First;
        [FieldOrder(1)]
        [FieldBitLength(7)]
        public byte SevenBits;
        [FieldOrder(2)]
        [FieldBitLength(1)]
        public byte OneBit;
    }

See attached project file and code to reproduce.

BinarySerializationMemoryIssue.csproj.txt
Program.cs.txt

@jefffhaynes jefffhaynes self-assigned this Dec 21, 2021
@jefffhaynes
Copy link
Owner

Fixed in 8.6.2.1. Thanks!

@knutsr
Copy link
Author

knutsr commented Dec 23, 2021

Great! Thank you.

@knutsr
Copy link
Author

knutsr commented Dec 23, 2021

@jefffhaynes Unless I'm doing something stupid, it still fails in 8.6.2.1.

@jefffhaynes
Copy link
Owner

In the same place?

@knutsr
Copy link
Author

knutsr commented Dec 23, 2021

I cloned the repo and added the attached project file and code to the solution. The test you added runs fine, but the test program does not.

@jefffhaynes
Copy link
Owner

Sorry, it was me doing something stupid :)

@jefffhaynes jefffhaynes reopened this Dec 23, 2021
jefffhaynes added a commit that referenced this issue Dec 23, 2021
@jefffhaynes
Copy link
Owner

Try 8.6.2.2

@knutsr
Copy link
Author

knutsr commented Dec 23, 2021

All is good now. Thanks a lot for fixing this 👍

@mathiash98
Copy link

We changed some models internally by removing some fields and encountered this issue again. I will see if I can create a minimal reproducible example. Would expect the parser to throw error instead of ending in infinite loop.

@jefffhaynes
Copy link
Owner

Ok, the only thing I'll mention is that it is easy to get into an infinite loop if you have recursive graphs. There is currently no check for that.

@mathiash98
Copy link

mathiash98 commented Dec 10, 2024

Finally got to produce a minimal reproducible unit test, I ran this unit test inside Issue180Tests.cs, tried stepping through the code to identify where the infinite loop occured, but I was unable to identify where it breaks.

The first Testmethod causes infinite loop which is parsing the ObservationWrapper, while the second TestMethod is OK which is not parsing on the wrapper. I'm not sure if this is related to the original issue or not

        public class ObservationWrapper
        {
            [FieldOrder(0)]
            public ObservationData[] Observations;
        }
        public class ObservationData
        {
            [FieldOrder(0)]
            [FieldCount(1)]
            public byte[] OneByte;
            
            [FieldOrder(1)]
            [FieldBitLength(7)]
            public byte SevenBits;
            
            [FieldOrder(2)]
            [FieldBitLength(1)]
            public byte OneBit;
        }

        [TestMethod]
        public void TestInfinityLoop_EndsUpInInfinityLoop()
        {
            var msg = new byte[]
            {
                1, // 1 observation
                67, // OneByteArray
                75, // 7 bits + 1 bit
            };
            var ser = new BinarySerializer();
            var message = ser.Deserialize<ObservationWrapper>(msg); // Infinite loop

            Assert.IsNotNull(message);
            Assert.AreEqual(1, message.Observations.Length);
            
            Assert.AreEqual(67, message.Observations[0].OneByte[0]);
            
            Assert.AreEqual(0, message.Observations[0].OneBit);
            Assert.AreEqual(75, message.Observations[0].SevenBits);
        }

        [TestMethod]
        public void TestInfinityLoop_DoesNotEndUpInInfinityLoop()
        {
            var msg = new byte[]
            {
                67, // OneByte
                75, // 7 bits + 1 bit
            };
            var ser = new BinarySerializer();
            var message = ser.Deserialize<ObservationData[]>(msg);

            Assert.IsNotNull(message);
            Assert.AreEqual(67, message[0].OneByte[0]);
            Assert.AreEqual(0, message[0].OneBit);
            Assert.AreEqual(75, message[0].SevenBits);
        }

I am happy to to spend more time debugging and looking into some fix, but I am a bit unsure on where to look for the issue.

@jefffhaynes
Copy link
Owner

Ok I'll take a look.

mathiash98 added a commit to mathiash98/BinarySerializer that referenced this issue Dec 17, 2024
…tLength

Infinity loop occurs when trying to parse a binary message that does not fit into the provided type.

This commit adds an additional check when parsing to throw exception if stream.AvailableForReading < 0.

This commit breaks following unit tests:
- Issue55Tests
- ImmutableTest2
- IOExceptionTest - ShouldThrowIOExceptionNotInvalidOperationExceptionTest
@mathiash98
Copy link

Ok I'll take a look.

I have spent some more time looking into it

Reason for infinity loop -> The provided binary message does not fit into the provided type

Observation: Inside ValueNode.cs#Deserialize we can see that stream.AvailableForReading goes below 0, during the infinity loop of serializing the binary message.

Potential fix -> Throw exception if stream.AvailableForReading < 0
See #236 where I have added the proposed exception check.

The check should probably be somewhere else?

@jefffhaynes jefffhaynes reopened this Dec 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants