Yes this looks like a bug to me, I would raise a case with Salesforce with your repo code. The deserializer is placing invalid values into the SObject fields. If you comment out the insert and delete DML operations and just leave String.valueOf(contact.BirthDate) you get a nice Java internal error, 'java.lang.String cannot be converted to java.util.Date' :)  

**Workaround:**

Based on the bug manifesting around serialising / deserialising SObject Date fields, I created a wrapped Apex class that used Apex Properties to created an Apex Date accessor to the underlying SObject Date field. Like so...

    @RemoteAction
    public static String acceptContact(WrappedContact wc) {
        insert wc.contact;
        delete wc.contact;
        return 'got Contact with date:' + String.valueOf(wc.contact.BirthDate);
    }
    
    public class WrappedContact
    {
    	public Contact contact;
    	public Date birthDate { get { return contact.BirthDate; } set { contact.Birthdate = value; } }
    }

This is the JavaScript code...

    DateRemotingTest.acceptContact(
         {
    	    BirthDate: new Date(1953,5,1).toUTCString(),
    	    Contact: 
            {
               LastName: 'Xi',
               FirstName: 'JinPing'
            }
         },
         function(result, event) 
            {
                if (event.status) {
                    console.log('plain date worked ' + result);
                } else {
                    console.log('plain date had some kind of error: ' + event.message);
                }
            }
    );

The emits to the browser console...

> plain date worked1353196800000 

> plain date worked got Contact with date:1953-05-31