-
Notifications
You must be signed in to change notification settings - Fork 36.5k
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
[Qt] payment request work - part 1 #5216
Conversation
@gavinandresen @mikehearn Gavin, Mike, can you help me out with BIP70, I need to understand for some code I want to use, if even for insecure payment-requests ( I only found this |
I don't think most implementations enforce it, but serving any kind of payment request for any kind of payment network without SSL, not just bitcoin, would seem very dangerous and ill advised. When the payment request is itself signed, then using SSL only adds some privacy but not security. It's still a good idea though. |
@mikehearn Thanks for your info. I found what I needed :). |
@Diapolo I'll take a look. |
This now could need some initial review as the pull gets to big otheriwse... |
.arg(reply->size()) | ||
.arg(BIP70_MAX_PAYMENTREQUEST_SIZE); | ||
|
||
qWarning() << QString("PaymentServer::%1:").arg(__func__) << msg; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@laanwj I was thinkig about how I could supply our debug.log without a translated string as that could improve debugging or web searches for that strings. Have you got a nice and clean idea without duplicating the whole string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't think of anything that would be cleaner than just duplicating the message, or logging a different message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if there is a way to tell Qt to download only a maximum size of request. The problem with handling it in Finished() is that part of the DoS already has been done: we downloaded all the data into memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind: this is before readAll, so supposedly we haven't read all the data yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, as the network is a 'sequential device' it is possible for readAll()
to return more bytes than size()
, for example in the case that no ContentLength header was passed. size()
is effectively useless in this case and just returns how many bytes are in the buffer now.
A more robust way would be to do read(BIP70_MAX_PAYMENTREQUEST_SIZE + 1)
, and then check for >=BIP70_MAX_PAYMENTREQUEST_SIZE
received bytes.
Edit: OK I'm completely confused here. Other sources suggest Qt has downloaded the entire file by the time finished()
is called and bytesAvailable()
will tell you how much readAll()
will return. Which means the above will work, but it doesn't avoid the problem ...
Edit2: QNetworkReply has a setReadBufferSize which may actually limit the read buffer. It defaults to unlimited. But I'm not sure finished() will even be called in that case or it will wait for someone to consume the buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, so now I'm also confused ^^... what can I do to fix/improve what I did here?
Can we use QNetworkRequest::ContentLengthHeader
-> Corresponds to the HTTP Content-Length header and contains the length in bytes of the data transmitted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@laanwj I found that code:
void MainWindow::on_download_button_clicked(){ QUrl url("http://someurl"); QNetworkAccessManager * manager = new QNetworkAccessManager(this); connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(getHeaders(QNetworkReply*))); manager->head(QNetworkRequest(url)); } void MainWindow::getHeaders(QNetworkReply * reply){ if (reply->operation() == QNetworkAccessManager::HeadOperation){ int content_length = reply->header(QNetworkRequest::ContentLengthHeader).toInt(); } }
Perhaps we can just start by querying the headers, check the content length and abort if it's > BIP70_MAX_PAYMENTREQUEST_SIZE?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The server doesn't have to send a Content-Length
header.
And it certainly shouldn't do an extra HEAD-request. This causes double the number of requests and the server may just lie on a HEAD request.
If it's possible to look at the headers before starting the download one could, for example, reject requests without Content-Length header or with too large content.
However, the only robust solution would be to limit the receive buffer, and bail out when it is full.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you take any measure to prevent the whole file to be downloaded or this is pending?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There were no addidional changes made after the above discussion with @laanwj. If you come up with something to improve I'm going to pick it up ;).
Code review ACK. Can you add a unit test to src/qt/test/paymentservertests.cpp to test the new DoS-protection code? |
@gavinandresen Thanks for your review, I'm taking a look at your suggestion about adding a test now. |
@gavinandresen Not sure how to test the new code, as the DoS rules are in I could perhaps add a dummy file with garbage data > BIP70_MAX_PAYMENTREQUEST_SIZE and then use |
@gavinandresen Test added, can you review!? |
Sure, though it'll first have to pass travis:
|
@laanwj I guess the code was missing |
Now passes Travis! |
Ok did some testing with regard to Qt5's http client. Created a simple http server that serves an arbitrary number of bytes with the correct Content-Type, in this case 50MB: #!/usr/bin/python3
'''
bitcoin:?r=http://127.0.0.1:8000/test
'''
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "application/bitcoin-payment")
self.end_headers()
count = 0
while count < 50000:
self.wfile.write(b' ' * 1000)
count += 1
server_class=HTTPServer
handler_class=Handler
server_address = ('', 8000)
httpd = server_class(server_address, handler_class)
httpd.serve_forever() Extended netRequestFinished with logging: diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp
index 707de55..ead6675 100644
--- a/src/qt/paymentserver.cpp
+++ b/src/qt/paymentserver.cpp
@@ -657,7 +657,10 @@ void PaymentServer::netRequestFinished(QNetworkReply* reply)
return;
}
+ qWarning() << __func__ << ": size() is " << reply->size() << " bytesAvailable() is " << reply->bytesAvailable();
+
QByteArray data = reply->readAll();
+ qWarning() << __func__ << ": data.size() is " << data.size();
QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
if (requestType == "PaymentRequest") Then made qt fetch the
Conclusion
The DoS protection code in this pull at most protects against parsing a huge protocol buffer. Which is a good start, although it would be nice to stop reading at some point when the server keeps sending data. OTOH I suppose this is an unlikely attack over the internet as the adversary has to actually send the data. |
@Diapolo you don't have to solve that issue in this pull, ACK on these changes, they don't make matters worse, |
@@ -85,6 +88,9 @@ class PaymentServer : public QObject | |||
// OptionsModel is used for getting proxy settings and display unit | |||
void setOptionsModel(OptionsModel *optionsModel); | |||
|
|||
// This is now public, because we us it in paymentservertests.cpp | |||
static bool readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/us/use/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, changed to use
;).
- the function only returned true, so make it void - add a comment about payment request network detection
- also rename current ones to match the new ones - remove constant from guiconstant.h and add it to paymentserver.cpp
- current code only does this for payment request files, which are used on Mac - also rename readPaymentRequest to readPaymentRequestFromFile, so it's obvious that function only handles payment request files and not URIs - small logging changes in readPaymentRequestFromFile
- this test required to make readPaymentRequestFromFile() public in order to be able to is it in paymentservertests.cpp
@laanwj Is it okay to say this request is done!? As I said I have some more changes I'd like to add for payment requests. If you agree I would base new ones on this (or even better this gets merged soon). |
5ec654b [Qt] update paymentserver license and cleanup ordering (Philip Kaufmann) 4333e26 [Qt] add BIP70 DoS protection test (Philip Kaufmann) 31f8494 [Qt] add BIP70 payment request size DoS protection for URIs (Philip Kaufmann) 2284ccb [Qt] remove dup lock that is done in SetAddressBook() (Philip Kaufmann) 1ec753f [Qt] ensure socket is set to NULL in PaymentServer::ipcSendCommandLine (Philip Kaufmann) 814429d [Qt] add BIP70/BIP71 constants for all messages and mime types (Philip Kaufmann) b82695b [Qt] make PaymentServer::ipcParseCommandLine void (Philip Kaufmann)
I'm going to once more dig into our payment request code to better polish and add some more things to it. This is just the start and I intend to create some small pulls and not a big one, so it's easier to review and merge. After I get an ACK for this I will add a cleanup commit to this one, which I leave out for now.
For reference the BIPs:
https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki
https://github.com/bitcoin/bips/blob/master/bip-0071.mediawiki
@laanwj Perhaps you can help me getting this done smoothly :)?
@fanquake After the initial work here, do you want to participate by converting the paymentserver and paymentrequestplus files into the doxygen format after I got ACKs :)?