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

Feature/serialtube #90

Merged
merged 41 commits into from
Aug 25, 2018
Merged

Conversation

JonathanBeverley
Copy link
Contributor

@JonathanBeverley JonathanBeverley commented Jan 29, 2018

Add a class to handle serial connections.
There is a significant bugfix in lib/pwnlib/tubes/tube.rb, which is necessary for it to work properly.
I've bumped version to 1.0.2 (mostly so I could tell I was using it in testing.)


This change is Reviewable

@david942j
Copy link
Collaborator

david942j commented Jan 30, 2018

Thanks for contributing!

But please fix these issues before we start to review:

  • Don't bump the version, you can do it locally but don't commit it
  • Resolve conflicts
  • Add tests and ensure 100% coverage
    • Beware of our tests run under three operating systems
  • Add documents
  • Follow STYLE.md, some issues I found in a quick review:
    • Use first instead of [0]
    • Sort the requiring
    • Sort the dependencies in gemspec

Thanks again!

@david942j
Copy link
Collaborator

And it would be great if you file an issue to describe how the bug effects in tube.rb

@@ -54,8 +54,9 @@ def countdown(timeout = nil)
begin
yield
ensure
raise ::Pwnlib::Errors::TimeoutError unless active?
was_active = active?
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

Copy link
Collaborator

@david942j david942j Jun 9, 2018

Choose a reason for hiding this comment

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

Thanks for pointing out this,
it's fixed by #116

@@ -159,7 +166,7 @@ def recvuntil(delims, drop: false, timeout: nil)
while @timer.active?
s = recv(1)

return '' if s.empty?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this change needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. I raised it in issue #91, and it was classified as "notbug".

Copy link
Collaborator

Choose a reason for hiding this comment

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

So please rollback it

data << c
end
data.slice!(0..-1)
ensure
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why this?
Original code seems correct

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This might be a disagreement on what the function is supposed to do. In my opinion, it should not return until the timeout expires unless it has a match with the predicate. Just because a particular recv(1) didn't return anything doesn't mean that one .5s later won't.

Swap from rescue->raise is different. That's because we must not unrecv(data) if we match the predicate. With ensure, we always unrecv.

Copy link
Collaborator

Choose a reason for hiding this comment

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

But recv_raw is a block call
The situation you mentioned should never happen


class SerialTest < MiniTest::Test
include ::Pwnlib::Tubes

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is the serial tube intend to work on macOS?
If yes, let the tests pass on macOS (install socat)
If no, skip the test unless the environment is on Linux

Copy link
Collaborator

Choose a reason for hiding this comment

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

And skip on Windows as well

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 don't have access to a mac to test it on. It seems likely that it will work off-the-shelf.
How do I "install socat"?

rubyserial is supported on Windows, and so it's possible that serialtube will work there. I don't have a Windows/ruby setup in-place, nor do I have any idea how to test it there.

Copy link
Collaborator

Choose a reason for hiding this comment

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

To install socat, simply add brew install socat in travis/install.sh#setup_osx

Is that possible to test serialtube on Windows? I said skip on Windows because the tests you added (socat ...) can't be supported on Windows.

If serialtube is supposed to support on Windows, maybe you should try another testing method to make it work on Windows as well.
Otherwise, just forget Windows supports and skip the tests.

@@ -13,6 +13,7 @@
require 'pwnlib/reg_sort'
require 'pwnlib/shellcraft/shellcraft'
require 'pwnlib/tubes/sock'
require 'pwnlib/tubes/serialtube'

Copy link
Collaborator

Choose a reason for hiding this comment

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

Put this line before 'pwnlib/tubes/sock'

#
# @!macro raise_eof
def recv_raw(numbytes)
raise EOFError if @conn.nil?
Copy link
Collaborator

Choose a reason for hiding this comment

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

raise ::Pwnlib::Errors::EndOfTubeError

return data
rescue RubySerial::Error
close
raise EOFError
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here

#
# @!macro raise_eof
def send_raw(data)
raise EOFError if @conn.nil?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same

return @conn.write(data)
rescue RubySerial::Error
close
raise EOFError
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here


# Sets the +timeout+ to use for subsequent +recv_raw+ calls.
#
# @param [Integer] timeout
Copy link
Collaborator

Choose a reason for hiding this comment

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

The type is Float?
And it's ok to not document private methods if there's nothing worth to be mentioned

# @return [String]
# A string containing read bytes.
#
# @!macro raise_eof
Copy link
Collaborator

Choose a reason for hiding this comment

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

@@ -51,6 +51,9 @@ def initialize(timeout: nil)
# @return [String]
# A string contains bytes received from the tube, or +''+ if a timeout occurred while
# waiting.
#
# @!macro raise_eof
# @!macro raise_timeout
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one isn't cross-file.

