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

SASL Code Quality and Feature Improvements #598

Merged
merged 119 commits into from Dec 26, 2017
Merged

SASL Code Quality and Feature Improvements #598

merged 119 commits into from Dec 26, 2017

Conversation

aaronmdjones
Copy link
Member

The saslserv/main commits that end in 'move' only move functions around; they don't change any code. This should speed up review.

- Rename sasl_mech_register_func_t to sasl_core_functions_t
  and make it const

- All mechanism providers: use explicit structure initialisation
  for sasl_mechanism_t (named elements)

- All mechanism providers: remove .mech_start and .mech_finish
  function pointers from sasl_mechanism_t where they do nothing

- saslserv/main: only call ->mech_start() and ->mech_finish()
  if they are defined
This puts them below the functions they point to, so that we
don't need any forward declarations.
This makes `egrep '^funcname'` possible.
- Use appropriate qualifiers and attributes on function parameters
- Ensure we don't blow up if the input data isn't NULL-terminated
- Use pointer arithmetic to extract client-provided data
- Use appropriate qualifiers and attributes on function parameters
- Avoid enum and struct typedefs that end in _t as these are reserved
- Move variables to where they are used
- Use arc4random_buf() not RAND_pseudo_bytes(3ssl) for challenge
- Check return value of o2i_ECPublicKey(3ssl)
- Cast signature length parameter on ECDSA_verify(3ssl) correctly
- Use libathemecore/memory instead of mowgli allocator
- Set p->mechdata to NULL after freeing it
- Use appropriate qualifiers and attributes on function parameters
- Move variables to where they are used
- Use appropriate qualifiers and attributes on function parameters
- Ensure we don't blow up if the input data isn't NULL-terminated
- Use pointer arithmetic to extract client-provided data
- Rename mechanism functions consistent with other modules
- Move mech_finish() to below mech_step() consistent with other modules
- Remove an unnecessary level of indentation in mech_finish()
- Remove some duplicate code for unregistering mechanisms
This avoids numerous pedantic warnings about casting a data
pointer to a function pointer when the SCRAM-SHA module imports
them.
This makes `egrep -R '^function_name'` possible.
All of the SASL mechanism modules import a symbol from the
saslserv/main module when they are loaded, which puts
saslserv/main into their dependency tree.

If saslserv/main is unloaded, all of the modules that depend on
it are unloaded first, and those modules are responsible for
unregistering the mechanism(s) they added. This will result in
any sessions using that mechanism also being destroyed.

If there are still sessions left when saslserv/main is being
unloaded, it must be because one of the mechanism modules did
not unregister itself.

However, the moddeinit function then went on to try and clean up
the sessions itself. The problem is that destroy_session() will
call ->mech_finish() (if it is provided by the module) but the
module that contains that function is now unloaded, so we will
be calling a function that no longer exists.

This isn't actually possible if people don't write their own
SASL mechanism modules, because all of the in-tree modules
correctly unregister themselves when they are being unloaded.

Remove the loop and upgrade the existing warning to a higher log
level so that it is seen by more users.
@aaronmdjones
Copy link
Member Author

I have discovered a bug in sasl_input() that has been there forever (as far as I can tell) and was not accidentally corrected by my refactoring. The following commit should resolve the issue and then the commit after that removes some (now) duplicate code.

@aaronmdjones
Copy link
Member Author

Apologies for GitHub; it put the 2 aforementioned commits BEFORE my comment.

SASL_C2S_MAXLEN is the maximum length of *base64-encoded* data we will
process, so we don't need a buffer larger than this for encoding.
- More const-correctness
- Rename a 1-letter variable
- Calculate mechanism name length once instead of 4 times
- Avoid potential buffer overrun (incorrect length comparison logic)
- This function only ever writes to 1 buffer so just hardcode that
ASASL_FAIL results in a bad_password() on the user, even if the
failure was due to some kind of internal error (e.g. too small a
buffer passed to base64_encode()), which is undesirable.

Add a new code (ASASL_ERROR) that aborts the sessions all the
same but does not bad_password() the user, and reserve ASASL_FAIL
only for cases where the client supplied correctly-formatted data
but nonetheless invalid credentials.
Clang's static analyzer identified the following problematic call chain
introduced in this branch:

  sasl_input()
    p = find_or_make_session()
    sasl_buf_process(p)
      sasl_packet(p)
        sasl_session_abort(p)
          destroy_session(p)
            free(p->buf)
            free(p)
      free(p->buf)

This would have been remotely exploitable.

Fix the issue as follows:

* `sasl_packet()` and `sasl_buf_process()` will return a failure code

* `sasl_packet()` and `sasl_buf_process()` are annotated with the
  `warn_unused_result` function attribute to ensure this failure is
  propagated in the future (and remove the possibility of leaking memory)

* `sasl_input()` will abort/destroy the session if `sasl_packet()` fails

* `sasl_session_abort()` and `destroy_session()` are moved to below
  `sasl_buf_process()` and `sasl_packet()` so that these 2 functions can't
  abort or destroy sessions any more (there are no forward declarations)

This commit should serve as a reminder of the usefulness of static
analysis tools.
This makes the changes introduced in the previous commit less ugly.
Some users (e.g. Freenode) have created modules that hook user_can_login
and use various pieces of SASL information by casting the sourceinfo
back to this structure.

Such modules would need a duplicate definition which is likely to
introduce problems and incompatibilities in the future. Make this
impossible by making the definition shared among the entire codebase.
Copy link
Member

@ilbelkyr ilbelkyr left a comment

Choose a reason for hiding this comment

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

Looks good to me, though two things stand out a little – the "Account ... dropped, login cancelled" message is the actually important one to me, though.

if (! mu)
{
(void) notice(saslsvs->nick, u->nick, "Account %s dropped, login cancelled",
p->authzid ? p->authzid : "??");
p->authzeid ? p->authzeid : "??");
Copy link
Member

Choose a reason for hiding this comment

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

As remarked on IRC, this should possibly indicate the client-specified authzid instead. We would probably need to keep track of it alongside the EID for logging purposes.

}

static bool
sasl_authzid_can_login(struct sasl_session *const restrict p, const char *const restrict authzid,
Copy link
Member

Choose a reason for hiding this comment

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

I'll admit the code duplication between sasl_authzid_can_login and sasl_authcid_can_login makes me a little unhappy.

@ilbelkyr
Copy link
Member

(Not really related to this PR, but something I thought of while reviewing it – doesn't saslserv/scram-sha need to be taught about the MU_NOPASSWORD flag?)

@ilbelkyr ilbelkyr merged commit 45a7f19 into master Dec 26, 2017
@aaronmdjones aaronmdjones deleted the sasl-fixups branch December 26, 2017 13:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants