UserMeta Changes Meta Value for Post ID

I believe your issue is related to getting the correct post object on your single post page. From what I can detect from your question is that $post does not contain the post object which you expect.

You must remember, $post is one of those very crappy globals which WordPress uses to store the current post object in. Inside the loop, $post is normally quite reliable, but outside the loop, it is a totally different ball game altogether. The issue is, $post can be altered by any code on your page, even by the main loop. Any query making use of setup_postdata( $post ) or the_post() sets the $post global to the current post in the loop, so $post at any given time can be something different depending where on the page you are.

Forgetting to use something simple like wp_reset_postdata() after a custom query loop will leave the value of $post to the last post in the custom query, and not the current page object. It seems to be the issue you are having, something has set the $post global to something other that the current page object.

There are ways to get the current single post page’s object more reliably. You can have a look at my post here and read up on the subject. Because functions like query_posts also breaks the main query object, I lately started using the values from $GLOBALS['wp_the_query'] which is the most reliable way to get correct data.

One other issue here is, you are running this on every page load. Simply checking for post type is not enough. Remember, you using the $post global which can hold any post object. If that particular post object has the correct post type, your code will execute regardless of the page you are currently on, so we must check whether or not if we are on the desired post type’s single post page

Lets rewrite your function (watered down version) to be more reliable: (NOTE: The code is untested and requires PHP 5.4+, also, you can always sub this by making use of cookies to store the data on client side, although it is a less reliable method)

VERSION 1

If you need the complete post object, this will be the version to use

/**
 * We can use the template_redirect hook here aswell. Change back to
 * wp_footer if this suits you better
 */ 
add_action( 'template_redirect', 'nb_course_post_actions' );
function nb_course_post_actions(){
    global $current_user;

    // Make sure we are on a singular course post type page, if not, bail
    if ( !is_singular( 'course' )
        return;

    // Make sure we have a logged in user
    if ( !is_user_logged_in() )
        return; 

    // Great, we are on a single course post page and user is logged in, lets continue

    $post = sanitize_post( $GLOBALS['wp_the_query']->get_queried_object() );

    $sid         = $current_user->ID;
    $bookmark_id = $post->ID;

    // Update the usermeta
    update_user_meta( $sid, 'course_bookmarks', $bookmark_id );
}

VERSION 2

If you only need the post ID, and nothing else

/**
 * We can use the template_redirect hook here aswell. Change back to
 * wp_footer if this suits you better
 */ 
add_action( 'template_redirect', 'nb_course_post_actions' );
function nb_course_post_actions(){
    global $current_user;

    // Make sure we are on a singular course post type page, if not, bail
    if ( !is_singular( 'course' )
        return;

    // Make sure we have a logged in user
    if ( !is_user_logged_in() )
        return; 

    // Great, we are on a single course post page and user is logged in, lets continue

    $sid         = $current_user->ID;
    $bookmark_id = filter_var( $GLOBALS['wp_the_query']->get_queried_object_id(), FILTER_VALIDATE_INT );

    // Update the usermeta
    update_user_meta( $sid, 'course_bookmarks', $bookmark_id );
}