Copy link
Collaborator

Choose a reason for hiding this comment

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

my fault

@@ -159,7 +166,7 @@ def recvuntil(delims, drop: false, timeout: nil)
while @timer.active?
s = recv(1)

return '' if s.empty?
Copy link
Collaborator

Choose a reason for hiding this comment

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

So please rollback it

data << c
end
data.slice!(0..-1)
ensure
Copy link
Collaborator

Choose a reason for hiding this comment

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

But recv_raw is a block call
The situation you mentioned should never happen

@@ -105,9 +108,13 @@ def recvpred(timeout: nil)
data << c
end
data.slice!(0..-1)
ensure
# rubocop:disable Style/RescueStandardError
Copy link
Collaborator

Choose a reason for hiding this comment

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

And this must be changed back to ensure

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 think I finally get what I misunderstood about this code, but I'll raise it as an issue so it can be discussed properly.

# @param [Integer] baudrate
# Baud rate.
# @param [Boolean] convert_newlines
# If +true+, will convert local +context.newline+ to +"\\r\\n"+ for remote
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess no escape needed here?
+"\r\n"+ should be enough

Copy link
Collaborator

Choose a reason for hiding this comment

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

And this sentence seems not complete

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What I try it without the double escape, in the .html files generated by YARD, I see
If true, will MARKER convert local context.newline to "rn" for remote
I copied the syntax from tube.rb, which does the same thing. Note that in @example blocks the rules are different.

break unless data.empty?
sleep 0.1
end
# TODO: should we reverse @convert_newlines here?
Copy link
Collaborator

Choose a reason for hiding this comment

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

TODO(username)


module Pwnlib
module Tubes
# Serial Connections
Copy link
Collaborator

Choose a reason for hiding this comment

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

change here to

# @!macro [new] raise_eof
#   @raise [Pwnlib::Errors::EndOfTubeError]
#     If the request is not satisfied when all data is received.

# Serial Connections
class SerialTube < Tube

otherwise YARD will have wrong result

# XXX(JonathanBeverey): should we reverse @convert_newlines here?
return data
rescue RubySerial::Error
close
Copy link
Collaborator

Choose a reason for hiding this comment

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

Current tests don't cover this rescue
add tests and ensure 100% coverage

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 can't see any way to do this. open(2) checks for all the same errors as read(2). Nothing I've done will cause an error in the right place, without writing bad code.

If you know a way the test code can break Ruby's encapsulation model and directly invoke SerialTube.conn.close(), that could do it. But I really don't want to write a method to allow that, and honestly, if I did, I would then tighten up the error checking around @conn and close off that avenue as well.

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 found a way.

begin
return @conn.write(data)
rescue RubySerial::Error
close
Copy link
Collaborator

Choose a reason for hiding this comment

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

same here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

@david942j
Copy link
Collaborator

Seems the tests sometimes hang forever
Any idea?

@JonathanBeverley
Copy link
Contributor Author

I've tried chasing it down, but I can't reproduce it locally. It's probably happening because we're stuck in the Open3.popen3() block. Before I got the ensure working properly something similar would happen when an exception tried to break out of that block, but popen3 would wait for socat to complete before allowing it.

@david942j
Copy link
Collaborator

david942j commented Aug 22, 2018

With while true; do bundle exec rake; done I can reproduce the hanging forever case.
It stucks on Serial#read, i.e. the read syscall

With quickly review I know the rubyserial gem do set VMIN=0 for a non-blocking IO, so this should not happen.
Trying to figure out why.

@david942j
Copy link
Collaborator

david942j commented Aug 23, 2018

Got it.
The main reason is a race condition between rubyserial and socat,
they both need to set (different) attributes on the same tty device.
Just add one line to make sure socat have finished its setup fix the issue: 764ada5#diff-8dfbabae43dfe4e708d49fc1f86db83dR36

Now dealing with another failed case.. https://travis-ci.org/peter50216/pwntools-ruby/jobs/419754587

@david942j
Copy link
Collaborator

@JonathanBeverley I fixed the issues
While I don't have permission to push those fixes to the forked repo, could you push them to your branch?

The fixes contained in two commits: bda6f8b and 764ada5 (plz ignore the debug messages)

@JonathanBeverley
Copy link
Contributor Author

done.

@david942j
Copy link
Collaborator

david942j commented Aug 25, 2018

Sorry those commits contain debug code..
please checked-out the changes in test/asm_test.rb and uncomment (# install_keystone_from_source) in travis/install.sh

@david942j david942j merged commit c01a52e into peter50216:master Aug 25, 2018
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.

2 participants